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

Create new project action in searcher. #1566

Merged
merged 25 commits into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
074aefe
WIP
farmaazon Apr 27, 2021
ca89039
WIP
farmaazon Apr 27, 2021
c44ad20
Refactored project setup
farmaazon May 12, 2021
ec93597
Create new project action
farmaazon May 12, 2021
1be198c
Fixes
farmaazon May 12, 2021
1b11b70
Code cleanup
farmaazon May 13, 2021
6df60f5
Self-review
farmaazon May 13, 2021
99f32f1
Merge remote-tracking branch 'origin/develop' into wip/ao/create-new-…
farmaazon May 13, 2021
9a65231
Fix compilation
farmaazon May 14, 2021
679d654
Update src/rust/ide/src/controller/ide/desktop.rs
farmaazon May 17, 2021
a0ee9e5
Update src/rust/ide/src/controller/ide/desktop.rs
farmaazon May 17, 2021
589a403
Update src/rust/ide/src/controller/ide/desktop.rs
farmaazon May 17, 2021
5148154
Update src/rust/ide/src/controller/project.rs
farmaazon May 17, 2021
58b10ec
Update src/rust/ide/src/controller/ide/plain.rs
farmaazon May 17, 2021
b929f64
Update src/rust/ide/src/controller/project.rs
farmaazon May 17, 2021
75d5366
Update src/rust/ide/src/controller/project.rs
farmaazon May 17, 2021
384b9a0
Fixes
farmaazon May 14, 2021
b675ee4
Applying @mwu-two review
farmaazon May 17, 2021
ab849cb
Merge remote-tracking branch 'origin/develop' into wip/ao/create-new-…
farmaazon May 17, 2021
245c21f
Applying @wdanilo review
farmaazon May 18, 2021
84921d7
Merge remote-tracking branch 'origin/develop' into wip/ao/create-new-…
farmaazon May 21, 2021
a58d8bc
Linter fixes
farmaazon May 21, 2021
9b9c14d
Merge branch 'develop' into wip/ao/create-new-project2
farmaazon May 21, 2021
ccda769
prettify CHANGELOG
farmaazon May 21, 2021
c264a2c
Fix tests
farmaazon May 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@

#### Visual Environment

- [Create New Project action in Searcher][1566]. When you bring the searcher
with tab having no node selected, a new action will be available next to the
examples and code suggestions: `Create New Project`. When you choose it by
clicking with mouse or selecting and pressing enter, a new unnamed project
will be created and opened in the application. Then you can give a name to
this project.

#### EnsoGL (rendering engine)

- [Components for picking numbers and ranges.][1524]. We now have some internal
re-usable UI components for selecting numbers or a range. Stay tuned for them
appearing in the IDE.

#### EnsoGL (rendering engine)

<br/>![Bug Fixes](/docs/assets/tags/bug_fixes.svg)

#### Visual Environment

- [Delete key will delete selected nodes][1538].
- [It is possible to move around after deleting a node with a selected
visualization][1556]. Deleting a node while its attached visualization was
selected made it impossible to pan or zoom around the stage afterwards. This
Expand All @@ -27,16 +33,37 @@

#### Enso Compiler

[1524]: https://github.com/enso-org/ide/pull/1524
[1556]: https://github.com/enso-org/ide/pull/1556
[1561]: https://github.com/enso-org/ide/pull/1561
[1566]: https://github.com/enso-org/ide/pull/1566

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

<br/>![New Features](/docs/assets/tags/new_features.svg)

#### Visual Environment

#### EnsoGL (rendering engine)

<br/>![Bug Fixes](/docs/assets/tags/bug_fixes.svg)

#### Visual Environment

- [Delete key will delete selected nodes][1538].

#### EnsoGL (rendering engine)

#### Enso Compiler

- [Updated Enso engine to version 0.2.11][1541].

If you're interested in the enhancements and fixes made to the Enso compiler,
you can find their release notes
[here](https://github.com/enso-org/enso/blob/main/RELEASES.md).

[1524]: https://github.com/enso-org/ide/pull/1524
[1541]: https://github.com/enso-org/ide/pull/1511
[1541]: https://github.com/enso-org/ide/pull/1541
[1538]: https://github.com/enso-org/ide/pull/1538
[1561]: https://github.com/enso-org/ide/pull/1561

<br/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ pub type Event = json_rpc::handler::Event<Notification>;
// ============

/// A path is a representation of a path relative to a specified content root.
// FIXME [mwu] Consider rename to something like `FilePath`, see https://github.com/enso-org/enso/issues/708
#[derive(Clone,Debug,Serialize,Deserialize,Hash,PartialEq,Eq)]
#[serde(rename_all="camelCase")]
pub struct Path {
Expand Down
10 changes: 9 additions & 1 deletion src/rust/ide/lib/enso-protocol/src/project_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl Display for IpWithSocket {
}

/// Project name.
#[derive(Debug,Display,Clone,Serialize,Deserialize,From,PartialEq,Shrinkwrap)]
#[derive(Clone,Debug,Deserialize,Display,Eq,From,Hash,PartialEq,Serialize,Shrinkwrap)]
#[shrinkwrap(mutable)]
pub struct ProjectName(pub String);

Expand All @@ -111,6 +111,14 @@ impl ProjectName {
}
}

impl AsRef<str> for ProjectName {
fn as_ref(&self) -> &str { &self.0 }
}

impl From<ProjectName> for String {
fn from(name:ProjectName) -> Self { name.0 }
}

/// Project information, such as name, its id and last time it was opened.
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq)]
pub struct ProjectMetadata {
Expand Down
18 changes: 6 additions & 12 deletions src/rust/ide/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,22 @@
//! between clients of remote services (like language server and file manager)
//! and views.
//!
//! The controllers create a tree-like structure, with project controller being
//! a root, then module controllers below, then graph/text controller and so on.
//!
//! As a general rule, while the "upper" (i.e. closer to root) nodes may keep
//! handles to the "lower" nodes (e.g. to allow their reuse), they should never
//! manage their lifetime.
//!
//! Primarily views are considered owners of their respective controllers.
//! Additionally, controllers are allowed to keep strong handle "upwards".
//!
//! Controllers store their handles using `utils::cell` handle types to ensure
//! that mutable state is safely accessed.
//! The API of each controller is "view-facing", in contrast to the models in [`crate::model`] which
//! are focusing on reflecting the Engine entities (thus can be called "Engine-facing").

pub mod graph;
pub mod ide;
pub mod module;
pub mod text;
pub mod project;
pub mod visualization;
pub mod searcher;

pub use graph::Handle as Graph;
pub use graph::executed::Handle as ExecutedGraph;
pub use self::ide::Ide;
pub use module::Handle as Module;
pub use project::Project;
pub use text::Handle as Text;
pub use visualization::Handle as Visualization;
pub use searcher::Searcher;
Expand Down
150 changes: 150 additions & 0 deletions src/rust/ide/src/controller/ide.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//! IDE controller
//!
//! The IDE controller expose functionality bound to the application as a whole, not to specific
//! component or opened project.

pub mod desktop;
pub mod plain;

use crate::prelude::*;

use crate::notification;

use mockall::automock;
use parser::Parser;



// ============================
// === Status Notifications ===
// ============================

/// The handle used to pair the ProcessStarted and ProcessFinished notifications.
pub type BackgroundTaskHandle = usize;

/// A notification which should be displayed to the User on the status bar.
#[allow(missing_docs)]
#[derive(Clone,Debug)]
pub enum StatusNotification {
/// Notification about single event, should be logged in an event log window.
Event { label:String },
/// Notification about new background task done in IDE (like compiling library).
BackgroundTaskStarted { label:String, handle: BackgroundTaskHandle },
/// Notification that some task notified in [`BackgroundTaskStarted`] has been finished.
BackgroundTaskFinished { handle:BackgroundTaskHandle },
}

/// A publisher for status notification events.
#[derive(Clone,CloneRef,Debug,Default)]
pub struct StatusNotificationPublisher {
publisher : notification::Publisher<StatusNotification>,
next_process_handle : Rc<Cell<usize>>,
}

impl StatusNotificationPublisher {
/// Constructor.
pub fn new() -> Self { default() }

/// Publish a new status event (see [`StatusNotification::Event`])
pub fn publish_event(&self, label:impl Into<String>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd bikeshed a few more names here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand this comment

let label = label.into();
let notification = StatusNotification::Event {label};
executor::global::spawn(self.publisher.publish(notification));
}

/// Publish a notification about new process (see [`StatusNotification::ProcessStarted`]).
///
/// Returns the handle to be used when notifying about process finishing.
pub fn publish_background_task(&self, label:impl Into<String>) -> BackgroundTaskHandle {
let label = label.into();
let handle = self.next_process_handle.get();
self.next_process_handle.set(handle + 1);
let notification = StatusNotification::BackgroundTaskStarted {label,handle};
executor::global::spawn(self.publisher.publish(notification));
handle
}

/// Publish a notfication that process has finished (see [`StatusNotification::ProcessFinished`])
pub fn published_background_task_finished(&self, handle:BackgroundTaskHandle) {
let notification = StatusNotification::BackgroundTaskFinished {handle};
executor::global::spawn(self.publisher.publish(notification));
}

/// The asynchronous stream of published notifications.
pub fn subscribe(&self) -> impl Stream<Item=StatusNotification> {
self.publisher.subscribe()
}
}



// ====================
// === Notification ===
// ====================

/// Notification of IDE Controller.
///
/// In contrast to [`StatusNotification`], which is a notification from any application part to
/// be delivered to User (displayed on some event log or status bar), this is a notification to be
/// used internally in code.
#[derive(Copy,Clone,Debug)]
pub enum Notification {
/// User created a new project. The new project is opened in IDE.
NewProjectCreated
}



// ===========
// === API ===
// ===========

/// The API of all project management operations.
///
/// It is a separate trait, because those methods are not supported in some environments (see also
/// [`API::manage_projects`]).
pub trait ManagingProjectAPI {

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

/// The API of IDE Controller.
#[automock]
pub trait API:Debug {
/// The model of currently opened project.
///
/// IDE can have only one project opened at a time.
fn current_project(&self) -> model::Project;

/// Getter of Status Notification Publisher.
fn status_notifications(&self) -> &StatusNotificationPublisher;
farmaazon marked this conversation as resolved.
Show resolved Hide resolved

/// The Parser Handle.
fn parser(&self) -> &Parser;

/// Subscribe the controller notifications.
fn subscribe(&self) -> StaticBoxStream<Notification>;

/// Return the Managing Project API.
///
/// It may be some delegated object or just the reference to self.
// Automock macro does not work without explicit lifetimes here.
#[allow(clippy::needless_lifetimes)]
fn manage_projects<'a>(&'a self) -> FallibleResult<&'a dyn ManagingProjectAPI>;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps IDE should also provide parser?


/// A polymorphic handle of IDE controller.
pub type Ide = Rc<dyn API>;

/// The IDE Controller for desktop environments.
pub type Desktop = desktop::Handle;

/// The Plain IDE controller with a single project and no possibility to change it.
pub type Plain = plain::Handle;

impl Debug for MockAPI {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"Mocked Ide Controller")
}
}
113 changes: 113 additions & 0 deletions src/rust/ide/src/controller/ide/desktop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! The Desktop IDE Controller
//!
//! See [`crate::controller::ide`] for more detailed description of IDE Controller API.

use crate::prelude::*;

use crate::controller::ide::API;
use crate::controller::ide::ManagingProjectAPI;
use crate::controller::ide::StatusNotificationPublisher;
use crate::controller::ide::Notification;
use crate::controller::project::ENGINE_VERSION_FOR_NEW_PROJECTS;
use crate::ide::initializer;
use crate::notification;

use enso_protocol::project_manager;
use enso_protocol::project_manager::MissingComponentAction;
use enso_protocol::project_manager::ProjectName;
use parser::Parser;



// =================
// === Constants ===
// =================

const UNNAMED_PROJECT_NAME:&str = "Unnamed";
farmaazon marked this conversation as resolved.
Show resolved Hide resolved



// =============================
// === The Controller Handle ===
// =============================

/// The Desktop IDE Controller handle.
///
/// The desktop controller has access to the Project Manager, and thus is able to perform all
/// project management operations.
#[derive(Clone,CloneRef,Derivative)]
#[derivative(Debug)]
pub struct Handle {
logger : Logger,
current_project : Rc<CloneRefCell<model::Project>>,
#[derivative(Debug="ignore")]
project_manager : Rc<dyn project_manager::API>,
status_notifications : StatusNotificationPublisher,
parser : Parser,
notifications : notification::Publisher<Notification>,
}

impl Handle {
/// Create a project controller handle with already loaded project model.
pub fn new_with_project
(project_manager:Rc<dyn project_manager::API>, initial_project:model::Project) -> Self {
let logger = Logger::new("controller::ide::Desktop");
let current_project = Rc::new(CloneRefCell::new(initial_project));
let status_notifications = default();
let parser = Parser::new_or_panic();
let notifications = default();
Self {logger,current_project,project_manager,status_notifications,parser,notifications}
}

/// Create a project controller handle which opens the project with the given name, or creates it
/// if it does not exist.
pub async fn new_with_opened_project
(project_manager:Rc<dyn project_manager::API>, name:ProjectName) -> FallibleResult<Self> {
// TODO[ao]: Reuse of initializer used in previous code design. It should be soon replaced
// anyway, because we will soon resign from the "open or create" approach when opening
// IDE. See https://github.com/enso-org/ide/issues/1492 for details.
let initializer = initializer::WithProjectManager::new(project_manager.clone_ref(),name);
let model = initializer.initialize_project_model().await?;
Ok(Self::new_with_project(project_manager,model))
}
}

impl API for Handle {
fn current_project (&self) -> model::Project { self.current_project.get() }
fn status_notifications(&self) -> &StatusNotificationPublisher { &self.status_notifications }
fn parser (&self) -> &Parser { &self.parser }

fn subscribe(&self) -> StaticBoxStream<Notification> {
self.notifications.subscribe().boxed_local()
}

fn manage_projects(&self) -> FallibleResult<&dyn ManagingProjectAPI> {
Ok(self)
}
}

impl ManagingProjectAPI for Handle {
fn create_new_project(&self) -> BoxFuture<FallibleResult> {
async move {
use model::project::Synchronized as Project;

let list = self.project_manager.list_projects(&None).await?;
let names:HashSet<String> = list.projects.into_iter().map(|p| p.name.into()).collect();
let without_suffix = UNNAMED_PROJECT_NAME.to_owned();
let with_suffix = (1..).map(|i| format!("{}_{}", UNNAMED_PROJECT_NAME, i));
let mut candidates = std::iter::once(without_suffix).chain(with_suffix);
// The iterator have no end, so we can safely unwrap.
let name = candidates.find(|c| names.contains(c)).unwrap();
let version = Some(ENGINE_VERSION_FOR_NEW_PROJECTS.to_owned());
let action = MissingComponentAction::Install;

let create_result = self.project_manager.create_project(&name,&version,&action).await?;
let new_project_id = create_result.project_id;
let project_mgr = self.project_manager.clone_ref();
let new_project = Project::new_opened(&self.logger,project_mgr,new_project_id,name);
self.current_project.set(new_project.await?);
executor::global::spawn(self.notifications.publish(Notification::NewProjectCreated));
Ok(())
}.boxed_local()
}
}
Loading