diff --git a/app/gui/language/span-tree/src/action.rs b/app/gui/language/span-tree/src/action.rs index e3eda3377f85..67f9da70d037 100644 --- a/app/gui/language/span-tree/src/action.rs +++ b/app/gui/language/span-tree/src/action.rs @@ -125,16 +125,21 @@ impl<'a> Implementation for node::Ref<'a> { let mut inserted_positional_placeholder_at = None; let new_ast = modify_preserving_id(ast, |ast| { if let Some(mut infix) = extended_infix { - let item = ArgWithOffset { arg: new, offset: DEFAULT_OFFSET }; - let has_target = infix.target.is_some(); let has_arg = infix.args.last().unwrap().operand.is_some(); + let offset = infix + .enumerate_non_empty_operands() + .last() + .map_or(DEFAULT_OFFSET, |op| op.offset); let last_elem = infix.args.last_mut().unwrap(); + let item = ArgWithOffset { arg: new, offset }; + let has_target = infix.target.is_some(); last_elem.offset = DEFAULT_OFFSET; match kind { + ExpectedTarget => infix.target = Some(item), BeforeArgument(0 | 1) if !has_target => infix.target = Some(item), BeforeArgument(idx) => infix.insert_operand(*idx, item), Append if has_arg => infix.push_operand(item), - Append => last_elem.operand = Some(item), + Append | ExpectedOperand => last_elem.operand = Some(item), ExpectedArgument { .. } => unreachable!( "Expected arguments should be filtered out before this if block" ), @@ -183,6 +188,8 @@ impl<'a> Implementation for node::Ref<'a> { }; prefix.args.push(item) } + ExpectedTarget | ExpectedOperand => + unreachable!("Wrong insertion point in method call"), } Ok(prefix.into_ast()) } diff --git a/app/gui/language/span-tree/src/generate.rs b/app/gui/language/span-tree/src/generate.rs index db1ebbcf4188..0f9de627b399 100644 --- a/app/gui/language/span-tree/src/generate.rs +++ b/app/gui/language/span-tree/src/generate.rs @@ -437,7 +437,12 @@ fn generate_node_for_opr_chain( let node = target.arg.generate_node(kind, context)?; Ok((node, target.offset)) } - None => Ok((Node::new().with_kind(InsertionPointType::BeforeArgument(0)), 0)), + None => { + let application_id = this.args.first().and_then(|app| app.infix_id); + let port_id = + application_id.map(|application| PortId::ArgPlaceholder { application, index: 0 }); + Ok((Node::new().with_kind(InsertionPointType::ExpectedTarget).with_port_id(port_id), 0)) + } }; // In this fold we pass last generated node and offset after it, wrapped in Result. @@ -448,6 +453,7 @@ fn generate_node_for_opr_chain( let is_first = i == 0; let is_last = i + 1 == this.args.len(); let has_left = !node.is_insertion_point(); + let has_right = elem.operand.is_some(); let opr_crumbs = elem.crumb_to_operator(has_left); let opr_ast = Located::new(opr_crumbs, elem.operator.ast()); let left_crumbs = if has_left { vec![elem.crumb_to_previous()] } else { vec![] }; @@ -477,7 +483,7 @@ fn generate_node_for_opr_chain( } } - let infix_right_argument_info = if !app_base.uses_method_notation { + let mut infix_right_argument_info = if !app_base.uses_method_notation { app_base.set_call_id(elem.infix_id); app_base.resolve(context).and_then(|mut resolved| { // For resolved infix arguments, the arity should always be 2. First always @@ -515,13 +521,21 @@ fn generate_node_for_opr_chain( }; let argument = gen.generate_ast_node(arg_ast, argument_kind, context)?; - if let Some((index, info)) = infix_right_argument_info { + if let Some((index, info)) = infix_right_argument_info.take() { + argument.node.set_argument_info(info); + argument.node.set_definition_index(index); + } + } else if !app_base.uses_method_notation { + let argument = gen.generate_empty_node(InsertionPointType::ExpectedOperand); + argument.port_id = + elem.infix_id.map(|application| PortId::ArgPlaceholder { application, index: 1 }); + if let Some((index, info)) = infix_right_argument_info.take() { argument.node.set_argument_info(info); argument.node.set_definition_index(index); } } - if is_last && !app_base.uses_method_notation { + if is_last && has_right && !app_base.uses_method_notation { gen.generate_empty_node(InsertionPointType::Append); } @@ -1387,6 +1401,75 @@ mod test { .build(); clear_expression_ids(&mut tree.root); clear_parameter_infos(&mut tree.root); + assert_eq!(tree, expected) + } + + #[test] + fn generate_span_tree_for_unfinished_infix() { + let parser = Parser::new(); + let this_param = |call_id| ArgumentInfo { + name: Some("self".to_owned()), + tp: Some("Any".to_owned()), + call_id, + ..default() + }; + let param1 = |call_id| ArgumentInfo { + name: Some("arg1".to_owned()), + tp: Some("Number".to_owned()), + call_id, + ..default() + }; + + + // === SectionLeft === + let mut id_map = IdMap::default(); + let call_id = id_map.generate(0..2); + let ast = parser.parse_line_ast_with_id_map("2+", id_map).unwrap(); + let invocation_info = + CalledMethodInfo { parameters: vec![this_param(ast.id), param1(ast.id)], ..default() }; + let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info); + let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); + match tree.root_ref().leaf_iter().collect_vec().as_slice() { + [_before, arg0, _opr, arg1] => { + assert_eq!(arg0.argument_info(), Some(&this_param(Some(call_id)))); + assert_eq!(arg1.argument_info(), Some(¶m1(Some(call_id)))); + } + sth_else => panic!("There should be 4 leaves, found: {}", sth_else.len()), + } + let expected = TreeBuilder::new(2) + .add_empty_child(0, BeforeArgument(0)) + .add_leaf(0, 1, node::Kind::argument().indexed(0), SectionLeftCrumb::Arg) + .add_leaf(1, 1, node::Kind::Operation, SectionLeftCrumb::Opr) + .add_empty_child(2, ExpectedOperand) + .build(); + clear_expression_ids(&mut tree.root); + clear_parameter_infos(&mut tree.root); + assert_eq!(tree, expected); + + + // === SectionRight === + let mut id_map = IdMap::default(); + let call_id = id_map.generate(0..2); + let ast = parser.parse_line_ast_with_id_map("+2", id_map).unwrap(); + let invocation_info = + CalledMethodInfo { parameters: vec![this_param(ast.id), param1(ast.id)], ..default() }; + let ctx = MockContext::new_single(ast.id.unwrap(), invocation_info); + let mut tree: SpanTree = SpanTree::new(&ast, &ctx).unwrap(); + match tree.root_ref().leaf_iter().collect_vec().as_slice() { + [arg0, _opr, arg1, _append] => { + assert_eq!(arg0.argument_info(), Some(&this_param(Some(call_id)))); + assert_eq!(arg1.argument_info(), Some(¶m1(Some(call_id)))); + } + sth_else => panic!("There should be 4 leaves, found: {}", sth_else.len()), + } + let expected = TreeBuilder::new(2) + .add_empty_child(0, ExpectedTarget) + .add_leaf(0, 1, node::Kind::Operation, SectionRightCrumb::Opr) + .add_leaf(1, 1, node::Kind::argument().indexed(1), SectionRightCrumb::Arg) + .add_empty_child(2, Append) + .build(); + clear_expression_ids(&mut tree.root); + clear_parameter_infos(&mut tree.root); assert_eq!(tree, expected); } diff --git a/app/gui/language/span-tree/src/node.rs b/app/gui/language/span-tree/src/node.rs index d993d4099730..1f8760e3a89c 100644 --- a/app/gui/language/span-tree/src/node.rs +++ b/app/gui/language/span-tree/src/node.rs @@ -101,9 +101,18 @@ impl Node { pub fn is_positional_insertion_point(&self) -> bool { self.kind.is_positional_insertion_point() } + pub fn is_expected_argument(&self) -> bool { self.kind.is_expected_argument() } + + pub fn is_expected_operand(&self) -> bool { + self.kind.is_expected_operand() + } + + pub fn is_placeholder(&self) -> bool { + self.is_expected_argument() || self.is_expected_operand() + } } diff --git a/app/gui/language/span-tree/src/node/kind.rs b/app/gui/language/span-tree/src/node/kind.rs index c6820b787467..c8c005aadf53 100644 --- a/app/gui/language/span-tree/src/node/kind.rs +++ b/app/gui/language/span-tree/src/node/kind.rs @@ -99,7 +99,7 @@ impl Kind { /// Match the value with `Kind::InsertionPoint{..}` but not /// `Kind::InsertionPoint(ExpectedArgument(_))`. pub fn is_positional_insertion_point(&self) -> bool { - self.is_insertion_point() && !self.is_expected_argument() + self.is_insertion_point() && !self.is_expected_argument() && !self.is_expected_operand() } /// Match the value with `Kind::InsertionPoint(ExpectedArgument(_))`. @@ -107,6 +107,17 @@ impl Kind { matches!(self, Self::InsertionPoint(t) if t.kind.is_expected_argument()) } + /// Check if given kind is an insertino point for expected operand of an unfinished infix. + pub fn is_expected_operand(&self) -> bool { + matches!( + self, + Self::InsertionPoint(InsertionPoint { + kind: InsertionPointType::ExpectedOperand | InsertionPointType::ExpectedTarget, + .. + }) + ) + } + /// Match the argument in a prefix method application. pub fn is_prefix_argument(&self) -> bool { matches!(self, Self::Argument(a) if a.in_prefix_chain) @@ -374,6 +385,10 @@ pub enum InsertionPointType { index: usize, named: bool, }, + /// Expected target of unfinished infix expression. + ExpectedTarget, + /// Expected operand of unfinished infix expression. + ExpectedOperand, } // === Matchers === diff --git a/app/gui/view/graph-editor/src/component/node/input/widget.rs b/app/gui/view/graph-editor/src/component/node/input/widget.rs index a3de4cfe9824..e99eaead7e20 100644 --- a/app/gui/view/graph-editor/src/component/node/input/widget.rs +++ b/app/gui/view/graph-editor/src/component/node/input/widget.rs @@ -1651,7 +1651,7 @@ impl<'a> TreeBuilder<'a> { let ptr_usage = self.pointer_usage.entry(main_ptr).or_default(); let widget_id = main_ptr.to_identity(ptr_usage); - let is_placeholder = span_node.is_expected_argument(); + let is_placeholder = span_node.is_expected_argument() || span_node.is_expected_operand(); let sibling_offset = span_node.sibling_offset.as_usize(); let usage_type = span_node.ast_id.and_then(|id| self.usage_type_map.get(&id)).cloned(); diff --git a/app/gui/view/graph-editor/src/component/node/input/widget/label.rs b/app/gui/view/graph-editor/src/component/node/input/widget/label.rs index cda44503bf10..83c66f1f647c 100644 --- a/app/gui/view/graph-editor/src/component/node/input/widget/label.rs +++ b/app/gui/view/graph-editor/src/component/node/input/widget/label.rs @@ -128,7 +128,7 @@ impl SpanWidget for Widget { } fn configure(&mut self, _: &Config, ctx: ConfigContext) { - let is_placeholder = ctx.span_node.is_expected_argument(); + let is_placeholder = ctx.span_node.is_placeholder(); let expr = ctx.span_expression(); let content = if is_placeholder || ctx.info.connection.is_some() { diff --git a/app/gui/view/graph-editor/src/component/node/input/widget/list_editor.rs b/app/gui/view/graph-editor/src/component/node/input/widget/list_editor.rs index de9a990226c0..91d9d590dd69 100644 --- a/app/gui/view/graph-editor/src/component/node/input/widget/list_editor.rs +++ b/app/gui/view/graph-editor/src/component/node/input/widget/list_editor.rs @@ -574,7 +574,7 @@ impl SpanWidget for Widget { type Config = Config; fn match_node(ctx: &ConfigContext) -> Score { - let is_placeholder = ctx.span_node.is_expected_argument(); + let is_placeholder = ctx.span_node.is_placeholder(); let decl_type = ctx.span_node.kind.tp().map(|t| t.as_str()); let first_decl_is_vector =