Skip to content

Commit

Permalink
Create new project action in searcher. (enso-org/ide#1566)
Browse files Browse the repository at this point in the history
Original commit: enso-org/ide@5adc952
  • Loading branch information
farmaazon authored May 21, 2021
1 parent 3869e80 commit 4b14e16
Show file tree
Hide file tree
Showing 20 changed files with 2,381 additions and 1,752 deletions.
39 changes: 33 additions & 6 deletions ide/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 ide/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 ide/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 ide/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>) {
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;

/// 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>;
}

/// 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 ide/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";



// =============================
// === 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

0 comments on commit 4b14e16

Please sign in to comment.