diff --git a/app/gui2/parser-codegen/codegen.ts b/app/gui2/parser-codegen/codegen.ts index 8a7b0f432233..bd0d15af022d 100644 --- a/app/gui2/parser-codegen/codegen.ts +++ b/app/gui2/parser-codegen/codegen.ts @@ -14,7 +14,6 @@ import { abstractTypeDeserializer, abstractTypeVariants, fieldDeserializer, - fieldDynValue, fieldVisitor, seekViewDyn, support, @@ -246,53 +245,6 @@ function makeReadFunction( ) } -function makeDebugFunction(fields: Field[], typeName?: string): ts.MethodDeclaration { - const ident = tsf.createIdentifier('fields') - const fieldAssignments = fields.map((field) => - tsf.createArrayLiteralExpression([ - tsf.createStringLiteral(field.name), - fieldDynValue(field.type, field.offset), - ]), - ) - if (typeName != null) { - fieldAssignments.push( - tsf.createArrayLiteralExpression([ - tsf.createStringLiteral('type'), - tsf.createObjectLiteralExpression([ - tsf.createPropertyAssignment('type', tsf.createStringLiteral('primitive')), - tsf.createPropertyAssignment('value', tsf.createStringLiteral(typeName)), - ]), - ]), - ) - } - return tsf.createMethodDeclaration( - [], - undefined, - ident, - undefined, - [], - [], - tsf.createTypeReferenceNode(`[string, ${support.DynValue}][]`), - tsf.createBlock([ - tsf.createReturnStatement( - tsf.createArrayLiteralExpression( - [ - tsf.createSpreadElement( - tsf.createCallExpression( - tsf.createPropertyAccessExpression(tsf.createSuper(), ident), - undefined, - undefined, - ), - ), - ...fieldAssignments, - ], - true, - ), - ), - ]), - ) -} - function makeVisitFunction(fields: Field[]): ts.MethodDeclaration { const ident = tsf.createIdentifier('visitChildren') const visitorParam = tsf.createIdentifier('visitor') @@ -347,7 +299,7 @@ function makeVisitFunction(fields: Field[]): ts.MethodDeclaration { ) } -function makeGetters(id: string, schema: Schema.Schema, typeName?: string): ts.ClassElement[] { +function makeGetters(id: string, schema: Schema.Schema): ts.ClassElement[] { const serialization = schema.serialization[id] const type = schema.types[id] if (serialization == null || type == null) throw new Error(`Invalid type id: ${id}`) @@ -360,7 +312,6 @@ function makeGetters(id: string, schema: Schema.Schema, typeName?: string): ts.C ...fields.map(makeGetter), ...fields.map(makeElementVisitor).filter((v): v is ts.ClassElement => v != null), makeVisitFunction(fields), - makeDebugFunction(fields, typeName), ] } @@ -394,7 +345,6 @@ type ChildType = { definition: ts.ClassDeclaration name: ts.Identifier enumMember: ts.EnumMember - case: ts.CaseClause } function makeChildType( @@ -457,12 +407,11 @@ function makeChildType( viewIdent, tsf.createNewExpression(ident, [], [seekViewDyn(viewIdent, addressIdent)]), ), - ...makeGetters(id, schema, name), + ...makeGetters(id, schema), ], ), name: tsf.createIdentifier(name), - enumMember: tsf.createEnumMember(toPascal(ty.name), discriminantInt), - case: tsf.createCaseClause(discriminantInt, [tsf.createReturnStatement(viewIdent)]), + enumMember: tsf.createEnumMember(name, discriminantInt), } } @@ -501,6 +450,12 @@ function makeAbstractType( 'Type', childTypes.map((child) => child.enumMember), ), + makeExportConstVariable( + 'typeNames', + tsf.createArrayLiteralExpression( + childTypes.map((child) => tsf.createStringLiteralFromNode(child.name)), + ), + ), ...childTypes.map((child) => child.definition), tsf.createTypeAliasDeclaration( [modifiers.export], @@ -522,11 +477,31 @@ function makeAbstractType( [modifiers.export], ident, undefined, - tsf.createTypeReferenceNode(tsf.createQualifiedName(tsf.createIdentifier(name), name)), + tsf.createTypeReferenceNode(tsf.createQualifiedName(ident, ident)), ) return { module: moduleDecl, export: abstractTypeExport } } +function makeExportConstVariable( + varName: string, + initializer: ts.Expression, +): ts.VariableStatement { + return tsf.createVariableStatement( + [modifiers.export], + tsf.createVariableDeclarationList( + [ + tsf.createVariableDeclaration( + varName, + undefined, + undefined, + tsf.createAsExpression(initializer, tsf.createTypeReferenceNode('const')), + ), + ], + ts.NodeFlags.Const, + ), + ) +} + function makeIsInstance(type: ts.TypeNode, baseIdent: ts.Identifier): ts.FunctionDeclaration { const param = tsf.createIdentifier('obj') const paramDecl = tsf.createParameterDeclaration( diff --git a/app/gui2/parser-codegen/serialization.ts b/app/gui2/parser-codegen/serialization.ts index 5bdebb3d0c4b..a544597bb5e7 100644 --- a/app/gui2/parser-codegen/serialization.ts +++ b/app/gui2/parser-codegen/serialization.ts @@ -17,8 +17,6 @@ export const supportImports = { ObjectVisitor: true, ObjectAddressVisitor: true, Result: true, - DynValue: true, - Dyn: false, readU8: false, readU32: false, readI32: false, @@ -43,8 +41,6 @@ export const support = { DataView: tsf.createTypeReferenceNode(tsf.createIdentifier('DataView')), Result: (t0: ts.TypeNode, t1: ts.TypeNode) => tsf.createTypeReferenceNode(tsf.createIdentifier('Result'), [t0, t1]), - DynValue: 'DynValue', - Dyn: tsf.createIdentifier('Dyn'), readU8: tsf.createIdentifier('readU8'), readU32: tsf.createIdentifier('readU32'), readI32: tsf.createIdentifier('readI32'), @@ -75,13 +71,6 @@ const baseReaders = { readOption: readerTransformer(support.readOption), readResult: readerTransformerTwoTyped(support.readResult), } as const -const dynBuilders = { - Primitive: dynReader('Primitive'), - Result: dynReader('Result'), - Sequence: dynReader('Sequence'), - Option: dynReader('Option'), - Object: dynReader('Object'), -} as const type ReadApplicator = (cursor: ts.Expression, offset: AccessOffset) => ts.Expression type VisitorApplicator = (cursor: ts.Expression, offset: AccessOffset) => ts.Expression @@ -91,51 +80,35 @@ type VisitorApplicator = (cursor: ts.Expression, offset: AccessOffset) => ts.Exp export class Type { readonly type: ts.TypeNode readonly reader: ReadApplicator - readonly dynReader: ReadApplicator readonly visitor: VisitorApplicator | undefined | 'visitValue' readonly size: number private constructor( type: ts.TypeNode, reader: ReadApplicator, - dynReader: ReadApplicator, visitor: VisitorApplicator | undefined | 'visitValue', size: number, ) { this.type = type this.reader = reader - this.dynReader = dynReader this.visitor = visitor this.size = size } static Abstract(name: string): Type { const valueReader = callRead(name) - return new Type( - tsf.createTypeReferenceNode(name), - valueReader, - dynBuilders.Object(valueReader), - 'visitValue', - POINTER_SIZE, - ) + return new Type(tsf.createTypeReferenceNode(name), valueReader, 'visitValue', POINTER_SIZE) } static Concrete(name: string, size: number): Type { const valueReader = callRead(name) - return new Type( - tsf.createTypeReferenceNode(name), - valueReader, - dynBuilders.Object(valueReader), - 'visitValue', - size, - ) + return new Type(tsf.createTypeReferenceNode(name), valueReader, 'visitValue', size) } static Sequence(element: Type): Type { return new Type( tsf.createTypeReferenceNode('Iterable', [element.type]), createSequenceReader(element.size, element.reader), - dynBuilders.Sequence(createSequenceReader(element.size, element.dynReader)), createSequenceVisitor(element.size, visitorClosure(element.visitor, element.reader)), POINTER_SIZE, ) @@ -145,7 +118,6 @@ export class Type { return new Type( tsf.createUnionTypeNode([element.type, noneType]), baseReaders.readOption(element.reader), - dynBuilders.Option(baseReaders.readOption(element.dynReader)), createOptionVisitor(visitorClosure(element.visitor, element.reader)), POINTER_SIZE + 1, ) @@ -155,7 +127,6 @@ export class Type { return new Type( support.Result(ok.type, err.type), baseReaders.readResult(ok.reader, err.reader), - dynBuilders.Result(baseReaders.readResult(ok.dynReader, err.dynReader)), createResultVisitor( visitorClosure(ok.visitor, ok.reader), visitorClosure(err.visitor, err.reader), @@ -167,49 +138,42 @@ export class Type { static Boolean: Type = new Type( tsf.createTypeReferenceNode('boolean'), baseReaders.readBool, - dynBuilders.Primitive(baseReaders.readBool), undefined, 1, ) static UInt32: Type = new Type( tsf.createTypeReferenceNode('number'), baseReaders.readU32, - dynBuilders.Primitive(baseReaders.readU32), undefined, 4, ) static Int32: Type = new Type( tsf.createTypeReferenceNode('number'), baseReaders.readI32, - dynBuilders.Primitive(baseReaders.readI32), undefined, 4, ) static UInt64: Type = new Type( tsf.createTypeReferenceNode('bigint'), baseReaders.readU64, - dynBuilders.Primitive(baseReaders.readU64), undefined, 8, ) static Int64: Type = new Type( tsf.createTypeReferenceNode('bigint'), baseReaders.readI64, - dynBuilders.Primitive(baseReaders.readI64), undefined, 8, ) static Char: Type = new Type( tsf.createTypeReferenceNode('number'), baseReaders.readU32, - dynBuilders.Primitive(baseReaders.readU32), undefined, 4, ) static String: Type = new Type( tsf.createTypeReferenceNode('string'), baseReaders.readString, - dynBuilders.Primitive(baseReaders.readString), undefined, POINTER_SIZE, ) @@ -303,10 +267,6 @@ export function fieldVisitor( ) } -export function fieldDynValue(type: Type, address: number): ts.Expression { - return type.dynReader(thisAccess(viewFieldIdent), makeConstantAddress(address)) -} - function thisAccess(ident: ts.Identifier): ts.PropertyAccessExpression { return tsf.createPropertyAccessExpression(tsf.createThis(), ident) } @@ -376,16 +336,6 @@ function readerTransformerTwoTyped( } } -function dynReader(name: string): (readValue: ReadApplicator) => ReadApplicator { - return (readValue) => (view, address) => { - return tsf.createCallExpression( - tsf.createPropertyAccessExpression(support.Dyn, name), - [], - [readValue(view, address)], - ) - } -} - export function callRead(ident: string): ReadApplicator { return (view, address) => tsf.createCallExpression( diff --git a/app/gui2/public/visualizations/ScatterplotVisualization.vue b/app/gui2/public/visualizations/ScatterplotVisualization.vue index 457711f51efb..3e45ad6d2f10 100644 --- a/app/gui2/public/visualizations/ScatterplotVisualization.vue +++ b/app/gui2/public/visualizations/ScatterplotVisualization.vue @@ -208,15 +208,15 @@ const margin = computed(() => { } }) const width = computed(() => - config.value.fullscreen + config.fullscreen ? containerNode.value?.parentElement?.clientWidth ?? 0 - : Math.max(config.value.width ?? 0, config.value.nodeSize.x), + : Math.max(config.width ?? 0, config.nodeSize.x), ) const height = computed(() => - config.value.fullscreen + config.fullscreen ? containerNode.value?.parentElement?.clientHeight ?? 0 - : config.value.height ?? (config.value.nodeSize.x * 3) / 4, + : config.height ?? (config.nodeSize.x * 3) / 4, ) const boxWidth = computed(() => Math.max(0, width.value - margin.value.left - margin.value.right)) diff --git a/app/gui2/shared/yjsModel.ts b/app/gui2/shared/yjsModel.ts index d97134ebc3b9..658facaadd59 100644 --- a/app/gui2/shared/yjsModel.ts +++ b/app/gui2/shared/yjsModel.ts @@ -164,7 +164,10 @@ export class DistributedModule { const start = range == null ? exprStart : exprStart + range[0] const end = range == null ? exprEnd : exprStart + range[1] if (start > end) throw new Error('Invalid range') - if (start < exprStart || end > exprEnd) throw new Error('Range out of bounds') + if (start < exprStart || end > exprEnd) + throw new Error( + `Range out of bounds. Got [${start}, ${end}], bounds are [${exprStart}, ${exprEnd}]`, + ) this.transact(() => { if (content.length > 0) { this.doc.contents.insert(start, content) @@ -230,7 +233,7 @@ export class IdMap { this.finished = false } - private static keyForRange(range: [number, number]): string { + private static keyForRange(range: readonly [number, number]): string { return `${range[0].toString(16)}:${range[1].toString(16)}` } @@ -256,7 +259,12 @@ export class IdMap { this.accessed.add(id) } - getOrInsertUniqueId(range: [number, number]): ExprId { + getIfExist(range: readonly [number, number]): ExprId | undefined { + const key = IdMap.keyForRange(range) + return this.rangeToExpr.get(key) + } + + getOrInsertUniqueId(range: readonly [number, number]): ExprId { if (this.finished) { throw new Error('IdMap already finished') } @@ -292,7 +300,7 @@ export class IdMap { * * Can be called at most once. After calling this method, the ID map is no longer usable. */ - finishAndSynchronize(): void { + finishAndSynchronize(): typeof this.yMap { if (this.finished) { throw new Error('IdMap already finished') } @@ -320,6 +328,7 @@ export class IdMap { this.yMap.set(expr, encoded) }) }) + return this.yMap } } diff --git a/app/gui2/src/App.vue b/app/gui2/src/App.vue index 4109ad176f32..755d64970c81 100644 --- a/app/gui2/src/App.vue +++ b/app/gui2/src/App.vue @@ -15,11 +15,22 @@ onMounted(() => useSuggestionDbStore()) diff --git a/app/gui2/src/assets/base.css b/app/gui2/src/assets/base.css index 923245122e17..6dd564a3ce02 100644 --- a/app/gui2/src/assets/base.css +++ b/app/gui2/src/assets/base.css @@ -68,23 +68,6 @@ margin: 0; } -body { - min-height: 100vh; - color: var(--color-text); - /* TEMPORARY. Will be replaced with actual background when it is integrated with the dashboard. */ - background: #e4d4be; - transition: - color 0.5s, - background-color 0.5s; - font-family: 'M PLUS 1', sans-serif; - font-size: 11.5px; - font-weight: 500; - line-height: 20px; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - .icon { width: 16px; height: 16px; diff --git a/app/gui2/src/bindings.ts b/app/gui2/src/bindings.ts index c9b971d8d120..da6245b3690d 100644 --- a/app/gui2/src/bindings.ts +++ b/app/gui2/src/bindings.ts @@ -18,18 +18,18 @@ export const graphBindings = defineKeybinds('graph-editor', { dragScene: ['PointerAux', 'Mod+PointerMain'], openComponentBrowser: ['Enter'], newNode: ['N'], -}) - -export const nodeSelectionBindings = defineKeybinds('node-selection', { + toggleVisualization: ['Space'], deleteSelected: ['Delete'], selectAll: ['Mod+A'], deselectAll: ['Escape', 'PointerMain'], +}) + +export const selectionMouseBindings = defineKeybinds('selection', { replace: ['PointerMain'], add: ['Mod+Shift+PointerMain'], remove: ['Shift+Alt+PointerMain'], toggle: ['Shift+PointerMain'], invert: ['Mod+Shift+Alt+PointerMain'], - toggleVisualization: ['Space'], }) export const nodeEditBindings = defineKeybinds('node-edit', { diff --git a/app/gui2/src/components/CodeEditor.vue b/app/gui2/src/components/CodeEditor.vue index 22871453f98b..3ba35fcefeb7 100644 --- a/app/gui2/src/components/CodeEditor.vue +++ b/app/gui2/src/components/CodeEditor.vue @@ -6,8 +6,8 @@ import type { Highlighter } from '@/util/codemirror' import { colorFromString } from '@/util/colors' import { usePointer } from '@/util/events' import { useLocalStorage } from '@vueuse/core' +import { rangeEncloses } from 'shared/yjsModel' import { computed, onMounted, ref, watchEffect } from 'vue' -import * as Y from 'yjs' import { qnJoin, tryQualifiedName } from '../util/qualifiedName' import { unwrap } from '../util/result' @@ -52,34 +52,34 @@ watchEffect(() => { foldGutter(), highlightSelectionMatches(), tooltips({ position: 'absolute' }), - hoverTooltip((ast) => { + hoverTooltip((ast, syn) => { const dom = document.createElement('div') - const ydoc = projectStore.module?.doc.ydoc - if (ydoc == null) return - const start = ast.whitespaceStartInCodeParsed + ast.whitespaceLengthInCodeParsed - const end = start + ast.childrenLengthInCodeParsed + const astSpan = ast.span() let foundNode: Node | undefined for (const node of graphStore.nodes.values()) { - const nodeStart = Y.createAbsolutePositionFromRelativePosition(node.docRange[0], ydoc) - ?.index - if (nodeStart == null || nodeStart > start) continue - const nodeEnd = Y.createAbsolutePositionFromRelativePosition(node.docRange[1], ydoc) - ?.index - if (nodeEnd == null || nodeEnd < end) continue - foundNode = node - break + if (rangeEncloses(node.rootSpan.span(), astSpan)) { + foundNode = node + break + } } - if (foundNode == null) return - const expressionInfo = projectStore.computedValueRegistry.getExpressionInfo( - foundNode.rootSpan.id, - ) - if (expressionInfo == null) return - dom - .appendChild(document.createElement('div')) - .appendChild(document.createTextNode(`AST ID: ${foundNode.rootSpan.id}`)) + const expressionInfo = foundNode + ? projectStore.computedValueRegistry.getExpressionInfo(foundNode.rootSpan.astId) + : undefined + + if (foundNode != null) { + dom + .appendChild(document.createElement('div')) + .appendChild(document.createTextNode(`AST ID: ${foundNode.rootSpan.astId}`)) + } + if (expressionInfo != null) { + dom + .appendChild(document.createElement('div')) + .appendChild(document.createTextNode(`Type: ${expressionInfo.typename ?? 'Unknown'}`)) + } + dom .appendChild(document.createElement('div')) - .appendChild(document.createTextNode(`Type: ${expressionInfo.typename ?? 'Unknown'}`)) + .appendChild(document.createTextNode(`Syntax: ${syn.toString()}`)) const method = expressionInfo?.methodCall?.methodPointer if (method != null) { const moduleName = tryQualifiedName(method.module) @@ -176,7 +176,8 @@ const editorStyle = computed(() => { position: absolute; width: 100%; height: 100%; - backdrop-filter: blur(16px); + backdrop-filter: var(--blur-app-bg); + border-radius: 7px; } &.v-enter-active, @@ -246,6 +247,7 @@ const editorStyle = computed(() => { content: ''; background-color: rgba(255, 255, 255, 0.35); backdrop-filter: blur(64px); + border-radius: 4px; } } diff --git a/app/gui2/src/components/ComponentBrowser/input.ts b/app/gui2/src/components/ComponentBrowser/input.ts index 71b70f940d29..18966411b003 100644 --- a/app/gui2/src/components/ComponentBrowser/input.ts +++ b/app/gui2/src/components/ComponentBrowser/input.ts @@ -1,6 +1,13 @@ import type { Filter } from '@/components/ComponentBrowser/filtering' import { SuggestionKind, type SuggestionEntry } from '@/stores/suggestionDatabase/entry' -import { Ast, astContainingChar, astSpan, parseEnso, readAstSpan, readTokenSpan } from '@/util/ast' +import { + Ast, + astContainingChar, + parseEnso, + parsedTreeRange, + readAstSpan, + readTokenSpan, +} from '@/util/ast' import { GeneralOprApp } from '@/util/ast/opr' import { qnLastSegment, @@ -135,7 +142,7 @@ export class Input { private static pathAsQualifiedName(accessOpr: GeneralOprApp, code: string): QualifiedName | null { const operandsAsIdents = Input.qnIdentifiers(accessOpr, code) - const segments = operandsAsIdents.map((ident) => readAstSpan(ident!, code)) + const segments = operandsAsIdents.map((ident) => readAstSpan(ident, code)) const rawQn = segments.join('.') const qn = tryQualifiedName(rawQn) return qn.ok ? qn.value : null @@ -162,10 +169,10 @@ export class Input { const changes = Array.from(this.inputChangesAfterApplying(entry)).reverse() const newCodeUpToLastChange = changes.reduce( (builder, change) => { - const oldCodeFragment = oldCode.substring(builder.oldCodeIndex, change.start) + const oldCodeFragment = oldCode.substring(builder.oldCodeIndex, change.range[0]) return { code: builder.code + oldCodeFragment + change.str, - oldCodeIndex: change.end, + oldCodeIndex: change.range[1], } }, { code: '', oldCodeIndex: 0 }, @@ -190,20 +197,20 @@ export class Input { */ private *inputChangesAfterApplying( entry: SuggestionEntry, - ): Generator<{ start: number; end: number; str: string }> { + ): Generator<{ range: [number, number]; str: string }> { const ctx = this.context.value const str = this.codeToBeInserted(entry) switch (ctx.type) { case 'insert': { - yield { start: ctx.position, end: ctx.position, str } + yield { range: [ctx.position, ctx.position], str } break } case 'changeIdentifier': { - yield { ...astSpan(ctx.identifier), str } + yield { range: parsedTreeRange(ctx.identifier), str } break } case 'changeLiteral': { - yield { ...astSpan(ctx.literal), str } + yield { range: parsedTreeRange(ctx.literal), str } break } } @@ -240,7 +247,7 @@ export class Input { * See `inputChangesAfterApplying`. */ private *qnChangesAfterApplying( entry: SuggestionEntry, - ): Generator<{ start: number; end: number; str: string }> { + ): Generator<{ range: [number, number]; str: string }> { if (entry.selfType != null) return [] if (entry.kind === SuggestionKind.Local || entry.kind === SuggestionKind.Function) return [] if (this.context.value.type === 'changeLiteral') return [] @@ -254,7 +261,7 @@ export class Input { for (const ident of writtenQn) { if (containingQn == null) break const [parent, segment] = qnSplit(containingQn) - yield { ...astSpan(ident), str: segment } + yield { range: parsedTreeRange(ident), str: segment } containingQn = parent } } diff --git a/app/gui2/src/components/GraphEditor.vue b/app/gui2/src/components/GraphEditor.vue index 852ebcfcbde0..a2eb5eb6612b 100644 --- a/app/gui2/src/components/GraphEditor.vue +++ b/app/gui2/src/components/GraphEditor.vue @@ -1,148 +1,50 @@ - -