diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a10f8d7d36..7d83db2df7b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,7 @@ belongs to the node.][6487]. - [List Editor Widget][6470]. Now you can edit lists by clicking buttons on nodes or by dragging the elements. +- [Visualisations now show a loading spinner while waiting on data.][6512]. - [Fixed text visualisations which were being cut off at the last line.][6421] - [Fixed a bug where, when scrolling or dragging on a full-screen visualization, the view of the graph changed as well.][6530] @@ -153,10 +154,13 @@ execution shortcuts changed from cmd+shift+t/r to cmd+alt+t/r. +- [Fixed a bug where selecting a nested breadcrumb would cause the order of + breadcrumbs to change incorrectly.][6617] [6421]: https://github.com/enso-org/enso/pull/6421 [6530]: https://github.com/enso-org/enso/pull/6530 [6620]: https://github.com/enso-org/enso/pull/6620 +[6617]: https://github.com/enso-org/enso/pull/6617 #### EnsoGL (rendering engine) @@ -224,6 +228,7 @@ [6487]: https://github.com/enso-org/enso/pull/6487 [6341]: https://github.com/enso-org/enso/pull/6341 [6470]: https://github.com/enso-org/enso/pull/6470 +[6512]: https://github.com/enso-org/enso/pull/6512 #### Enso Standard Library @@ -430,6 +435,7 @@ - [Implemented `Column.format` for in-memory `Column`s.][6538] - [Added `at_least_one` flag to `Table.tokenize_to_rows`.][6539] - [Moved `Redshift` connector into a separate `AWS` library.][6550] +- [Added `Date_Range`.][6621] [debug-shortcuts]: https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug @@ -639,6 +645,7 @@ [6538]: https://github.com/enso-org/enso/pull/6538 [6539]: https://github.com/enso-org/enso/pull/6539 [6550]: https://github.com/enso-org/enso/pull/6550 +[6621]: https://github.com/enso-org/enso/pull/6621 #### Enso Compiler diff --git a/Cargo.lock b/Cargo.lock index 97367fa5e9a0..7418b4e4d8c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2582,6 +2582,7 @@ dependencies = [ "ensogl-scrollbar", "ensogl-selector", "ensogl-shadow", + "ensogl-spinner", "ensogl-text", "ensogl-toggle-button", "ensogl-tooltip", @@ -3167,6 +3168,17 @@ dependencies = [ "ensogl-tooltip", ] +[[package]] +name = "ensogl-spinner" +version = "0.1.0" +dependencies = [ + "enso-frp", + "enso-prelude", + "enso-shapely", + "enso-types", + "ensogl-core", +] + [[package]] name = "ensogl-text" version = "0.1.0" diff --git a/app/gui/docs/product/shortcuts.md b/app/gui/docs/product/shortcuts.md index 815a2257e19b..d6933c97ec3b 100644 --- a/app/gui/docs/product/shortcuts.md +++ b/app/gui/docs/product/shortcuts.md @@ -126,7 +126,7 @@ broken and require further investigation. #### Debug | Shortcut | Action | -| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | ctrl + shift + d | Toggle Debug Mode. All actions below are only possible when it is activated. | | ctrl + alt + shift + i | Open the developer console. | | ctrl + alt + shift + r | Reload the visual interface. | @@ -137,4 +137,4 @@ broken and require further investigation. | ctrl + shift + enter | Push a hardcoded breadcrumb without navigating. | | ctrl + shift + arrow up | Pop a breadcrumb without navigating. | | cmd + i | Reload visualizations. To see the effect in the currently shown visualizations, you need to switch to another and switch back. | -| ctrl + shift + b | | Toggle read-only mode. | +| ctrl + shift + b | Toggle read-only mode. | diff --git a/app/gui/src/controller/graph/executed.rs b/app/gui/src/controller/graph/executed.rs index 4fd46904a3cf..f665b5a06f57 100644 --- a/app/gui/src/controller/graph/executed.rs +++ b/app/gui/src/controller/graph/executed.rs @@ -18,6 +18,8 @@ use crate::model::execution_context::VisualizationUpdateData; use double_representation::name::QualifiedName; use engine_protocol::language_server::ExecutionEnvironment; use engine_protocol::language_server::MethodPointer; +use futures::stream; +use futures::TryStreamExt; use span_tree::generate::context::CalledMethodInfo; use span_tree::generate::context::Context; @@ -65,10 +67,10 @@ pub enum Notification { /// The notification from the execution context about the computed value information /// being updated. ComputedValueInfo(model::execution_context::ComputedValueExpressions), - /// Notification emitted when the node has been entered. - EnteredNode(LocalCall), - /// Notification emitted when the node was step out. - SteppedOutOfNode(double_representation::node::Id), + /// Notification emitted when a call stack has been entered. + EnteredStack(Vec), + /// Notification emitted when a number of frames of the call stack have been exited. + ExitedStack(usize), } @@ -196,7 +198,7 @@ impl Handle { .boxed_local(); let streams = vec![value_stream, graph_stream, self_stream, db_stream, update_stream]; - futures::stream::select_all(streams) + stream::select_all(streams) } // Note [Argument Names-related invalidations] @@ -216,29 +218,38 @@ impl Handle { registry.clone_ref().get_type(id) } - /// Enter node by given node ID and method pointer. + /// Enter the given stack of nodes by node ID and method pointer. /// - /// This will cause pushing a new stack frame to the execution context and changing the graph - /// controller to point to a new definition. + /// This will push new stack frames to the execution context and change the graph controller to + /// point to a new definition. /// /// ### Errors /// - Fails if method graph cannot be created (see `graph_for_method` documentation). /// - Fails if the project is in read-only mode. - pub async fn enter_method_pointer(&self, local_call: &LocalCall) -> FallibleResult { + pub async fn enter_stack(&self, stack: Vec) -> FallibleResult { if self.project.read_only() { Err(ReadOnly.into()) } else { - debug!("Entering node {}.", local_call.call); - let method_ptr = &local_call.definition; - let graph = controller::Graph::new_method(&self.project, method_ptr); - let graph = graph.await?; - self.execution_ctx.push(local_call.clone()).await?; - debug!("Replacing graph with {graph:?}."); - self.graph.replace(graph); - debug!("Sending graph invalidation signal."); - self.notifier.publish(Notification::EnteredNode(local_call.clone())).await; - - Ok(()) + let mut successful_calls = Vec::new(); + let result = stream::iter(stack) + .then(|local_call| async { + debug!("Entering node {}.", local_call.call); + self.execution_ctx.push(local_call.clone()).await?; + Ok(local_call) + }) + .map_ok(|local_call| successful_calls.push(local_call)) + .try_collect::<()>() + .await; + if let Some(last_successful_call) = successful_calls.last() { + let graph = + controller::Graph::new_method(&self.project, &last_successful_call.definition) + .await?; + debug!("Replacing graph with {graph:?}."); + self.graph.replace(graph); + debug!("Sending graph invalidation signal."); + self.notifier.publish(Notification::EnteredStack(successful_calls)).await; + } + result } } @@ -255,22 +266,29 @@ impl Handle { Ok(node_info) } - /// Leave the current node. Reverse of `enter_node`. + /// Leave the given number of stack frames. Reverse of `enter_stack`. /// /// ### Errors /// - Fails if this execution context is already at the stack's root or if the parent graph /// cannot be retrieved. /// - Fails if the project is in read-only mode. - pub async fn exit_node(&self) -> FallibleResult { + pub async fn exit_stack(&self, frame_count: usize) -> FallibleResult { if self.project.read_only() { Err(ReadOnly.into()) } else { - let frame = self.execution_ctx.pop().await?; - let method = self.execution_ctx.current_method(); - let graph = controller::Graph::new_method(&self.project, &method).await?; - self.graph.replace(graph); - self.notifier.publish(Notification::SteppedOutOfNode(frame.call)).await; - Ok(()) + let mut successful_pop_count = 0; + let result = stream::iter(0..frame_count) + .then(|_| self.execution_ctx.pop()) + .map_ok(|_| successful_pop_count += 1) + .try_collect::<()>() + .await; + if successful_pop_count > 0 { + let method = self.execution_ctx.current_method(); + let graph = controller::Graph::new_method(&self.project, &method).await?; + self.graph.replace(graph); + self.notifier.publish(Notification::ExitedStack(successful_pop_count)).await; + } + result } } diff --git a/app/gui/src/controller/project.rs b/app/gui/src/controller/project.rs index e78384ead0cd..0cee5ddfc40d 100644 --- a/app/gui/src/controller/project.rs +++ b/app/gui/src/controller/project.rs @@ -189,13 +189,10 @@ impl Project { ) { // Restore the call stack from the metadata. let initial_call_stack = main_module.with_project_metadata(|m| m.call_stack.clone()); - for frame in initial_call_stack { - // Push as many frames as possible. We should not be too concerned about failure here. - // It is to be assumed that metadata can get broken. - if let Err(e) = main_graph.enter_method_pointer(&frame).await { - warn!("Failed to push initial stack frame: {frame:?}: {e}"); - break; - } + // We should not be too concerned about failure here. It is to be assumed that metadata can + // get broken. + if let Err(error) = main_graph.enter_stack(initial_call_stack).await { + warn!("Failed to push initial call stack. {error}"); } } diff --git a/app/gui/src/presenter/graph.rs b/app/gui/src/presenter/graph.rs index b0bba2172c31..28a3d6de59c9 100644 --- a/app/gui/src/presenter/graph.rs +++ b/app/gui/src/presenter/graph.rs @@ -833,8 +833,8 @@ impl Graph { }, executed::Notification::ComputedValueInfo(expressions) => update_expressions.emit(expressions), - executed::Notification::EnteredNode(_) => update_view.emit(()), - executed::Notification::SteppedOutOfNode(_) => update_view.emit(()), + executed::Notification::EnteredStack(_) + | executed::Notification::ExitedStack(_) => update_view.emit(()), } std::future::ready(()) }) diff --git a/app/gui/src/presenter/graph/call_stack.rs b/app/gui/src/presenter/graph/call_stack.rs index 55ae65dd5dba..026f3b4dd123 100644 --- a/app/gui/src/presenter/graph/call_stack.rs +++ b/app/gui/src/presenter/graph/call_stack.rs @@ -33,12 +33,49 @@ impl Model { Self { controller, view, state } } - fn expression_entered(&self, local_call: &view::graph_editor::LocalCall) { - let local_call = LocalCall { - definition: (**local_call.definition).clone(), - call: local_call.call, - }; - self.enter_expression(local_call); + fn push_stack(&self, stack: Vec) { + let store_stack = self.store_updated_stack_task(); + let controller = self.controller.clone_ref(); + executor::global::spawn(async move { + let stack = stack + .into_iter() + .map(|local_call| LocalCall { + definition: (**local_call.definition).clone(), + call: local_call.call, + }) + .collect(); + info!("Entering call stack {stack:?}."); + match controller.enter_stack(stack).await { + Ok(()) => store_stack(), + Err(error) => { + error!("Entering stack failed: {error}."); + let event = "integration::entering_node_failed"; + let field = "error"; + let data = analytics::AnonymousData(|| error.to_string()); + analytics::remote_log_value(event, field, data); + } + } + }); + } + + fn pop_stack(&self, frame_count: usize) { + debug!("Requesting exiting a part of the call stack."); + analytics::remote_log_event("integration::node_exited"); + let controller = self.controller.clone_ref(); + let store_stack = self.store_updated_stack_task(); + executor::global::spawn(async move { + info!("Exiting stack."); + match controller.exit_stack(frame_count).await { + Ok(()) => store_stack(), + Err(error) => { + error!("Exiting stack failed: {error}"); + let event = "integration::exiting_node_failed"; + let field = "error"; + let data = analytics::AnonymousData(|| error.to_string()); + analytics::remote_log_value(event, field, data) + } + } + }); } fn node_entered(&self, node_id: ViewNodeId) { @@ -47,8 +84,22 @@ impl Model { if let Some(call) = self.state.ast_node_id_of_view(node_id) { if let Ok(computed_value) = self.controller.node_computed_value(call) { if let Some(method_pointer) = computed_value.method_call.as_ref() { + let controller = self.controller.clone_ref(); let local_call = LocalCall { call, definition: method_pointer.clone() }; - self.enter_expression(local_call) + let store_stack = self.store_updated_stack_task(); + executor::global::spawn(async move { + info!("Entering expression {local_call:?}."); + match controller.enter_stack(vec![local_call]).await { + Ok(()) => store_stack(), + Err(error) => { + error!("Entering node failed: {error}."); + let event = "integration::entering_node_failed"; + let field = "error"; + let data = analytics::AnonymousData(|| error.to_string()); + analytics::remote_log_value(event, field, data); + } + }; + }); } else { info!("Ignoring request to enter non-enterable node {call}.") } @@ -61,70 +112,34 @@ impl Model { } fn node_exited(&self) { - debug!("Requesting exiting the current node."); - analytics::remote_log_event("integration::node_exited"); - let controller = self.controller.clone_ref(); - let store_stack = self.store_updated_stack_task(); - executor::global::spawn(async move { - info!("Exiting node."); - match controller.exit_node().await { - Ok(()) => - if let Err(err) = store_stack() { - // We cannot really do anything when updating metadata fails. - // Can happen in improbable case of serialization failure. - error!("Failed to store an updated call stack: {err}"); - }, - Err(err) => { - error!("Exiting node failed: {err}"); - - let event = "integration::exiting_node_failed"; - let field = "error"; - let data = analytics::AnonymousData(|| err.to_string()); - analytics::remote_log_value(event, field, data) - } - } - }); - } - - fn enter_expression(&self, local_call: LocalCall) { - let controller = self.controller.clone_ref(); - let store_stack = self.store_updated_stack_task(); - executor::global::spawn(async move { - info!("Entering expression {local_call:?}."); - match controller.enter_method_pointer(&local_call).await { - Ok(()) => - if let Err(err) = store_stack() { - // We cannot really do anything when updating metadata fails. - // Can happen in improbable case of serialization failure. - error!("Failed to store an updated call stack: {err}"); - }, - Err(err) => { - error!("Entering node failed: {err}."); - let event = "integration::entering_node_failed"; - let field = "error"; - let data = analytics::AnonymousData(|| err.to_string()); - analytics::remote_log_value(event, field, data) - } - }; - }); + self.pop_stack(1) } - fn store_updated_stack_task(&self) -> impl FnOnce() -> FallibleResult + 'static { + fn store_updated_stack_task(&self) -> impl FnOnce() { let main_module = self.controller.graph().module.clone_ref(); let controller = self.controller.clone_ref(); move || { let new_call_stack = controller.call_stack(); - main_module.update_project_metadata(|metadata| { + let result = main_module.update_project_metadata(|metadata| { metadata.call_stack = new_call_stack; - }) + }); + if let Err(error) = result { + // We cannot really do anything when updating metadata fails. + // Can happen in improbable case of serialization failure. + error!("Failed to store an updated call stack: {error}"); + } } } - fn add_breadcrumb_in_view(&self, frame: LocalCall) { - let definition = frame.definition.clone().into(); - let call = frame.call; - let local_call = view::graph_editor::LocalCall { call, definition }; - self.view.model.breadcrumbs.push_breadcrumb(local_call); + fn add_breadcrumbs_in_view(&self, stack: Vec) { + let view_stack = stack + .into_iter() + .map(|frame| view::graph_editor::LocalCall { + call: frame.call, + definition: frame.definition.into(), + }) + .collect::>(); + self.view.model.breadcrumbs.push_breadcrumbs(view_stack); } } @@ -158,12 +173,8 @@ impl CallStack { eval view.node_entered ((node) model.node_entered(*node)); eval_ view.node_exited (model.node_exited()); - eval_ breadcrumbs.output.breadcrumb_pop(model.node_exited()); - eval breadcrumbs.output.breadcrumb_push ([model](opt_local_call) { - if let Some(local_call) = opt_local_call { - model.expression_entered(local_call); - } - }); + eval breadcrumbs.output.breadcrumb_push ((stack) model.push_stack(stack.clone())); + eval breadcrumbs.output.breadcrumb_pop ((count) model.pop_stack(*count)); } Self { _network: network, model } @@ -178,8 +189,9 @@ impl CallStack { spawn_stream_handler(weak, graph_notifications, move |notification, model| { info!("Received controller notification {notification:?}"); match notification { - Notification::EnteredNode(frame) => model.add_breadcrumb_in_view(frame), - Notification::SteppedOutOfNode(_) => model.view.model.breadcrumbs.pop_breadcrumb(), + Notification::EnteredStack(stack) => model.add_breadcrumbs_in_view(stack), + Notification::ExitedStack(count) => + model.view.model.breadcrumbs.pop_breadcrumbs(count), _ => {} } std::future::ready(()) @@ -188,9 +200,8 @@ impl CallStack { } fn initialize_breadcrumbs(self) -> Self { - for frame in self.model.controller.call_stack() { - self.model.add_breadcrumb_in_view(frame) - } + let stack = self.model.controller.call_stack(); + self.model.add_breadcrumbs_in_view(stack); self } } diff --git a/app/gui/src/presenter/graph/visualization.rs b/app/gui/src/presenter/graph/visualization.rs index 9bf1036bbf03..2d8ee8d20de5 100644 --- a/app/gui/src/presenter/graph/visualization.rs +++ b/app/gui/src/presenter/graph/visualization.rs @@ -265,8 +265,8 @@ impl Visualization { spawn_stream_handler(weak, notifications, move |notification, model| { match notification { Notification::Graph(graph::Notification::Invalidate) - | Notification::EnteredNode(_) - | Notification::SteppedOutOfNode(_) => match graph_controller.graph().nodes() { + | Notification::EnteredStack(_) + | Notification::ExitedStack(_) => match graph_controller.graph().nodes() { Ok(nodes) => { let nodes_set = nodes.into_iter().map(|n| n.id()).collect(); model.manager.retain_visualizations(&nodes_set); diff --git a/app/gui/view/documentation/src/lib.rs b/app/gui/view/documentation/src/lib.rs index 1436b07675a7..cec832c739ed 100644 --- a/app/gui/view/documentation/src/lib.rs +++ b/app/gui/view/documentation/src/lib.rs @@ -188,7 +188,7 @@ impl Model { } /// Load an HTML file into the documentation view when user is waiting for data to be received. - /// TODO(#184315201): This should be replaced with a EnsoGL spinner. + /// TODO(#5214): This should be replaced with a EnsoGL spinner. fn load_waiting_screen(&self) { let spinner = include_str!("../assets/spinner.html"); self.inner_dom.dom().set_inner_html(spinner) diff --git a/app/gui/view/examples/text-grid-visualization/src/lib.rs b/app/gui/view/examples/text-grid-visualization/src/lib.rs index f5f0494e016f..38aa43587507 100644 --- a/app/gui/view/examples/text-grid-visualization/src/lib.rs +++ b/app/gui/view/examples/text-grid-visualization/src/lib.rs @@ -73,7 +73,7 @@ fn init(app: &Application) { font_tag.set_attribute_or_warn("rel", "stylesheet"); font_tag.set_attribute_or_warn("media", "screen"); font_tag.set_attribute_or_warn("type", "text/css"); - font_tag.set_attribute_or_warn("href", "https://fontlibrary.org/face/dejavu-sans-mono"); + font_tag.set_attribute_or_warn("href", "https://fonts.cdnfonts.com/css/dejavu-sans-mono"); web::document .head() diff --git a/app/gui/view/graph-editor/src/builtin/visualization/java_script/heatmap.js b/app/gui/view/graph-editor/src/builtin/visualization/java_script/heatmap.js index 481480c5e23c..86dcfbb05370 100644 --- a/app/gui/view/graph-editor/src/builtin/visualization/java_script/heatmap.js +++ b/app/gui/view/graph-editor/src/builtin/visualization/java_script/heatmap.js @@ -1,7 +1,7 @@ /** Heatmap Visualization. */ // TODO refactor this to avoid loading on startup. See issue #985 . loadScript('https://d3js.org/d3.v4.min.js') -loadStyle('https://fontlibrary.org/face/dejavu-sans-mono') +loadStyle('https://fonts.cdnfonts.com/css/dejavu-sans-mono') /** * A d3.js heatmap visualization. diff --git a/app/gui/view/graph-editor/src/builtin/visualization/java_script/helpers/loading.js b/app/gui/view/graph-editor/src/builtin/visualization/java_script/helpers/loading.js index ce1eee0f6fe8..0c90a8f066d1 100644 --- a/app/gui/view/graph-editor/src/builtin/visualization/java_script/helpers/loading.js +++ b/app/gui/view/graph-editor/src/builtin/visualization/java_script/helpers/loading.js @@ -3,6 +3,7 @@ */ function loadScript(url, onload) { let script = document.createElement('script') + script.crossOrigin = 'anonymous' script.onload = onload script.src = url @@ -14,6 +15,7 @@ function loadScript(url, onload) { */ function loadStyle(url, onload) { let style = document.createElement('link') + style.crossOrigin = 'anonymous' style.onload = onload style.href = url style.rel = 'stylesheet' diff --git a/app/gui/view/graph-editor/src/builtin/visualization/java_script/histogram.js b/app/gui/view/graph-editor/src/builtin/visualization/java_script/histogram.js index 942cddc12bb9..58483c130033 100644 --- a/app/gui/view/graph-editor/src/builtin/visualization/java_script/histogram.js +++ b/app/gui/view/graph-editor/src/builtin/visualization/java_script/histogram.js @@ -1,7 +1,7 @@ /** Histogram Visualization. */ // TODO refactor this to avoid loading on startup. See issue #985 . loadScript('https://d3js.org/d3.v4.min.js') -loadStyle('https://fontlibrary.org/face/dejavu-sans-mono') +loadStyle('https://fonts.cdnfonts.com/css/dejavu-sans-mono') let shortcuts = { zoomIn: e => (e.ctrlKey || e.metaKey) && e.key === 'z', diff --git a/app/gui/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js b/app/gui/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js index 986f9e9a676e..c669f6ea5b75 100644 --- a/app/gui/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js +++ b/app/gui/view/graph-editor/src/builtin/visualization/java_script/scatterPlot.js @@ -1,7 +1,7 @@ /** ScatterPlot Visualization. */ // TODO refactor this to avoid loading on startup. See issue #985 . loadScript('https://d3js.org/d3.v4.min.js') -loadStyle('https://fontlibrary.org/face/dejavu-sans-mono') +loadStyle('https://fonts.cdnfonts.com/css/dejavu-sans-mono') let shortcuts = { zoomIn: e => (e.ctrlKey || e.metaKey) && e.key === 'z', diff --git a/app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js b/app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js index 49d5fa889792..0abc083096e1 100644 --- a/app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js +++ b/app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js @@ -28,7 +28,19 @@ class TableVisualization extends Visualization { constructor(data) { super(data) - this.setPreprocessor('Standard.Visualization.Table.Visualization', 'prepare_visualization') + this.setRowLimitAndPage(1000, 0) + } + + setRowLimitAndPage(row_limit, page) { + if (this.row_limit !== row_limit || this.page !== page) { + this.row_limit = row_limit + this.page = page + this.setPreprocessor( + 'Standard.Visualization.Table.Visualization', + 'prepare_visualization', + this.row_limit.toString() + ) + } } onDataReceived(data) { @@ -79,21 +91,46 @@ class TableVisualization extends Visualization { return content } + function cellRenderer(params) { + if (params.value === null) { + return 'Nothing' + } else if (params.value === undefined) { + return '' + } else if (params.value === '') { + return 'Empty' + } + return params.value.toString() + } + if (!this.tabElem) { while (this.dom.firstChild) { this.dom.removeChild(this.dom.lastChild) } const style = - '.ag-theme-alpine { --ag-grid-size: 3px; --ag-list-item-height: 20px; display: inline; }' + '.ag-theme-alpine { --ag-grid-size: 3px; --ag-list-item-height: 20px; display: inline; }\n' + + '.vis-status-bar { height: 20x; background-color: white; font-size:14px; white-space:nowrap; padding: 0 5px; overflow:hidden; border-radius: 16px }\n' + + '.vis-status-bar > button { width: 12px; margin: 0 2px; display: none }\n' + + '.vis-tbl-grid { height: calc(100% - 20px); width: 100%; }\n' const styleElem = document.createElement('style') styleElem.innerHTML = style this.dom.appendChild(styleElem) + const statusElem = document.createElement('div') + statusElem.setAttributeNS(null, 'id', 'vis-tbl-status') + statusElem.setAttributeNS(null, 'class', 'vis-status-bar') + this.dom.appendChild(statusElem) + this.statusElem = statusElem + + const gridElem = document.createElement('div') + gridElem.setAttributeNS(null, 'id', 'vis-tbl-grid') + gridElem.className = 'vis-tbl-grid' + this.dom.appendChild(gridElem) + const tabElem = document.createElement('div') tabElem.setAttributeNS(null, 'id', 'vis-tbl-view') tabElem.setAttributeNS(null, 'class', 'scrollable ag-theme-alpine') - this.dom.appendChild(tabElem) + gridElem.appendChild(tabElem) this.tabElem = tabElem this.agGridOptions = { @@ -106,6 +143,7 @@ class TableVisualization extends Visualization { resizable: true, minWidth: 25, headerValueGetter: params => params.colDef.field, + cellRenderer: cellRenderer, }, onColumnResized: e => this.lockColumnSize(e), } @@ -198,28 +236,106 @@ class TableVisualization extends Visualization { dataTruncated = parsedData.all_rows_count !== rowData.length } - // If the table contains more rows than an upper limit, the engine will send only some of all rows. + // Update Status Bar + this.updateStatusBarControls( + parsedData.all_rows_count === undefined ? 1 : parsedData.all_rows_count, + dataTruncated + ) + // If data is truncated, we cannot rely on sorting/filtering so will disable. - // A pinned row is added to tell the user the row count and that filter/sort is disabled. - const col_span = '__COL_SPAN__' - if (dataTruncated) { - columnDefs[0].colSpan = p => p.data[col_span] || 1 - } this.agGridOptions.defaultColDef.filter = !dataTruncated this.agGridOptions.defaultColDef.sortable = !dataTruncated this.agGridOptions.api.setColumnDefs(columnDefs) - if (dataTruncated) { - const field = columnDefs[0].field - const extraRow = { - [field]: `Showing ${rowData.length} of ${parsedData.all_rows_count} rows. Sorting and filtering disabled.`, - [col_span]: columnDefs.length, - } - this.agGridOptions.api.setPinnedTopRowData([extraRow]) - } this.agGridOptions.api.setRowData(rowData) this.updateTableSize(this.dom.getAttributeNS(null, 'width')) } + makeOption(value, label) { + const optionElem = document.createElement('option') + optionElem.value = value + optionElem.appendChild(document.createTextNode(label)) + return optionElem + } + + makeButton(label, onclick) { + const buttonElem = document.createElement('button') + buttonElem.name = label + buttonElem.appendChild(document.createTextNode(label)) + buttonElem.addEventListener('click', onclick) + return buttonElem + } + + // Updates the status bar to reflect the current row limit and page, shown at top of the visualization. + // - Creates the row dropdown and page buttons. + // - Updated the row counts and filter available options. + updateStatusBarControls(all_rows_count, dataTruncated) { + const pageLimit = Math.ceil(all_rows_count / this.row_limit) + if (this.page > pageLimit) { + this.page = pageLimit + } + + if (this.statusElem.childElementCount === 0) { + this.statusElem.appendChild( + this.makeButton('«', () => this.setRowLimitAndPage(this.row_limit, 0)) + ) + this.statusElem.appendChild( + this.makeButton('‹', () => this.setRowLimitAndPage(this.row_limit, this.page - 1)) + ) + + const selectElem = document.createElement('select') + selectElem.name = 'row-limit' + selectElem.addEventListener('change', e => { + this.setRowLimitAndPage(e.target.value, this.page) + }) + this.statusElem.appendChild(selectElem) + + const rowCountSpanElem = document.createElement('span') + this.statusElem.appendChild(rowCountSpanElem) + + this.statusElem.appendChild( + this.makeButton('›', () => this.setRowLimitAndPage(this.row_limit, this.page + 1)) + ) + this.statusElem.appendChild( + this.makeButton('»', () => this.setRowLimitAndPage(this.row_limit, pageLimit - 1)) + ) + } + + // Enable/Disable Page buttons + this.statusElem.children.namedItem('«').disabled = this.page === 0 + this.statusElem.children.namedItem('‹').disabled = this.page === 0 + this.statusElem.children.namedItem('›').disabled = this.page === pageLimit - 1 + this.statusElem.children.namedItem('»').disabled = this.page === pageLimit - 1 + + // Update row limit dropdown and row count + const rowCountElem = this.statusElem.getElementsByTagName('span')[0] + const rowLimitElem = this.statusElem.children.namedItem('row-limit') + if (all_rows_count > 1000) { + rowLimitElem.style.display = 'inline-block' + const rowCounts = [1000, 2500, 5000, 10000, 25000, 50000, 100000].filter( + r => r <= all_rows_count + ) + if ( + all_rows_count < rowCounts[rowCounts.length - 1] && + rowCounts.indexOf(all_rows_count) === -1 + ) { + rowCounts.push(all_rows_count) + } + rowLimitElem.innerHTML = '' + rowCounts.forEach(r => { + const option = this.makeOption(r, r.toString()) + rowLimitElem.appendChild(option) + }) + rowLimitElem.value = this.row_limit + + rowCountElem.innerHTML = dataTruncated + ? ` of ${all_rows_count} rows (Sorting/Filtering disabled).` + : ` rows.` + } else { + rowLimitElem.style.display = 'none' + rowCountElem.innerHTML = all_rows_count === 1 ? '1 row.' : `${all_rows_count} rows.` + } + } + updateTableSize(clientWidth) { // Check the grid has been initialised and return if not. if (!this.agGridOptions) { diff --git a/app/gui/view/graph-editor/src/component/breadcrumbs.rs b/app/gui/view/graph-editor/src/component/breadcrumbs.rs index 17ba6ff12ff3..cf825a7787e2 100644 --- a/app/gui/view/graph-editor/src/component/breadcrumbs.rs +++ b/app/gui/view/graph-editor/src/component/breadcrumbs.rs @@ -53,31 +53,19 @@ const TEXT_Y_OFFSET: f32 = 2.0; -// ======================== -// === RelativePosition === -// ======================== - -#[derive(Debug, Clone, Copy)] -enum RelativePosition { - Left, - Right, -} - - - // =========== // === Frp === // =========== ensogl::define_endpoints! { Input { - /// Pushes a new breadcrumb after the selected breadcrumb. If the pushed breadcrumb already - /// exists as the next one of the stack, it's just selected. If the next breadcrumb isn't - /// the breadcrumb being pushed, any existing breadcrumb following the currently selected - /// breadcrumb is removed from the panel. - push_breadcrumb (Option), - /// Pops the selected breadcrumb. - pop_breadcrumb (), + /// Pushes new breadcrumbs after the selected breadcrumb. Any breadcrumbs that are already + /// part of the stack are ignored. If the next breadcrumb isn't part of the breadcrumbs + /// being pushed, any existing breadcrumb following the currently selected breadcrumb is + /// removed from the panel. + push_breadcrumbs (Vec), + /// Pops the given number of breadcrumbs. + pop_breadcrumbs (usize), /// Signalizes a mouse press happened outside the breadcrumb panel. It's used to finish /// project renaming, committing the name in text field. outside_press (), @@ -103,16 +91,16 @@ ensogl::define_endpoints! { set_read_only(bool), } Output { - /// Signalizes when a new breadcrumb is pushed. - breadcrumb_push (Option), - /// Signalizes when a breadcrumb is popped. - breadcrumb_pop (), + /// Signalizes new breadcrumbs to be pushed. + breadcrumb_push (Vec), + /// Signalizes how many breadcrumbs to pop. + breadcrumb_pop (usize), /// Signalizes when project name is changed. project_name (String), /// Signalizes when a breadcrumb is selected, returning a tuple with the amount of /// breadcrumbs to be popped, in case the selection happens on the left of the currently /// selected breadcrumb, or else a vector of existing breadcrumbs to be pushed. - breadcrumb_select ((usize,Vec>)), + breadcrumb_select ((usize, Vec)), /// Indicates the pointer style that should be shown based on the interactions with the /// breadcrumb. pointer_style (cursor::Style), @@ -251,7 +239,7 @@ impl BreadcrumbsModel { /// Selects the breadcrumb identified by its `index` and returns `(popped_count,local_calls)`, /// where `popped_count` is the number of breadcrumbs in the right side of `index` that needs to /// be popped or a list of `LocalCall`s identifying the breadcrumbs we need to push. - fn select_breadcrumb(&self, index: usize) -> (usize, Vec>) { + fn select_breadcrumb(&self, index: usize) -> (usize, Vec) { debug!("Selecting breadcrumb #{index}."); let current_index = self.current_index.get(); match index.cmp(¤t_index) { @@ -271,8 +259,8 @@ impl BreadcrumbsModel { }) .as_ref() .cloned(); - if info.is_some() { - local_calls.push(info); + if let Some(local_call) = info { + local_calls.push(local_call); } else { error!("LocalCall info is not present."); self.remove_breadcrumbs_history_beginning_from(index); @@ -284,34 +272,34 @@ impl BreadcrumbsModel { } } - /// Pushes a breadcrumb and returns the index of the previously selected breadcrumb and the - /// index of the newly selected one in the form of (old,new). - fn push_breadcrumb(&self, local_call: &Option) -> Option<(usize, usize)> { - local_call.as_ref().map(|local_call| { + /// Pushes breadcrumbs and returns the index of the previously selected breadcrumb and the + /// index of the newly selected one in the form of (old, new). + fn push_breadcrumbs(&self, stack: &[LocalCall]) -> (usize, usize) { + let old_index = self.current_index.get(); + for (substack_index, local_call) in stack.iter().enumerate() { let method_pointer = &local_call.definition; let expression_id = &local_call.call; - let old_index = self.current_index.get(); - let new_index = old_index + 1; + let breadcrumb_index = old_index + substack_index; let breadcrumb_exists = self .breadcrumbs - .borrow_mut() - .get(old_index) + .borrow() + .get(breadcrumb_index) .contains_if(|breadcrumb| breadcrumb.info.expression_id == *expression_id); if breadcrumb_exists { debug!("Entering an existing {} breadcrumb.", method_pointer.name); } else { debug!("Creating a new {} breadcrumb.", method_pointer.name); - self.remove_breadcrumbs_history_beginning_from(self.current_index.get()); + self.remove_breadcrumbs_history_beginning_from(breadcrumb_index); + let new_index = breadcrumb_index + 1; let breadcrumb = Breadcrumb::new(&self.app, method_pointer, expression_id); let network = &breadcrumb.frp.network; - let breadcrumb_index = new_index; let frp_inputs = &self.frp_inputs; frp::extend! { network eval_ breadcrumb.frp.outputs.clicked( - frp_inputs.select_breadcrumb.emit(breadcrumb_index); + frp_inputs.select_breadcrumb.emit(new_index); ); } @@ -320,10 +308,11 @@ impl BreadcrumbsModel { self.breadcrumbs_container.add_child(&breadcrumb); self.breadcrumbs.borrow_mut().push(breadcrumb); } - self.current_index.set(new_index); - self.update_layout(); - (old_index, new_index) - }) + } + let new_index = old_index + stack.len(); + self.current_index.set(new_index); + self.update_layout(); + (old_index, new_index) } /// Selects the breadcrumb, without signalizing the controller, identified by its `index` and @@ -331,57 +320,54 @@ impl BreadcrumbsModel { /// the right side of `index` that needs to be popped, or a list of `LocalCall`s identifying the /// breadcrumbs we need to push. fn debug_select_breadcrumb(&self, index: usize) -> (usize, Vec>) { - self.select_breadcrumb(index) + let (pop_count, stack_to_push) = self.select_breadcrumb(index); + let stack_to_push = stack_to_push.into_iter().map(Some).collect(); + (pop_count, stack_to_push) } /// Pushes a breadcrumb, without signalizing the controller, and returns the index of the /// previously selected breadcrumb, and the index of the newly selected one in the form of /// `(old,new)`. - fn debug_push_breadcrumb(&self, local_call: &Option) -> Option<(usize, usize)> { + fn debug_push_breadcrumb(&self, local_call: &Option) -> (usize, usize) { let is_new_breadcrumb = local_call.is_none(); - let local_call = local_call.clone().or_else(|| { + let local_call = local_call.clone().unwrap_or_else(|| { let defined_on_type = default(); let module = default(); let name = "Hardcoded".to_string(); let method_pointer = MethodPointer { module, defined_on_type, name }; let definition = method_pointer.into(); let call = uuid::Uuid::new_v4(); - Some(LocalCall { call, definition }) + LocalCall { call, definition } }); - let result = self.push_breadcrumb(&local_call); - + let (old_index, new_index) = self.push_breadcrumbs(&[local_call]); if is_new_breadcrumb { - result.as_ref().map(|(_, new_index)| { - self.get_breadcrumb(*new_index).map(|breadcrumb| { - let new_index = *new_index; - let network = &breadcrumb.frp.network; - let frp_inputs = &self.frp_inputs; - frp::extend! { network - eval_ breadcrumb.frp.outputs.clicked( - frp_inputs.debug_select_breadcrumb.emit(new_index); - ); - } - }) - }); + if let Some(breadcrumb) = self.get_breadcrumb(new_index) { + let network = &breadcrumb.frp.network; + let frp_inputs = &self.frp_inputs; + frp::extend! { network + eval_ breadcrumb.frp.outputs.clicked( + frp_inputs.debug_select_breadcrumb.emit(new_index); + ); + } + } } - result + (old_index, new_index) } /// Pops a breadcrumb, without signalizing the controller, and returns the index of the /// previously selected breadcrumb, and the index of the newly selected one in the form of /// `(old,new)`. fn debug_pop_breadcrumb(&self) -> Option<(usize, usize)> { - self.pop_breadcrumb() + self.pop_breadcrumbs(1) } - /// Pops a breadcrumb and returns the index of the previously selected breadcrumb, and the - /// index of the newly selected one in the form of (old,new). - fn pop_breadcrumb(&self) -> Option<(usize, usize)> { - debug!("Popping {}", self.current_index.get()); + /// Pops the given number of breadcrumbs and returns the index of the previously selected + /// breadcrumb, and the index of the newly selected one in the form of (old, new). + fn pop_breadcrumbs(&self, count: usize) -> Option<(usize, usize)> { (self.current_index.get() > 0).as_option().map(|_| { - debug!("Popping breadcrumb view."); let old_index = self.current_index.get(); - let new_index = old_index - 1; + let new_index = old_index - count; + debug!("Popping {count} breadcrumbs, from {old_index} to {new_index} (excl.)."); self.current_index.set(new_index); self.update_layout(); (old_index, new_index) @@ -395,6 +381,26 @@ impl BreadcrumbsModel { } self.update_layout(); } + + fn update_selection(&self, old_index: usize, new_index: usize) { + // Deselect breadcrumbs between the old index (incl.) and the new index (excl.) + let indices_to_deselect = + if new_index > old_index { old_index..new_index } else { new_index + 1..old_index + 1 }; + for index_to_deselect in indices_to_deselect { + if let Some(breadcrumb) = self.get_breadcrumb(index_to_deselect) { + breadcrumb.frp.deselect.emit((index_to_deselect, new_index)); + } else { + self.project_name.frp.deselect.emit(()); + } + } + // Select new breadcrumb + if let Some(breadcrumb) = self.get_breadcrumb(new_index) { + breadcrumb.frp.select.emit(()); + breadcrumb.frp.fade_in.emit(()); + } else { + self.project_name.frp.select.emit(()); + } + } } impl display::Object for BreadcrumbsModel { @@ -437,36 +443,15 @@ impl Breadcrumbs { // === Stack Operations === - push_indices <= frp.push_breadcrumb.map(f!((local_call) - model.push_breadcrumb(local_call)) - ); - pop_indices <= frp.pop_breadcrumb.map(f_!(model.pop_breadcrumb())); - debug_push_indices <= frp.input.debug_push_breadcrumb.map(f!((local_call) + push_indices <- frp.push_breadcrumbs.map(f!((stack) model.push_breadcrumbs(stack))); + pop_indices <= frp.pop_breadcrumbs.map(f!((count) model.pop_breadcrumbs(*count))); + debug_push_indices <- frp.input.debug_push_breadcrumb.map(f!((local_call) model.debug_push_breadcrumb(local_call) )); debug_pop_indices <= frp.input.debug_pop_breadcrumb.map (f_!(model.debug_pop_breadcrumb())); - indices <- any4(&push_indices,&pop_indices,&debug_push_indices,&debug_pop_indices); - old_breadcrumb <- indices.map(f!([model] (indices) { - (Some(*indices),model.get_breadcrumb(indices.0)) - })); - new_breadcrumb <- indices.map(f!((indices) model.get_breadcrumb(indices.1))); - eval old_breadcrumb([model] ((indices,breadcrumb)) { - if let Some(breadcrumb) = breadcrumb.as_ref() { - indices.map(|indices| breadcrumb.frp.deselect.emit((indices.0,indices.1))); - } else { - model.project_name.frp.deselect.emit(()); - } - }); - eval new_breadcrumb([model] (breadcrumb) { - if let Some(breadcrumb) = breadcrumb.as_ref() { - breadcrumb.frp.select.emit(()); - breadcrumb.frp.fade_in.emit(()); - } else { - model.project_name.frp.select.emit(()); - } - }); + eval indices (((old_index, new_index)) model.update_selection(*old_index, *new_index)); // === Project Name === @@ -489,24 +474,23 @@ impl Breadcrumbs { frp.select_breadcrumb <+ model.project_name.frp.output.mouse_down.constant(0); model.project_name.frp.outside_press <+ frp.outside_press; - popped_count <= frp.output.breadcrumb_select.map(|selected| (0..selected.0).collect_vec()); - local_calls <= frp.output.breadcrumb_select.map(|selected| selected.1.clone()); - frp.source.breadcrumb_pop <+ popped_count.constant(()); - frp.source.breadcrumb_push <+ local_calls; + breadcrumbs_to_pop_count <- frp.output.breadcrumb_select.filter_map(|(pop_count, _)| + (*pop_count > 0).then_some(*pop_count) + ); + breadcrumbs_to_push <- frp.output.breadcrumb_select.filter_map(|(_, breadcrumbs_to_push)| + (!breadcrumbs_to_push.is_empty()).as_some_from(|| breadcrumbs_to_push.clone()) + ); + frp.source.breadcrumb_pop <+ breadcrumbs_to_pop_count; + frp.source.breadcrumb_push <+ breadcrumbs_to_push; - // === Select === + // === Debug Select === - selected_project_name <- model.project_name.frp.output.mouse_down.map(f_!([model] - model.debug_select_breadcrumb(0)) - ); - selected_breadcrumb <- frp.input.debug_select_breadcrumb.map(f!((index) + debug_selected_breadcrumb <- frp.input.debug_select_breadcrumb.map(f!((index) model.debug_select_breadcrumb(*index)) ); - selected <- any(&selected_project_name,&selected_breadcrumb); - - popped_count <= selected.map(|selected| (0..selected.0).collect_vec()); - local_calls <= selected.map(|selected| selected.1.clone()); + popped_count <= debug_selected_breadcrumb.map(|selected| (0..selected.0).collect_vec()); + local_calls <= debug_selected_breadcrumb.map(|selected| selected.1.clone()); frp.input.debug_pop_breadcrumb <+ popped_count.constant(()); frp.input.debug_push_breadcrumb <+ local_calls; diff --git a/app/gui/view/graph-editor/src/component/breadcrumbs/breadcrumb.rs b/app/gui/view/graph-editor/src/component/breadcrumbs/breadcrumb.rs index 35baf10ed1a7..51bcb78445fa 100644 --- a/app/gui/view/graph-editor/src/component/breadcrumbs/breadcrumb.rs +++ b/app/gui/view/graph-editor/src/component/breadcrumbs/breadcrumb.rs @@ -7,7 +7,6 @@ use crate::component::breadcrumbs; use crate::component::breadcrumbs::project_name::LINE_HEIGHT; use crate::MethodPointer; -use super::RelativePosition; use super::GLYPH_WIDTH; use super::HORIZONTAL_MARGIN; use super::TEXT_SIZE; @@ -116,6 +115,20 @@ mod separator { +// ======================== +// === RelativePosition === +// ======================== + +/// The position of this breadcrumb relative to the selected breadcrumb. We use this to determine +/// the color. +#[derive(Debug, Clone, Copy)] +enum RelativePosition { + Left, + Right, +} + + + // ================== // === Animations === // ================== 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 a63d70cb42e2..7f8fa09ea7ea 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 @@ -306,7 +306,9 @@ impl Configuration { } else { Self::always(label::Config::default()) }, - Kind::Token | Kind::Operation if !has_children => Self::inert(label::Config::default()), + Kind::Operation if !has_children => + Self::maybe_with_port(label::Config::default(), is_directly_connected), + Kind::Token if !has_children => Self::inert(label::Config::default()), Kind::NamedArgument => Self::inert(hierarchy::Config), Kind::InsertionPoint(_) => Self::maybe_with_port(insertion_point::Config, is_directly_connected), diff --git a/app/gui/view/graph-editor/src/component/visualization/container.rs b/app/gui/view/graph-editor/src/component/visualization/container.rs index cfdbeb15ce49..081c1ed1b13f 100644 --- a/app/gui/view/graph-editor/src/component/visualization/container.rs +++ b/app/gui/view/graph-editor/src/component/visualization/container.rs @@ -20,6 +20,7 @@ use crate::visualization; use action_bar::ActionBar; use enso_frp as frp; use ensogl::application::Application; +use ensogl::data::color::Rgba; use ensogl::display; use ensogl::display::scene; use ensogl::display::scene::Scene; @@ -162,10 +163,11 @@ ensogl::define_endpoints! { pub struct View { display_object: display::object::Instance, - background: background::View, - overlay: overlay::View, - background_dom: DomSymbol, - scene: Scene, + background: background::View, + overlay: overlay::View, + background_dom: DomSymbol, + scene: Scene, + loading_spinner: ensogl_component::spinner::View, } impl View { @@ -176,16 +178,38 @@ impl View { let overlay = overlay::View::new(); display_object.add_child(&background); display_object.add_child(&overlay); + let div = web::document.create_div_or_panic(); + let background_dom = DomSymbol::new(&div); + display_object.add_child(&background_dom); + let loading_spinner = ensogl_component::spinner::View::new(); ensogl::shapes_order_dependencies! { scene => { background -> overlay; + background -> ensogl_component::spinner; } }; + Self { display_object, background, overlay, background_dom, scene, loading_spinner }.init() + } + + fn set_layer(&self, layer: visualization::Layer) { + layer.apply_for_html_component(&self.scene, &self.background_dom); + } + + fn show_waiting_screen(&self) { + self.add_child(&self.loading_spinner); + } + + fn disable_waiting_screen(&self) { + self.loading_spinner.unset_parent(); + } + + fn init_background(&self) { + let background = &self.background_dom; // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // system (#795) - let styles = StyleWatch::new(&scene.style_sheet); + let styles = StyleWatch::new(&self.scene.style_sheet); let bg_color = styles.get_color(ensogl_hardcoded_theme::graph_editor::visualization::background); let bg_hex = format!( @@ -196,29 +220,29 @@ impl View { bg_color.alpha ); - let div = web::document.create_div_or_panic(); - let background_dom = DomSymbol::new(&div); // TODO : We added a HTML background to the `View`, because "shape" background was // overlapping the JS visualization. This should be further investigated // while fixing rust visualization displaying. (#796) - background_dom.dom().set_style_or_warn("width", "0"); - background_dom.dom().set_style_or_warn("height", "0"); - background_dom.dom().set_style_or_warn("z-index", "1"); - background_dom.dom().set_style_or_warn("overflow-y", "auto"); - background_dom.dom().set_style_or_warn("overflow-x", "auto"); - background_dom.dom().set_style_or_warn("background", bg_hex); - background_dom.dom().set_style_or_warn("border-radius", "14px"); - shadow::add_to_dom_element(&background_dom, &styles); - display_object.add_child(&background_dom); - - Self { display_object, background, overlay, background_dom, scene }.init() + background.dom().set_style_or_warn("width", "0"); + background.dom().set_style_or_warn("height", "0"); + background.dom().set_style_or_warn("z-index", "1"); + background.dom().set_style_or_warn("overflow-y", "auto"); + background.dom().set_style_or_warn("overflow-x", "auto"); + background.dom().set_style_or_warn("background", bg_hex); + background.dom().set_style_or_warn("border-radius", "14px"); + shadow::add_to_dom_element(background, &styles); } - fn set_layer(&self, layer: visualization::Layer) { - layer.apply_for_html_component(&self.scene, &self.background_dom); + fn init_spinner(&self) { + let spinner = &self.loading_spinner; + spinner.scale.set(5.0); + spinner.rgba.set(Rgba::black().into()) } fn init(self) -> Self { + self.init_background(); + self.init_spinner(); + self.show_waiting_screen(); self.set_layer(visualization::Layer::Default); self.scene.layers.viz.add(&self); self @@ -300,6 +324,8 @@ impl ContainerModel { // FIXME: These 2 lines fix a bug with display objects visible on stage. self.set_visibility(true); self.set_visibility(false); + + self.view.show_waiting_screen(); self } @@ -409,6 +435,7 @@ impl ContainerModel { self.view.background.radius.set(CORNER_RADIUS); self.view.overlay.set_size(size); self.view.background.set_size(size + 2.0 * Vector2(PADDING, PADDING)); + self.view.loading_spinner.set_size(size + 2.0 * Vector2(PADDING, PADDING)); dom.set_style_or_warn("width", format!("{}px", size[0])); dom.set_style_or_warn("height", format!("{}px", size[1])); bg_dom.set_style_or_warn("width", "0"); @@ -584,9 +611,17 @@ impl Container { } vis_definition.clone() })); + + + // === Visualisation Loading Spinner === + + eval_ frp.source.visualisation ( model.view.show_waiting_screen() ); + eval_ frp.set_data ( model.view.disable_waiting_screen() ); + } + // === Selecting Visualization === frp::extend! { network diff --git a/app/ide-desktop/lib/client/src/security.ts b/app/ide-desktop/lib/client/src/security.ts index 81b36206f862..cb038d4228e4 100644 --- a/app/ide-desktop/lib/client/src/security.ts +++ b/app/ide-desktop/lib/client/src/security.ts @@ -8,7 +8,13 @@ import * as electron from 'electron' // ================= /** The list of hosts that the app can access. They are required for user authentication to work. */ -const TRUSTED_HOSTS = ['accounts.google.com', 'accounts.youtube.com', 'github.com'] +const TRUSTED_HOSTS = [ + 'accounts.google.com', + 'accounts.youtube.com', + 'github.com', + 'production-enso-domain.auth.eu-west-1.amazoncognito.com', + 'pb-enso-domain.auth.eu-west-1.amazoncognito.com', +] /** The list of hosts that the app can open external links to. */ const TRUSTED_EXTERNAL_HOSTS = ['discord.gg'] diff --git a/app/ide-desktop/lib/content/esbuild-config.ts b/app/ide-desktop/lib/content/esbuild-config.ts index eb80ad2b9f50..7896bdfe5001 100644 --- a/app/ide-desktop/lib/content/esbuild-config.ts +++ b/app/ide-desktop/lib/content/esbuild-config.ts @@ -135,7 +135,12 @@ export function bundlerOptions(args: Arguments) { GIT_HASH: JSON.stringify(git('rev-parse HEAD')), GIT_STATUS: JSON.stringify(git('status --short --porcelain')), BUILD_INFO: JSON.stringify(BUILD_INFO), + /** Whether the application is being run locally. This enables a service worker that + * properly serves `/index.html` to client-side routes like `/login`. */ IS_DEV_MODE: JSON.stringify(devMode), + /** Overrides the redirect URL for OAuth logins in the production environment. + * This is needed for logins to work correctly under `./run gui watch`. */ + REDIRECT_OVERRIDE: 'undefined', }, sourcemap: true, minify: true, diff --git a/app/ide-desktop/lib/content/src/index.ts b/app/ide-desktop/lib/content/src/index.ts index ea413eb888ff..af9bb17ba35a 100644 --- a/app/ide-desktop/lib/content/src/index.ts +++ b/app/ide-desktop/lib/content/src/index.ts @@ -21,7 +21,7 @@ const ESBUILD_PATH = '/esbuild' /** SSE event indicating a build has finished. */ const ESBUILD_EVENT_NAME = 'change' /** Path to the service worker that resolves all extensionless paths to `/index.html`. - * This service worker is required for client-side routing to work when doing local development. */ + * This service worker is required for client-side routing to work when doing `./run gui watch`. */ const SERVICE_WORKER_PATH = '/serviceWorker.js' /** One second in milliseconds. */ const SECOND = 1000 diff --git a/app/ide-desktop/lib/content/src/serviceWorker.ts b/app/ide-desktop/lib/content/src/serviceWorker.ts index 332b8849acc8..4ccd2ddb0028 100644 --- a/app/ide-desktop/lib/content/src/serviceWorker.ts +++ b/app/ide-desktop/lib/content/src/serviceWorker.ts @@ -16,8 +16,11 @@ declare const self: ServiceWorkerGlobalScope self.addEventListener('fetch', event => { const url = new URL(event.request.url) if (url.hostname === 'localhost' && url.pathname !== '/esbuild') { + const responsePromise = /\/[^.]+$/.test(event.request.url) + ? fetch('/index.html') + : fetch(event.request.url) event.respondWith( - fetch(event.request.url).then(response => { + responsePromise.then(response => { const clonedResponse = new Response(response.body, response) for (const [header, value] of common.COOP_COEP_CORP_HEADERS) { clonedResponse.headers.set(header, value) diff --git a/app/ide-desktop/lib/content/watch.ts b/app/ide-desktop/lib/content/watch.ts index a35afb242d0d..8efc916b0894 100644 --- a/app/ide-desktop/lib/content/watch.ts +++ b/app/ide-desktop/lib/content/watch.ts @@ -13,9 +13,10 @@ import * as dashboardBundler from '../dashboard/esbuild-config' // === Constants === // ================= +/** The path of this file. */ +const THIS_PATH = path.resolve(path.dirname(url.fileURLToPath(import.meta.url))) const PORT = 8080 const HTTP_STATUS_OK = 200 -const THIS_PATH = path.resolve(path.dirname(url.fileURLToPath(import.meta.url))) // =============== // === Watcher === @@ -33,6 +34,7 @@ async function watch() { ...bundler.argumentsFromEnv(), devMode: true, }) + opts.define.REDIRECT_OVERRIDE = JSON.stringify('http://localhost:8080') opts.entryPoints.push({ in: path.resolve(THIS_PATH, 'src', 'serviceWorker.ts'), out: 'serviceWorker', diff --git a/app/ide-desktop/lib/dashboard/esbuild-config.ts b/app/ide-desktop/lib/dashboard/esbuild-config.ts index 1c5debd6da0a..eda5af830718 100644 --- a/app/ide-desktop/lib/dashboard/esbuild-config.ts +++ b/app/ide-desktop/lib/dashboard/esbuild-config.ts @@ -103,7 +103,7 @@ function esbuildPluginGenerateTailwind(): esbuild.Plugin { /** Generate the bundler options. */ export function bundlerOptions(args: Arguments) { - const { outputPath } = args + const { outputPath, devMode } = args const buildOptions = { absWorkingDir: THIS_PATH, bundle: true, @@ -119,9 +119,15 @@ export function bundlerOptions(args: Arguments) { esbuildPluginGenerateTailwind(), ], define: { - // We are defining a constant, so it should be `CONSTANT_CASE`. - // eslint-disable-next-line @typescript-eslint/naming-convention - IS_DEV_MODE: JSON.stringify(args.devMode), + // We are defining constants, so it should be `CONSTANT_CASE`. + /* eslint-disable @typescript-eslint/naming-convention */ + /** Whether the application is being run locally. This enables a service worker that + * properly serves `/index.html` to client-side routes like `/login`. */ + IS_DEV_MODE: JSON.stringify(devMode), + /** Overrides the redirect URL for OAuth logins in the production environment. + * This is needed for logins to work correctly under `./run gui watch`. */ + REDIRECT_OVERRIDE: 'undefined', + /* eslint-enable @typescript-eslint/naming-convention */ }, sourcemap: true, minify: true, diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/config.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/config.ts index 3088d550c654..106a70990645 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/config.ts +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/config.ts @@ -16,8 +16,10 @@ const CLOUD_REDIRECTS = { * The redirect URL must be known ahead of time because it is registered with the OAuth provider * when it is created. In the native app, the port is unpredictable, but this is not a problem * because the native app does not use port-based redirects, but deep links. */ - development: newtype.asNewtype('http://localhost:8081'), - production: newtype.asNewtype('https://cloud.enso.org'), + development: newtype.asNewtype('http://localhost:8080'), + production: newtype.asNewtype( + REDIRECT_OVERRIDE ?? 'https://cloud.enso.org' + ), } /** All possible API URLs, sorted by environment. */ diff --git a/app/ide-desktop/lib/dashboard/watch.ts b/app/ide-desktop/lib/dashboard/watch.ts index 948e3188b334..09456b5e5b71 100644 --- a/app/ide-desktop/lib/dashboard/watch.ts +++ b/app/ide-desktop/lib/dashboard/watch.ts @@ -7,14 +7,14 @@ import chalk from 'chalk' import * as bundler from './esbuild-config' -export const THIS_PATH = path.resolve(path.dirname(url.fileURLToPath(import.meta.url))) - // ================= // === Constants === // ================= -/** This must be port `8081` because it is defined as such in AWS. */ -const PORT = 8081 +/** The path of this file. */ +const THIS_PATH = path.resolve(path.dirname(url.fileURLToPath(import.meta.url))) +/** This must be port `8080` because it is defined as such in AWS. */ +const PORT = 8080 const HTTP_STATUS_OK = 200 // `outputPath` does not have to be a real directory because `write` is `false`, // meaning that files will not be written to the filesystem. diff --git a/app/ide-desktop/lib/types/globals.d.ts b/app/ide-desktop/lib/types/globals.d.ts index 27ca6b15a947..4a937908cbb4 100644 --- a/app/ide-desktop/lib/types/globals.d.ts +++ b/app/ide-desktop/lib/types/globals.d.ts @@ -63,6 +63,9 @@ declare global { const BUILD_INFO: BuildInfo const PROJECT_MANAGER_IN_BUNDLE_PATH: string const IS_DEV_MODE: boolean + // This will be `undefined` when it is not defined by esbuild. + // eslint-disable-next-line no-restricted-syntax + const REDIRECT_OVERRIDE: string | undefined /* eslint-disable @typescript-eslint/naming-convention */ } diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Array.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Array.enso index 70a5e1668893..726f68e4137d 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Array.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Array.enso @@ -241,6 +241,7 @@ type Array If an `Index_Sub_Range`, then the selection is interpreted following the rules of that type. If a `Range`, the selection is specified by two indices, from and to. + @range Index_Sub_Range.default_widget take : (Index_Sub_Range | Range | Integer) -> Vector Any take self range=(Index_Sub_Range.First 1) = Vector.take self range @@ -251,6 +252,7 @@ type Array If an `Index_Sub_Range`, then the selection is interpreted following the rules of that type. If a `Range`, the selection is specified by two indices, from and to. + @range Index_Sub_Range.default_widget drop : (Index_Sub_Range | Range | Integer) -> Vector Any drop self range=(Index_Sub_Range.First 1) = Vector.drop self range @@ -574,7 +576,7 @@ type Array map_with_index self function = Vector.map_with_index self function slice : Integer -> Integer -> Vector Any - slice self start end = @Builtin_Method "Array.slice" + slice self start end = Vector.slice self start end ## Returns the first element of the array that satisfies the predicate or `if_missing` if no elements of the array satisfy it. @@ -745,7 +747,9 @@ type Array reverse : Vector Any reverse self = Vector.reverse self - ## Applies a function to each element of the array. + ## PRIVATE + ADVANCED + Applies a function to each element of the array. Unlike `map`, this method does not return the individual results, therefore it is only useful for side-effecting computations. @@ -760,7 +764,9 @@ type Array each : (Any -> Any) -> Nothing each self f = Vector.each self f - ## Applies a function to each element of the array. + ## PRIVATE + ADVANCED + Applies a function to each element of the array. Arguments: - function: A function to apply that takes an index and an item. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Index_Sub_Range.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Index_Sub_Range.enso index ed5742ece304..2d633f23b83a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Index_Sub_Range.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Index_Sub_Range.enso @@ -5,15 +5,22 @@ import project.Data.Range.Extensions import project.Data.Text.Text import project.Data.Vector.Vector import project.Errors.Common.Index_Out_Of_Bounds +import project.Errors.Common.Type_Error import project.Error.Error import project.Errors.Illegal_Argument.Illegal_Argument +import project.Function.Function import project.Math +import project.Meta import project.Panic.Panic import project.Random import project.Runtime.Ref.Ref from project.Data.Boolean import Boolean, True, False +from project.Metadata.Widget import Single_Choice +from project.Metadata.Choice import Option +import project.Metadata.Display + type Index_Sub_Range ## Select the first `count` items. @@ -40,7 +47,7 @@ type Index_Sub_Range Only ranges with positive step and positive indices are supported. Individual integer indices can be negative which allows for indexing from the end of the collection. - By_Index (indexes : (Integer | Range | Vector (Integer | Range)) = [0]) + By_Index (indexes : (Integer | Range | Vector (Integer | Range))) ## Gets a random sample of entries, without repetitions. @@ -67,6 +74,26 @@ type Index_Sub_Range Index_Sub_Range.Sample count _ -> "Sample " + count.to_display_text Index_Sub_Range.Every step first -> "Every " + step.to_display_text + (if first == 0 then "" else " from " + first.to_display_text) + ## PRIVATE + It includes all constructors of `Index_Sub_Range` but also `Range`, since + the `Index_Sub_Range` type is by default used in sum types containing + `Range` too. + default_options : Vector Option + default_options = + o1 = Option "First" "(Index_Sub_Range.First 1)" + o2 = Option "Last" "(Index_Sub_Range.Last 1)" + o3 = Option "While" "(Index_Sub_Range.While (x-> False))" + o4 = Option "By_Index" "(Index_Sub_Range.By_Index [0])" + o5 = Option "Sample" "(Index_Sub_Range.Sample 10)" + o6 = Option "Every" "(Index_Sub_Range.Every 2)" + o7 = Option "Range" "(Range.new 0 100)" + [o1, o2, o3, o4, o5, o6, o7] + + ## PRIVATE + default_widget : Single_Choice + default_widget = + Single_Choice display=Display.Always Index_Sub_Range.default_options + ## PRIVATE Resolves a vector of ranges or indices into a vector of ranges that fit within a sequence. @@ -169,9 +196,9 @@ sort_and_merge_ranges ranges = normalization on its own. - range: The `Index_Sub_Range` to take from the collection. take_helper : Integer -> (Integer -> Any) -> (Integer -> Integer -> Any) -> (Vector (Integer | Range) -> Vector Any) -> (Index_Sub_Range | Range | Integer) -> Any -take_helper length at single_slice slice_ranges index_sub_range = case index_sub_range of +take_helper length at single_slice slice_ranges range = case range of count : Integer -> take_helper length at single_slice slice_ranges (Index_Sub_Range.First count) - _ : Range -> take_helper length at single_slice slice_ranges (Index_Sub_Range.By_Index index_sub_range) + _ : Range -> take_helper length at single_slice slice_ranges (Index_Sub_Range.By_Index range) Index_Sub_Range.First count -> single_slice 0 (Math.min length count) Index_Sub_Range.Last count -> single_slice length-count length Index_Sub_Range.While predicate -> @@ -193,6 +220,7 @@ take_helper length at single_slice slice_ranges index_sub_range = case index_sub if start >= length then single_slice 0 0 else range = start.up_to length . with_step step take_helper length at single_slice slice_ranges (Index_Sub_Range.By_Index range) + _ -> handle_unmatched_type [Index_Sub_Range, Range, Integer] range ## PRIVATE A helper that implements dropping from an arbitrary collection using a set of @@ -219,9 +247,9 @@ take_helper length at single_slice slice_ranges index_sub_range = case index_sub normalized. - range: The `Index_Sub_Range` to drop from the collection. drop_helper : Integer -> (Integer -> Any) -> (Integer -> Integer -> Any) -> (Vector (Integer | Range) -> Vector Any) -> (Index_Sub_Range | Range | Integer) -> Any -drop_helper length at single_slice slice_ranges index_sub_range = case index_sub_range of - _ : Integer -> single_slice index_sub_range length - _ : Range -> drop_helper length at single_slice slice_ranges (Index_Sub_Range.By_Index index_sub_range) +drop_helper length at single_slice slice_ranges range = case range of + _ : Integer -> single_slice range length + _ : Range -> drop_helper length at single_slice slice_ranges (Index_Sub_Range.By_Index range) Index_Sub_Range.First count -> single_slice count length Index_Sub_Range.Last count -> single_slice 0 length-count Index_Sub_Range.While predicate -> @@ -245,3 +273,21 @@ drop_helper length at single_slice slice_ranges index_sub_range = case index_sub if start >= length then single_slice 0 length else range = start.up_to length . with_step step drop_helper length at single_slice slice_ranges (Index_Sub_Range.By_Index range) + _ -> handle_unmatched_type [Index_Sub_Range, Range, Integer] range + +## PRIVATE +handle_unmatched_type expected_types actual_value = + m = Meta.meta actual_value + return_type_error = + expected_types_str = expected_types . map .to_text . join " | " + Error.throw (Type_Error.Error expected_types_str actual_value "range") + case m of + _ : Meta.Constructor -> + declaring_type = m.declaring_type + is_expected_constructor = expected_types.map Meta.meta . contains declaring_type + if is_expected_constructor.not then return_type_error else + msg = "The constructor " + m.name + " is missing some arguments." + Error.throw (Illegal_Argument.Error msg) + _ -> case actual_value.is_a Function of + True -> Error.throw (Illegal_Argument.Error "Got a Function instead of a range, is a constructor argument missing?") + False -> return_type_error diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index 0702fd08cc39..0cfda7c1acaa 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -286,7 +286,9 @@ type List go t res.fill res.value - ## Applies a function to each element of the list. + ## PRIVATE + ADVANCED + Applies a function to each element of the list. Arguments: - f: The function to apply to each element of the list. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map.enso index 10767ffea671..b75143d0d5de 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map.enso @@ -308,7 +308,9 @@ type Map key value self.to_vector.fold init acc-> pair-> function acc pair.first pair.last - ## Applies a function to each value in the map. + ## PRIVATE + ADVANCED + Applies a function to each value in the map. Arguments: - function: The function to apply to each value in the map, taking a @@ -329,7 +331,9 @@ type Map key value kv_func = _ -> function self.each_with_key kv_func - ## Applies a function to each key-value pair in the map. + ## PRIVATE + ADVANCED + Applies a function to each key-value pair in the map. Arguments: - function: The function to apply to each key-value pair in the map, diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Pair.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Pair.enso index 0689b53f58c7..bb0bc630d22d 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Pair.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Pair.enso @@ -232,7 +232,9 @@ type Pair reverse : Pair reverse self = Pair.new self.second self.first - ## Applies a function to each element of the pair. + ## PRIVATE + ADVANCED + Applies a function to each element of the pair. Unlike `map`, this method does not return the individual results, therefore it is only useful for side-effecting computations. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso index 23a9738bff70..df9b105f93b3 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso @@ -1,7 +1,6 @@ import project.Any.Any import project.Data.Filter_Condition.Filter_Condition import project.Data.Numbers.Integer -import project.Data.Numbers.Number import project.Data.Text.Text import project.Data.Vector.Vector import project.Errors.Common.Index_Out_Of_Bounds @@ -55,18 +54,21 @@ type Range _ -> Error.throw (Illegal_Argument.Error "Range step should be an integer.") - ## Returns the first element that is included within the range or `Nothing` - if the range is empty. + ## Returns the first element that is included within the range. + + It will raise `Index_Out_Of_Bounds` if the range is empty. first : Integer ! Index_Out_Of_Bounds first self = if self.is_empty then Error.throw (Index_Out_Of_Bounds.Error 0 0) else self.start - ## Returns the second element that is included within the range or `Nothing` - if the range has less than 2 element + ## Returns the second element that is included within the range. + + It will raise `Index_Out_Of_Bounds` if the range has less than two elements. second : Integer ! Index_Out_Of_Bounds second self = if self.length < 2 then Error.throw (Index_Out_Of_Bounds.Error 1 self.length) else self.start + self.step - ## Returns the last element that is included within the range or `Nothing` - if the range is empty. + ## Returns the last element that is included within the range. + + It will raise `Index_Out_Of_Bounds` if the range is empty. last : Integer ! Index_Out_Of_Bounds last self = if self.is_empty then Error.throw (Index_Out_Of_Bounds.Error 0 0) else self.start + self.step*(self.length - 1) @@ -77,18 +79,22 @@ type Range The following range has 100 elements. 0.up_to 100 . length - length : Number + length : Integer length self = if self.is_empty then 0 else diff = self.end - self.start steps = diff . div self.step - if self.start + steps*self.step == self.end then steps else steps+1 + exact_fit = (self.start + steps*self.step) == self.end + ## The `end` is excluded if it is reached exactly by the last step. + If it is not reached, that means that the last step is also included, + so we increase by one. + if exact_fit then steps else steps+1 ## Gets an element from the Range at a specified index (0-based). Arguments: - index: The location in the Range to get the element from. The index is - also allowed be negative, then the elements are indexed from the back - of the final item, i.e. -1 will correspond to the last element. + also allowed be negative, then the elements are indexed from the back, + i.e. -1 will correspond to the last element. > Example Get the second element of a range. @@ -108,8 +114,8 @@ type Range Arguments: - index: The location in the Range to get the element from. The index is - also allowed be negative, then the elements are indexed from the back - of the Range, i.e. -1 will correspond to the last element. + also allowed be negative, then the elements are indexed from the back, + i.e. -1 will correspond to the last element. - if_missing: The value to return if the index is out of bounds. get : Integer -> Any -> Any get self index ~if_missing=Nothing = @@ -149,7 +155,7 @@ type Range the range. 1.up_to 10 . map (*2) - map : (Number -> Any) -> Vector Any + map : (Integer -> Any) -> Vector Any map self function = Vector.new self.length (i -> function (self.start + i*self.step)) @@ -166,7 +172,7 @@ type Range (0.up_to 7).filter (> 3) (0.up_to 7).filter (Filter_Condition.Greater than=3) - filter : (Filter_Condition | (Any -> Boolean)) -> Vector Any + filter : (Filter_Condition | (Integer -> Boolean)) -> Vector Integer filter self filter = case filter of _ : Filter_Condition -> self.filter filter.to_predicate predicate : Function -> @@ -174,7 +180,9 @@ type Range if predicate elem then builder.append elem else builder builder.to_vector - ## Applies a function for each element in the range. + ## PRIVATE + ADVANCED + Applies a function for each element in the range. Arguments: - function: The function to apply to each integer in the range. @@ -182,7 +190,7 @@ type Range > Example To print all the numbers from 1 to 10 use: 1.up_to 11 . each IO.println - each : (Number -> Any) -> Nothing + each : (Integer -> Any) -> Nothing each self function = if self.step == 0 then throw_zero_step_error else end_condition = if self.step > 0 then (>=) else (<=) @@ -193,6 +201,7 @@ type Range go self.start ## PRIVATE + ADVANCED Applies a function to each element of the range. Essentially acts like `range.to_vector.each_with_index`, but it is more @@ -206,7 +215,7 @@ type Range Print range elements with their indices within the range. (10.up_to 13).each_with_index ix-> elem-> IO.println (Pair ix elem) # Will print Pair 0 10, Pair 1 11, Pair 2 12 - each_with_index : (Integer -> Any -> Any) -> Nothing + each_with_index : (Integer -> Integer -> Nothing) -> Nothing each_with_index self function = if self.step == 0 then throw_zero_step_error else end_condition = if self.step > 0 then (>=) else (<=) @@ -220,7 +229,7 @@ type Range passed function with next elements of the range. Arguments: - - init: The initial integral value for the fold. + - init: The initial value for the fold. - function: A binary function taking an item and a number, and returning an item. @@ -234,7 +243,7 @@ type Range less than 100. 0.up_to 100 . with_step 2 . fold 0 (+) - fold : Any -> (Any -> Number -> Any) -> Any + fold : Any -> (Any -> Integer -> Any) -> Any fold self init function = if self.step == 0 then throw_zero_step_error else end_condition = if self.step > 0 then (>=) else (<=) @@ -253,9 +262,9 @@ type Range - function: A function taking two elements and combining them. > Example - Compute the running sum of all of the elements in a vector + Compute the running sum of all of the elements in a range. - [1, 2, 3].running_fold 0 (+) + (0.up_to 4).running_fold 0 (+) running_fold : Any -> (Any -> Any -> Any) -> Vector Any running_fold self init function = wrapped builder value = @@ -275,7 +284,7 @@ type Range Checking that all numbers in the range are greater than 5. 10.up_to 100 . all (> 5) - all : (Number -> Boolean) -> Boolean + all : (Integer -> Boolean) -> Boolean all self predicate = self . any (predicate >> .not) . not ## Checks whether `predicate` is satisfied for any number in this range. @@ -289,7 +298,7 @@ type Range Checking that at least one number in the range is greater than 10. 1.up_to 100 . any (> 10) - any : (Number -> Boolean) -> Boolean + any : (Integer -> Boolean) -> Boolean any self predicate = self.find predicate . is_nothing . not ## Gets the first index when `predicate` is satisfied this range. @@ -347,7 +356,7 @@ type Range 0.up_to 100 . index_of 20 == 20 0.up_to 100 . with_step 5 . index_of 20 == 4 0.up_to 100 . with_step 5 . index_of (>10) == 3 - index_of : (Integer | (Any -> Boolean)) -> Integer -> Integer | Nothing + index_of : (Integer | (Integer -> Boolean)) -> Integer -> Integer | Nothing index_of self element start=0 = check_start_valid start self used_start-> case element of @@ -370,7 +379,7 @@ type Range Find the last index of an element in a pair. Pair.new 2 2 . last_index_of 2 == 1 - last_index_of : (Any | (Any -> Boolean)) -> Integer -> Integer | Nothing + last_index_of : (Integer | (Integer -> Boolean)) -> Integer -> Integer | Nothing last_index_of self element start=-1 = if self.is_empty && (start==-1 || start==0) then Nothing else check_start_valid start self include_end=False used_start-> @@ -403,21 +412,21 @@ type Range Getting a vector of the numbers 1 to 5. 1.up_to 6 . to_vector - to_vector : Vector + to_vector : Vector Integer to_vector self = self.map x->x ## Combines all the elements of a non-empty range using a binary operation. If the range is empty, returns `if_empty`. Arguments: - - function: A binary operation that takes two items and combines them. + - function: A binary operation that takes two integers and combines them. - if_empty: Value returned if the range is empty. > Example Compute the sum of all the elements in a range. 0.up_to 10 . reduce (+) - reduce : (Any -> Any -> Any) -> Any -> Any + reduce : (Integer -> Integer -> Integer) -> Any -> Any reduce self function ~if_empty=(Error.throw Empty_Error) = len = self.length case len of diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range/Extensions.enso index 0996f5cf57f5..4aa1d37887f6 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range/Extensions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range/Extensions.enso @@ -3,20 +3,31 @@ import project.Data.Range.Range import project.Error.Error import project.Errors.Illegal_Argument.Illegal_Argument +from project.Data.Boolean import Boolean, True, False + ## ALIAS Range - Creates an increasing right-exclusive range of integers from `self` to `n`. + Creates an increasing range of integers from `self` to `n`. Arguments: - n: The end of the range. + - include_end: Specifies if the right end of the range should be included. By + default, the range is right-exclusive. > Example Create a range containing the numbers 0, 1, 2, 3, 4. 0.up_to 5 -Integer.up_to : Integer -> Range -Integer.up_to self n = case n of - _ : Integer -> Range.Between self n + + > Example + Create a range containing elements 1, 2, 3. + + 1.up_to 3 include_end=True +Integer.up_to : Integer -> Boolean -> Range +Integer.up_to self n include_end=False = case n of + _ : Integer -> + effective_end = if include_end then n+1 else n + Range.Between self effective_end 1 _ -> Error.throw (Illegal_Argument.Error "Expected range end to be an Integer.") ## ALIAS Range @@ -25,12 +36,21 @@ Integer.up_to self n = case n of Arguments: - n: The end of the range. + - include_end: Specifies if the right end of the range should be included. By + default, the range is right-exclusive. > Example Create a range containing the numbers 5, 4, 3, 2, 1. 5.down_to 0 -Integer.down_to : Integer -> Range -Integer.down_to self n = case n of - _ : Integer -> Range.Between self n -1 + + > Example + Create a range containing elements 3, 2, 1. + + 3.down_to 1 include_end=True +Integer.down_to : Integer -> Boolean -> Range +Integer.down_to self n include_end=False = case n of + _ : Integer -> + effective_end = if include_end then n-1 else n + Range.Between self effective_end -1 _ -> Error.throw (Illegal_Argument.Error "Expected range end to be an Integer.") diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso index fc2c1c6d3b5f..9eec4dd13f2e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso @@ -40,6 +40,8 @@ from project.Data.Json import Json, Invalid_JSON, JS_Object from project.Data.Numbers import Decimal, Integer, Number, Number_Parse_Error from project.Data.Text.Text_Sub_Range import Codepoint_Ranges, Text_Sub_Range +from project.Metadata import make_single_choice + import project.Data.Index_Sub_Range as Index_Sub_Range_Module polyglot java import com.ibm.icu.lang.UCharacter @@ -71,7 +73,9 @@ Text.reverse self = @Tail_Call iterate next iterator.previous iterate iterator.last iterator.previous -## Applies the provided `function` to each character in `self`. +## PRIVATE + ADVANCED + Applies the provided `function` to each character in `self`. Arguments: - function: The operation to apply to each character in the text. @@ -331,7 +335,8 @@ Text.match self pattern=".*" case_sensitivity=Case_Sensitivity.Sensitive = Split with a vector of strings. 'azbzczdzezfzg'.split ['b', 'zez'] == ['az', 'zczd', 'fzg'] -Text.split : Text | Vector Text -> Case_Sensitivity -> Boolean -> Vector Text | Illegal_Argument +@delimiter (make_single_choice [',', ';', '|', ['{tab}', "'\t'"], ['{space}', "' '"], ['{newline}', "['\n', '\r\n', '\r']"], ['Custom', ""]]) +Text.split : Text | Vector Text -> Case_Sensitivity -> Boolean -> Vector Text | Illegal_Argument Text.split self delimiter="," case_sensitivity=Case_Sensitivity.Sensitive use_regex=False = delimiter_is_empty = case delimiter of _ : Text -> delimiter.is_empty @@ -937,6 +942,7 @@ Text.repeat self count=1 = "Hello World!".take (By_Index [1, 0, 0, 6, 0]) == "eHHWH" "Hello World!".take (By_Index [Range 0 3, 6, Range 6 12 2]) == "HelWWrd" "Hello World!".take (Sample 3 seed=42) == "l d" +@range Text_Sub_Range.default_widget Text.take : (Text_Sub_Range | Index_Sub_Range | Range | Integer) -> Text ! Index_Out_Of_Bounds Text.take self range=(Index_Sub_Range.First 1) = ranges = Codepoint_Ranges.resolve self range @@ -983,6 +989,7 @@ Text.take self range=(Index_Sub_Range.First 1) = "Hello World!".drop (By_Index [1, 0, 0, 6, 0]) == "llo orld!" "Hello World!".drop (By_Index [Range 0 3, 6, Range 6 12 2]) == "lo ol!" "Hello World!".drop (Sample 3 seed=42) == "HeloWorl!" +@range Text_Sub_Range.default_widget Text.drop : (Text_Sub_Range | Index_Sub_Range | Range) -> Text ! Index_Out_Of_Bounds Text.drop self range=(Index_Sub_Range.First 1) = ranges = Codepoint_Ranges.resolve self range diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Sub_Range.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Sub_Range.enso index 81526f8d9387..24f04bce3a27 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Sub_Range.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Sub_Range.enso @@ -6,6 +6,7 @@ import project.Data.Range.Range import project.Data.Text.Text import project.Data.Vector.Vector import project.Errors.Common.Index_Out_Of_Bounds +import project.Errors.Common.Type_Error import project.Error.Error import project.Errors.Illegal_Argument.Illegal_Argument import project.Nothing.Nothing @@ -16,6 +17,11 @@ from project.Data.Boolean import Boolean, True, False import project.Data.Index_Sub_Range as Index_Sub_Range_Module import project.Data.Text.Span as Span_Module +from project.Data.Index_Sub_Range import handle_unmatched_type + +from project.Metadata.Widget import Single_Choice +from project.Metadata.Choice import Option +import project.Metadata.Display polyglot java import com.ibm.icu.text.BreakIterator polyglot java import org.enso.base.Text_Utils @@ -41,13 +47,31 @@ type Text_Sub_Range After_Last (delimiter : Text) ## PRIVATE - Convert to a display representation of this Index_Sub_Range. + Convert to a display representation of this `Text_Sub_Range`. to_display_text : Text to_display_text self = case self of - Text_Sub_Range.Before delimiter -> "Before " + delimiter.to_display_text - Text_Sub_Range.Before_Last delimiter -> "Before Last " + delimiter.to_display_text - Text_Sub_Range.After delimiter -> "After " + delimiter.to_display_text - Text_Sub_Range.After_Last delimiter -> "After Last " + delimiter.to_display_text + Text_Sub_Range.Before delimiter -> "Before " + delimiter.pretty + Text_Sub_Range.Before_Last delimiter -> "Before Last " + delimiter.pretty + Text_Sub_Range.After delimiter -> "After " + delimiter.pretty + Text_Sub_Range.After_Last delimiter -> "After Last " + delimiter.pretty + + ## PRIVATE + default_options : Vector Option + default_options = + o1 = Option "Before" "(Text_Sub_Range.Before ' ')" + o2 = Option "Before_Last" "(Text_Sub_Range.Before_Last ' ')" + o3 = Option "After" "(Text_Sub_Range.After ' ')" + o4 = Option "After_Last" "(Text_Sub_Range.After_Last ' ')" + [o1, o2, o3, o4] + + ## PRIVATE + The widget for `Text_Sub_Range` also displays options for + `Index_Sub_Range` since the former is supposed to 'expand' the latter and + is always used together with it. + default_widget : Single_Choice + default_widget = + options = Index_Sub_Range.default_options + Text_Sub_Range.default_options + Single_Choice display=Display.Always options type Codepoint_Ranges ## PRIVATE @@ -64,7 +88,7 @@ type Codepoint_Ranges Returns a new sorted list of ranges where intersecting ranges have been merged. - Empty subranges are not discarded. + Empty ranges are not discarded. sorted_and_distinct_ranges : Vector Range sorted_and_distinct_ranges self = if self.is_sorted_and_distinct then self.ranges else Index_Sub_Range_Module.sort_and_merge_ranges self.ranges @@ -80,8 +104,8 @@ type Codepoint_Ranges in such a way that the ranges returned by this method always have a step equal to 1. resolve : Text -> (Text_Sub_Range | Index_Sub_Range | Range | Integer) -> (Range | Codepoint_Ranges) - resolve text subrange = - case subrange of + resolve text range = + case range of Text_Sub_Range.Before delimiter -> if delimiter.is_empty then (0.up_to 0) else span = Text_Utils.span_of text delimiter @@ -134,12 +158,14 @@ type Codepoint_Ranges if step <= 0 then Error.throw (Illegal_Argument.Error "Step within Every must be positive.") else len = text.length if start >= len then 0.up_to 0 else - range = start.up_to text.length . with_step step - Codepoint_Ranges.resolve text (Index_Sub_Range.By_Index range) + simple_range = start.up_to text.length . with_step step + Codepoint_Ranges.resolve text (Index_Sub_Range.By_Index simple_range) _ : Range -> - Codepoint_Ranges.resolve text (Index_Sub_Range.By_Index subrange) + Codepoint_Ranges.resolve text (Index_Sub_Range.By_Index range) _ : Integer -> - Codepoint_Ranges.resolve text (Index_Sub_Range.First subrange) + Codepoint_Ranges.resolve text (Index_Sub_Range.First range) + _ -> handle_unmatched_type [Text_Sub_Range, Index_Sub_Range, Range, Integer] range + ## PRIVATE Utility function to find char indices for Text_Sub_Range. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso index a1f2d1d7c44c..ee7212ba0549 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso @@ -6,6 +6,7 @@ import project.Data.Ordering.Ordering import project.Data.Ordering.Comparable import project.Data.Text.Text import project.Data.Time.Date_Period.Date_Period +import project.Data.Time.Date_Range.Date_Range import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Day_Of_Week.Day_Of_Week import project.Data.Time.Day_Of_Week_From @@ -445,6 +446,56 @@ type Date _ -> Error.throw (Illegal_Argument.Error "Illegal period argument") + ## ALIAS Date Range + + Creates an increasing range of dates from `self` to `end`. + + Arguments: + - end: The end of the range. + - include_end: Specifies if the right end of the range should be included. By + default, the range is right-exclusive. + + > Example + Create a range of dates. + + (Date.new 2021 12 05).up_to (Date.new 2021 12 10) + + > Example + Create a range containing dates [2021-12-05, 2021-12-06]. + + (Date.new 2021 12 05).up_to (Date.new 2021 12 06) include_end=True + up_to : Date -> Boolean -> Date_Range + up_to self end include_end=False = case end of + _ : Date -> + effective_end = if include_end then end.next else end + Date_Range.new_internal self effective_end increasing=True step=(Period.new days=1) + _ -> Error.throw (Type_Error.Error Date end "end") + + ## ALIAS Date Range + + Creates a decreasing range of dates from `self` to `end`. + + Arguments: + - end: The end of the range. + - include_end: Specifies if the right end of the range should be included. By + default, the range is right-exclusive. + + > Example + Create a reverse range of dates. + + (Date.new 2021 12 10).down_to (Date.new 2021 12 05) + + > Example + Create a range containing dates [2021-12-06, 2021-12-05]. + + (Date.new 2021 12 06).down_to (Date.new 2021 12 05) include_end=True + down_to : Date -> Boolean -> Date_Range + down_to self end include_end=False = case end of + _ : Date -> + effective_end = if include_end then end.previous else end + Date_Range.new_internal self effective_end increasing=False step=(Period.new days=1) + _ -> Error.throw (Type_Error.Error Date end "end") + ## Shift the date by the specified amount of business days. For the purpose of this method, the business days are defined to be diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Period.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Period.enso index 4eab0356cb2d..80e639f280af 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Period.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Period.enso @@ -25,12 +25,6 @@ type Date_Period Day - ## PRIVATE - This method could be replaced with matching on `Date_Period` supertype - if/when that is supported. - is_date_period : Boolean - is_date_period self = True - ## PRIVATE adjust_start : (Date | Date_Time) -> (Date | Date_Time) adjust_start self date = diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Range.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Range.enso new file mode 100644 index 000000000000..c548855796d7 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Range.enso @@ -0,0 +1,504 @@ +import project.Any.Any +import project.Data.Filter_Condition.Filter_Condition +import project.Data.Json.JS_Object +import project.Data.Numbers.Integer +import project.Data.Range.Empty_Error +import project.Data.Range.Extensions +import project.Data.Text.Text +import project.Data.Time.Date.Date +import project.Data.Time.Date_Period.Date_Period +import project.Data.Time.Period.Period +import project.Data.Vector.Vector +import project.Error.Error +import project.Errors.Common.Index_Out_Of_Bounds +import project.Errors.Illegal_Argument.Illegal_Argument +import project.Function.Function +import project.Nothing.Nothing + +from project.Data.Boolean import Boolean, True, False + +polyglot java import org.enso.base.Time_Utils + +## Represents a range of dates. +type Date_Range + ## PRIVATE + Never use the constructor directly to construct a range, as it does not + allow to verify invariants and may lead to unexpected behavior. + Internal_Constructor (start : Date) (end : Date) (step : Period) (increasing : Boolean) (cached_length : Integer) + + ## Create a representation of a right-exclusive range of dates. + + The range is increasing or decreasing, depending on if the start date is + before or after the end date. + + Arguments: + - start: The left boundary of the range. Its value is included. + - end: The right boundary of the range. Its value is excluded. + - step: The step between dates. It must be positive - to construct a + decreasing range, flip the start and the end or use `down_to`, but + keeping the positive step. + new : Date -> Date -> Date_Period|Period -> Date_Range + new start=Date.now end=Date.now step=Date_Period.Day = + increasing = start <= end + Date_Range.new_internal start end increasing step + + ## PRIVATE + new_internal : Date -> Date -> Boolean -> Date_Period|Period -> Date_Range + new_internal start end increasing step = + one_day = Period.new days=1 + base_length = start.days_until end . abs + Date_Range.Internal_Constructor start end one_day increasing base_length . with_step step + + ## Creates a copy of this range with a changed step. + + Arguments: + - new_step: The new step to use. It can either be a `Date_Period` or + `Period`. The provided `Period` must be positive, i.e. all of `years`, + `months` and `days` must be non-negative and at least one of them has + to be positive. + + > Example + Create a range representing the first day of every month in a year. + + (Date.new 2020 1 1).up_to (Date.new 2020 12 31) . with_step Date_Period.Month + + > Example + Create a a decreasing range of every other day between two dates. + + (Date.new 2022 10 23).down_to (Date.new 2022 10 1) . with_step (Period.new days=2) + with_step : Date_Period|Period -> Date_Range ! Illegal_Argument + with_step self new_step = case new_step of + _ : Period -> + effective_length = compute_length_and_verify self.start self.end new_step self.increasing + Date_Range.Internal_Constructor self.start self.end new_step self.increasing effective_length + _ : Date_Period -> + self.with_step new_step.to_period + + ## Convert to a textual representation. + to_text : Text + to_text self = + middle = if self.increasing then " up to " else " down to " + step = if self.step == (Period.new days=1) then "" else + " by " + self.step.to_display_text + "(Date_Range from " + self.start.to_text + middle + self.end.to_text + step + ")" + + ## Convert to a display representation. + to_display_text : Text + to_display_text self = + start = "[" + self.start.to_display_text + " .. " + self.end.to_display_text + step = if self.step == (Period.new days=1) then "" else + effective_step = if self.increasing then self.step else self.step.negated + " by " + effective_step.to_display_text + start + step + "]" + + ## Convert to a human-readable representation. + pretty : Text + pretty self = self.to_text + + ## PRIVATE + Converts this value to a JSON serializable object. + to_js_object : JS_Object + to_js_object self = + JS_Object.from_pairs [["type", "Date_Range"], ["start", self.start.to_js_object], ["end", self.end.to_js_object], ["step", self.step.to_js_object], ["increasing", self.increasing]] + + ## Returns the first element that is included within the range or `Nothing` + if the range is empty. + first : Integer ! Index_Out_Of_Bounds + first self = if self.is_empty then Error.throw (Index_Out_Of_Bounds.Error 0 0) else self.start + + ## Returns the second element that is included within the range or `Nothing` + if the range has less than 2 element + second : Integer ! Index_Out_Of_Bounds + second self = if self.length < 2 then Error.throw (Index_Out_Of_Bounds.Error 1 self.length) else self.start + self.step + + ## Returns the last element that is included within the range or `Nothing` + if the range is empty. + last : Integer ! Index_Out_Of_Bounds + last self = if self.is_empty then Error.throw (Index_Out_Of_Bounds.Error 0 0) else + self.start + self.step*(self.length - 1) + + ## Get the number of elements in the range. + + > Example + The following range has 2 elements. + + (Date.new 2023 04 05) . up_to (Date.new 2023 04 07) . length + length : Integer + length self = self.cached_length + + ## Gets an element from the range at a specified index (0-based). + + Arguments: + - index: The location in the range to get the element from. The index is + also allowed be negative, then the elements are indexed from the back, + i.e. -1 will correspond to the last element. + + > Example + Get the second element of a range. + + (Date.new 2023 04 05) . up_to (Date.new 2023 04 07) . get 1 == (Date.new 2023 04 06) + + > Example + Get the last element of a range with step. + + (Date.new 2023 04 05) . up_to (Date.new 2023 10 07) . with_step Date_Period.Month . get -1 == (Date.new 2023 10 05) + at : Integer -> Any ! Index_Out_Of_Bounds + at self index = + self.get index (Error.throw (Index_Out_Of_Bounds.Error index self.length)) + + ## Gets an element from the range at a specified index (0-based). + If the index is invalid then `if_missing` is returned. + + Arguments: + - index: The location in the range to get the element from. The index is + also allowed be negative, then the elements are indexed from the back, + i.e. -1 will correspond to the last element. + - if_missing: The value to return if the index is out of bounds. + get : Integer -> Any -> Any + get self index ~if_missing=Nothing = + len = self.length + effective_index = if index < 0 then len + index else index + if effective_index >= 0 && effective_index < len then self.internal_at effective_index else + if_missing + + ## PRIVATE + Generates the i-th element of the range. + + This method does no bounds checking, it should be used only internally. + internal_at self i = + nth_element_of_range self.start self.step self.increasing i + + ## Applies a function to each element in the range, producing a vector of + results. + + Arguments: + - function: The function to apply to each date in the range. + + > Example + Create a vector that contains the numbers twice that of the numbers in + the range. + + 1.up_to 10 . map (*2) + map : (Date -> Any) -> Vector Any + map self function = + Vector.new self.length (i -> function (self.internal_at i)) + + ## Converts the range to a vector containing the dates in the range. + + > Example + Getting a vector of dates from 2021-05-07 to 2021-05-10 (exclusive). + + (Date.new 2021 05 07).up_to (Date.new 2021 05 10) . to_vector + to_vector : Vector Date + to_vector self = self.map x->x + + ## Checks if this range is empty. + + > Example + Checking if the range of days from start of 2020 to start of 2023 is empty. + + (Date.new 2020).up_to (Date.new 2023) . is_empty + is_empty : Boolean + is_empty self = self.length == 0 + + ## Checks if this range is not empty. + + > Example + Checking if the range of days from start of 2020 to start of 2023 is not empty. + + (Date.new 2020).up_to (Date.new 2023) . is_empty + not_empty : Boolean + not_empty self = self.is_empty.not + + ## Returns a vector of all elements of this range which satisfy a condition. + + Arguments: + - filter: The filter to apply to the range. It can either be an instance + of `Filter_Condition` or a predicate taking a value and returning a + boolean value indicating whether the corresponding element should be + kept or not. + + > Example + Selecting all elements that are greater than 2020-10-12. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 15) . filter (> (Date.new 2020 10 12)) + (Date.new 2020 10 01).up_to (Date.new 2020 10 15) . filter (Filter_Condition.Greater than=(Date.new 2020 10 12)) + filter : (Filter_Condition | (Date -> Boolean)) -> Vector Date + filter self filter = case filter of + _ : Filter_Condition -> self.filter filter.to_predicate + predicate : Function -> + builder = self.fold Vector.new_builder builder-> elem-> + if predicate elem then builder.append elem else builder + builder.to_vector + + ## PRIVATE + ADVANCED + Applies a function for each element in the range. + + Arguments: + - function: The function to apply to each integer in the range. + + > Example + To print all dates from 2020-10-01 to 2020-10-05. + (Date.new 2020 10 01).up_to (Date.new 2020 10 05) include_end=True . each IO.println + each : (Date -> Any) -> Nothing + each self function = + (0.up_to self.length).each ix-> + function (self.internal_at ix) + + ## PRIVATE + ADVANCED + Applies a function to each element of the range. + + Essentially acts like `range.to_vector.each_with_index`, but it is more + efficient. + + Arguments: + - function: A function to apply that takes two parameters: first the + index of a given range element and then the actual range element. + + > Example + Print range elements with their indices within the range. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 05).each_with_index ix-> elem-> IO.println (Pair ix elem) + each_with_index : (Integer -> Date -> Nothing) -> Nothing + each_with_index self function = + (0.up_to self.length).each_with_index ix-> + function ix (self.internal_at ix) + + ## Combines all the elements of the range, by iteratively applying the + passed function with next elements of the range. + + Arguments: + - init: The initial value for the fold. + - function: A binary function taking an item and a date, and returning + an item. + + > Example + In the following example, we'll compute how many days in the range are + a Monday. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 31) . fold 0 acc-> date-> + if date.day_of_week == Day_Of_Week.Monday then acc+1 else acc + fold : Any -> (Any -> Date -> Any) -> Any + fold self init function = + (0.up_to self.length).fold init acc-> ix-> + function acc (self.internal_at ix) + + ## Combines all the elements of the range, by iteratively applying the + passed function with the next element of the range. After each step the + value is stored resulting in a new Vector of the same size as self. + + Arguments: + - init: The initial value for the fold. + - function: A function taking two elements and combining them. + running_fold : Any -> (Any -> Date -> Any) -> Vector Any + running_fold self init function = + (0.up_to self.length).running_fold init acc-> ix-> + function acc (self.internal_at ix) + + ## Checks whether `predicate` is satisfied for all dates in this range. + + Arguments: + - predicate: A function that takes a list element and returns a boolean + value that says whether that value satisfies the conditions of the + function. + + > Example + Checking that all dates in the range are after 2020-10-01. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 31) . all (> (Date.new 2020 10 01)) + all : (Date -> Boolean) -> Boolean + all self predicate = self . any (predicate >> .not) . not + + ## Checks whether `predicate` is satisfied for any date in this range. + + Arguments: + - predicate: A function that takes a list element and returns a boolean + value that says whether that value satisfies the conditions of the + function. + + > Example + Checking that at least one date in the range is after 2020-10-01. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 31) . any (> (Date.new 2020 10 01)) + any : (Date -> Boolean) -> Boolean + any self predicate = self.find predicate . is_nothing . not + + ## Gets the first index when `predicate` is satisfied this range. + If no index satisfies the predicate, returns `if_missing`. + + Arguments: + - predicate: A function that takes a list element and returns a boolean + value that says whether that value satisfies the conditions of the + function. + - start: The index to start searching from. If the index is negative, it + is counted from the end of the range. + - if_missing: Value returned if no element satisfies the predicate. + + > Example + Get the first date in the range that is a Monday. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 31) . find (d-> d.day_of_week == Day_Of_Week.Monday) + find : (Date -> Boolean) -> Integer -> Any -> Any + find self predicate start=0 ~if_missing=Nothing = + index = self.index_of predicate start + case index of + Nothing -> if_missing + _ : Integer -> self.internal_at index + + ## Checks if the range contains the specified value. + + > Example + Check if a particular date is in the range. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 31) . with_step (Period.new days=2) . contains (Date.new 2020 10 15) + contains : Date -> Boolean + contains self value = self.find (== value) . is_nothing . not + + ## Returns the index of an element in the range. + Returns Nothing if the element is not found. + + Arguments: + - element: The date to search for or a predicate function to test for + each element. + - start: The index to start searching from. If the index is negative, it + is counted from the end of the range. + + > Example + Find the index of a first day that is a Monday. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 31) . index_of (d-> d.day_of_week == Day_Of_Week.Monday) + index_of : (Date | (Date -> Boolean)) -> Integer -> Integer | Nothing + index_of self element start=0 = + predicate = case element of + d : Date -> + ix-> self.internal_at ix == d + f : Function -> + ix-> f (self.internal_at ix) + (0.up_to self.length).index_of predicate start + + ## Returns the last index of an element in the range. + Returns Nothing if the element is not found. + + Arguments: + - element: The date to search for or a predicate function to test for + each element. + - start: The index to start searching backwards from. If the index is + negative, it is counted from the end of the range. + + > Example + Find the index of a first day that is a Monday. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 31) . last_index_of (d-> d.day_of_week == Day_Of_Week.Monday) + last_index_of : (Date | (Date -> Boolean)) -> Integer -> Integer | Nothing + last_index_of self element start=-1 = + predicate = case element of + d : Date -> + ix-> self.internal_at ix == d + f : Function -> + ix-> f (self.internal_at ix) + (0.up_to self.length).last_index_of predicate start + + ## Reverses the range, returning a vector with the same elements as the + original range, but in the opposite order. + + > Example + Reverse a range of dates. + + (Date.new 2020 10 01).up_to (Date.new 2020 10 31) . reverse + + ? Returning a `Vector` + + This method cannot return back a `Date_Range`, as some ranges are not + reversible. For example, the range `(Date.new 2020 02 29).up_to (Date.new 2023) . with_step Date_Period.Year` + will have `2022-02-28` as its last entry. But if we create a + range starting at `2022-02-28` and going backwards by a year, its last + element will be `2020-02-28` and not `2020-02-29` as in the original. + Thus, to preserve the contents we need to return a vector. + reverse : Vector Date + reverse self = self.to_vector.reverse + + ## Combines all the elements of a non-empty range using a binary operation. + If the range is empty, returns `if_empty`. + + Arguments: + - function: A binary operation that takes two dates and combines them + into a new date. + - if_empty: Value returned if the range is empty. + reduce : (Date -> Date -> Date) -> Any -> Any + reduce self function ~if_empty=(Error.throw Empty_Error) = + case self.length of + 0 -> if_empty + 1 -> self.start + length -> + (1.up_to length).fold self.start acc-> ix-> + function acc (self.internal_at ix) + + +## PRIVATE + Computes the length of the range and verifies its invariants. + + If any of the invariants are violated, a dataflow error is raised. +compute_length_and_verify : Date -> Date -> Period -> Boolean -> Integer ! Illegal_Argument +compute_length_and_verify start end step increasing = + if is_period_positive step . not then Error.throw (Illegal_Argument.Error "The step `Period` for `Date_Range` must be positive, i.e. all of `years`, `months` and `days` must be non-negative and at least one of them must be strictly positive.") else + is_range_empty = case increasing of + True -> start >= end + False -> start <= end + if is_range_empty then 0 else + # First a few heuristics for a fast path. + # If there are no years or months, we can perform a simple computation on day difference. + if step.total_months == 0 then compute_length_step_days start end step.days increasing else + # Similarly, if we are only shifting by months, we can rely on a simpler computation. + if step.days == 0 then compute_length_step_months start end step.total_months increasing else + # Then we go brute force for the general case. + compute_length_step_brute_force start end step increasing + +## PRIVATE +is_period_positive period = + if (period.years < 0) || (period.months < 0) || (period.days < 0) then False else + (period.total_months > 0) || (period.days > 0) + +## PRIVATE + Assumes that the range is not empty. +compute_length_step_days : Date -> Date -> Integer -> Boolean -> Integer +compute_length_step_days start end step increasing = + # Logic analogous to `Range.length`. + diff = case increasing of + True -> Time_Utils.days_between start end + False -> Time_Utils.days_between end start + # assert (diff >= 0) + steps = diff . div step + exact_fit = diff % step == 0 + if exact_fit then steps else steps+1 + +## PRIVATE + Assumes that the range is not empty. +compute_length_step_months start end step increasing = + diff = case increasing of + True -> Time_Utils.months_between start end + False -> Time_Utils.months_between end start + # assert (diff >= 0) + steps = diff . div step + exact_fit = case increasing of + True -> start + Period.new months=steps*step == end + False -> start - Period.new months=steps*step == end + if exact_fit then steps else steps+1 + + +## PRIVATE +nth_element_of_range start step increasing n = case increasing of + True -> start + step*n + False -> start - step*n + +## PRIVATE +compute_length_step_brute_force start end step increasing = + is_exceeded = case increasing of + True -> (x -> x >= end) + False -> (x -> x <= end) + go current_date acc_length = + if is_exceeded current_date then acc_length else + next_date = nth_element_of_range start step increasing (acc_length + 1) + @Tail_Call go next_date (acc_length + 1) + go start 0 diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 3a85f9d09849..e2bd0ad1eb7d 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -457,18 +457,18 @@ type Date_Time start_of : (Date_Period|Time_Period) -> Date_Time start_of self period=Date_Period.Month = adjusted = period.adjust_start self - case period.is_date_period of - True -> Time_Period.Day.adjust_start adjusted - False -> adjusted + case period of + _ : Date_Period -> Time_Period.Day.adjust_start adjusted + _ : Time_Period -> adjusted ## Returns the last date within the `Time_Period` or `Date_Period` containing self. end_of : (Date_Period|Time_Period) -> Date_Time end_of self period=Date_Period.Month = adjusted = period.adjust_end self - case period.is_date_period of - True -> Time_Period.Day.adjust_end adjusted - False -> adjusted + case period of + _ : Date_Period -> Time_Period.Day.adjust_end adjusted + _ : Time_Period -> adjusted ## ALIAS Time to Date diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Period.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Period.enso index cc2fbab1670a..6e2c81dc4f98 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Period.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Period.enso @@ -78,7 +78,7 @@ type Period Arguments: - internal_period: An internal representation of period of type java.time.Period. - Value internal_period + Value (internal_period : Java_Period) ## Get the portion of the period expressed in years. years : Integer @@ -88,6 +88,11 @@ type Period months : Integer months self = self.internal_period.getMonths + ## Get the portion of the period coming from months and years as months + (every year is translated to 12 months). + total_months : Integer + total_months self = self.internal_period.toTotalMonths + ## Get the portion of the period expressed in days. days : Integer days self = self.internal_period.getDays @@ -107,10 +112,8 @@ type Period + : Period -> Period ! (Time_Error | Illegal_Argument) + self other_period = ensure_period other_period <| - Panic.catch Any (Period.Value (self.internal_period.plus other_period.internal_period)) err-> - case err of - _ : DateTimeException -> Error.throw Time_Error.Error "Period addition failed:"+err.getMessage - _ : ArithmeticException -> Error.throw Illegal_Argument.Error "Arithmetic error:"+err.getMessage cause=err + catch_java_exceptions "Period.+" <| + Period.Value (self.internal_period.plus other_period.internal_period) ## Subtract a specified amount of time from this period. @@ -128,10 +131,31 @@ type Period - : Period -> Period ! (Time_Error | Illegal_Argument) - self other_period = ensure_period other_period <| - Panic.catch Any (Period.Value (self.internal_period.minus other_period.internal_period)) err-> - case err of - DateTimeException -> Error.throw Time_Error.Error "Period subtraction failed" - ArithmeticException -> Error.throw Illegal_Argument.Error "Arithmetic error" + catch_java_exceptions "Period.-" <| + Period.Value (self.internal_period.minus other_period.internal_period) + + ## Multiply the amount of time in this period by the specified scalar. + + Arguments: + - factor: The scalar to multiply by. + + > Example + Multiply a period of 1 year and 2 months by 2 + + import Standard.Base.Data.Time.Period + + example_multiply = (Period.new years=1 months=2) * 2 + * : Integer -> Period ! Time_Error + * self factor = + catch_java_exceptions "Period.*" <| + Period.Value (self.internal_period.multipliedBy factor) + + ## Negate all amounts in the period. + + This is useful when a period used for going forward in time needs to be + used for going backwards instead. + negated : Period + negated self = Period.Value (self.internal_period.negated) ## PRIVATE Convert Period to a friendly string. @@ -163,3 +187,13 @@ type Period if self.months==0 . not then b.append ["months", self.months] if self.days==0 . not then b.append ["days", self.days] JS_Object.from_pairs b.to_vector + +## PRIVATE +catch_java_exceptions operation ~action = + handle_arithmetic_exception caught_panic = + Error.throw (Time_Error.Error "An overflow has occurred during the "+operation+" operation:"+caught_panic.payload.getMessage) + handle_date_time_exception caught_panic = + Error.throw (Time_Error.Error "The operation "+operation+" has failed:"+caught_panic.payload.getMessage) + Panic.catch ArithmeticException handler=handle_arithmetic_exception <| + Panic.catch DateTimeException handler=handle_date_time_exception <| + action diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Period.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Period.enso index 84c752811ee4..2c9580e59634 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Period.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Period.enso @@ -16,12 +16,6 @@ type Time_Period Second - ## PRIVATE - This method could be replaced with matching on `Date_Period` supertype - if/when that is supported. - is_date_period : Boolean - is_date_period self = False - ## PRIVATE to_java_unit : TemporalUnit to_java_unit self = case self of diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso index 656270126a5f..46f95679045e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso @@ -566,7 +566,9 @@ type Vector a map_with_index : (Integer -> Any -> Any) -> Vector Any map_with_index self function = Vector.new self.length i-> function i (self.at i) - ## Applies a function to each element of the vector. + ## PRIVATE + ADVANCED + Applies a function to each element of the vector. Unlike `map`, this method does not return the individual results, therefore it is only useful for side-effecting computations. @@ -583,7 +585,9 @@ type Vector a 0.up_to self.length . each ix-> f (self.at ix) - ## Applies a function to each element of the vector. + ## PRIVATE + ADVANCED + Applies a function to each element of the vector. Arguments: - function: A function to apply that takes an index and an item. @@ -754,6 +758,7 @@ type Vector a If an `Index_Sub_Range`, then the selection is interpreted following the rules of that type. If a `Range`, the selection is specified by two indices, from and to. + @range Index_Sub_Range.default_widget take : (Index_Sub_Range | Range | Integer) -> Vector Any take self range=(Index_Sub_Range.First 1) = case range of ## We are using a specialized implementation for `take Sample`, because @@ -773,6 +778,7 @@ type Vector a If an `Index_Sub_Range`, then the selection is interpreted following the rules of that type. If a `Range`, the selection is specified by two indices, from and to. + @range Index_Sub_Range.default_widget drop : (Index_Sub_Range | Range | Integer) -> Vector Any drop self range=(Index_Sub_Range.First 1) = drop_helper self.length (self.at _) self.slice (slice_ranges self) range diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index d8d4cfc58bb7..dd0822e2858d 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -100,6 +100,7 @@ import project.Data.Text.Text_Ordering.Text_Ordering import project.Data.Text.Text_Sub_Range.Text_Sub_Range import project.Data.Time.Date.Date import project.Data.Time.Date_Period.Date_Period +import project.Data.Time.Date_Range.Date_Range import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Day_Of_Week.Day_Of_Week import project.Data.Time.Day_Of_Week_From @@ -149,6 +150,7 @@ export project.Data.Text.Text_Ordering.Text_Ordering export project.Data.Text.Text_Sub_Range.Text_Sub_Range export project.Data.Time.Date.Date export project.Data.Time.Date_Period.Date_Period +export project.Data.Time.Date_Range.Date_Range export project.Data.Time.Date_Time.Date_Time export project.Data.Time.Day_Of_Week.Day_Of_Week export project.Data.Time.Day_Of_Week_From diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index af4675d0fdd3..ccb64e2ac45b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -44,10 +44,18 @@ type Type ## ADVANCED Returns the fully qualified name of the type. + qualified_name : Text + qualified_name self = + c = self.value + get_qualified_type_name c + + ## ADVANCED + + Returns the short name of the type. name : Text name self = - c = self.value ... - get_constructor_name c + c = self.value + get_short_type_name c type Atom ## PRIVATE @@ -111,6 +119,13 @@ type Constructor ctor = self.value ... new_atom ctor fields.to_array + ## ADVANCED + Returns the type that this constructor is a part of. + declaring_type : Type + declaring_type self = + c = self.value ... + Type.Value (get_constructor_declaring_type c) + type Primitive ## PRIVATE ADVANCED @@ -504,3 +519,19 @@ get_simple_type_name value = @Builtin_Method "Meta.get_simple_type_name" - value: the value to get the type of. get_qualified_type_name : Any -> Text get_qualified_type_name value = @Builtin_Method "Meta.get_qualified_type_name" + +## PRIVATE + Returns a short name of a type (the last part of its qualified name). + + Arguments: + - typ: the type to get the short name of. +get_short_type_name : Any -> Text +get_short_type_name typ = @Builtin_Method "Meta.get_short_type_name" + +## PRIVATE + Returns the type that this constructor is a part of. + + Arguments: + - constructor: the constructor to get the declaring type of. +get_constructor_declaring_type : Any -> Any +get_constructor_declaring_type constructor = @Builtin_Method "Meta.get_constructor_declaring_type" diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Metadata.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Metadata.enso index 6f669108d347..c985a248e65e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Metadata.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Metadata.enso @@ -77,3 +77,11 @@ type Widget ## Describes a file chooser. File_Browse label:(Nothing | Text)=Nothing display:Display=Display.When_Modified action:File_Action=File_Action.Open file_types:(Vector Pair)=[Pair.new "All Files" "*.*"] + +## PRIVATE +make_single_choice : Vector -> Display -> Widget +make_single_choice values display=Display.Always = + make_option value = case value of + _ : Vector -> Choice.Option value.first value.second + _ : Text -> Choice.Option value value.pretty + Widget.Single_Choice (values.map make_option) Nothing display diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso index e12484b17da8..d1d8563df84c 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso @@ -750,6 +750,7 @@ type Column Arguments: - range: The selection of rows from the table to return. + @range Index_Sub_Range.default_widget take : (Index_Sub_Range | Range | Integer) -> Column take self range=(First 1) = _ = range @@ -762,6 +763,7 @@ type Column Arguments: - range: The selection of rows from the table to remove. + @range Index_Sub_Range.default_widget drop : (Index_Sub_Range | Range | Integer) -> Column drop self range=(First 1) = _ = range diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso index 7173af43bc86..ddbe8fb305dc 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso @@ -522,6 +522,7 @@ type Table Arguments: - range: The selection of rows from the table to return. + @range Index_Sub_Range.default_widget take : (Index_Sub_Range | Range | Integer) -> Table take self range=(First 1) = _ = range @@ -535,6 +536,7 @@ type Table Arguments: - range: The selection of rows from the table to remove. + @range Index_Sub_Range.default_widget drop : (Index_Sub_Range | Range | Integer) -> Table drop self range=(First 1) = _ = range diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso index 7af78ca3648f..483782c184e8 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso @@ -1547,6 +1547,7 @@ type Column Arguments: - range: The selection of rows from the table to return. + @range Index_Sub_Range.default_widget take : (Index_Sub_Range | Range | Integer) -> Column take self range=(First 1) = Index_Sub_Range_Module.take_helper self.length self.at self.slice (slice_ranges self) range @@ -1556,6 +1557,7 @@ type Column Arguments: - range: The selection of rows from the table to remove. + @range Index_Sub_Range.default_widget drop : (Index_Sub_Range | Range | Integer) -> Column drop self range=(First 1) = Index_Sub_Range_Module.drop_helper self.length self.at self.slice (slice_ranges self) range diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso index ec9f272aa79f..cc3b3e700e6c 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso @@ -1148,6 +1148,7 @@ type Table Take rows from the top of the table as long as their values sum to 10. table.take (While row-> row.to_vector.compute Statistic.Sum == 10) + @range Index_Sub_Range.default_widget take : (Index_Sub_Range | Range | Integer) -> Table take self range=(First 1) = Index_Sub_Range_Module.take_helper self.row_count self.rows.at self.slice (slice_ranges self) range @@ -1170,6 +1171,7 @@ type Table Drop rows from the top of the table as long as their values sum to 10. table.drop (While row-> row.to_vector.compute Statistic.Sum == 10) + @range Index_Sub_Range.default_widget drop : (Index_Sub_Range | Range | Integer) -> Table drop self range=(First 1) = Index_Sub_Range_Module.drop_helper self.row_count self.rows.at self.slice (slice_ranges self) range diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Value_Type.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Value_Type.enso index a36b8e0231bf..5e46b043806e 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Value_Type.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Value_Type.enso @@ -133,7 +133,7 @@ type Value_Type Fallback provided to allow describing types that are not supported by Enso at this time. - Unsupported_Data_Type type_name:(Text|Nothing)=Nothing (underlying_type:SQL_Type|Nothing=Nothing) + Unsupported_Data_Type type_name:(Text|Nothing)=Nothing (underlying_type:Any|Nothing=Nothing) ## A mix of values can be stored in the Column. diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso index 8ace594b0be2..483cd271bb39 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso @@ -259,7 +259,7 @@ type Invalid_Location format. type Invalid_Format ## PRIVATE - Error column:(Text|Nothing) (value_type:Value_Type|Integer|Number|Date|Time_Of_Day|Boolean) (cells:[Text]) + Error column:(Text|Nothing) (value_type:Value_Type|Integer|Number|Date|Date_Time|Time_Of_Day|Boolean) (cells:[Text]) ## PRIVATE Pretty print the invalid format error. diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Excel_Reader.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Excel_Reader.enso index 61a74c22efaf..1eaba6026df4 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Excel_Reader.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Excel_Reader.enso @@ -30,7 +30,7 @@ prepare_reader_table on_problems result_with_problems = ## PRIVATE Convert Boolean|Infer to the correct HeaderBehavior -make_java_headers : (Boolean | Infer) -> ExcelHeaders.HeaderBehavior +make_java_headers : (Boolean | Infer) -> Any make_java_headers headers = case headers of True -> ExcelHeaders.HeaderBehavior.USE_FIRST_ROW_AS_HEADERS Infer -> ExcelHeaders.HeaderBehavior.INFER diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Test.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Test.enso index 8b2f6f62ed75..2064ec40bda3 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Test.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Test.enso @@ -143,7 +143,7 @@ type Test ## PRIVATE Reports an unexpected dataflow error has occurred. - fail_match_on_unexpected_error : Error.Error -> Integer -> Nothing + fail_match_on_unexpected_error : Error -> Integer -> Nothing fail_match_on_unexpected_error error frames_to_skip = payload = error.catch loc = Meta.get_source_location 1+frames_to_skip diff --git a/distribution/lib/Standard/Visualization/0.0.0-dev/src/SQL/Visualization.enso b/distribution/lib/Standard/Visualization/0.0.0-dev/src/SQL/Visualization.enso index b93d4c12b542..bc63d55ad3d9 100644 --- a/distribution/lib/Standard/Visualization/0.0.0-dev/src/SQL/Visualization.enso +++ b/distribution/lib/Standard/Visualization/0.0.0-dev/src/SQL/Visualization.enso @@ -1,15 +1,16 @@ from Standard.Base import all import Standard.Database.Data.SQL_Type.SQL_Type +import Standard.Database.Data.Table.Table import project.Helpers ## PRIVATE - Prepares the query for visualization. + Prepares the database table for visualization. Arguments: - - x: The query to prepare for visualisation. + - x: The database table to prepare for visualisation. For each interpolation it provides its value, its actual type name, its expected SQL type name and if it was possible to infer it, its expected Enso @@ -17,7 +18,7 @@ import project.Helpers Expected Enso types are inferred based on known SQL types and their mapping to Enso types. -prepare_visualization : Table.IR.Query -> Text +prepare_visualization : Table -> Text prepare_visualization x = prepared = x.to_sql.prepare code = prepared.first diff --git a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Table/Visualization.enso b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Table/Visualization.enso index 16d12e2819fa..8b4be931d5f0 100644 --- a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Table/Visualization.enso +++ b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Table/Visualization.enso @@ -39,6 +39,10 @@ prepare_visualization y max_rows=1000 = dataframe = x.read max_rows all_rows_count = x.row_count make_json_for_table dataframe [] all_rows_count + _ : Function -> + pairs = [['_display_text_', '[Function '+x.to_text+']']] + value = JS_Object.from_pairs pairs + JS_Object.from_pairs [["json", value]] _ -> js_value = x.to_js_object value = if js_value.is_a JS_Object . not then js_value else @@ -96,7 +100,7 @@ make_json_for_object_matrix current vector idx=0 = if idx == vector.length then _ -> js_object = row.to_js_object if js_object.is_a JS_Object . not then False else - if js_object.field_names.sort == ["type" , "constructor"] then False else + if js_object.field_names.sort == ["constructor", "type"] then False else pairs = js_object.field_names.map f-> [f, make_json_for_value (js_object.get f)] JS_Object.from_pairs pairs if to_append == False then Nothing else @@ -184,4 +188,5 @@ make_json_for_value val level=0 = case val of truncated = val.columns.take 5 . map _.name prepared = if val.column_count > 5 then truncated + ["… " + (val.column_count - 5).to_text+ " more"] else truncated "Table{" + val.row_count.to_text + " rows x [" + (prepared.join ", ") + "]}" + _ : Function -> "[Function "+val.to_text+"]" _ -> val.to_display_text diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/immutable/SliceArrayVectorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/immutable/SliceArrayVectorNode.java new file mode 100644 index 000000000000..807b399d124a --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/immutable/SliceArrayVectorNode.java @@ -0,0 +1,60 @@ +package org.enso.interpreter.node.expression.builtin.immutable; + +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.data.Vector; +import org.enso.interpreter.runtime.error.PanicException; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; + +@BuiltinMethod(type = "Vector", name = "slice", description = "Returns a slice of this Vector.") +public abstract class SliceArrayVectorNode extends Node { + SliceArrayVectorNode() {} + + public static SliceArrayVectorNode build() { + return SliceArrayVectorNodeGen.create(); + } + + abstract Object execute(Object self, long start, long end); + + @Specialization + Object executeArray(Array self, long start, long end) { + return Array.slice(self, start, end, self.length()); + } + + @Specialization + Object executeVector( + Vector self, long start, long end, @CachedLibrary(limit = "3") InteropLibrary iop) { + try { + return Array.slice(self, start, end, self.length(iop)); + } catch (UnsupportedMessageException ex) { + CompilerDirectives.transferToInterpreter(); + throw unsupportedMessageException(self); + } + } + + @Specialization(replaces = {"executeArray", "executeVector"}) + Object executeArrayLike( + Object self, long start, long end, @CachedLibrary(limit = "3") InteropLibrary iop) { + try { + long len = iop.getArraySize(self); + return Array.slice(self, start, end, len); + } catch (UnsupportedMessageException ex) { + CompilerDirectives.transferToInterpreter(); + throw unsupportedMessageException(self); + } + } + + private PanicException unsupportedMessageException(Object self) throws PanicException { + var ctx = EnsoContext.get(this); + var arrayType = ctx.getBuiltins().array(); + throw new PanicException( + ctx.getBuiltins().error().makeTypeError(arrayType, self, "self"), this); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetConstructorDeclaringTypeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetConstructorDeclaringTypeNode.java new file mode 100644 index 000000000000..0ae474e989a3 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetConstructorDeclaringTypeNode.java @@ -0,0 +1,17 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.data.Type; + +@BuiltinMethod( + type = "Meta", + name = "get_constructor_declaring_type", + description = "Gets the type that declared this constructor.", + autoRegister = false) +public class GetConstructorDeclaringTypeNode extends Node { + Type execute(AtomConstructor cons) { + return cons.getType(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetConstructorNameNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetConstructorNameNode.java index 5061a0990e66..2fe7f7c1c258 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetConstructorNameNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetConstructorNameNode.java @@ -12,20 +12,8 @@ name = "get_constructor_name", description = "Gets the name of a constructor.", autoRegister = false) -public abstract class GetConstructorNameNode extends Node { - static GetConstructorNameNode build() { - return GetConstructorNameNodeGen.create(); - } - - abstract Text execute(Object atom_constructor); - - @Specialization - Text doConstructor(AtomConstructor cons) { +public class GetConstructorNameNode extends Node { + Text execute(AtomConstructor cons) { return Text.create(cons.getName()); } - - @Specialization - Text doType(Type type) { - return Text.create(type.getQualifiedName().toString()); - } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetShortTypeNameNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetShortTypeNameNode.java new file mode 100644 index 000000000000..e4f103764e2a --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetShortTypeNameNode.java @@ -0,0 +1,19 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.data.text.Text; + +@BuiltinMethod( + type = "Meta", + name = "get_short_type_name", + description = "Gets the short name of a Type.", + autoRegister = false) +public class GetShortTypeNameNode extends Node { + Text execute(Type type) { + return Text.create(type.getName()); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index 019c1ff6a0de..f6ed3908d80e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -1,5 +1,15 @@ package org.enso.interpreter.runtime.data; +import java.util.Arrays; + +import org.enso.interpreter.dsl.Builtin; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.WarningsLibrary; +import org.enso.interpreter.runtime.error.WithWarnings; +import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; +import org.graalvm.collections.EconomicSet; + import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.interop.InteropLibrary; @@ -130,13 +140,18 @@ public static Array empty() { return allocate(0); } - @Builtin.Method(name = "slice", description = "Returns a slice of this Array.") - @Builtin.Specialize - @Builtin.WrapException(from = UnsupportedMessageException.class) - public final Object slice(long start, long end, InteropLibrary interop) - throws UnsupportedMessageException { - var slice = ArraySlice.createOrNull(this, start, length(), end); - return slice == null ? this : slice; + /** + * Takes a slice from an array like object. + * + * @param self array like object + * @param start start of the slice + * @param end end of the slice + * @param len the length of the array + * @return an array-like object representing the slice + */ + public static Object slice(Object self, long start, long end, long len) { + var slice = ArraySlice.createOrNull(self, start, len, end); + return slice == null ? self : slice; } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Vector.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Vector.java index 31c282702bc1..d7c98951657d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Vector.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Vector.java @@ -1,5 +1,15 @@ package org.enso.interpreter.runtime.data; +import org.enso.interpreter.dsl.Builtin; +import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.error.DataflowError; +import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.WarningsLibrary; +import org.enso.interpreter.runtime.error.WithWarnings; +import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; + import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.interop.ArityException; @@ -11,17 +21,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; - import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.Builtin; -import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; -import org.enso.interpreter.runtime.EnsoContext; -import org.enso.interpreter.runtime.callable.function.Function; -import org.enso.interpreter.runtime.error.DataflowError; -import org.enso.interpreter.runtime.error.Warning; -import org.enso.interpreter.runtime.error.WarningsLibrary; -import org.enso.interpreter.runtime.error.WithWarnings; -import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; @ExportLibrary(InteropLibrary.class) @ExportLibrary(TypesLibrary.class) @@ -67,15 +67,6 @@ public final Object toArray() { return this.storage; } - @Builtin.Method(name = "slice", description = "Returns a slice of this Vector.") - @Builtin.Specialize - @Builtin.WrapException(from = UnsupportedMessageException.class) - public final Vector slice(long start, long end, InteropLibrary interop) - throws UnsupportedMessageException { - var slice = ArraySlice.createOrNull(storage, start, length(interop), end); - return slice == null ? this : slice; - } - @Builtin.Method(description = "Returns the length of this Vector.") @Builtin.Specialize @Builtin.WrapException(from = UnsupportedMessageException.class) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java index f74cd6947bff..d5f6423ef8a6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java @@ -1,14 +1,5 @@ package org.enso.interpreter.runtime.error; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.exception.AbstractTruffleException; -import com.oracle.truffle.api.interop.*; -import com.oracle.truffle.api.library.CachedLibrary; -import com.oracle.truffle.api.library.ExportLibrary; -import com.oracle.truffle.api.library.ExportMessage; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.source.SourceSection; import org.enso.interpreter.node.BaseNode.TailStatus; import org.enso.interpreter.node.callable.IndirectInvokeMethodNode; import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode; @@ -23,10 +14,22 @@ import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.interpreter.runtime.state.State; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.exception.AbstractTruffleException; +import com.oracle.truffle.api.interop.ExceptionType; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.source.SourceSection; + /** An exception type for user thrown panic exceptions. */ @ExportLibrary(value = InteropLibrary.class, delegateTo = "payload") @ExportLibrary(TypesLibrary.class) -public class PanicException extends AbstractTruffleException { +public final class PanicException extends AbstractTruffleException { final Object payload; String cacheMessage; diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala index 7ae22e757c03..9c5aa819f4f0 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala @@ -23,6 +23,7 @@ import scala.collection.mutable * * @param source the text source * @param typeGraph the type hierarchy + * @param compiler the compiler instance * @tparam A the type of the text source */ final class SuggestionBuilder[A: IndexedSource]( @@ -91,8 +92,7 @@ final class SuggestionBuilder[A: IndexedSource]( } val getters = members .flatMap(_.arguments) - .map(_.name.name) - .distinct + .distinctBy(_.name) .map(buildGetter(module, tpName.name, _)) val tpSuggestions = tpe +: conses ++: getters @@ -360,8 +360,9 @@ final class SuggestionBuilder[A: IndexedSource]( private def buildGetter( module: QualifiedName, typeName: String, - getterName: String + argument: IR.DefinitionArgument ): Suggestion = { + val getterName = argument.name.name val thisArg = IR.DefinitionArgument.Specified( name = IR.Name.Self(None), ascribedType = None, @@ -377,7 +378,7 @@ final class SuggestionBuilder[A: IndexedSource]( isStatic = false, args = Seq(thisArg), doc = None, - typeSignature = None + typeSignature = argument.name.getMetadata(TypeSignatures) ) } @@ -636,14 +637,21 @@ final class SuggestionBuilder[A: IndexedSource]( * @param arg the value argument * @return the suggestion argument */ - private def buildArgument(arg: IR.DefinitionArgument): Suggestion.Argument = + private def buildArgument(arg: IR.DefinitionArgument): Suggestion.Argument = { + val signatureOpt = arg.name.getMetadata(TypeSignatures).map { meta => + buildTypeSignature(meta.signature) match { + case Vector(targ) => buildTypeArgumentName(targ) + case _ => Any + } + } Suggestion.Argument( name = arg.name.name, - reprType = Any, + reprType = signatureOpt.getOrElse(Any), isSuspended = arg.suspended, hasDefault = arg.defaultValue.isDefined, defaultValue = arg.defaultValue.flatMap(buildDefaultValue) ) + } /** Build return type from the type definition. * @@ -688,6 +696,7 @@ object SuggestionBuilder { /** Creates the suggestion builder for a module. * * @param module the module to index + * @param compiler the compiler instance * @return the suggestions builder for the module */ def apply( @@ -700,6 +709,7 @@ object SuggestionBuilder { * * @param source the text source * @param typeGraph the type hierarchy + * @param compiler the compiler instance * @tparam A the type of the text source */ def apply[A: IndexedSource]( @@ -712,6 +722,7 @@ object SuggestionBuilder { /** Create the suggestion builder. * * @param source the text source + * @param compiler the compiler instance * @tparam A the type of the text source */ def apply[A: IndexedSource]( diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala index 5b878520795b..8f3db34c2a0b 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala @@ -300,7 +300,7 @@ object IR { * @param diagnostics compiler diagnostics for this node */ @SerialVersionUID( - 6584L // verify ascribed types + 6655L // SuggestionBuilder needs to send ascribedType of constructor parameters ) // prevents reading broken caches, see PR-3692 for details sealed case class Module( imports: List[Module.Scope.Import], @@ -3040,7 +3040,6 @@ object IR { /** A representation of the name `Self`, used to refer to the current type. * * @param location the source location that the node corresponds to - * @param synthetic synthetic determines if this `self` was generated by the compiler * @param passData the pass metadata associated with this node * @param diagnostics compiler diagnostics for this node */ @@ -4723,6 +4722,7 @@ object IR { def mapExpressions(fn: Expression => Expression): Specified = { copy( name = name.mapExpressions(fn), + ascribedType = ascribedType.map(fn), defaultValue = defaultValue.map(fn) ) } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala index 32239dee9299..3556ce4e7b90 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala @@ -324,7 +324,7 @@ case object AliasAnalysis extends IRPass { * instead of creating a new scope * @return `expression`, potentially with aliasing information attached */ - def analyseExpression( + private def analyseExpression( expression: IR.Expression, graph: Graph, parentScope: Scope, @@ -467,7 +467,7 @@ case object AliasAnalysis extends IRPass { * defined * @return `args`, potentially */ - def analyseArgumentDefs( + private def analyseArgumentDefs( args: List[IR.DefinitionArgument], graph: Graph, scope: Scope @@ -492,9 +492,13 @@ case object AliasAnalysis extends IRPass { arg.getExternalId ) scope.addDefinition(definition) - arg.updateMetadata( - this -->> Info.Occurrence(graph, occurrenceId) - ) + arg + .updateMetadata(this -->> Info.Occurrence(graph, occurrenceId)) + .copy( + ascribedType = + arg.ascribedType.map(analyseExpression(_, graph, scope)) + ) + case arg @ IR.DefinitionArgument.Specified( name, _, @@ -523,7 +527,11 @@ case object AliasAnalysis extends IRPass { scope.addDefinition(definition) arg - .copy(defaultValue = newDefault) + .copy( + defaultValue = newDefault, + ascribedType = + arg.ascribedType.map(analyseExpression(_, graph, scope)) + ) .updateMetadata(this -->> Info.Occurrence(graph, occurrenceId)) } else { throw new CompilerError( diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala index b3cbcc6c9123..53a76eab3945 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala @@ -10,7 +10,7 @@ import org.enso.compiler.pass.analyse.BindingAnalysis import scala.annotation.unused -/** Resolves and desugars referent name occurences in type positions. +/** Resolves and desugars referent name occurrences in type positions. */ case object TypeNames extends IRPass { @@ -100,32 +100,39 @@ case object TypeNames extends IRPass { params: List[IR.DefinitionArgument], bindingsMap: BindingsMap, expression: IR.Expression - ): IR.Expression = + ): IR.Expression = { + params.hashCode() expression.transformExpressions { case expr if SuspendedArguments.representsSuspended(expr) => expr case n: IR.Name.Literal => - if (params.find(_.name.name == n.name).nonEmpty) { - n - } else { - bindingsMap - .resolveName(n.name) - .map(res => n.updateMetadata(this -->> Resolution(res))) - .fold( - error => - IR.Error - .Resolution(n, IR.Error.Resolution.ResolverError(error)), - n => - n.getMetadata(this).get.target match { - case _: ResolvedModule => - IR.Error.Resolution( - n, - IR.Error.Resolution.UnexpectedModule("type signature") - ) - case _ => n - } - ) - } + processResolvedName(n, bindingsMap.resolveName(n.name)) + case n: IR.Name.Qualified => + processResolvedName( + n, + bindingsMap.resolveQualifiedName(n.parts.map(_.name)) + ) } + } + + private def processResolvedName( + name: IR.Name, + resolvedName: Either[BindingsMap.ResolutionError, BindingsMap.ResolvedName] + ): IR.Name = + resolvedName + .map(res => name.updateMetadata(this -->> Resolution(res))) + .fold( + error => + IR.Error.Resolution(name, IR.Error.Resolution.ResolverError(error)), + n => + n.getMetadata(this).get.target match { + case _: ResolvedModule => + IR.Error.Resolution( + n, + IR.Error.Resolution.UnexpectedModule("type signature") + ) + case _ => n + } + ) /** Executes the pass on the provided `ir`, and returns a possibly transformed * or annotated version of `ir` in an inline context. diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala index 82ecc79e8ab6..766ffdae926f 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala @@ -134,14 +134,14 @@ case object TypeSignatures extends IRPass { lastSignature = None res case ut: IR.Module.Scope.Definition.Type => - Some(ut.mapExpressions(resolveExpression)).map(typ => - typ.copy( - members = typ.members.map(d => { - verifyAscribedArguments(d.arguments) - d - }) - ) - ); + Some( + ut + .copy( + params = ut.params.map(resolveArgument), + members = ut.members.map(resolveDefinitionData) + ) + .mapExpressions(resolveExpression) + ) case err: IR.Error => Some(err) case ann: IR.Name.GenericAnnotation => Some(ann) case _: IR.Module.Scope.Definition.SugaredType => @@ -180,6 +180,36 @@ case object TypeSignatures extends IRPass { } } + private def resolveDefinitionData( + data: IR.Module.Scope.Definition.Data + ): IR.Module.Scope.Definition.Data = { + data.copy( + arguments = data.arguments.map(resolveArgument) + ) + } + + private def resolveArgument( + argument: IR.DefinitionArgument + ): IR.DefinitionArgument = + argument match { + case specified @ IR.DefinitionArgument.Specified( + _, + Some(ascribedType), + _, + _, + _, + _, + _ + ) => + val sig = resolveExpression(ascribedType.duplicate()) + specified.copy( + name = specified.name.updateMetadata(this -->> Signature(sig)), + ascribedType = + Some(ascribedType.updateMetadata(this -->> Signature(sig))) + ) + case argument => argument + } + /** Resolves type signatures in an ascription. * * @param sig the signature to convert diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/context/SuggestionBuilderTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/context/SuggestionBuilderTest.scala index f5eccc34261a..5976ff3f28d1 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/context/SuggestionBuilderTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/context/SuggestionBuilderTest.scala @@ -183,7 +183,9 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { "build method with an argument" in { val code = - """ + """import Standard.Base.Data.Text.Text + |import Standard.Base.Data.Numbers.Number + | |foo : Text -> Number |foo a = 42""".stripMargin val module = code.preprocessModule @@ -198,10 +200,16 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { name = "foo", arguments = Seq( Suggestion.Argument("self", "Unnamed.Test", false, false, None), - Suggestion.Argument("a", "Text", false, false, None) + Suggestion.Argument( + "a", + "Standard.Base.Data.Text.Text", + false, + false, + None + ) ), selfType = "Unnamed.Test", - returnType = "Number", + returnType = "Standard.Base.Data.Numbers.Number", isStatic = true, documentation = None ), @@ -2005,6 +2013,234 @@ class SuggestionBuilderTest extends AnyWordSpecLike with Matchers { ) } + "build type with ascribed constructor" in { + + val code = + """type X + | + |type T + | A (x : X) + |""".stripMargin + val module = code.preprocessModule + + build(code, module) shouldEqual Tree.Root( + Vector( + ModuleNode, + Tree.Node( + Suggestion.Type( + externalId = None, + module = "Unnamed.Test", + name = "X", + params = Seq(), + returnType = "Unnamed.Test.X", + parentType = Some(SuggestionBuilder.Any), + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Type( + externalId = None, + module = "Unnamed.Test", + name = "T", + params = Seq(), + returnType = "Unnamed.Test.T", + parentType = Some(SuggestionBuilder.Any), + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Constructor( + externalId = None, + module = "Unnamed.Test", + name = "A", + arguments = Seq( + Suggestion + .Argument("x", "Unnamed.Test.X", false, false, None) + ), + returnType = "Unnamed.Test.T", + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Method( + externalId = None, + module = "Unnamed.Test", + name = "x", + arguments = Seq( + Suggestion + .Argument("self", "Unnamed.Test.T", false, false, None) + ), + selfType = "Unnamed.Test.T", + returnType = "Unnamed.Test.X", + isStatic = false, + documentation = None + ), + Vector() + ) + ) + ) + } + + "build type with qualified ascribed constructor" in { + + val code = + """import Standard.Base.Data.Numbers + | + |type T + | A (x : Numbers.Number) + |""".stripMargin + val module = code.preprocessModule + + build(code, module) shouldEqual Tree.Root( + Vector( + ModuleNode, + Tree.Node( + Suggestion.Type( + externalId = None, + module = "Unnamed.Test", + name = "T", + params = Seq(), + returnType = "Unnamed.Test.T", + parentType = Some(SuggestionBuilder.Any), + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Constructor( + externalId = None, + module = "Unnamed.Test", + name = "A", + arguments = Seq( + Suggestion + .Argument( + "x", + "Standard.Base.Data.Numbers.Number", + false, + false, + None + ) + ), + returnType = "Unnamed.Test.T", + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Method( + externalId = None, + module = "Unnamed.Test", + name = "x", + arguments = Seq( + Suggestion + .Argument("self", "Unnamed.Test.T", false, false, None) + ), + selfType = "Unnamed.Test.T", + returnType = "Standard.Base.Data.Numbers.Number", + isStatic = false, + documentation = None + ), + Vector() + ) + ) + ) + } + + "build type with ascribed type parameter in constructor" in { + + val code = + """ + |type E a b + | L (x : a) + | R (y : b) + |""".stripMargin + val module = code.preprocessModule + + build(code, module) shouldEqual Tree.Root( + Vector( + ModuleNode, + Tree.Node( + Suggestion.Type( + externalId = None, + module = "Unnamed.Test", + name = "E", + params = Seq( + Suggestion + .Argument("a", SuggestionBuilder.Any, false, false, None), + Suggestion + .Argument("b", SuggestionBuilder.Any, false, false, None) + ), + returnType = "Unnamed.Test.E", + parentType = Some(SuggestionBuilder.Any), + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Constructor( + externalId = None, + module = "Unnamed.Test", + name = "L", + arguments = Seq( + Suggestion.Argument("x", "a", false, false, None) + ), + returnType = "Unnamed.Test.E", + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Constructor( + externalId = None, + module = "Unnamed.Test", + name = "R", + arguments = Seq( + Suggestion.Argument("y", "b", false, false, None) + ), + returnType = "Unnamed.Test.E", + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Method( + externalId = None, + module = "Unnamed.Test", + name = "x", + arguments = Seq( + Suggestion + .Argument("self", "Unnamed.Test.E", false, false, None) + ), + selfType = "Unnamed.Test.E", + returnType = "a", + isStatic = false, + documentation = None + ), + Vector() + ), + Tree.Node( + Suggestion.Method( + externalId = None, + module = "Unnamed.Test", + name = "y", + arguments = Seq( + Suggestion + .Argument("self", "Unnamed.Test.E", false, false, None) + ), + selfType = "Unnamed.Test.E", + returnType = "b", + isStatic = false, + documentation = None + ), + Vector() + ) + ) + ) + } + "build Integer type" in { val code = "type Integer" diff --git a/lib/rust/ensogl/component/Cargo.toml b/lib/rust/ensogl/component/Cargo.toml index d81cafa13271..a3ee0094a36d 100644 --- a/lib/rust/ensogl/component/Cargo.toml +++ b/lib/rust/ensogl/component/Cargo.toml @@ -23,3 +23,4 @@ ensogl-shadow = { path = "shadow" } ensogl-text = { path = "text" } ensogl-tooltip = { path = "tooltip" } ensogl-toggle-button = { path = "toggle-button" } +ensogl-spinner = { path = "spinner" } diff --git a/lib/rust/ensogl/component/spinner/Cargo.toml b/lib/rust/ensogl/component/spinner/Cargo.toml new file mode 100644 index 000000000000..3d3e3b41ce62 --- /dev/null +++ b/lib/rust/ensogl/component/spinner/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ensogl-spinner" +version = "0.1.0" +authors = ["Enso Team "] +edition = "2021" + +[lib] +crate-type = ["rlib", "cdylib"] + +[dependencies] +enso-frp = { path = "../../../frp" } +enso-prelude = { path = "../../../prelude" } +enso-shapely = { path = "../../../shapely" } +enso-types = { path = "../../../types" } +ensogl-core = { path = "../../core" } diff --git a/lib/rust/ensogl/component/spinner/src/lib.rs b/lib/rust/ensogl/component/spinner/src/lib.rs new file mode 100644 index 000000000000..6b3e7c3678fc --- /dev/null +++ b/lib/rust/ensogl/component/spinner/src/lib.rs @@ -0,0 +1,58 @@ +//! An animated spinner component that can be used to indicate that a process +//! is running. + +use ensogl_core::display::shape::*; +use ensogl_core::prelude::*; + +use ensogl_core::display::IntoGlsl; + + + +// =============== +// === Spinner === +// =============== + +const ANIMATION_SPEED: f32 = 0.001; +const SHAPE_RADIUS: f32 = 1.0; +const SHAPE_OFFSET: f32 = 2.0; +const ANIMATION_OFFSET_MS: f32 = 100.0; + +/// Convert a time value to an alpha value for the spinner animation. The +/// animation is a sine wave that oscillates between 0 and 1. +fn time_to_alpha>, F2: Into>, F3: Into>>( + time: F1, + offset: F2, + scale: F3, +) -> Var { + let time = time.into(); + let offset = offset.into(); + let scale = scale.into(); + Var::from(0.5) + ((time + offset) * scale).sin() / 2.0 +} + +ensogl_core::shape! { + alignment = center; + (style: Style, scale: f32, rgba: Vector4) { + let time = &Var::::from("input_time"); + let radius = (&scale * SHAPE_RADIUS).px(); + let offset = (&scale * (SHAPE_RADIUS * 2.0 + SHAPE_OFFSET)).px(); + let dot1 = Circle(&radius).translate_x(-&offset); + let dot2 = Circle(&radius); + let dot3 = Circle(&radius).translate_x(offset); + let dot3_anim_start = 0.0; + let dot2_anim_start = dot3_anim_start + ANIMATION_OFFSET_MS; + let dot1_anim_start = dot2_anim_start + ANIMATION_OFFSET_MS; + let dot1_alpha = rgba.w() * time_to_alpha(time, dot1_anim_start, ANIMATION_SPEED); + let dot2_alpha = rgba.w() * time_to_alpha(time, dot2_anim_start, ANIMATION_SPEED); + let dot3_alpha = rgba.w() * time_to_alpha(time, dot3_anim_start, ANIMATION_SPEED); + let rgb = rgba.xyz(); + let color1 = format!("srgba({}.x,{}.y,{}.z,{})", rgb, rgb, rgb, dot1_alpha.glsl()); + let color2 = format!("srgba({}.x,{}.y,{}.z,{})", rgb, rgb, rgb, dot2_alpha.glsl()); + let color3 = format!("srgba({}.x,{}.y,{}.z,{})", rgb, rgb, rgb, dot3_alpha.glsl()); + let dot1 = dot1.fill(color1); + let dot2 = dot2.fill(color2); + let dot3 = dot3.fill(color3); + let shape = dot1 + dot2 + dot3; + shape.into() + } +} diff --git a/lib/rust/ensogl/component/src/lib.rs b/lib/rust/ensogl/component/src/lib.rs index 17ee2e6b46f6..bf2d2ab1feb0 100644 --- a/lib/rust/ensogl/component/src/lib.rs +++ b/lib/rust/ensogl/component/src/lib.rs @@ -24,6 +24,7 @@ pub use ensogl_scroll_area as scroll_area; pub use ensogl_scrollbar as scrollbar; pub use ensogl_selector as selector; pub use ensogl_shadow as shadow; +pub use ensogl_spinner as spinner; pub use ensogl_text as text; pub use ensogl_toggle_button as toggle_button; pub use ensogl_tooltip as tooltip; diff --git a/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs b/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs index b2b30d19d3f9..25a2bc76cf69 100644 --- a/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs +++ b/lib/rust/ensogl/core/src/display/render/passes/pixel_read.rs @@ -158,7 +158,6 @@ impl PixelReadPass { let flags = 0; let sync = context.fence_sync(*condition, flags).unwrap(); self.sync = Some(sync); - context.flush(); } #[profile(Detail)] diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java index ec4f8bc88201..bd555ffda66a 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java @@ -1,13 +1,26 @@ package org.enso.interpreter.dsl.model; -import org.enso.interpreter.dsl.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; -import java.util.*; +import javax.tools.Diagnostic.Kind; + +import org.enso.interpreter.dsl.AcceptsError; +import org.enso.interpreter.dsl.AcceptsWarning; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.dsl.Suspend; /** A domain-specific representation of a builtin method. */ public class MethodDefinition { @@ -26,7 +39,7 @@ public class MethodDefinition { private final Set imports; private final boolean needsCallerInfo; private final boolean needsFrame; - private final String constructorExpression; + private final Object constructorExpression; /** * Creates a new instance of this class. @@ -70,7 +83,7 @@ public String[] aliases() { } } - private String initConstructor(TypeElement element) { + private Object initConstructor(TypeElement element) { boolean useBuild = element.getEnclosedElements().stream() .anyMatch( @@ -88,7 +101,7 @@ private String initConstructor(TypeElement element) { } else { boolean isClassAbstract = element.getModifiers().contains(Modifier.ABSTRACT); if (isClassAbstract) { - throw new RuntimeException( + return new RuntimeException( "Class " + element.getSimpleName() + " is abstract, and has no static `build()` method."); @@ -153,6 +166,11 @@ private List initArguments(ExecutableElement method) { * @return whether the definition is fully valid. */ public boolean validate(ProcessingEnvironment processingEnvironment) { + if (this.constructorExpression instanceof Exception ex) { + processingEnvironment.getMessager().printMessage(Kind.ERROR, ex.getMessage(), element); + return false; + } + boolean argsValid = arguments.stream().allMatch(arg -> arg.validate(processingEnvironment)); return argsValid; @@ -214,7 +232,7 @@ public boolean needsFrame() { } public String getConstructorExpression() { - return constructorExpression; + return (String) constructorExpression; } public boolean isStatic() { diff --git a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectManagementApiSpec.scala b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectManagementApiSpec.scala index 546524e559e5..c4abc75624cd 100644 --- a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectManagementApiSpec.scala +++ b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectManagementApiSpec.scala @@ -806,6 +806,7 @@ class ProjectManagementApiSpec } "return a list of projects even if editions of some of them cannot be resolved" taggedAs Retry in { + pending // flaky implicit val client: WsTestClient = new WsTestClient(address) //given val fooId = createProject("Foo") diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 5a80e3bc76fc..44ae223ce62e 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -204,10 +204,15 @@ public static ZoneOffset get_datetime_offset(ZonedDateTime datetime) { /** * Counts days within the range from start (inclusive) to end (exclusive). - * - *

If start is before end, it will return 0. */ public static long days_between(LocalDate start, LocalDate end) { return ChronoUnit.DAYS.between(start, end); } + + /** + * Counts months within the range from start (inclusive) to end (exclusive). + */ + public static long months_between(LocalDate start, LocalDate end) { + return ChronoUnit.MONTHS.between(start, end); + } } diff --git a/test/Benchmarks/src/Vector/Utils.enso b/test/Benchmarks/src/Vector/Utils.enso index 0da2b8651615..2cadbdba633d 100644 --- a/test/Benchmarks/src/Vector/Utils.enso +++ b/test/Benchmarks/src/Vector/Utils.enso @@ -2,7 +2,7 @@ from Standard.Base import all polyglot java import java.util.Random -make_random_vec : Integer -> Vector.Vector +make_random_vec : Integer -> Vector make_random_vec n = random_gen = Random.new n Vector.fill n random_gen.nextLong diff --git a/test/Table_Tests/src/Common_Table_Operations/Take_Drop_Spec.enso b/test/Table_Tests/src/Common_Table_Operations/Take_Drop_Spec.enso index a05dceb989a6..711d1a11f147 100644 --- a/test/Table_Tests/src/Common_Table_Operations/Take_Drop_Spec.enso +++ b/test/Table_Tests/src/Common_Table_Operations/Take_Drop_Spec.enso @@ -1,6 +1,7 @@ from Standard.Base import all from Standard.Base.Data.Index_Sub_Range.Index_Sub_Range import While, Sample, Every import Standard.Base.Errors.Common.Index_Out_Of_Bounds +import Standard.Base.Errors.Common.Type_Error import Standard.Base.Errors.Illegal_Argument.Illegal_Argument from Standard.Table.Errors import all @@ -151,8 +152,30 @@ spec setup = rnd.at "alpha" . to_vector . should_equal alpha_sample rnd.at "beta" . to_vector . should_equal beta_sample - Test.specify "should allow selecting rows as long as they satisfy a predicate" pending="While is not implemented for Table until the Row type is implemented." <| - Nothing + Test.specify "should allow selecting rows as long as they satisfy a predicate" <| + t = table_builder [["a", [1, 2, 3, 4]], ["b", [5, 6, 7, 8]]] + + t2 = t.take (While (row -> row.at "a" < 3)) + t2.row_count . should_equal 2 + t2.at "a" . to_vector . should_equal [1, 2] + t2.at "b" . to_vector . should_equal [5, 6] + + Test.specify "should gracefully handle missing constructor arguments" <| + t = table_builder [["X", [1, 2, 3]]] + t.take "FOO" . should_fail_with Type_Error + t.drop "FOO" . should_fail_with Type_Error + + r1 = t.take (Index_Sub_Range.While) + r1.should_fail_with Illegal_Argument + r1.catch.to_display_text . should_contain "The constructor While is missing some arguments" + + r2 = t.drop (Index_Sub_Range.Every ...) + r2.should_fail_with Illegal_Argument + r2.catch.to_display_text . should_contain "The constructor Every is missing some arguments" + + r3 = t.take (Index_Sub_Range.First _) + r3.should_fail_with Illegal_Argument + r3.catch.to_display_text . should_contain "Got a Function instead of a range, is a constructor argument missing?" Test.group prefix+"Column.take/drop" pending=take_drop_by_pending <| table = @@ -310,3 +333,20 @@ spec setup = three.drop (While (_ > 10)) . should_equal three three.drop (While (_ < 10)) . should_equal empty + + Test.specify "should gracefully handle missing constructor arguments" <| + c = table_builder [["X", [1, 2, 3]]] . at "X" + c.take "FOO" . should_fail_with Type_Error + c.drop "FOO" . should_fail_with Type_Error + + r1 = c.take (Index_Sub_Range.While) + r1.should_fail_with Illegal_Argument + r1.catch.to_display_text . should_contain "The constructor While is missing some arguments" + + r2 = c.drop (Index_Sub_Range.Every ...) + r2.should_fail_with Illegal_Argument + r2.catch.to_display_text . should_contain "The constructor Every is missing some arguments" + + r3 = c.take (Index_Sub_Range.First _) + r3.should_fail_with Illegal_Argument + r3.catch.to_display_text . should_contain "Got a Function instead of a range, is a constructor argument missing?" diff --git a/test/Tests/src/Data/Range_Spec.enso b/test/Tests/src/Data/Range_Spec.enso index 582b25850873..64766a5052f3 100644 --- a/test/Tests/src/Data/Range_Spec.enso +++ b/test/Tests/src/Data/Range_Spec.enso @@ -29,6 +29,13 @@ spec = Test.group "Range" <| range_3.end . should_equal 0 range_3.step . should_equal -1 + Test.specify "should allow to include the end" <| + 1.up_to 3 include_end=True . to_vector . should_equal [1, 2, 3] + 3.down_to 1 include_end=True . to_vector . should_equal [3, 2, 1] + + 1.up_to 1 include_end=True . to_vector . should_equal [1] + 1.down_to 1 include_end=True . to_vector . should_equal [1] + Test.specify "should allow creation with Range.new" <| Range.new . should_equal (Range.Between 0 100 1) Range.new 5 20 . should_equal (Range.Between 5 20 1) diff --git a/test/Tests/src/Data/Text_Spec.enso b/test/Tests/src/Data/Text_Spec.enso index d0d20f136ca3..f07b7c751c6b 100644 --- a/test/Tests/src/Data/Text_Spec.enso +++ b/test/Tests/src/Data/Text_Spec.enso @@ -736,6 +736,25 @@ spec = "".drop (Sample 0) . should_equal "" "".drop (Sample 100) . should_equal "" + Test.specify "take and drop should gracefully handle missing constructor arguments" <| + "".take "FOO" . should_fail_with Type_Error + "".drop "FOO" . should_fail_with Type_Error + + r1 = "".take (Index_Sub_Range.While) + r1.should_fail_with Illegal_Argument + r1.catch.to_display_text . should_contain "The constructor While is missing some arguments" + + r2 = "".drop (Text_Sub_Range.Before ...) + r2.should_fail_with Illegal_Argument + r2.catch.to_display_text . should_contain "The constructor Before is missing some arguments" + + r3 = "".take (Index_Sub_Range.First _) + r3.should_fail_with Illegal_Argument + r3.catch.to_display_text . should_contain "Got a Function instead of a range, is a constructor argument missing?" + + # Double-check that constructors of _unexpected_ types are still yielding a type error. + "".take (Case_Sensitivity.Insensitive ...) . should_fail_with Type_Error + Test.specify "should correctly convert character case" <| "FooBar Baz".to_case Case.Lower . should_equal "foobar baz" "FooBar Baz".to_case Case.Upper . should_equal "FOOBAR BAZ" diff --git a/test/Tests/src/Data/Time/Date_Range_Spec.enso b/test/Tests/src/Data/Time/Date_Range_Spec.enso new file mode 100644 index 000000000000..20fe0933ef22 --- /dev/null +++ b/test/Tests/src/Data/Time/Date_Range_Spec.enso @@ -0,0 +1,176 @@ +from Standard.Base import all +import Standard.Base.Errors.Illegal_Argument.Illegal_Argument + +from Standard.Test import Problems, Test, Test_Suite +import Standard.Test.Extensions + +main = Test_Suite.run_main spec + +spec = + Test.group "Date_Range" <| + Test.specify "should be created with up_to and down_to extension methods" <| + (Date.new 2020 02 28).up_to (Date.new 2020 03 02) . to_vector . should_equal [Date.new 2020 02 28, Date.new 2020 02 29, Date.new 2020 03 01] + (Date.new 2020 02 28).up_to (Date.new 2020 03 02) include_end=True . to_vector . should_equal [Date.new 2020 02 28, Date.new 2020 02 29, Date.new 2020 03 01, Date.new 2020 03 02] + + (Date.new 2021 03 01).down_to (Date.new 2021 02 28) . to_vector . should_equal [Date.new 2021 03 01] + (Date.new 2021 03 01).down_to (Date.new 2021 02 28) include_end=True . to_vector . should_equal [Date.new 2021 03 01, Date.new 2021 02 28] + + (Date.new 2023 12 31).up_to (Date.new 2023 12 31) . to_vector . should_equal [] + (Date.new 2023 12 31).up_to (Date.new 2023 12 31) include_end=True . to_vector . should_equal [Date.new 2023 12 31] + + (Date.new 2023 12 31).down_to (Date.new 2023 12 31) . to_vector . should_equal [] + (Date.new 2023 12 31).down_to (Date.new 2023 12 31) include_end=True . to_vector . should_equal [Date.new 2023 12 31] + + (Date.new 2023 12 31).down_to (Date.new 2023 12 31) . with_step Date_Period.Month . to_vector . should_equal [] + + Test.specify ".new should infer if the range should be increasing or not" <| + Date_Range.new (Date.new 2023 10 01) (Date.new 2023 10 04) . to_vector . should_equal [Date.new 2023 10 01, Date.new 2023 10 02, Date.new 2023 10 03] + Date_Range.new (Date.new 2023 10 04) (Date.new 2023 10 01) . to_vector . should_equal [Date.new 2023 10 04, Date.new 2023 10 03, Date.new 2023 10 02] + + Test.specify "will be empty if the start and end are swapped with up_to or down_to" <| + (Date.new 2023 10 01).down_to (Date.new 2023 10 04) . to_vector . should_equal [] + (Date.new 2023 10 04).up_to (Date.new 2023 10 01) . to_vector . should_equal [] + + (Date.new 2023 10 01).down_to (Date.new 2023 10 04) . with_step Date_Period.Month . to_vector . should_equal [] + (Date.new 2023 10 04).up_to (Date.new 2023 10 01) . with_step Date_Period.Month . to_vector . should_equal [] + + Test.specify "should allow setting a custom step" <| + (Date.new 2020 01 10).up_to (Date.new 2020 01 31) . with_step (Period.new days=5) . to_vector . should_equal [Date.new 2020 01 10, Date.new 2020 01 15, Date.new 2020 01 20, Date.new 2020 01 25, Date.new 2020 01 30] + (Date.new 2020 01 10).up_to (Date.new 2020 01 30) . with_step (Period.new days=5) . to_vector . should_equal [Date.new 2020 01 10, Date.new 2020 01 15, Date.new 2020 01 20, Date.new 2020 01 25] + (Date.new 2020 01 10).up_to (Date.new 2020 01 30) include_end=True . with_step (Period.new days=5) . to_vector . should_equal [Date.new 2020 01 10, Date.new 2020 01 15, Date.new 2020 01 20, Date.new 2020 01 25, Date.new 2020 01 30] + + (Date.new 2020 01 10).down_to (Date.new 2020 01 01) . with_step Date_Period.Week . to_vector . should_equal [Date.new 2020 01 10, Date.new 2020 01 03] + + (Date.new 2020 01 01).up_to (Date.new 2020 12 31) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 01, Date.new 2020 02 01, Date.new 2020 03 01, Date.new 2020 04 01, Date.new 2020 05 01, Date.new 2020 06 01, Date.new 2020 07 01, Date.new 2020 08 01, Date.new 2020 09 01, Date.new 2020 10 01, Date.new 2020 11 01, Date.new 2020 12 01] + (Date.new 2020 01 01).up_to (Date.new 2026) . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2022 01 01, Date.new 2024 01 01] + (Date.new 2020 01 01).up_to (Date.new 2026) include_end=True . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2022 01 01, Date.new 2024 01 01, Date.new 2026 01 01] + (Date.new 2060 11 25).down_to (Date.new 2020 11 24) . with_step (Period.new years=20) . to_vector . should_equal [Date.new 2060 11 25, Date.new 2040 11 25, Date.new 2020 11 25] + + (Date.new 2020).up_to (Date.new 2023) . with_step (Period.new years=1 months=2 days=3) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2021 03 04, Date.new 2022 05 07] + + Test.specify "should handle end of month edge cases" <| + (Date.new 2020 01 31).up_to (Date.new 2020 12 31) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 02 29, Date.new 2020 03 31, Date.new 2020 04 30, Date.new 2020 05 31, Date.new 2020 06 30, Date.new 2020 07 31, Date.new 2020 08 31, Date.new 2020 09 30, Date.new 2020 10 31, Date.new 2020 11 30, Date.new 2020 12 31] + (Date.new 2021 01 28).up_to (Date.new 2021 05 10) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2021 01 28, Date.new 2021 02 28, Date.new 2021 03 28, Date.new 2021 04 28] + (Date.new 2023 01 30).up_to (Date.new 2023 06 10) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2023 01 30, Date.new 2023 02 28, Date.new 2023 03 30, Date.new 2023 04 30, Date.new 2023 05 30] + (Date.new 2023 01 30).up_to (Date.new 2023 06 10) . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2023 01 30, Date.new 2023 03 30, Date.new 2023 05 30] + (Date.new 2020 02 29).up_to (Date.new 2023) . with_step Date_Period.Year . to_vector . should_equal [Date.new 2020 02 29, Date.new 2021 02 28, Date.new 2022 02 28] + + Test.specify "should handle edge cases" <| + (Date.new 2020 02 27).up_to (Date.new 2020 03 02) include_end=True . with_step (Period.new days=2) . to_vector . should_equal [Date.new 2020 02 27, Date.new 2020 02 29, Date.new 2020 03 02] + + (Date.new 2020 02 27).up_to (Date.new 2020 02 28) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 02 27] + (Date.new 2020 02 27).up_to (Date.new 2020 04 27) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 02 27, Date.new 2020 03 27] + (Date.new 2020 02 27).up_to (Date.new 2020 04 27) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 02 27, Date.new 2020 03 27, Date.new 2020 04 27] + (Date.new 2020 02 27).up_to (Date.new 2020 04 01) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 02 27, Date.new 2020 03 27] + + (Date.new 2021 02 01).up_to (Date.new 2021 03 01) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2021 02 01, Date.new 2021 03 01] + + (Date.new 2020 01 31).up_to (Date.new 2020 04 30) . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 02 29, Date.new 2020 03 31] + (Date.new 2020 01 31).up_to (Date.new 2020 04 30) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 02 29, Date.new 2020 03 31, Date.new 2020 04 30] + (Date.new 2020 01 31).up_to (Date.new 2020 04 01) include_end=True . with_step Date_Period.Month . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 02 29, Date.new 2020 03 31] + + v = (Date.new 2020 01 01).up_to (Date.new 2020 12 31) include_end=True . with_step Date_Period.Month . to_vector + v.length . should_equal 12 + v.first . should_equal (Date.new 2020 01 01) + v.last . should_equal (Date.new 2020 12 01) + + (Date.new 2020 01 01).up_to (Date.new 2020 12 31) include_end=True . with_step (Period.new months=3) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2020 04 01, Date.new 2020 07 01, Date.new 2020 10 01] + (Date.new 2020 01 01).up_to (Date.new 2021 01 01) include_end=True . with_step (Period.new months=3) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2020 04 01, Date.new 2020 07 01, Date.new 2020 10 01, Date.new 2021 01 01] + (Date.new 2020 01 01).up_to (Date.new 2021 01 01) include_end=False . with_step (Period.new months=3) . to_vector . should_equal [Date.new 2020 01 01, Date.new 2020 04 01, Date.new 2020 07 01, Date.new 2020 10 01] + + (Date.new 2020 01 31).up_to (Date.new 2020 05 01) . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 03 31] + (Date.new 2020 01 31).up_to (Date.new 2020 03 31) include_end=True . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 03 31] + (Date.new 2020 01 31).up_to (Date.new 2020 03 31) . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2020 01 31] + (Date.new 2020 01 31).up_to (Date.new 2020 04 02) . with_step (Period.new months=2) . to_vector . should_equal [Date.new 2020 01 31, Date.new 2020 03 31] + + (Date.new 2020 12 31).up_to (Date.new 2021 01 01) . with_step (Period.new years=1) . to_vector . should_equal [Date.new 2020 12 31] + (Date.new 2020 12 31).up_to (Date.new 2021 01 01) . with_step (Period.new years=10) . to_vector . should_equal [Date.new 2020 12 31] + (Date.new 2020 12 31).up_to (Date.new 2023 01 01) . with_step (Period.new years=1) . to_vector . should_equal [Date.new 2020 12 31, Date.new 2021 12 31, Date.new 2022 12 31] + (Date.new 2020 12 31).up_to (Date.new 2023 01 01) . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2020 12 31, Date.new 2022 12 31] + (Date.new 2020 12 31).up_to (Date.new 2023 01 01) . with_step (Period.new years=10) . to_vector . should_equal [Date.new 2020 12 31] + (Date.new 2021 01 01).up_to (Date.new 2023 12 31) . with_step (Period.new years=1) . to_vector . should_equal [Date.new 2021 01 01, Date.new 2022 01 01, Date.new 2023 01 01] + (Date.new 2021 01 01).up_to (Date.new 2023 12 31) . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2021 01 01, Date.new 2023 01 01] + (Date.new 2021 01 01).up_to (Date.new 2023 12 31) include_end=True . with_step (Period.new years=2) . to_vector . should_equal [Date.new 2021 01 01, Date.new 2023 01 01] + + Test.specify "should not allow a non-positive step" <| + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=0 months=0 days=0) . should_fail_with Illegal_Argument + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=0 months=-1 days=0) . should_fail_with Illegal_Argument + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=0 months=0 days=-1) . should_fail_with Illegal_Argument + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=-1 months=0 days=0) . should_fail_with Illegal_Argument + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=2 months=-1 days=0) . should_fail_with Illegal_Argument + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=-1 months=40 days=0) . should_fail_with Illegal_Argument + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=1 months=40 days=-20) . should_fail_with Illegal_Argument + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=-1 months=-2 days=2) . should_fail_with Illegal_Argument + (Date.new 2010).up_to (Date.new 2050) . with_step (Period.new years=-1 months=-2 days=-1) . should_fail_with Illegal_Argument + + # e.g. 2021-06-05 + 1 month - 30 days == 2021-06-05 --> no progression + (Date.new 2021 06 05).up_to (Date.new 2021 06 08) . with_step (Period.new months=1 days=(-30)) . should_fail_with Illegal_Argument + (Date.new 2021 05 05).up_to (Date.new 2021 06 08) . with_step (Period.new months=1 days=(-30)) . should_fail_with Illegal_Argument + (Date.new 2021 02 28).up_to (Date.new 2021 03 31) . with_step ((Period.new years=1 months=(-11) days=(-28))) . should_fail_with Illegal_Argument + + Test.specify "should allow to reverse a range, returning a vector" <| + (Date.new 2020 01 02).up_to (Date.new 2020 01 02) . reverse . should_equal [] + (Date.new 2020 01 02).up_to (Date.new 2020 01 02) include_end=True . reverse . should_equal [Date.new 2020 01 02] + + (Date.new 2020 01 03).down_to (Date.new 2020 01 01) . reverse . should_equal [Date.new 2020 01 02, Date.new 2020 01 03] + + (Date.new 2020 02 29).up_to (Date.new 2023) . with_step Date_Period.Year . reverse . should_equal [Date.new 2022 02 28, Date.new 2021 02 28, Date.new 2020 02 29] + + Test.specify "should be consistent with its to_vector representation" <| + r1 = (Date.new 2020 02 28).up_to (Date.new 2020 03 02) + r2 = (Date.new 2020 02 28).up_to (Date.new 2020 03 02) include_end=True + r3 = (Date.new 2021 03 01).down_to (Date.new 2021 02 28) + r4 = (Date.new 2021 03 01).down_to (Date.new 2021 02 28) include_end=True + r5 = (Date.new 2023 12 31).up_to (Date.new 2023 12 31) + r6 = (Date.new 2023 12 31).up_to (Date.new 2023 12 31) include_end=True + r7 = (Date.new 2023 12 31).down_to (Date.new 2023 12 31) + r8 = (Date.new 2023 12 31).down_to (Date.new 2023 12 31) include_end=True + + r9 = (Date.new 2020 01 10).down_to (Date.new 2020 01 01) . with_step Date_Period.Week + r10 = (Date.new 2020 01 01).up_to (Date.new 2020 12 31) . with_step Date_Period.Month + r11 = (Date.new 2020 01 01).up_to (Date.new 2026) . with_step (Period.new years=2) + r12 = (Date.new 2020 01 01).up_to (Date.new 2026) include_end=True . with_step (Period.new years=2) + r13 = (Date.new 2060 11 25).down_to (Date.new 2020 11 24) . with_step (Period.new years=20) + + r14 = (Date.new 2020 01 31).up_to (Date.new 2020 12 31) include_end=True . with_step Date_Period.Month + r15 = (Date.new 2020 02 29).up_to (Date.new 2023) . with_step Date_Period.Year + + ranges = [r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15] + ranges.each r-> Test.with_clue r.to_text+": " <| + r.length . should_equal r.to_vector.length + r.is_empty . should_equal r.to_vector.is_empty + r.not_empty . should_equal r.to_vector.not_empty + + r.map .day_of_week . should_equal (r.to_vector.map .day_of_week) + p = d-> d.day_of_week == Day_Of_Week.Monday + r.filter p . should_equal (r.to_vector.filter p) + r.all p . should_equal (r.to_vector.all p) + r.any p . should_equal (r.to_vector.any p) + r.find p . should_equal (r.to_vector.find p) + r.index_of p . should_equal (r.to_vector.index_of p) + r.last_index_of p . should_equal (r.to_vector.last_index_of p) + count_mondays acc date = + if date.day_of_week == Day_Of_Week.Monday then acc+1 else acc + r.fold 0 count_mondays . should_equal (r.to_vector.fold 0 count_mondays) + r.running_fold 0 count_mondays . should_equal (r.to_vector.running_fold 0 count_mondays) + + reducer x y = if x > y then x else y + # Catch+to_text to fix Empty_Error equality. + r.reduce reducer . catch . to_text . should_equal (r.to_vector.reduce reducer . catch . to_text) + + Test.specify "should define friendly text representations" <| + r1 = (Date.new 2020 02 28).up_to (Date.new 2020 03 02) + r2 = (Date.new 2020 03 20).down_to (Date.new 2020 03 01) include_end=True . with_step Date_Period.Week + + r1.to_text . should_equal '(Date_Range from 2020-02-28 up to 2020-03-02)' + r2.to_text . should_equal '(Date_Range from 2020-03-20 down to 2020-02-29 by 7D)' + + r1.pretty . should_equal r1.to_text + r2.pretty . should_equal r2.to_text + + r1.to_display_text . should_equal '[2020-02-28 .. 2020-03-02]' + r2.to_display_text . should_equal '[2020-03-20 .. 2020-02-29 by -7D]' + + Test.specify "should be serializable to JSON" <| + r = (Date.new 2020 01 01).up_to (Date.new 2020 01 03) + r.to_json . should_equal '{"type":"Date_Range","start":{"type":"Date","constructor":"new","day":1,"month":1,"year":2020},"end":{"type":"Date","constructor":"new","day":3,"month":1,"year":2020},"step":{"type":"Period","constructor":"new","days":1},"increasing":true}' diff --git a/test/Tests/src/Data/Time/Spec.enso b/test/Tests/src/Data/Time/Spec.enso index 9d19faa68d2b..1322d2dd627c 100644 --- a/test/Tests/src/Data/Time/Spec.enso +++ b/test/Tests/src/Data/Time/Spec.enso @@ -7,12 +7,14 @@ import project.Data.Time.Duration_Spec import project.Data.Time.Period_Spec import project.Data.Time.Time_Of_Day_Spec import project.Data.Time.Date_Spec +import project.Data.Time.Date_Range_Spec import project.Data.Time.Date_Time_Spec import project.Data.Time.Time_Zone_Spec import project.Data.Time.Day_Of_Week_Spec spec = Date_Spec.spec + Date_Range_Spec.spec Duration_Spec.spec Period_Spec.spec Time_Of_Day_Spec.spec diff --git a/test/Tests/src/Data/Vector_Spec.enso b/test/Tests/src/Data/Vector_Spec.enso index 586ec76064ab..9ed592143ad4 100644 --- a/test/Tests/src/Data/Vector_Spec.enso +++ b/test/Tests/src/Data/Vector_Spec.enso @@ -1,4 +1,5 @@ from Standard.Base import all +from Standard.Base.Data.Array_Proxy import Array_Proxy import Standard.Base.Data.Vector.Empty_Error import Standard.Base.Errors.Common.Incomparable_Values import Standard.Base.Errors.Common.Index_Out_Of_Bounds @@ -13,6 +14,8 @@ from Standard.Base.Data.Index_Sub_Range.Index_Sub_Range import While, By_Index, from Standard.Test import Test, Test_Suite import Standard.Test.Extensions +polyglot java import java.util.ArrayList + type T Value a b @@ -265,9 +268,9 @@ type_spec name alter = Test.group name <| boolvec.filter Filter_Condition.Is_False . should_equal [False] Test.specify "should filter elements with indices" <| - alter [0, 10, 2, 2] . filter_with_index (==) . should_equal [0, 2] + (alter [0, 10, 2, 2] . filter_with_index (==)) . should_equal [0, 2] (alter [1, 2, 3, 4] . filter_with_index ix-> _-> ix < 2) . should_equal [1, 2] - alter ([1, 2, 3, 4] . filter_with_index ix-> _-> if ix == 1 then Error.throw <| My_Error.Value "foo" else True) . should_fail_with My_Error + (alter [1, 2, 3, 4] . filter_with_index ix-> _-> if ix == 1 then Error.throw <| My_Error.Value "foo" else True) . should_fail_with My_Error Test.specify "should partition elements" <| alter [1, 2, 3, 4, 5] . partition (x -> x % 2 == 0) . should_equal <| Pair.new [2, 4] [1, 3, 5] @@ -410,7 +413,7 @@ type_spec name alter = Test.group name <| vec.slice 1 1 . should_equal [] vec.slice 0 100 . should_equal [1, 2, 3, 4, 5, 6] Meta.is_same_object vec (vec.slice 0 100) . should_be_true - Meta.meta Vector . name . should_equal (Meta.get_qualified_type_name (vec.slice 1 1)) + Meta.get_qualified_type_name (vec.slice 1 1) . should_equal (Meta.meta Vector . qualified_name) Test.specify "should define take and drop family of operations" <| vec = alter [1, 2, 3, 4, 5, 6] @@ -533,6 +536,22 @@ type_spec name alter = Test.group name <| alter ["a", "a", "a"] . drop (Sample 1) . should_equal ["a", "a"] alter ["a", "a", "a"] . drop (Sample 100) . should_equal [] + Test.specify "take/drop should gracefully handle missing constructor arguments" <| + [].take "FOO" . should_fail_with Type_Error + [].drop "FOO" . should_fail_with Type_Error + + r1 = [].take (Index_Sub_Range.While) + r1.should_fail_with Illegal_Argument + r1.catch.to_display_text . should_contain "The constructor While is missing some arguments" + + r2 = [].drop (Index_Sub_Range.Every ...) + r2.should_fail_with Illegal_Argument + r2.catch.to_display_text . should_contain "The constructor Every is missing some arguments" + + r3 = [].take (Index_Sub_Range.First _) + r3.should_fail_with Illegal_Argument + r3.catch.to_display_text . should_contain "Got a Function instead of a range, is a constructor argument missing?" + Test.specify "should allow getting the last element of the vector" <| non_empty_vec = alter [1, 2, 3, 4, 5] singleton_vec = alter [1] @@ -752,7 +771,18 @@ spec = [True, False, 'a'].pretty . should_equal "[True, False, 'a']" [Foo.Value True].pretty . should_equal "[(Foo.Value True)]" - type_spec "Use Vector as vectors" (x -> x) - type_spec "Use Array as vectors" (x -> x.to_array) + type_spec "Use Vector as vectors" identity + type_spec "Use Array as vectors" (v -> v.to_array) + type_spec "Use Java ArrayList as vectors" v-> + arr = ArrayList.new + v.each (x -> arr.add x) + arr + type_spec "Use Array_Proxy as vectors" v-> + Array_Proxy.new v.length (ix -> v.at ix) + type_spec "Use a slice of an array as vectors" v-> + v2 = v+[Nothing] + sliced_vector = v2.slice 0 v.length + sliced_array = sliced_vector.to_array + sliced_array main = Test_Suite.run_main spec diff --git a/test/Tests/src/Semantic/Default_Args_Spec.enso b/test/Tests/src/Semantic/Default_Args_Spec.enso index df97e417dd83..e486820eea8c 100644 --- a/test/Tests/src/Semantic/Default_Args_Spec.enso +++ b/test/Tests/src/Semantic/Default_Args_Spec.enso @@ -6,7 +6,7 @@ import Standard.Test.Extensions from project.Semantic.Default_Args_Spec.Box import all type Box - type Foo (v : Bool = True) + Foo (v : Bool = True) type Bar (a : Integer = 1) (b : Box = (Foo False)) (c : Boolean = b.v) diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index 3b3fe4364f8b..8119ea30703a 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -207,6 +207,7 @@ spec = Meta.is_atom typ . should_be_false meta_typ = Meta.meta typ meta_typ . should_be_a Meta.Type + meta_typ.name . should_equal "Boolean" cons = case meta_typ of Meta.Type.Value _ -> meta_typ.constructors _ -> Test.fail "Should be a Meta.Type.Value: " + meta_typ.to_text @@ -222,6 +223,7 @@ spec = Meta.is_atom typ . should_be_false meta_typ = Meta.meta typ meta_typ . should_be_a Meta.Type + meta_typ.name . should_equal "My_Type" cons = case meta_typ of Meta.Type.Value _ -> meta_typ.constructors _ -> Test.fail "Should be a Meta.Type.Value: " + meta_typ.to_text @@ -229,6 +231,8 @@ spec = cons.length . should_equal 1 cons.at 0 . should_be_a Meta.Constructor cons . map (x -> x.name) . sort . should_equal [ "Value" ] + cons.each ctor-> + ctor.declaring_type . should_equal meta_typ Test.specify "methods of MyType" <| typ = My_Type diff --git a/test/Visualization_Tests/src/Widgets/Text_Widgets_Spec.enso b/test/Visualization_Tests/src/Widgets/Text_Widgets_Spec.enso new file mode 100644 index 000000000000..071068fe931d --- /dev/null +++ b/test/Visualization_Tests/src/Widgets/Text_Widgets_Spec.enso @@ -0,0 +1,35 @@ +from Standard.Base import all +import Standard.Base.Runtime.State + +import Standard.Base.Metadata.Choice +import Standard.Base.Metadata.Widget +import Standard.Base.Metadata.Display + +import Standard.Visualization.Widgets + +from Standard.Test import Test, Test_Suite +import Standard.Test.Extensions + +spec = + Test.group "Widgets for the Text type" <| + Test.specify "works for `take` and `drop`" <| + mock_text = "abc def" + default_widget = Text_Sub_Range.default_widget + expect = [["range", default_widget]] . to_json + json = Widgets.get_widget_json mock_text "take" ["range"] + json . should_equal expect + Widgets.get_widget_json mock_text "drop" ["range"] . should_equal expect + obj = json.parse_json + widget = obj.first.second + options = widget . at "values" + options.each o-> Test.with_clue o.to_text+": " <| + o.should_be_a JS_Object + labels = options.map o-> + o.at "label" + labels.should_be_a Vector + labels.should_contain "First" + labels.should_contain "While" + labels.should_contain "After" + labels.should_contain "Before_Last" + +main = Test_Suite.run_main spec diff --git a/test/Visualization_Tests/src/Widgets_Spec.enso b/test/Visualization_Tests/src/Widgets_Spec.enso index 66f577c0cf52..63af975b7f87 100644 --- a/test/Visualization_Tests/src/Widgets_Spec.enso +++ b/test/Visualization_Tests/src/Widgets_Spec.enso @@ -4,9 +4,11 @@ from Standard.Test import Test_Suite import project.Widgets.Database_Widgets_Spec import project.Widgets.Table_Widgets_Spec +import project.Widgets.Text_Widgets_Spec spec = Table_Widgets_Spec.spec Database_Widgets_Spec.spec + Text_Widgets_Spec.spec main = Test_Suite.run_main spec