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

Undo Redo — Part 1 #1602

Merged
merged 18 commits into from
Jun 11, 2021
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
operating system about being untrusted.
- [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.
- [Initial support for undo-redo][1602]. Press <kbd>cmd</kbd>+<kbd>z</kbd> to
undo last action and <kbd>cmd</kbd>+<kbd>z</kbd> to redo last undone action.
This version of undo redo does not have proper support for text editor and
undoing UI changes (like selecting nodes).

#### EnsoGL (rendering engine)

Expand All @@ -28,6 +32,7 @@
[1577]: https://github.com/enso-org/ide/pull/1577
[1587]: https://github.com/enso-org/ide/pull/1587
[1366]: https://github.com/enso-org/ide/pull/1366
[1602]: https://github.com/enso-org/ide/pull/1602

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

Expand Down
2 changes: 2 additions & 0 deletions docs/product/shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ further investigation.
| <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>z</kbd> | Undo last action |
| <kbd>cmd</kbd>+<kbd>y</kbd> | Redo last undone action |
| <kbd>cmd</kbd>+<kbd>q</kbd> | Close the application (MacOS) |
| <kbd>ctrl</kbd>+<kbd>q</kbd> | Close the application (Linux) |
| <kbd>alt</kbd>+<kbd>F4</kbd> | Close the application (MacOS, Windows, Linux) |
Expand Down
4 changes: 1 addition & 3 deletions src/rust/ensogl/lib/components/src/list_view/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,14 @@ impl SingleMaskedProvider {
/// accounting for the removal of the selected item.
///
/// Example:
/// ```text
/// Mask `Some(1)`
/// Masked indices [0, 1, 2]
/// Unmasked Index [0, 1, 2, 3]
/// -------------------------------
/// Mask `None`
/// Masked indices [0, 1, 2, 3]
/// Unmasked Index [0, 1, 2, 3]
///
/// ```
///
/// ```
pub fn unmasked_index(&self, ix:Id) -> Id {
match self.mask.get() {
Expand Down
8 changes: 4 additions & 4 deletions src/rust/ensogl/lib/components/src/selector/bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl From<(f32,f32)> for Bounds {
/// Frp utility method to normalise the given value to the given Bounds.
///
/// Example usage:
/// ```ignore
/// ```text
/// normalised <- all2(&value,&bounds).map(normalise_value);
/// ````
pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 {
Expand All @@ -69,7 +69,7 @@ pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 {
/// Inverse of `normalise_value`.
///
/// Example usage:
/// ```ignore
/// ```text
/// value <- all(&bounds,&normalised).map(absolute_value);
/// ````
pub fn absolute_value((bounds,normalised_value):&(Bounds,f32)) -> f32 {
Expand Down Expand Up @@ -101,7 +101,7 @@ pub fn bounds_in_bounds(bounds_inner:Bounds, bounds_outer:Bounds) -> bool {
/// For use in FRP `map` method, thus taking references.
///
/// Example usage:
/// ```ignore
/// ```text
/// clamped <- value_update.map2(&normalised_overflow_bounds,clamp_with_overflow);
/// ```
#[allow(clippy::trivially_copy_pass_by_ref)]
Expand All @@ -117,7 +117,7 @@ pub fn clamp_with_overflow(value:&f32, overflow_bounds:&Option<Bounds>) -> f32 {
/// For use in FRP `map` method, thus taking references.
///
/// Example usage:
/// ```ignore
/// ```text
/// is_in_bounds <- bounds_update.map2(&normalised_overflow_bounds,should_clamp_with_overflow);
/// ```
#[allow(clippy::trivially_copy_pass_by_ref)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ impl Thresholds {
/// function.
///
/// The equations used here are:
/// ```ignore
/// ```text
/// spring_force = spring_stretch * self.spring;
/// drag_force = - velocity * self.drag;
/// force = spring_force + drag_force;
Expand Down
2 changes: 1 addition & 1 deletion src/rust/ensogl/lib/core/src/data/color/space/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ impl Lcha {
/// chroma is scaled by `LCH_MAX_CHROMA_IN_SRGB_IN_STD_EQUATIONS`.
///
///
/// ```ignore
/// ```text
/// •••• ├ 40
/// •••• • │
/// •••• • │
Expand Down
2 changes: 1 addition & 1 deletion src/rust/ensogl/lib/core/src/display/scene/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ impl std::borrow::Borrow<LayerModel> for Layer {
/// For example, you can create a layer which displays the same symbols as another layer, but from a
/// different camera to create a "mini-map view" of a graph editor.
///
/// ```ignore
/// ```text
/// +------+.
/// |`. | `. Layer 1 (top)
/// | `+--+---+ (Camera 1 and symbols [1,2,3])
Expand Down
14 changes: 14 additions & 0 deletions src/rust/ide/lib/parser/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ pub struct SourceFile {
pub metadata : Range<ByteIndex>,
}

impl Display for SourceFile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.content)
}
}

impl SourceFile {
/// Describe source file contents. Uses heuristics to locate the metadata section.
///
Expand Down Expand Up @@ -140,6 +146,14 @@ impl<M:Metadata> TryFrom<&ParsedSourceFile<M>> for String {
}
}

impl<M:Metadata> Display for ParsedSourceFile<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.serialize() {
Ok(serialized) => write!(f, "{}", serialized),
Err(_) => write!(f, "[NOT REPRESENTABLE SOURCE FILE]")
}
}
}

// === Parsed Source File Serialization ===

Expand Down
6 changes: 3 additions & 3 deletions src/rust/ide/lib/span-tree/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,9 @@ impl Crumbs {
}
}

impl From<Vec<Crumb>> for Crumbs {
fn from(crumbs:Vec<usize>) -> Self {
Self::new(crumbs)
impl<T:IntoIterator<Item=Crumb>> From<T> for Crumbs {
fn from(crumbs:T) -> Self {
Self::new(crumbs.into_iter().collect())
}
}

Expand Down
46 changes: 39 additions & 7 deletions src/rust/ide/src/controller/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::double_representation::module;
use crate::double_representation::node;
use crate::double_representation::node::NodeInfo;
use crate::model::module::NodeMetadata;
use crate::model::traits::*;

use ast::crumbs::InfixCrumb;
use enso_protocol::language_server;
Expand Down Expand Up @@ -88,12 +89,25 @@ pub struct Node {
}

impl Node {
/// Get the node's position.
pub fn position(&self) -> Option<model::module::Position> {
self.metadata.as_ref().and_then(|m| m.position)
}

/// Check if node has a specific position set in metadata.
pub fn has_position(&self) -> bool {
self.metadata.as_ref().map_or(false, |m| m.position.is_some())
}
}

impl Deref for Node {
type Target = NodeInfo;

fn deref(&self) -> &Self::Target {
&self.info
}
}



// ===================
Expand Down Expand Up @@ -179,7 +193,7 @@ pub struct Connection {

// === NodeTrees ===

/// Stores node's span trees: one for inputs (expression) and optionally another one for inputs
/// Stores node's span trees: one for inputs (expression) and optionally another one for outputs
/// (pattern).
#[derive(Clone,Debug,Default)]
pub struct NodeTrees {
Expand All @@ -202,7 +216,7 @@ impl NodeTrees {
}

/// Converts AST crumbs (as obtained from double rep's connection endpoint) into the
/// appriopriate span-tree node reference.
/// appropriate span-tree node reference.
pub fn get_span_tree_node<'a,'b>(&'a self, ast_crumbs:&'b [ast::Crumb])
-> Option<span_tree::node::NodeFoundByAstCrumbs<'a,'b>> {
if let Some(outputs) = self.outputs.as_ref() {
Expand Down Expand Up @@ -649,6 +663,7 @@ impl Handle {
/// Create connection in graph.
pub fn connect
(&self, connection:&Connection, context:&impl SpanTreeContext) -> FallibleResult {
let _transaction_guard = self.get_or_open_transaction("Connect");
if connection.source.port.is_empty() {
// If we create connection from node's expression root, we are able to introduce missing
// pattern with a new variable.
Expand All @@ -670,6 +685,7 @@ impl Handle {
/// Remove the connections from the graph.
pub fn disconnect
(&self, connection:&Connection, context:&impl SpanTreeContext) -> FallibleResult {
let _transaction_guard = self.get_or_open_transaction("Disconnect");
let info = self.destination_info(connection,context)?;

let updated_expression = if connection.destination.var_crumbs.is_empty() {
Expand Down Expand Up @@ -777,12 +793,21 @@ impl Handle {
Ok(())
}

/// Set node's position.
pub fn set_node_position(&self, node_id:ast::Id, position:impl Into<model::module::Position>) -> FallibleResult {
let _transaction_guard = self.get_or_open_transaction("Set node position");
self.module.with_node_metadata(node_id, Box::new(|md| {
md.position = Some(position.into());
}))
}

/// Collapses the selected nodes.
///
/// Lines corresponding to the selection will be extracted to a new method definition.
pub fn collapse
(&self, nodes:impl IntoIterator<Item=node::Id>, new_method_name_base:&str)
-> FallibleResult<node::Id> {
let _transaction_guard = self.get_or_open_transaction("Collapse nodes");
analytics::remote_log_event("graph::collapse");
use double_representation::refactorings::collapse::collapse;
use double_representation::refactorings::collapse::Collapsed;
Expand Down Expand Up @@ -867,6 +892,12 @@ impl span_tree::generate::Context for Handle {
}
}

impl model::undo_redo::Aware for Handle {
fn undo_redo_repository(&self) -> Rc<model::undo_redo::Repository> {
self.module.undo_redo_repository()
}
}



// ============
Expand Down Expand Up @@ -943,11 +974,12 @@ pub mod tests {

/// Create a graph controller from the current mock data.
pub fn graph(&self) -> Handle {
let logger = Logger::new("Test");
let parser = Parser::new().unwrap();
let module = self.module_data().plain(&parser);
let id = self.graph_id.clone();
let db = self.suggestion_db();
let logger = Logger::new("Test");
let parser = Parser::new().unwrap();
let urm = Rc::new(model::undo_redo::Repository::new(&logger));
let module = self.module_data().plain(&parser,urm);
let id = self.graph_id.clone();
let db = self.suggestion_db();
Handle::new(logger,module,db,parser,id).unwrap()
}

Expand Down
12 changes: 10 additions & 2 deletions src/rust/ide/src/controller/graph/executed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ impl Context for Handle {
}
}

impl model::undo_redo::Aware for Handle {
fn undo_redo_repository(&self) -> Rc<model::undo_redo::Repository> {
self.graph.borrow().undo_redo_repository()
}
}



// ============
Expand Down Expand Up @@ -325,8 +331,10 @@ pub mod tests {

impl MockData {
pub fn controller(&self) -> Handle {
let logger = Logger::new("test");
let parser = parser::Parser::new_or_panic();
let module = self.module.plain(&parser);
let repository = Rc::new(model::undo_redo::Repository::new(&logger));
let module = self.module.plain(&parser,repository);
let method = self.graph.method();
let mut project = model::project::MockAPI::new();
let ctx = Rc::new(self.ctx.create());
Expand All @@ -340,7 +348,7 @@ pub mod tests {
let suggestion_db = self.graph.suggestion_db();
model::project::test::expect_suggestion_db(&mut project,suggestion_db);
let project = Rc::new(project);
Handle::new(Logger::new("test"),project.clone_ref(),method).boxed_local().expect_ok()
Handle::new(logger,project.clone_ref(),method).boxed_local().expect_ok()
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/rust/ide/src/controller/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub struct Handle {
impl Handle {
/// Create a module controller for given path.
pub async fn new
(parent:impl AnyLogger, path:Path, project:&model::Project) -> FallibleResult<Self> {
(parent:impl AnyLogger, path:Path, project:&dyn model::project::API) -> FallibleResult<Self> {
let logger = Logger::sub(parent,format!("Module Controller {}", path));
let model = project.module(path).await?;
let language_server = project.json_rpc();
Expand Down Expand Up @@ -160,11 +160,12 @@ impl Handle {
, id_map : ast::IdMap
, language_server : Rc<language_server::Connection>
, parser : Parser
, repository : Rc<model::undo_redo::Repository>
) -> FallibleResult<Self> {
let logger = Logger::new("Mocked Module Controller");
let ast = parser.parse(code.to_string(),id_map)?.try_into()?;
let metadata = default();
let model = Rc::new(model::module::Plain::new(path,ast,metadata));
let model = Rc::new(model::module::Plain::new(&logger,path,ast,metadata,repository));
Ok(Handle {model,language_server,parser,logger})
}

Expand Down Expand Up @@ -213,7 +214,7 @@ mod test {
, (Span::new(Index::new(2),Size::new(1)),uuid3)
, (Span::new(Index::new(0),Size::new(3)),uuid4)
]);
let controller = Handle::new_mock(location,code,id_map,ls,parser).unwrap();
let controller = Handle::new_mock(location,code,id_map,ls,parser,default()).unwrap();

// Change code from "2+2" to "22+2"
let change = TextChange::insert(Index::new(0),"2".to_string());
Expand Down
5 changes: 3 additions & 2 deletions src/rust/ide/src/controller/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,16 @@ mod tests {
// Check that module without main gets it after the call.
let empty_module_code = "";
data.set_code(empty_module_code);
let module = data.module();
let urm = data.undo_redo_manager();
let module = data.module(urm.clone_ref());
assert!(module.lookup_method(&data.project_name,&main_ptr).is_err());
Project::add_main_if_missing(&data.project_name, &module, &main_ptr, &parser).unwrap();
assert!(module.lookup_method(&data.project_name,&main_ptr).is_ok());

// Now check that modules that have main already defined won't get modified.
let mut expect_intact = move |code:&str| {
data.set_code(code);
let module = data.module();
let module = data.module(urm.clone_ref());
Project::add_main_if_missing(&data.project_name, &module, &main_ptr, &parser).unwrap();
assert_eq!(code,module.ast().repr());
};
Expand Down
12 changes: 7 additions & 5 deletions src/rust/ide/src/controller/searcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::model::module::MethodId;
use crate::model::module::NodeMetadata;
use crate::model::module::Position;
use crate::model::suggestion_database::entry::CodeToInsert;
use crate::model::traits::*;
use crate::notification;

use data::text::TextLocation;
Expand Down Expand Up @@ -703,6 +704,7 @@ 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 _transaction_guard = self.graph.get_or_open_transaction("Commit node");
let expr_and_method = || {
let input_chain = self.data.borrow().input.as_prefix_chain(self.ide.parser());

Expand Down Expand Up @@ -796,11 +798,11 @@ impl Searcher {


fn add_required_imports(&self) -> FallibleResult {
let data_borrowed = self.data.borrow();
let fragments = data_borrowed.fragments_added_by_picking.iter();
let imports = fragments.map(|frag| self.code_to_insert(frag).imports).flatten();
let mut module = self.module();
let here = self.module_qualified_name();
let data_borrowed = self.data.borrow();
let fragments = data_borrowed.fragments_added_by_picking.iter();
let imports = fragments.map(|frag| self.code_to_insert(frag).imports).flatten();
let mut module = self.module();
let here = self.module_qualified_name();
// TODO[ao] this is a temporary workaround. See [`Searcher::add_enso_project_entries`]
// documentation.
let without_enso_project = imports.filter(|i| i.to_string() != ENSO_PROJECT_SPECIAL_MODULE);
Expand Down