diff --git a/CHANGELOG.md b/CHANGELOG.md
index 363b6caa9d6b..0c242d4d9b54 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -962,6 +962,7 @@
- [Support runtime checks of intersection types][7769]
- [Merge `Small_Integer` and `Big_Integer` types][7636]
- [Inline type ascriptions][7796]
+- [Always persist `TRACE` level logs to a file][7825]
- [Downloadable VSCode extension][7861]
- [New `project/status` route for reporting LS state][7801]
@@ -1106,6 +1107,7 @@
[7636]: https://github.com/enso-org/enso/pull/7636
[7796]: https://github.com/enso-org/enso/pull/7796
[7801]: https://github.com/enso-org/enso/pull/7801
+[7825]: https://github.com/enso-org/enso/pull/7825
[7861]: https://github.com/enso-org/enso/pull/7861
# Enso 2.0.0-alpha.18 (2021-10-12)
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 =
diff --git a/app/gui2/.gitignore b/app/gui2/.gitignore
index 0c02498e7692..71964f63cabb 100644
--- a/app/gui2/.gitignore
+++ b/app/gui2/.gitignore
@@ -9,6 +9,7 @@ dist
dist-ssr
coverage
*.local
+*.tsbuildinfo
# Editor directories and files
.vscode/*
diff --git a/app/gui2/env.d.ts b/app/gui2/env.d.ts
index d9394cc17246..08eb639e7240 100644
--- a/app/gui2/env.d.ts
+++ b/app/gui2/env.d.ts
@@ -1,3 +1,9 @@
///
declare const PROJECT_MANAGER_URL: string
+
+// This is an augmentation to the built-in `ImportMeta` interface.
+// This file MUST NOT contain any top-level imports.
+interface ImportMeta {
+ vitest: typeof import('vitest') | undefined
+}
diff --git a/app/gui2/index.html b/app/gui2/index.html
index 36f22655d191..dce1cc9575e8 100644
--- a/app/gui2/index.html
+++ b/app/gui2/index.html
@@ -4,7 +4,7 @@
-
Vite App
+ Enso GUI
diff --git a/app/gui2/node.env.d.ts b/app/gui2/node.env.d.ts
index 940588d51398..fe84ae93c4dc 100644
--- a/app/gui2/node.env.d.ts
+++ b/app/gui2/node.env.d.ts
@@ -3,3 +3,9 @@ module 'tailwindcss/nesting' {
declare const plugin: PluginCreator
export default plugin
}
+
+// This is an augmentation to the built-in `ImportMeta` interface.
+// This file MUST NOT contain any top-level imports.
+interface ImportMeta {
+ vitest: typeof import('vitest') | undefined
+}
diff --git a/app/gui2/package.json b/app/gui2/package.json
index 97552b9c1e8f..a7f95c18f7b2 100644
--- a/app/gui2/package.json
+++ b/app/gui2/package.json
@@ -22,28 +22,41 @@
"preinstall": "npm run build-rust-ffi"
},
"dependencies": {
+ "@babel/parser": "^7.22.16",
"@open-rpc/client-js": "^1.8.1",
"@vueuse/core": "^10.4.1",
+ "codemirror": "^6.0.1",
"enso-authentication": "^1.0.0",
+ "install": "^0.13.0",
+ "hash-sum": "^2.0.0",
"isomorphic-ws": "^5.0.0",
"lib0": "^0.2.83",
+ "magic-string": "^0.30.3",
"pinia": "^2.1.6",
"postcss-inline-svg": "^6.0.0",
+ "postcss-nesting": "^12.0.1",
"sha3": "^2.1.4",
+ "sucrase": "^3.34.0",
"vue": "^3.3.4",
+ "vue-codemirror": "^6.1.1",
"ws": "^8.13.0",
+ "y-codemirror.next": "^0.3.2",
"y-protocols": "^1.0.5",
"y-textarea": "^1.0.0",
"y-websocket": "^1.5.0",
"yjs": "^13.6.7"
},
"devDependencies": {
+ "@danmarshall/deckgl-typings": "^4.9.28",
"@eslint/eslintrc": "^2.1.2",
"@eslint/js": "^8.49.0",
"@playwright/test": "^1.37.0",
"@rushstack/eslint-patch": "^1.3.2",
"@tsconfig/node18": "^18.2.0",
+ "@types/d3": "^7.4.0",
+ "@types/hash-sum": "^1.0.0",
"@types/jsdom": "^21.1.1",
+ "@types/mapbox-gl": "^2.7.13",
"@types/node": "^18.17.5",
"@types/shuffle-seed": "^1.1.0",
"@types/ws": "^8.5.5",
@@ -54,6 +67,8 @@
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/test-utils": "^2.4.1",
"@vue/tsconfig": "^0.4.0",
+ "ag-grid-community": "^30.1.0",
+ "ag-grid-enterprise": "^30.1.0",
"esbuild": "^0.19.3",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.16.1",
@@ -62,6 +77,7 @@
"prettier": "^3.0.0",
"prettier-plugin-organize-imports": "^3.2.3",
"shuffle-seed": "^1.1.6",
+ "sql-formatter": "^13.0.0",
"tailwindcss": "^3.2.7",
"typescript": "~5.2.2",
"vite": "^4.4.9",
diff --git a/app/gui2/public/visualizations/GeoMapVisualization.vue b/app/gui2/public/visualizations/GeoMapVisualization.vue
new file mode 100644
index 000000000000..178af1edf246
--- /dev/null
+++ b/app/gui2/public/visualizations/GeoMapVisualization.vue
@@ -0,0 +1,429 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/gui2/public/visualizations/ScatterplotVisualization.vue b/app/gui2/public/visualizations/ScatterplotVisualization.vue
new file mode 100644
index 000000000000..9f602f6da2e2
--- /dev/null
+++ b/app/gui2/public/visualizations/ScatterplotVisualization.vue
@@ -0,0 +1,627 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/gui2/public/visualizations/builtins.ts b/app/gui2/public/visualizations/builtins.ts
new file mode 100644
index 000000000000..a3bbf84ffafc
--- /dev/null
+++ b/app/gui2/public/visualizations/builtins.ts
@@ -0,0 +1,31 @@
+export interface Vec2 {
+ readonly x: number
+ readonly y: number
+}
+
+export interface RGBA {
+ red: number
+ green: number
+ blue: number
+ alpha: number
+}
+
+export interface Theme {
+ getColorForType(type: string): RGBA
+}
+
+export const DEFAULT_THEME: Theme = {
+ getColorForType(type) {
+ let hash = 0
+ for (const c of type) {
+ hash = 0 | (hash * 31 + c.charCodeAt(0))
+ }
+ if (hash < 0) {
+ hash += 0x80000000
+ }
+ const red = (hash >> 24) / 0x180
+ const green = ((hash >> 16) & 0xff) / 0x180
+ const blue = ((hash >> 8) & 0xff) / 0x180
+ return { red, green, blue, alpha: 1 }
+ },
+}
diff --git a/app/gui2/public/visualizations/dependencyTypesPatches.ts b/app/gui2/public/visualizations/dependencyTypesPatches.ts
new file mode 100644
index 000000000000..19cf190c976a
--- /dev/null
+++ b/app/gui2/public/visualizations/dependencyTypesPatches.ts
@@ -0,0 +1,36 @@
+// Fixes and extensions for dependencies' type definitions.
+
+import type * as d3Types from 'd3'
+
+declare module 'd3' {
+ // d3 treats `null` and `undefined` as a selection of 0 elements, so they are a valid selection
+ // for any element type.
+ function select(
+ node: GElement | null | undefined,
+ ): d3Types.Selection
+
+ // These type parameters are present on the original type.
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ interface ScaleSequential