Correctly display connections to lambdas (#7550)

Closes #7261

It's impossible to connect to the lambda arguments, but they are displayed as in code, and the correct span tree is generated for the lambda body. (hence you can connect to items inside)

https://github.com/enso-org/enso/assets/6566674/60af6413-e1b9-4e8c-a958-2906b5534d62
This commit is contained in:
Ilya Bogdanov 2023-08-11 18:45:15 +04:00 committed by GitHub
parent 2cc35fde01
commit 9f4a5f90c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 10 deletions

View File

@ -215,6 +215,8 @@
the selected entry in the component browser. Clear separating lines between
method arguments were added. The node selection was made easier with
additional thick interactive selection border.
- [Connections to lamdas are displayed correctly][7550]. It is possible to drag
a connection to any expression inside the lambda body.
[5910]: https://github.com/enso-org/enso/pull/5910
[6279]: https://github.com/enso-org/enso/pull/6279
@ -239,6 +241,7 @@
[7372]: https://github.com/enso-org/enso/pull/7372
[7337]: https://github.com/enso-org/enso/pull/7337
[7311]: https://github.com/enso-org/enso/pull/7311
[7550]: https://github.com/enso-org/enso/pull/7550
#### EnsoGL (rendering engine)

23
Cargo.lock generated
View File

@ -1717,6 +1717,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ea6706d74fca54e15f1d40b5cf7fe7f764aaec61352a9fcec58fe27e042fc8"
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "difference"
version = "2.0.0"
@ -5600,6 +5606,16 @@ dependencies = [
"termtree",
]
[[package]]
name = "pretty_assertions"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
@ -6497,6 +6513,7 @@ dependencies = [
"enso-text",
"failure",
"parser",
"pretty_assertions",
"wasm-bindgen-test",
]
@ -7774,6 +7791,12 @@ version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zeroize"
version = "1.5.7"

View File

@ -15,3 +15,4 @@ parser = { path = "../parser" }
[dev-dependencies]
wasm-bindgen-test = { workspace = true }
pretty_assertions = "1.4"

View File

@ -97,4 +97,10 @@ impl<Parent> ChildBuilder<Parent> {
self.built.node.ast_id = Some(id);
self
}
/// Set tree type for this node.
pub fn set_tree_type(mut self, r#type: Option<ast::TreeType>) -> Self {
self.built.node.tree_type = r#type;
self
}
}

View File

@ -344,8 +344,7 @@ fn generate_node_for_ast(
match ast.shape() {
ast::Shape::Prefix(_) =>
ast::prefix::Chain::from_ast(ast).unwrap().generate_node(kind, context),
ast::Shape::Tree(tree) if tree.type_info != ast::TreeType::Lambda =>
tree_generate_node(tree, kind, context, ast.id),
ast::Shape::Tree(tree) => tree_generate_node(tree, kind, context, ast.id),
ast::Shape::Block(block) => block_generate_node(block, kind, context, ast.id),
_ => {
let size = (ast.repr_len().value as i32).byte_diff();
@ -818,11 +817,44 @@ fn generate_trailing_expected_arguments(
})
}
/// A single child node produced out of the lambda argument and the `->` token.
#[derive(Debug)]
struct FoldedLambdaArguments {
/// Both the lambda argument and the `->` token, as a single [`node::Kind::Token`] node.
child: node::Child,
/// The number of tree nodes that were folded into the `child`.
nodes_replaced: usize,
}
// =========================
// === SpanTree for Tree ===
// =========================
/// Fold the lambda arguments into a single [`node::Kind::Token`] node.
/// It is needed to ignore lambda arguments as connection targets, but still generate a valid
/// SpanTree from the lambda body.
fn fold_lambda_arguments(tree: &ast::Tree<Ast>) -> FoldedLambdaArguments {
let is_arrow = |span_info| matches!(span_info, SpanSeed::Token(ast::SpanSeedToken { token }) if token == "->");
let arrow_index = tree.span_info.iter().cloned().position(is_arrow).unwrap_or(0);
let bytes_till_body = tree
.span_info
.iter()
.take(arrow_index + 1)
.map(|raw_span_info| match raw_span_info {
SpanSeed::Space(ast::SpanSeedSpace { space }) => ByteDiff::from(space),
SpanSeed::Token(ast::SpanSeedToken { token }) => ByteDiff::from(token.len()),
SpanSeed::Child(ast::SpanSeedChild { node }) => node.repr_len().to_diff(),
})
.sum::<ByteDiff>();
let size = bytes_till_body;
let kind = node::Kind::Token;
let node = Node::new().with_kind(kind).with_size(size);
let ast_crumbs = vec![TreeCrumb { index: 0 }.into()];
let nodes_replaced = arrow_index + 1;
let child = node::Child {
node,
parent_offset: ByteDiff::from(0),
sibling_offset: ByteDiff::from(0),
ast_crumbs,
};
FoldedLambdaArguments { child, nodes_replaced }
}
fn tree_generate_node(
tree: &ast::Tree<Ast>,
@ -846,7 +878,19 @@ fn tree_generate_node(
let last_token_index =
tree.span_info.iter().rposition(|span| matches!(span, SpanSeed::Token(_)));
for (index, raw_span_info) in tree.span_info.iter().enumerate() {
// If the node is a lambda, we fold the lambda arguments into a single child node,
// and then continue handling the lambda body as usual.
let skip = if tree.type_info == ast::TreeType::Lambda {
let FoldedLambdaArguments { child, nodes_replaced } = fold_lambda_arguments(tree);
parent_offset += child.node.size;
children.push(child);
nodes_replaced
} else {
0
};
for (index, raw_span_info) in tree.span_info.iter().skip(skip).enumerate() {
let index = index + skip;
match raw_span_info {
SpanSeed::Space(ast::SpanSeedSpace { space }) => {
parent_offset += ByteDiff::from(space);
@ -985,6 +1029,7 @@ mod test {
use ast::Crumbs;
use ast::IdMap;
use parser::Parser;
use pretty_assertions::assert_eq;
/// A helper function which removes information about expression id from thw tree rooted at
@ -1150,6 +1195,10 @@ mod test {
#[test]
fn generating_span_tree_for_lambda() {
let parser = Parser::new();
// === Simple lambda ===
let ast = parser.parse_line_ast("foo a-> b + c").unwrap();
let mut tree: SpanTree = ast.generate_tree(&context::Empty).unwrap();
clear_expression_ids(&mut tree.root);
@ -1158,10 +1207,51 @@ mod test {
let expected = TreeBuilder::new(13)
.add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func)
.add_empty_child(3, BeforeArgument(0))
.add_leaf(4, 9, node::Kind::prefix_argument(), PrefixCrumb::Arg)
.add_child(4, 9, node::Kind::prefix_argument(), PrefixCrumb::Arg)
.set_tree_type(Some(ast::TreeType::Lambda))
.add_leaf(0, 3, node::Kind::Token, TreeCrumb { index: 0 })
.add_child(4, 5, node::Kind::argument(), TreeCrumb { index: 3 })
.add_empty_child(0, BeforeArgument(0))
.add_leaf(0, 1, node::Kind::argument(), InfixCrumb::LeftOperand)
.add_leaf(2, 1, node::Kind::Operation, InfixCrumb::Operator)
.add_empty_child(3, BeforeArgument(1))
.add_leaf(4, 1, node::Kind::argument(), InfixCrumb::RightOperand)
.add_empty_child(5, Append)
.done()
.done()
.add_empty_child(13, Append)
.build();
assert_eq!(expected, tree);
// === Lambda with two arguments ===
let ast = parser.parse_line_ast("foo a->b-> a + b").unwrap();
let mut tree: SpanTree = ast.generate_tree(&context::Empty).unwrap();
clear_expression_ids(&mut tree.root);
clear_parameter_infos(&mut tree.root);
let expected = TreeBuilder::new(16)
.add_leaf(0, 3, node::Kind::Operation, PrefixCrumb::Func)
.add_empty_child(3, BeforeArgument(0))
.add_child(4, 12, node::Kind::prefix_argument(), PrefixCrumb::Arg)
.set_tree_type(Some(ast::TreeType::Lambda))
.add_leaf(0, 3, node::Kind::Token, TreeCrumb { index: 0 })
.add_child(3, 9, node::Kind::argument(), TreeCrumb { index: 2 })
.set_tree_type(Some(ast::TreeType::Lambda))
.add_leaf(0, 3, node::Kind::Token, TreeCrumb { index: 0 })
.add_child(4, 5, node::Kind::argument(), TreeCrumb { index: 3 })
.add_empty_child(0, BeforeArgument(0))
.add_leaf(0, 1, node::Kind::argument(), InfixCrumb::LeftOperand)
.add_leaf(2, 1, node::Kind::Operation, InfixCrumb::Operator)
.add_empty_child(3, BeforeArgument(1))
.add_leaf(4, 1, node::Kind::argument(), InfixCrumb::RightOperand)
.add_empty_child(5, Append)
.done()
.done()
.done()
.add_empty_child(16, Append)
.build();
assert_eq!(expected, tree);
}

View File

@ -10,7 +10,6 @@ use crate::notification::logged::UpdateOptions;
use ensogl::application::Application;
// ==============
// === Export ===
// ==============

View File

@ -16,7 +16,6 @@ use crate::notification::js::HandleJsError;
use uuid::Uuid;
// ==============
// === Export ===
// ==============

View File

@ -5,6 +5,7 @@ use crate::index::*;
use crate::prelude::*;
use enso_types::unit;
use std::iter::Sum;
@ -118,6 +119,12 @@ impl SubAssign<Bytes> for ByteDiff {
}
}
impl Sum<ByteDiff> for ByteDiff {
fn sum<I: Iterator<Item = ByteDiff>>(iter: I) -> Self {
iter.fold(ByteDiff(0), |acc, x| acc + x)
}
}
// ================