diff --git a/app/gui2/playwright.config.ts b/app/gui2/playwright.config.ts index 110b5e4618d5..06cd20c2415c 100644 --- a/app/gui2/playwright.config.ts +++ b/app/gui2/playwright.config.ts @@ -82,7 +82,10 @@ export default defineConfig({ // }, // ], webServer: { - command: 'E2E=true vite build && vite preview', + env: { + E2E: 'true', + }, + command: 'vite build && vite preview', port: 4173, // We use our special, mocked version of server, thus do not want to re-use user's one. reuseExistingServer: false, diff --git a/app/gui2/shared/languageServer.ts b/app/gui2/shared/languageServer.ts index d32a8f60d6b1..00876fdb758c 100644 --- a/app/gui2/shared/languageServer.ts +++ b/app/gui2/shared/languageServer.ts @@ -168,7 +168,7 @@ export class LanguageServer extends ObservableV2 { /** [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#textopenfile) */ openTextFile(path: Path): Promise { - return this.request('text/openFile', { path }) + return this.request('text/openFile', { path }) } /** [Documentation](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#textclosefile) */ diff --git a/app/gui2/src/components/ComponentBrowser.vue b/app/gui2/src/components/ComponentBrowser.vue index a6b223cd57e2..3463241093c8 100644 --- a/app/gui2/src/components/ComponentBrowser.vue +++ b/app/gui2/src/components/ComponentBrowser.vue @@ -176,7 +176,7 @@ const previewDataSource: ComputedRef = comp return { type: 'expression', expression: previewedExpression.value, - contextId: body.astId, + contextId: body.exprId, } }) diff --git a/app/gui2/src/components/GraphEditor.vue b/app/gui2/src/components/GraphEditor.vue index b1da47ee59b7..3622d6bac8b4 100644 --- a/app/gui2/src/components/GraphEditor.vue +++ b/app/gui2/src/components/GraphEditor.vue @@ -551,15 +551,16 @@ function handleEdgeDrop(source: ExprId, position: Vec2) { toastClassName="text-sm leading-170 bg-frame-selected rounded-2xl backdrop-blur-3xl" transition="Vue-Toastification__bounce" /> - - -
+ + + + { // When the source is not set (i.e. edge is dragged), use the currently hovered over expression // as the source, as long as it is not from the same node as the target. if (selection?.hoveredNode != null) { - const rawTargetNode = graph.db.getExpressionNodeId(props.edge.target) + const rawTargetNode = props.edge.target && graph.getPortNodeId(props.edge.target) if (selection.hoveredNode != rawTargetNode) return selection.hoveredNode } } @@ -45,30 +45,29 @@ const targetExpr = computed(() => { }) const targetNode = computed( - () => targetExpr.value && graph.db.getExpressionNodeId(targetExpr.value), + () => targetExpr.value && (graph.getPortNodeId(targetExpr.value) ?? selection?.hoveredNode), ) const targetNodeRect = computed(() => targetNode.value && graph.nodeRects.get(targetNode.value)) -const targetRect = computed(() => { +const targetRect = computed(() => { const expr = targetExpr.value - if (expr != null) { - if (targetNode.value == null) return null - const targetRectRelative = graph.exprRects.get(expr) - if (targetRectRelative == null || targetNodeRect.value == null) return null + if (expr != null && targetNode.value != null && targetNodeRect.value != null) { + const targetRectRelative = graph.getPortRelativeRect(expr) + if (targetRectRelative == null) return return targetRectRelative.offsetBy(targetNodeRect.value.pos) } else if (navigator?.sceneMousePos != null) { return new Rect(navigator.sceneMousePos, Vec2.Zero) } else { - return null + return undefined } }) -const sourceRect = computed(() => { +const sourceRect = computed(() => { if (sourceNode.value != null) { - return graph.nodeRects.get(sourceNode.value) ?? null + return graph.nodeRects.get(sourceNode.value) } else if (navigator?.sceneMousePos != null) { return new Rect(navigator.sceneMousePos, Vec2.Zero) } else { - return null + return undefined } }) diff --git a/app/gui2/src/components/GraphEditor/GraphEdges.vue b/app/gui2/src/components/GraphEditor/GraphEdges.vue index 3cbe3616206a..c2948ac127f7 100644 --- a/app/gui2/src/components/GraphEditor/GraphEdges.vue +++ b/app/gui2/src/components/GraphEditor/GraphEdges.vue @@ -3,9 +3,11 @@ import GraphEdge from '@/components/GraphEditor/GraphEdge.vue' import type { GraphNavigator } from '@/providers/graphNavigator' import { injectGraphSelection } from '@/providers/graphSelection' import { injectInteractionHandler, type Interaction } from '@/providers/interactionHandler' +import type { PortId } from '@/providers/portInfo' import { useGraphStore } from '@/stores/graph' +import { Ast } from '@/util/ast' import { Vec2 } from '@/util/data/vec2' -import type { ExprId } from 'shared/yjsModel' +import { isUuid, type ExprId } from 'shared/yjsModel.ts' const graph = useGraphStore() const selection = injectGraphSelection(true) @@ -27,7 +29,7 @@ const editingEdge: Interaction = { if (graph.unconnectedEdge == null) return false const source = graph.unconnectedEdge.source ?? selection?.hoveredNode const target = graph.unconnectedEdge.target ?? selection?.hoveredPort - const targetNode = target && graph.db.getExpressionNodeId(target) + const targetNode = target && graph.getPortNodeId(target) graph.transact(() => { if (source != null && source != targetNode) { if (target == null) { @@ -43,18 +45,34 @@ const editingEdge: Interaction = { return true }, } + interaction.setWhen(() => graph.unconnectedEdge != null, editingEdge) -function disconnectEdge(target: ExprId) { - graph.setExpressionContent(target, '_') +function disconnectEdge(target: PortId) { + if (!graph.updatePortValue(target, undefined)) { + const targetStr: string = target + if (isUuid(targetStr)) { + console.warn(`Failed to disconnect edge from port ${target}, falling back to direct edit.`) + graph.setExpressionContent(targetStr as ExprId, '_') + } else { + console.error(`Failed to disconnect edge from port ${target}, no fallback possible.`) + } + } } -function createEdge(source: ExprId, target: ExprId) { +function createEdge(source: ExprId, target: PortId) { const ident = graph.db.getOutputPortIdentifier(source) if (ident == null) return - // TODO: Check alias analysis to see if the binding is shadowed. - graph.setExpressionContent(target, ident) - // TODO: Use alias analysis to ensure declarations are in a dependency order. + const identAst = Ast.parse(ident) + if (!graph.updatePortValue(target, identAst)) { + const targetStr: string = target + if (isUuid(targetStr)) { + console.warn(`Failed to connect edge to port ${target}, falling back to direct edit.`) + graph.setExpressionContent(targetStr as ExprId, ident) + } else { + console.error(`Failed to connect edge to port ${target}, no fallback possible.`) + } + } } diff --git a/app/gui2/src/components/GraphEditor/GraphNode.vue b/app/gui2/src/components/GraphEditor/GraphNode.vue index 5bde53f1df33..af96c00eeb0e 100644 --- a/app/gui2/src/components/GraphEditor/GraphNode.vue +++ b/app/gui2/src/components/GraphEditor/GraphNode.vue @@ -19,7 +19,7 @@ import { Rect } from '@/util/data/rect' import { Vec2 } from '@/util/data/vec2' import { displayedIconOf } from '@/util/getIconName' import { setIfUndefined } from 'lib0/map' -import { type ExprId, type VisualizationIdentifier } from 'shared/yjsModel' +import type { ExprId, VisualizationIdentifier } from 'shared/yjsModel' import { computed, ref, watch, watchEffect } from 'vue' const MAXIMUM_CLICK_LENGTH_MS = 300 @@ -71,7 +71,7 @@ const outputPortsSet = computed(() => { }) const widthOverridePx = ref() -const nodeId = computed(() => props.node.rootSpan.astId) +const nodeId = computed(() => props.node.rootSpan.exprId) const rootNode = ref() const contentNode = ref() @@ -194,7 +194,7 @@ const isOutputContextOverridden = computed({ disableOutputContext: undefined, }, ) - graph.setNodeContent(props.node.rootSpan.astId, newAst.code()) + graph.setNodeContent(props.node.rootSpan.exprId, newAst.code()) }, }) diff --git a/app/gui2/src/components/GraphEditor/NodeWidget.vue b/app/gui2/src/components/GraphEditor/NodeWidget.vue index 04b3ae64f3f1..143a34c73429 100644 --- a/app/gui2/src/components/GraphEditor/NodeWidget.vue +++ b/app/gui2/src/components/GraphEditor/NodeWidget.vue @@ -1,4 +1,5 @@ + + diff --git a/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue b/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue index 3679a56b1c88..68f003af6fef 100644 --- a/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue +++ b/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue @@ -1,16 +1,17 @@ @@ -48,10 +67,6 @@ provideWidgetTree(toRef(props, 'ast'), layoutTransitions.active) display: flex; align-items: center; - & :deep(span) { - vertical-align: middle; - } - &:has(.WidgetPort.newToConnect) { margin-left: calc(4px - var(--widget-port-extra-pad)); } diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetApplication.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetApplication.vue index 48a2040d1890..177f690bbc6b 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetApplication.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetApplication.vue @@ -10,6 +10,21 @@ const props = defineProps(widgetProps(widgetDefinition)) const targetMaybePort = computed(() => props.input.target instanceof Ast.Ast ? new ForcePort(props.input.target) : props.input.target, ) + +const appClass = computed(() => { + return props.input.infixOperator != null ? 'infix' : 'prefix' +}) + +const operatorStyle = computed(() => { + if (props.input.appTree instanceof Ast.OprApp) { + const [_lhs, opr, rhs] = props.input.appTree.concreteChildren() + return { + '--whitespace-pre': `${JSON.stringify(opr?.whitespace ?? '')}`, + '--whitespace-post': `${JSON.stringify(rhs?.whitespace ?? '')}`, + } + } + return {} +}) diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetArgumentName.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetArgumentName.vue index 154052e983cc..37b0014c7528 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetArgumentName.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetArgumentName.vue @@ -11,7 +11,9 @@ const portInfo = injectPortInfo(true) const showArgumentValue = computed(() => { return ( props.input instanceof ArgumentAst && - (portInfo == null || !portInfo.connected || portInfo.portId !== props.input.ast.astId) + (portInfo == null || + !portInfo.connected || + (portInfo.portId as string) !== (props.input.ast.exprId as string)) ) }) @@ -32,16 +34,22 @@ export const widgetDefinition = defineWidget([ArgumentPlaceholder, ArgumentAst], diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue index aa148dbe3c38..4fc78bd83640 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue @@ -1,26 +1,23 @@ diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetToken.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetToken.vue index a16971bc8e17..ee7f93391014 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetToken.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetToken.vue @@ -5,8 +5,6 @@ import { computed, ref } from 'vue' const props = defineProps(widgetProps(widgetDefinition)) -const rootNode = ref() - const spanClass = computed(() => props.input.typeName()) const repr = computed(() => props.input.code()) @@ -19,14 +17,14 @@ export const widgetDefinition = defineWidget((expression) => expression instance diff --git a/app/gui2/src/components/widgets/DropdownWidget.vue b/app/gui2/src/components/widgets/DropdownWidget.vue index 934d9c12213f..f59ac67cd12b 100644 --- a/app/gui2/src/components/widgets/DropdownWidget.vue +++ b/app/gui2/src/components/widgets/DropdownWidget.vue @@ -72,7 +72,7 @@ const NEXT_SORT_DIRECTION: Record = { .Dropdown { position: absolute; top: 100%; - margin-top: 4px; + margin-top: 8px; height: 136px; } diff --git a/app/gui2/src/components/widgets/ListWidget.vue b/app/gui2/src/components/widgets/ListWidget.vue index c8d9c30957c2..eee82f9771df 100644 --- a/app/gui2/src/components/widgets/ListWidget.vue +++ b/app/gui2/src/components/widgets/ListWidget.vue @@ -425,6 +425,12 @@ watchPostEffect(() => {