diff --git a/package.json b/package.json index 13d7e317b..f630a655d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "3.4.0-rc1", "scripts": { "ng": "ng", + "generate-grammar": "lezer-generator --typeScript src/framework/codemirror-lang-typeql/typeql.grammar -o src/framework/codemirror-lang-typeql/generated/typeql.grammar.generated", "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", @@ -23,9 +24,12 @@ "@angular/platform-browser-dynamic": "20.0.3", "@angular/router": "20.0.3", "@codemirror/lint": "6.8.5", + "@codemirror/autocomplete": "6.18.6", "@customerio/cdp-analytics-browser": "0.2.0", + "@codemirror/commands": "6.8.1", "@hhangular/resizable": "1.18.1", "@intercom/messenger-js-sdk": "0.0.14", + "@lezer/common": "^1.0.0", "@lezer/lr": "1.4.2", "@sigma/edge-curve": "3.1.0", "@sigma/node-square": "3.0.0", @@ -52,6 +56,7 @@ "@codemirror/language": "6.11.0", "@codemirror/state": "6.5.2", "@codemirror/view": "6.36.5", + "@lezer/generator": "1.7.3", "@lezer/highlight": "1.2.1", "@sanity/asset-utils": "1.3.0", "@sanity/icons": "3.4.0", diff --git a/src/framework/codemirror-lang-typeql/complete.ts b/src/framework/codemirror-lang-typeql/complete.ts new file mode 100644 index 000000000..1606e3752 --- /dev/null +++ b/src/framework/codemirror-lang-typeql/complete.ts @@ -0,0 +1,176 @@ + +import { CompletionContext, Completion, CompletionResult } from "@codemirror/autocomplete"; +import { syntaxTree } from "@codemirror/language" +import { SyntaxNode, NodeType, Tree } from "@lezer/common" + +type TokenID = number; +export interface SuggestionMap { + [key: TokenID]: SuffixOfPrefixSuggestion[] +} + + +export type SuffixCandidate = TokenID[]; // A SuffixCandidate 's' "matches" a prefix if prefix[-s.length:] == s +export interface SuffixOfPrefixSuggestion { + suffixes: SuffixCandidate[], // If any of the suffix candidates match, the suggestions will be used. + suggestions: SuggestionFunction[] +} + +export type SuggestionFunction = (context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode, prefix: NodeType[], state: STATE) => Completion[] | null; + +export function suggest(type: string, label: string, boost: number = 0): Completion { + // type (docs): used to pick an icon to show for the completion. Icons are styled with a CSS class created by appending the type name to "cm-completionIcon-". + return { + label: label, + type: type, + apply: label, + info: type, + boost: boost, + }; +} + +interface NodePrefixAutoCompleteState { + mayUpdateFromEditorState(context: CompletionContext, tree: Tree): void; +} + +// See: https://codemirror.net/examples/autocompletion/ and maybe the SQL / HTML Example there. +export class NodePrefixAutoComplete { + suggestionMap: SuggestionMap; + suggestorState: STATE; + + constructor(suggestionMap: SuggestionMap, suggestorState: STATE) { + // This is where we would set up the autocompletion, but we do it in the index.ts file. + // See: https://codemirror.net/docs/ref/#autocomplete.autocompletion + this.suggestionMap = suggestionMap; + this.suggestorState = suggestorState; + } + + getState(): STATE { + return this.suggestorState; + } + + autocomplete(context: CompletionContext): CompletionResult | null { + let tree: Tree = syntaxTree(context.state); + this.suggestorState.mayUpdateFromEditorState(context, tree); + let currentNode: SyntaxNode = tree.resolveInner(context.pos, -1); // https://lezer.codemirror.net/docs/ref/#common.SyntaxNode + let options = this.getSuggestions(context, tree, currentNode); + if (options != null) { + // And once we figure out, we have to create a list of completion objects + // It may be worth changing the grammar to be able to do this more easily, rather than replicate the original TypeQL grammar. + // https://codemirror.net/docs/ref/#autocomplete.Completion + let from = findStartOfCompletion(context) + 1; + return { + from: from, + options: options, + // Docs: "regular expression that tells the extension that, as long as the updated input (the range between the result's from property and the completion point) matches that value, it can continue to use the list of completions." + validFor: /^([\w\$]+)?$/ + } + } else { + return null; + } + } + + getSuggestions(context: CompletionContext, tree: Tree, parseAt: SyntaxNode): Completion[] | null { + return this.climbTillWeRecogniseSomething(context, tree, parseAt, parseAt, collectPrecedingChildrenOf(context, parseAt)); + } + + + climbTillWeRecogniseSomething(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode | null, prefix: NodeType[]): Completion[] | null { + if (climbedTo == null) { + // Uncomment this if you don't see suggestions + // this.logInterestingStuff(context, tree, parseAt, climbedTo, prefix); + return null; + } + let suggestionEither = this.suggestionMap[climbedTo.type.id]; + if (suggestionEither != null) { + for (let sops of (suggestionEither as SuffixOfPrefixSuggestion[])) { + if (prefixHasAnyOfSuffixes(prefix, sops.suffixes)) { + return this.combineSuggestions(context, tree, parseAt, climbedTo, prefix, sops.suggestions); + } + } + // None match? Fall through. + // console.log("Fell through!!!: ", climbedTo.type.name, "with prefix", prefix); + } + let newPrefix = collectSiblingsOf(climbedTo).concat(prefix); + return this.climbTillWeRecogniseSomething(context, tree, parseAt, climbedTo.parent, newPrefix); + } + + + combineSuggestions(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode, prefix: NodeType[], suggestionFunctions: SuggestionFunction[]): Completion[] { + let suggestions = suggestionFunctions.map((f) => { + return f(context, tree, parseAt, climbedTo, prefix, this.suggestorState); + }).reduce((acc, curr) => { + return (curr == null) ? acc : acc!.concat(curr); + }, []); + // console.log("Matched:", climbedTo.type.name, "with prefix", prefix, ". Suggestions:", suggestions); + return suggestions!; + } + + logInterestingStuff(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode | null, prefix: NodeType[]) { + console.log("Current Node:", parseAt.name); + console.log("ClimbedTo Node:", climbedTo?.name); + + let at: SyntaxNode | null = parseAt; + let climbThrough = []; + while (at != null && at.name != climbedTo?.name) { + climbThrough.push(at.name); + at = at.parent; + } + climbThrough.push(at?.name); + console.log("Climbed through", climbThrough); + console.log("Prefix:", prefix); + } +} + +function isPartOfWord(s: string): boolean { + let matches = s.match(/^[A-Za-z0-9_\-\$]+/); + return matches != null && matches.length > 0; +} + +function findStartOfCompletion(context: CompletionContext): number { + let str = context.state.doc.sliceString(0, context.pos); + let at = context.pos - 1; + while (at >= 0 && isPartOfWord(str.charAt(at))) { + at -= 1; + } + return at; +} + +function collectSiblingsOf(node: SyntaxNode): NodeType[] { + let siblings = []; + let prev: SyntaxNode | null = node; + while (null != (prev = prev.prevSibling)) { + siblings.push(prev.type); + } + return siblings.reverse(); +} + +function collectPrecedingChildrenOf(context: CompletionContext, node: SyntaxNode): NodeType[] { + let lastChild = node.childBefore(context.pos); + if (lastChild == null) { + return []; + } + let precedingChildren = collectSiblingsOf(lastChild); + precedingChildren.push(lastChild.type); + return precedingChildren; +} + +function prefixHasAnyOfSuffixes(prefix: NodeType[], suffixes: SuffixCandidate[]): boolean { + for (let i = 0; i < suffixes.length; i++) { + if (prefixHasSuffix(prefix, suffixes[i])) { + return true; + } + } + return false; +} + +function prefixHasSuffix(prefix: NodeType[], suffix: TokenID[]): boolean { + if (prefix.length < suffix.length) { + return false; + } + for (let i = 0; i < suffix.length; i++) { + if (prefix[prefix.length - suffix.length + i].id != suffix[i]) { + return false; + } + } + return true; +} diff --git a/src/framework/codemirror-lang-typeql/generated/typeql.grammar.generated.terms.ts b/src/framework/codemirror-lang-typeql/generated/typeql.grammar.generated.terms.ts new file mode 100644 index 000000000..fd7139195 --- /dev/null +++ b/src/framework/codemirror-lang-typeql/generated/typeql.grammar.generated.terms.ts @@ -0,0 +1,224 @@ +// This file was generated by lezer-generator. You probably shouldn't edit it. +export const + Query = 1, + QuerySchema = 2, + QueryDefine = 3, + DEFINE = 4, + Definables = 5, + Definable = 6, + DefinitionType = 7, + KIND = 8, + LABEL = 9, + COMMA = 10, + Annotations = 11, + Annotation = 12, + ANNOTATION_ABSTRACT = 13, + ANNOTATION_CASCADE = 14, + ANNOTATION_DISTINCT = 15, + ANNOTATION_INDEPENDENT = 16, + ANNOTATION_KEY = 17, + ANNOTATION_UNIQUE = 18, + ANNOTATION_CARD = 19, + PARENOPEN = 20, + Cardinality = 21, + INTEGERLITERAL = 22, + DOUBLE_DOT = 23, + PARENCLOSE = 24, + ANNOTATION_RANGE = 25, + Range = 26, + RangeBound = 27, + ValueLiteral = 28, + BOOLEANLITERAL = 29, + STRINGLITERAL = 30, + DOUBLELITERAL = 31, + ANNOTATION_REGEX = 32, + ANNOTATION_SUBKEY = 33, + IDENTIFIER = 34, + ANNOTATION_VALUES = 35, + TypeCapability = 36, + TypeCapabilityBase = 37, + SubDeclaration = 38, + SUB = 39, + ValueTypeDeclaration = 40, + VALUEKEYWORD = 41, + ValueType = 42, + BOOLEAN = 43, + INTEGER = 44, + DOUBLE = 45, + DECIMAL = 46, + DATETIMETZ = 47, + DATETIME = 48, + DATE = 49, + DURATION = 50, + STRING = 51, + OwnsDeclaration = 52, + OWNS = 53, + LabelList = 54, + SQBRACKETOPEN = 55, + SQBRACKETCLOSE = 56, + PlaysDeclaration = 57, + PLAYS = 58, + RelatesDeclaration = 59, + RELATES = 60, + AS = 61, + DefinitionFunction = 62, + FUN = 63, + FunctionSignature = 64, + FunctionArguments = 65, + FunctionArgument = 66, + VAR = 67, + COLON = 68, + NamedTypeAny = 69, + NamedTypeList = 70, + NamedType = 71, + ARROW = 72, + FunctionOutput = 73, + FunctionOutputStream = 74, + CURLYOPEN = 75, + CURLYCLOSE = 76, + FunctionOutputSingle = 77, + FunctionBlock = 78, + QueryStage = 79, + ClauseMatch = 80, + MATCH = 81, + Patterns = 82, + Pattern = 83, + Statement = 84, + StatementAssignment = 85, + LET = 86, + AssignmentLeft = 87, + VarsAssignment = 88, + VarAssignment = 89, + QUESTIONMARK = 90, + ASSIGN = 91, + IN = 92, + Expression = 93, + ExpressionValue = 94, + ExpressionParenthesis = 95, + FunctionCall = 96, + FunctionName = 97, + FunctionCallArguments = 98, + ExpressionOperator = 99, + POWER = 100, + TIMES = 101, + DIVIDE = 102, + MODULO = 103, + PLUS = 104, + MINUS = 105, + StatementType = 106, + TypeRef = 107, + TypeConstraint = 108, + TypeConstraintBase = 109, + SubConstraint = 110, + ValueTypeConstraint = 111, + LabelConstraint = 112, + LABELKEYWORD = 113, + OwnsConstraint = 114, + TypeRefList = 115, + RelatesConstraint = 116, + PlaysConstraint = 117, + StatementThing = 118, + ThingConstraintList = 119, + ThingConstraint = 120, + IsaConstraint = 121, + ISA = 122, + Comparison = 123, + ComparisonOperator = 124, + EQUAL = 125, + NOT_EQUAL = 126, + GREATER = 127, + GREATER_EQUAL = 128, + LESS = 129, + LESS_EQUAL = 130, + LIKE = 131, + CONTAINS = 132, + IidConstraint = 133, + IID = 134, + IID_VALUE = 135, + HasConstraint = 136, + HAS = 137, + LinksConstraint = 138, + LINKS = 139, + Relation = 140, + RolePlayer = 141, + PatternDisjunction = 142, + OR = 143, + PatternConjunction = 144, + PatternNegation = 145, + NOT = 146, + PatternTry = 147, + TRY = 148, + SEMICOLON = 149, + ClauseInsert = 150, + INSERT = 151, + ClausePut = 152, + PUT = 153, + ClauseUpdate = 154, + UPDATE = 155, + ClauseDelete = 156, + DELETE = 157, + StatementDeletable = 158, + OF = 159, + OperatorStream = 160, + OperatorSelect = 161, + SELECT = 162, + Vars = 163, + OperatorSort = 164, + SORT = 165, + VAROrder = 166, + ORDER = 167, + OperatorDistinct = 168, + DISTINCT = 169, + OperatorOffset = 170, + OFFSET = 171, + OperatorLimit = 172, + LIMIT = 173, + OperatorRequire = 174, + REQUIRE = 175, + OperatorReduce = 176, + REDUCE = 177, + ReduceAssign = 178, + Reducer = 179, + COUNT = 180, + MAX = 181, + MIN = 182, + MEAN = 183, + MEDIAN = 184, + STD = 185, + SUM = 186, + LIST = 187, + GROUPBY = 188, + ReturnStatement = 189, + RETURN = 190, + ReturnStream = 191, + ReturnSingle = 192, + ReturnSingleSelector = 193, + FIRST = 194, + LAST = 195, + ReturnReduce = 196, + DefinitionStruct = 197, + STRUCT = 198, + DefinitionStructFields = 199, + DefinitionStructField = 200, + StructFieldValueType = 201, + ValueTypeOptional = 202, + QueryUndefine = 203, + UNDEFINE = 204, + Undefinables = 205, + Undefinable = 206, + UndefineFrom = 207, + UndefineAnnotationFromCapability = 208, + AnnotationCategory = 209, + FROM = 210, + UndefineAnnotationFromType = 211, + UndefineCapability = 212, + UndefineSpecialise = 213, + UndefineStruct = 214, + QueryRedefine = 215, + REDEFINE = 216, + Redefinables = 217, + Redefinable = 218, + RedefinableType = 219, + QueryPipelinePreambled = 220, + Pipeline = 221, + END = 222 diff --git a/src/framework/codemirror-lang-typeql/generated/typeql.grammar.generated.ts b/src/framework/codemirror-lang-typeql/generated/typeql.grammar.generated.ts new file mode 100644 index 000000000..acf0573fa --- /dev/null +++ b/src/framework/codemirror-lang-typeql/generated/typeql.grammar.generated.ts @@ -0,0 +1,16 @@ +// This file was generated by lezer-generator. You probably shouldn't edit it. +import {LRParser} from "@lezer/lr" +export const parser = LRParser.deserialize({ + version: 14, + states: "!+`O!QQPOOO!_QQO'#EOO!vQPO'#FhO!vQPO'#FjO!vQPO'#FlO!{QPO'#FnO#ZQPO'#FsO#`QPO'#FvO#eQPO'#FzO#jQPO'#F|O#oQPO'#GOO#ZQPO'#GQO#tQPO'#GSOOQO'#Fr'#FrOOQO'#D}'#D}OOQO'#HZ'#HZO#yQPO'#HRO$TQQO'#C_O$cQPO'#GoO%pQQO'#G{OOQO'#C^'#C^OOQO'#HQ'#HQQOQPOOQ%{QPOOO&QQPO'#ETOOQO'#Ek'#EkO&hQPO'#EkO'gQPO'#EjO'nQQO'#EjOOQO'#ES'#ESO!_QQO'#FbO'vQPO'#FcO'{QPO'#FeOOQO'#ER'#ERO(QQPO'#H[O(VQQO'#EQOOQO,5:j,5:jO&VQPO'#EvO)]QPO'#HdO)bQPO,5O,5>OOOQO-E;b-E;bO7bQPO'#EkO7pQPO'#EsO7sQPO'#F_O7xQPO,5;xO8QQPO,5<[O8VQSO,5<[OOQO,5>P,5>POOQO-E;c-E;cO8[QPO,5PQQO,5=dO>UQPO,5=eOOQO,5=f,5=fOOQO,5=^,5=^OOQO,5>V,5>VOOQO-E;i-E;iO>ZQPO,5=kO>cQPO,5=kOOQO,5>W,5>WOOQO-E;j-E;jOOQO,5:s,5:sO>jQSO,5:rO>uQSO,5:rO?QQWO1G0ZO?iQWO,5;eOOQO,5;q,5;qO@]QPO,5;tO@tQPO,5;tOOQO,5;v,5;vOAVQPO,5;cOAhQPO,5;cOOQO1G0|1G0|OOQO,5;Y,5;YOOQO,5;Z,5;ZOOQO,5;[,5;[OApQPO,5;^OOQO,5;^,5;^OCiQPO,5;`OCsQPO,5;`OOQO,5;a,5;aOOQO,5;W,5;WOCzQPO1G0pODRQPO1G0pODRQPO1G0pODZQPO1G1hODcQPO1G1iODhQPO1G1kODmQPO,5;_ODrQPO,5;yODwQQO1G1dOESQPO1G1dOOQO1G1d1G1dOOQO1G1v1G1vOE[QPO1G1vOOQO,5>Q,5>QOEaQPO1G1{OOQO-E;d-E;dOOQO,5>R,5>ROOQO-E;e-E;eOOQO7+'h7+'hOElQPO'#GVOEzQPO'#GVOOQO1G2[1G2[OOQO,5>S,5>SOOQO-E;f-E;fOOQO7+'t7+'tO#ZQPO7+'tOFPQPO7+'tOFUQPO,59SOFZQPO,59SOFlQPO,59SO1jQQO,59SOFqQPO,59SOOQO-E;S-E;SOOQO,59l,59lOGPQPO1G.iOGeQPO1G.iOGeQPO1G.iOGmQPO1G.iOGtQPO,5:YOYQPO1G/rO1jQQO1G2oOG|QPO,5:OOHRQQO1G/oOHWQQO1G/oOH]QPO1G2}OOQO1G3O1G3OOHdQQO1G3POHiQPO1G3VOHpQPO1G3VOHpQPO1G3VOOQO,5=w,5=wOHxQSO1G0^OOQO-E;Z-E;ZOOQO'#Cx'#CxO?QQWO'#E_OOQO'#Ea'#EaOITQPO'#E`OOQO'#E^'#E^OInQQO'#E]OOQO7+%u7+%uOOQO'#E|'#E|O?QQWO'#E{OOQO1G1P1G1POOQO1G1`1G1`OOQO,5=|,5=|OI{QPO1G0}OOQO-E;`-E;`O'nQQO1G0zO'nQQO1G0zOOQO,5=z,5=zOJ^QPO7+&[OOQO-E;^-E;^OJeQPO7+&[OJmQPO'#HcOJrQPO7+'QOOQO7+'T7+'TOOQO7+'V7+'VOOQO1G0y1G0yOOQO1G1e1G1eOOQO,5={,5={OOQO7+'O7+'OOJzQQO7+'OOOQO-E;_-E;_OOQO7+'b7+'bPKVQPO'#HfOK[QPO,5U,5>UO!%qQQO1G2qOOQO-E;h-E;hO!%yQWO,5:|O!&QQPO,5:|OOQO7+&Q7+&QOOQO7+)T7+)TOOQOAN=`AN=`PFqQPO'#HVO!&YQPO,5:`PGwQPO'#HXO!&_QPO,5:dO!&gQQO,5:gO!&qQPO,5:gOOQOAN>fAN>fO!&yQPO,5<}OOQO,5=O,5=OO!'OQPO,5=SO!'VQPO,5=SOOQO,5=Y,5=YP1jQQO'#HjOOQO,5=x,5=xO!'_QWO1G0hOOQO-E;[-E;[OOQO1G/z1G/zO!'fQQO1G0OO!'pQPO1G0OOOQO1G0O1G0OOOQO,5=t,5=tO!'xQQO1G0ROOQO-E;W-E;WOOQO1G2i1G2iOOQO,5>T,5>TO!(SQPO1G2nOOQO-E;g-E;gP?QQWO'#H^OOQO7+%j7+%jO!(ZQQO7+%jP!!RQQO'#HYP8zQPO'#HiOOQO<T#V#WB[#W#XH}#X#Y!7Z#Y#Z!:|#Z#[!Cn#[#]!Gx#]#^!Ig#^#`5^#`#a#$u#a#b#1`#b#c#:k#c#d#<]#d#e#Bh#e#f5^#f#g#Fq#g#h$)t#h#i$7P#i#j$8w#j#k$@v#k#o5^#o#p$Cq#q#r$Cv~$pS&c~XY$kYZ$k]^$kpq$k~%PP!_!`%S~%XO#r~~%[VOr%Xrs%qs#O%X#O#P%v#P;'S%X;'S;=`&o<%lO%X~%vOn~~%yRO;'S%X;'S;=`&S;=`O%X~&VWOr%Xrs%qs#O%X#O#P%v#P;'S%X;'S;=`&o;=`<%l%X<%lO%X~&rP;=`<%l%X~&zS&d~OY&uZ;'S&u;'S;=`'W<%lO&u~'ZP;=`<%l&u~'aT}!O'p!Q!['p!c!}'p#R#S'p#T#o'p~'uT!e~}!O'p!Q!['p!c!}'p#R#S'p#T#o'p~(ZO#Z~~(`Od~~(eOh~~(jO#X~~(oO#[~~(tOY~Z(yQ#]Q!Q![)P!`!a)lX)WQoXfX!O!P)^!Q![)PX)aP!Q![)dX)iPoX!Q![)dP)qO!jP~)tP!O!P)w~)|Og~~*RO#Y~Z*YRoXfX!O!P)^!Q![)P#l#m*cQ*fR!Q![*o!c!i*o#T#Z*oQ*tR#{Q!Q![*o!c!i*o#T#Z*o~+SO!f~~+XO$Z~~+^P#u~!_!`+a~+fO#v~]+kP!}T!_!`+nW+sO#qW~+xP#s~!_!`+{~,QO#t~~,VO!|~~,YX#T#U,u#V#W-v#W#X/P#]#^0Q#_#`1e#f#g1v#g#h3P#i#j3t#j#k4i~,xP#U#V,{~-OP#g#h-R~-UP#h#i-X~-[P#f#g-_~-bP#T#U-e~-hP#V#W-k~-nP#h#i-q~-vO]~~-yP#T#U-|~.PQ#f#g.V#g#h.b~.YP#W#X.]~.bOc~~.eP#V#W.h~.kP#T#U.n~.qP#W#X.t~.wP#X#Y.z~/PO^~~/SP#]#^/V~/YP#g#h/]~/`P#h#i/c~/fP#]#^/i~/lP#b#c/o~/rP#V#W/u~/xP#h#i/{~0QO_~~0TP#b#c0W~0ZP#W#X0^~0aP#X#Y0d~0gP#d#e0j~0mP#X#Y0p~0sP#b#c0v~0yP#W#X0|~1PP#X#Y1S~1VP#b#c1Y~1]P#h#i1`~1eO`~~1hP#X#Y1k~1nP#m#n1q~1vOa~~1yQ#T#U2P#X#Y2h~2SP#b#c2V~2YP#Z#[2]~2`P#X#Y2c~2hOi~~2kP#Z#[2n~2qP#X#Y2t~2wP#l#m2z~3POp~~3SP#i#j3V~3YP#U#V3]~3`P#_#`3c~3fP#X#Y3i~3lP#m#n3o~3tOq~~3wP#b#c3z~3}P#]#^4Q~4TP#e#f4W~4ZP#i#j4^~4aP#X#Y4d~4iOb~~4lP#T#U4o~4rP#`#a4u~4xP#i#j4{~5OP#X#Y5R~5UP#g#h5X~5^Os~Y5aT}!O5p!Q![5p!c!}5p#R#S5p#T#o5pY5uUXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pY6[T}!O6k!Q![6k!c!}6k#R#S6k#T#o6kY6pTXY}!O6k!Q![6k!c!}6k#R#S6k#T#o6k~7UO!X~~7ZO!Y~~7`O#W~~7cW}!O5p!Q![5p!c!}5p#R#S5p#T#g5p#g#h7{#h#i9V#i#o5p_8SW!_PXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W8l#W#o5p^8sU$mSXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~9[WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i9t#i#o5p~9yWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g:c#g#o5p~:hWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^;Q#^#o5p~;VWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V;o#V#o5p~;tWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j<^#j#o5p~WV}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#d>m#d#o5p~>rWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d?[#d#o5p~?aWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a?y#a#o5p~@OWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y@h#Y#o5p~@mVXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#UAS#U#o5p~AXWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cAq#c#o5p~AxU{~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~B_V}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#dBt#d#o5p~ByYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cCi#c#i5p#i#jGW#j#o5p~CnWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iDW#i#o5p~D]VXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#UDr#U#o5p~DwWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^Ea#^#o5p~EfWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cFO#c#o5p~FTWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#hFm#h#o5p~FtU#x~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZG]WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cGu#c#o5pZGzWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iHd#i#o5pZHkU$zPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~IQ^}!O5p!Q![5p!c!}5p#R#S5p#T#UI|#U#X5p#X#Y!!R#Y#]5p#]#^!+k#^#c5p#c#d!/z#d#i5p#i#j!2}#j#o5p~JRWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iJk#i#o5p~JpWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#YKY#Y#o5p~KaW!R~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iKy#i#o5p~LOWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^Lh#^#o5p~LmWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#bMV#b#o5p~M[WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#YMt#Y#o5p~M{U!Q~XY}!ON_!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~NdWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iN|#i#o5p~! RVXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#n5p#n#o! h~! oU!P~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!!W^XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W!#S#W#Y5p#Y#Z!&S#Z#`5p#`#a!(h#a#g5p#g#h!*|#h#o5p~!#XWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!#q#^#o5p~!#vWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#b!$`#b#o5p~!$eVXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U!$z#U#o5p~!%PWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!%i#a#o5p~!%pU!O~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!&XWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!&q#^#o5pZ!&vWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!'`#c#o5pZ!'eWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!'}#Y#o5pZ!(UUSPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!(mWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!)V#Y#o5p~!)[WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!)t#i#o5p~!)yWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!*c#Y#o5p~!*jU$c~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p^!+RWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W8l#W#o5p~!+pWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!,Y#h#o5p~!,_WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!,w#i#o5p~!,|WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!-f#^#o5p~!-kWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!.T#c#o5p~!.YWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W!.r#W#o5p~!.wWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!/a#i#o5p~!/hU$o~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!0PWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!0i#j#o5p~!0nWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V!1W#V#o5p~!1]WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!1u#a#o5p~!1zWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!2d#Y#o5p~!2kU}~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!3SWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!3l#g#o5p~!3qVXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U!4W#U#o5p~!4]WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!4u#i#o5p~!4zWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!5d#^#o5p~!5iWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!6R#d#o5p~!6WWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!6p#c#o5p~!6wU!S~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!7^V}!O5p!Q![5p!c!}5p#R#S5p#T#b5p#b#c!7s#c#o5p~!7xYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X!8h#X#h5p#h#i!9R#i#o5p~!8oU%v~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!9WWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!9p#^#o5p~!9uWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!:_#i#o5p~!:dWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n=j#n#o5p~!;P[}!O5p!Q![5p!c!}5p#R#S5p#T#U!;u#U#]5p#]#^!>Z#^#f5p#f#g!@o#g#i5p#i#j!Bf#j#o5p~!;zWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!`WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!>x#g#o5pZ!>}WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!?g#h#o5pZ!?lWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!@U#i#o5pZ!@]U%YPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!@tWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!A^#d#o5pZ!AcWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#b!A{#b#o5pZ!BSU%jPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!BkWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!CT#c#o5p~!C[U!a~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!CqV}!O5p!Q![5p!c!}5p#R#S5p#T#f5p#f#g!DW#g#o5pZ!D]WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!Du#d#o5pZ!DzWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!Ed#j#o5pZ!EiWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#d5p#d#e!FR#e#o5pZ!FWWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V!Fp#V#o5pZ!FuWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n!G_#n#o5pZ!GfU%SPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!G{U}!O5p!Q![5p!c!}5p#R#S5p#T#U!H_#U#o5pZ!HdWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!H|#h#o5pZ!ITU#}PXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_!IjZ}!O5p!Q![5p!c!}5p#R#S5p#T#]5p#]#^!J]#^#b5p#b#c!Ke#c#g5p#g#h##p#h#o5pZ!JbWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X!Jz#X#o5pZ!KRU#zPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_!KlX#OSXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!LX#h#i!Nm#i#o5pZ!L^WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!Lv#Y#o5pZ!L{WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!Me#g#o5pZ!MjWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!NS#i#o5pZ!NZU$]RXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!NrWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y# [#Y#o5pZ# aWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Z5p#Z#[# y#[#o5pZ#!OWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#!h#Y#o5pZ#!mWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g##V#g#o5pZ##^U|RXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ##uVXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#$[#U#o5pZ#$cU#nPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#$xY}!O5p!Q![5p!c!}5p#R#S5p#T#U#%h#U#X5p#X#Y#)[#Y#]5p#]#^#*d#^#o5pZ#%mYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V#&]#V#g5p#g#h#(S#h#o5pZ#&bWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#&z#Y#o5pZ#'PWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a#'i#a#o5pZ#'pU#ePXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#(XWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#(q#i#o5pZ#(xU%ZPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#)aWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#)y#i#o5p~#*QU!x~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#*i]XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#_5p#_#`#+b#`#a5p#a#b#,j#b#c#.a#c#g5p#g#h#0W#h#o5p~#+gWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#,P#Y#o5p~#,WU#w~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#,oWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#-X#^#o5p~#-^WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#-v#i#o5p~#-}U$s~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#.fWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#_5p#_#`#/O#`#o5pZ#/TWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#/m#h#o5pZ#/tU$PPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#0]WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#0u#i#o5pZ#0|U%RPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#1cY}!O5p!Q![5p!c!}5p#R#S5p#T#U#2R#U#X5p#X#Y#5W#Y#]5p#]#^#9c#^#o5p~#2WYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#2v#i#l5p#l#m#4m#m#o5p~#2{WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W#3e#W#o5p~#3jWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#[5p#[#]#4S#]#o5p~#4ZU!s~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#4tU${PXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#5]XXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#5x#U#W5p#W#X#7Q#X#o5pZ#5}WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#6g#c#o5pZ#6nU$}PXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#7VWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#7o#^#o5pZ#7tVXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#8Z#U#o5pZ#8`WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#8x#c#o5pZ#9PU%OPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#9hWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#:Q#c#o5pZ#:XU$|PXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#:nV}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#d#;T#d#o5p~#;YWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#;r#i#o5p~#;yU$W~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_#<`Z}!O5p!Q![5p!c!}5p#R#S5p#T#Y5p#Y#Z#=R#Z#f5p#f#g#@W#g#k5p#k#l#@q#l#o5p_#=YW$eSXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z#=r#Z#o5pZ#=wWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#>a#h#o5pZ#>fWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#?O#Y#o5pZ#?TWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#?m#i#o5pZ#?tU$qRXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#@_U$TPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#@vWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#A`#c#o5pZ#AeWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#A}#h#o5pZ#BUU!VPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#BkX}!O5p!Q![5p!c!}5p#R#S5p#T#`5p#`#a#CW#a#i5p#i#j#Ei#j#o5pZ#C]VXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#Cr#U#o5pZ#CwWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n#Da#n#o5pZ#DfWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#EO#h#o5pZ#EVU![PXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#EnWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#FW#i#o5p~#F_U$_~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#FtV}!O5p!Q![5p!c!}5p#R#S5p#T#X5p#X#Y#GZ#Y#o5p~#G`^XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X#H[#X#`5p#`#a#My#a#e5p#e#f$$]#f#h5p#h#i$'`#i#o5p~#HaYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#IP#Y#i5p#i#j#LS#j#o5pZ#IUWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z#In#Z#o5pZ#IsWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#J]#^#o5pZ#JbWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#Jz#c#o5pZ#KPWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#Ki#Y#o5pZ#KpU%pPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#LXWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W#Lq#W#o5p~#LvWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#M`#Y#o5p~#MgU$w~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#NOVXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#Ne#U#o5p~#NjWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$ S#i#o5p~$ XYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$ w#Y#]5p#]#^$#P#^#o5pZ$ |WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h$!f#h#o5pZ$!mU!^PXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$#UWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d$#n#d#o5p~$#sWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c=j#c#o5p~$$bWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$$z#j#o5p~$%PWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$%i#^#o5p~$%nWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$&W#g#o5p~$&]WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$&u#Y#o5p~$&|U$u~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$'eWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$'}#j#o5p~$(SWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$(l#g#o5p~$(qWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c$)Z#c#o5p~$)bU%U~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$)w[}!O5p!Q![5p!c!}5p#R#S5p#T#X5p#X#Y$*m#Y#c5p#c#d$-p#d#h5p#h#i$/g#i#j$5W#j#o5p~$*rWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a$+[#a#o5p~$+aWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$+y#Y#o5p~$,OWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W$,h#W#o5p~$,mWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$-V#i#o5p~$-^U$h~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$-uWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$._#g#o5p~$.dWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$.|#i#o5p~$/TU$k~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$/lYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X$0[#X#f5p#f#g$0u#g#o5pZ$0cU%PPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$0zYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$1j#^#i5p#i#j$3a#j#o5p~$1oWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c$2X#c#o5p~$2^WXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Z5p#Z#[$2v#[#o5p~$2}U!T~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$3fWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W$4O#W#o5p~$4TWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$4m#i#o5p~$4tU%^~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$5]YXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V$5{#V#a5p#a#b$6f#b#o5pZ$6SUwPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$6mU%QPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$7SV}!O5p!Q![5p!c!}5p#R#S5p#T#f5p#f#g$7i#g#o5p~$7nYXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!=R#j#m5p#m#n$8^#n#o5p~$8eU$Y~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$8zX}!O5p!Q![5p!c!}5p#R#S5p#T#b5p#b#c$9g#c#d5p#d#e$=v#e#o5pZ$9lWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X$:U#X#o5pZ$:ZWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$:s#Y#o5pZ$:xWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z$;b#Z#o5pZ$;gWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$e#X#o5p~$>jVXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U$?P#U#o5p~$?UWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$?n#i#o5p~$?sWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$@]#Y#o5p~$@dU$a~XY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$@yU}!O5p!Q![5p!c!}5p#R#S5p#T#U$A]#U#o5pZ$AbWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a$Az#a#o5pZ$BPWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$Bi#j#o5pZ$BnWXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$CW#Y#o5pZ$C_UyPXY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$CvO!m~~$C{O!n~", + tokenizers: [0, 1, 2, 3], + topRules: {"Query":[0,1]}, + tokenPrec: 2459 +}) diff --git a/src/framework/codemirror-lang-typeql/index.cjs b/src/framework/codemirror-lang-typeql/index.cjs deleted file mode 100644 index 42e809e4b..000000000 --- a/src/framework/codemirror-lang-typeql/index.cjs +++ /dev/null @@ -1,198 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, '__esModule', { value: true }); - -var lr = require('@lezer/lr'); -var language = require('@codemirror/language'); -var highlight = require('@lezer/highlight'); -var lint = require('@codemirror/lint'); - -// This file was generated by lezer-generator. You probably shouldn't edit it. -const parser = lr.LRParser.deserialize({ - version: 14, - states: "!+xOYQPOOO!ZQQO'#EPO!rQPO'#FiO!rQPO'#FkO!rQPO'#FmO!wQPO'#FoO#VQPO'#FtO#[QPO'#FwO#aQPO'#F{O#fQPO'#F}O#kQPO'#GPO#VQPO'#GRO#pQPO'#GTOOQO'#Fs'#FsOOQO'#EO'#EOOOQO'#H['#H[O$mQPO'#HSO$wQQO'#C`O%VQPO'#GpO&dQQO'#G|OOQO'#C_'#C_OOQO'#HR'#HROOQO'#C^'#C^Q&oQPOOO&tQPO'#EUOOQO'#El'#ElO'[QPO'#ElO(ZQPO'#EkO(bQQO'#EkOOQO'#ET'#ETO!ZQQO'#FcO(jQPO'#FdO(oQPO'#FfOOQO'#ES'#ESO(tQPO'#H]O(yQQO'#EROOQO,5:k,5:kO&yQPO'#EwO*PQPO'#HeO*UQPO,5P,5>POOQO-E;c-E;cO8_QPO'#ElO8mQPO'#EtO8pQPO'#F`O8uQPO,5;yO8}QPO,5<]O9SQSO,5<]OOQO,5>Q,5>QOOQO-E;d-E;dO9XQPO,5fQPO,5:UO>pQPO,5:UO>wQQO,5=dO>|QQO,5=eO?RQPO,5=fOOQO,5=g,5=gOOQO,5=_,5=_OOQO,5>W,5>WOOQO-E;j-E;jO?WQPO,5=lO?`QPO,5=lOOQO,5>X,5>XOOQO-E;k-E;kQYQPO,5>YOOQO-E;l-E;lOOQO,5:t,5:tO?gQSO,5:sO?rQSO,5:sO?}QWO1G0[O@fQWO,5;fOOQO,5;r,5;rOAYQPO,5;uOAqQPO,5;uOOQO,5;w,5;wOBSQPO,5;dOBeQPO,5;dOOQO1G0}1G0}OOQO,5;Z,5;ZOOQO,5;[,5;[OOQO,5;],5;]OBmQPO,5;_OOQO,5;_,5;_ODfQPO,5;aODpQPO,5;aOOQO,5;b,5;bOOQO,5;X,5;XODwQPO1G0qOEOQPO1G0qOEOQPO1G0qOEWQPO1G1iOE`QPO1G1jOEeQPO1G1lOEjQPO,5;`OEoQPO,5;zOEtQQO1G1eOFPQPO1G1eOOQO1G1e1G1eOOQO1G1w1G1wOFXQPO1G1wOOQO,5>R,5>ROF^QPO1G1|OOQO-E;e-E;eOOQO,5>S,5>SOOQO-E;f-E;fOOQO7+'i7+'iOFiQPO'#GWOFwQPO'#GWOOQO1G2]1G2]OOQO,5>T,5>TOOQO-E;g-E;gOOQO7+'u7+'uO#VQPO7+'uOF|QPO7+'uOGRQPO,59TOGWQPO,59TOGiQPO,59TO2^QQO,59TOGnQPO,59TOOQO-E;T-E;TOOQO,59m,59mOG|QPO1G.jOHbQPO1G.jOHbQPO1G.jOHjQPO1G.jOHqQPO,5:ZO#uQPO1G/sO2^QQO1G2pOHyQPO,5:POIOQQO1G/pOITQQO1G/pOIYQPO1G3OOOQO1G3P1G3POIaQQO1G3QOIfQPO1G3WOImQPO1G3WOImQPO1G3WOOQO1G3t1G3tOOQO,5=x,5=xOIuQSO1G0_OOQO-E;[-E;[OOQO'#Cy'#CyO?}QWO'#E`OOQO'#Eb'#EbOJQQPO'#EaOOQO'#E_'#E_OJkQQO'#E^OOQO7+%v7+%vOOQO'#E}'#E}O?}QWO'#E|OOQO1G1Q1G1QOOQO1G1a1G1aOOQO,5=},5=}OJxQPO1G1OOOQO-E;a-E;aO(bQQO1G0{O(bQQO1G0{OOQO,5={,5={OKZQPO7+&]OOQO-E;_-E;_OKbQPO7+&]OKjQPO'#HdOKoQPO7+'ROOQO7+'U7+'UOOQO7+'W7+'WOOQO1G0z1G0zOOQO1G1f1G1fOOQO,5=|,5=|OOQO7+'P7+'POKwQQO7+'POOQO-E;`-E;`OOQO7+'c7+'cPLSQPO'#HgOLXQPO,5OOOQO-E;b-E;bOOQO<V,5>VO!&nQQO1G2rOOQO-E;i-E;iO!&vQWO,5:}O!&}QPO,5:}OOQO7+&R7+&ROOQO7+)U7+)UOOQOAN=aAN=aPGnQPO'#HWO!'VQPO,5:aPHtQPO'#HYO!'[QPO,5:eO!'dQQO,5:hO!'nQPO,5:hOOQOAN>gAN>gO!'vQPO,5=OOOQO,5=P,5=PO!'{QPO,5=TO!(SQPO,5=TOOQO,5=Z,5=ZP2^QQO'#HkOOQO,5=y,5=yO!([QWO1G0iOOQO-E;]-E;]OOQO1G/{1G/{O!(cQQO1G0PO!(mQPO1G0POOQO1G0P1G0POOQO,5=u,5=uO!(uQQO1G0SOOQO-E;X-E;XOOQO1G2j1G2jOOQO,5>U,5>UO!)PQPO1G2oOOQO-E;h-E;hP?}QWO'#H_OOQO7+%k7+%kO!)WQQO7+%kP!#OQQO'#HZP9wQPO'#HjOOQO<T#V#WB[#W#XH}#X#Y!7Z#Y#Z!:|#Z#[!Cn#[#]!Gx#]#^!Ig#^#`5^#`#a#$u#a#b#1`#b#c#:k#c#d#<]#d#e#Bh#e#f5^#f#g#Fq#g#h$)t#h#i$7P#i#j$8w#j#k$@v#k#o5^#o#p$Cq#q#r$Cv~$pS&e~XY$kYZ$k]^$kpq$k~%PP!_!`%S~%XO#s~~%[VOr%Xrs%qs#O%X#O#P%v#P;'S%X;'S;=`&o<%lO%X~%vOo~~%yRO;'S%X;'S;=`&S;=`O%X~&VWOr%Xrs%qs#O%X#O#P%v#P;'S%X;'S;=`&o;=`<%l%X<%lO%X~&rP;=`<%l%X~&zS&f~OY&uZ;'S&u;'S;=`'W<%lO&u~'ZP;=`<%l&u~'aT}!O'p!Q!['p!c!}'p#R#S'p#T#o'p~'uT!f~}!O'p!Q!['p!c!}'p#R#S'p#T#o'p~(ZO#[~~(`Oe~~(eOi~~(jO#Y~~(oO#]~~(tOZ~Z(yQ#^Q!Q![)P!`!a)lX)WQpXgX!O!P)^!Q![)PX)aP!Q![)dX)iPpX!Q![)dP)qO!kP~)tP!O!P)w~)|Oh~~*RO#Z~Z*YRpXgX!O!P)^!Q![)P#l#m*cQ*fR!Q![*o!c!i*o#T#Z*oQ*tR#|Q!Q![*o!c!i*o#T#Z*o~+SO!g~~+XO$[~~+^P#v~!_!`+a~+fO#w~]+kP#OT!_!`+nW+sO#rW~+xP#t~!_!`+{~,QO#u~~,VO!}~~,YX#T#U,u#V#W-v#W#X/P#]#^0Q#_#`1e#f#g1v#g#h3P#i#j3t#j#k4i~,xP#U#V,{~-OP#g#h-R~-UP#h#i-X~-[P#f#g-_~-bP#T#U-e~-hP#V#W-k~-nP#h#i-q~-vO^~~-yP#T#U-|~.PQ#f#g.V#g#h.b~.YP#W#X.]~.bOd~~.eP#V#W.h~.kP#T#U.n~.qP#W#X.t~.wP#X#Y.z~/PO_~~/SP#]#^/V~/YP#g#h/]~/`P#h#i/c~/fP#]#^/i~/lP#b#c/o~/rP#V#W/u~/xP#h#i/{~0QO`~~0TP#b#c0W~0ZP#W#X0^~0aP#X#Y0d~0gP#d#e0j~0mP#X#Y0p~0sP#b#c0v~0yP#W#X0|~1PP#X#Y1S~1VP#b#c1Y~1]P#h#i1`~1eOa~~1hP#X#Y1k~1nP#m#n1q~1vOb~~1yQ#T#U2P#X#Y2h~2SP#b#c2V~2YP#Z#[2]~2`P#X#Y2c~2hOj~~2kP#Z#[2n~2qP#X#Y2t~2wP#l#m2z~3POq~~3SP#i#j3V~3YP#U#V3]~3`P#_#`3c~3fP#X#Y3i~3lP#m#n3o~3tOr~~3wP#b#c3z~3}P#]#^4Q~4TP#e#f4W~4ZP#i#j4^~4aP#X#Y4d~4iOc~~4lP#T#U4o~4rP#`#a4u~4xP#i#j4{~5OP#X#Y5R~5UP#g#h5X~5^Ot~Y5aT}!O5p!Q![5p!c!}5p#R#S5p#T#o5pY5uUYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pY6[T}!O6k!Q![6k!c!}6k#R#S6k#T#o6kY6pTYY}!O6k!Q![6k!c!}6k#R#S6k#T#o6k~7UO!Y~~7ZO!Z~~7`O#X~~7cW}!O5p!Q![5p!c!}5p#R#S5p#T#g5p#g#h7{#h#i9V#i#o5p_8SW!`PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W8l#W#o5p^8sU$nSYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~9[WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i9t#i#o5p~9yWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g:c#g#o5p~:hWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^;Q#^#o5p~;VWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V;o#V#o5p~;tWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j<^#j#o5p~WV}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#d>m#d#o5p~>rWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d?[#d#o5p~?aWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a?y#a#o5p~@OWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y@h#Y#o5p~@mVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#UAS#U#o5p~AXWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cAq#c#o5p~AxU|~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~B_V}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#dBt#d#o5p~ByYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cCi#c#i5p#i#jGW#j#o5p~CnWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iDW#i#o5p~D]VYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#UDr#U#o5p~DwWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^Ea#^#o5p~EfWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cFO#c#o5p~FTWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#hFm#h#o5p~FtU#y~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZG]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cGu#c#o5pZGzWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iHd#i#o5pZHkU${PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~IQ^}!O5p!Q![5p!c!}5p#R#S5p#T#UI|#U#X5p#X#Y!!R#Y#]5p#]#^!+k#^#c5p#c#d!/z#d#i5p#i#j!2}#j#o5p~JRWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iJk#i#o5p~JpWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#YKY#Y#o5p~KaW!S~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iKy#i#o5p~LOWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^Lh#^#o5p~LmWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#bMV#b#o5p~M[WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#YMt#Y#o5p~M{U!R~YY}!ON_!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~NdWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iN|#i#o5p~! RVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#n5p#n#o! h~! oU!Q~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!!W^YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W!#S#W#Y5p#Y#Z!&S#Z#`5p#`#a!(h#a#g5p#g#h!*|#h#o5p~!#XWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!#q#^#o5p~!#vWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#b!$`#b#o5p~!$eVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U!$z#U#o5p~!%PWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!%i#a#o5p~!%pU!P~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!&XWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!&q#^#o5pZ!&vWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!'`#c#o5pZ!'eWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!'}#Y#o5pZ!(UUTPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!(mWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!)V#Y#o5p~!)[WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!)t#i#o5p~!)yWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!*c#Y#o5p~!*jU$d~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p^!+RWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W8l#W#o5p~!+pWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!,Y#h#o5p~!,_WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!,w#i#o5p~!,|WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!-f#^#o5p~!-kWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!.T#c#o5p~!.YWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W!.r#W#o5p~!.wWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!/a#i#o5p~!/hU$p~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!0PWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!0i#j#o5p~!0nWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V!1W#V#o5p~!1]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!1u#a#o5p~!1zWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!2d#Y#o5p~!2kU!O~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!3SWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!3l#g#o5p~!3qVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U!4W#U#o5p~!4]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!4u#i#o5p~!4zWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!5d#^#o5p~!5iWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!6R#d#o5p~!6WWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!6p#c#o5p~!6wU!T~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!7^V}!O5p!Q![5p!c!}5p#R#S5p#T#b5p#b#c!7s#c#o5p~!7xYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X!8h#X#h5p#h#i!9R#i#o5p~!8oU%w~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!9WWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!9p#^#o5p~!9uWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!:_#i#o5p~!:dWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n=j#n#o5p~!;P[}!O5p!Q![5p!c!}5p#R#S5p#T#U!;u#U#]5p#]#^!>Z#^#f5p#f#g!@o#g#i5p#i#j!Bf#j#o5p~!;zWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!`WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!>x#g#o5pZ!>}WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!?g#h#o5pZ!?lWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!@U#i#o5pZ!@]U%ZPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!@tWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!A^#d#o5pZ!AcWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#b!A{#b#o5pZ!BSU%kPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!BkWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!CT#c#o5p~!C[U!b~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!CqV}!O5p!Q![5p!c!}5p#R#S5p#T#f5p#f#g!DW#g#o5pZ!D]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!Du#d#o5pZ!DzWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!Ed#j#o5pZ!EiWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#d5p#d#e!FR#e#o5pZ!FWWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V!Fp#V#o5pZ!FuWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n!G_#n#o5pZ!GfU%TPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!G{U}!O5p!Q![5p!c!}5p#R#S5p#T#U!H_#U#o5pZ!HdWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!H|#h#o5pZ!ITU$OPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_!IjZ}!O5p!Q![5p!c!}5p#R#S5p#T#]5p#]#^!J]#^#b5p#b#c!Ke#c#g5p#g#h##p#h#o5pZ!JbWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X!Jz#X#o5pZ!KRU#{PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_!KlX#PSYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!LX#h#i!Nm#i#o5pZ!L^WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!Lv#Y#o5pZ!L{WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!Me#g#o5pZ!MjWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!NS#i#o5pZ!NZU$^RYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!NrWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y# [#Y#o5pZ# aWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Z5p#Z#[# y#[#o5pZ#!OWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#!h#Y#o5pZ#!mWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g##V#g#o5pZ##^U}RYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ##uVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#$[#U#o5pZ#$cU#oPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#$xY}!O5p!Q![5p!c!}5p#R#S5p#T#U#%h#U#X5p#X#Y#)[#Y#]5p#]#^#*d#^#o5pZ#%mYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V#&]#V#g5p#g#h#(S#h#o5pZ#&bWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#&z#Y#o5pZ#'PWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a#'i#a#o5pZ#'pU#fPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#(XWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#(q#i#o5pZ#(xU%[PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#)aWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#)y#i#o5p~#*QU!y~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#*i]YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#_5p#_#`#+b#`#a5p#a#b#,j#b#c#.a#c#g5p#g#h#0W#h#o5p~#+gWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#,P#Y#o5p~#,WU#x~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#,oWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#-X#^#o5p~#-^WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#-v#i#o5p~#-}U$t~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#.fWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#_5p#_#`#/O#`#o5pZ#/TWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#/m#h#o5pZ#/tU$QPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#0]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#0u#i#o5pZ#0|U%SPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#1cY}!O5p!Q![5p!c!}5p#R#S5p#T#U#2R#U#X5p#X#Y#5W#Y#]5p#]#^#9c#^#o5p~#2WYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#2v#i#l5p#l#m#4m#m#o5p~#2{WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W#3e#W#o5p~#3jWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#[5p#[#]#4S#]#o5p~#4ZU!t~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#4tU$|PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#5]XYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#5x#U#W5p#W#X#7Q#X#o5pZ#5}WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#6g#c#o5pZ#6nU%OPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#7VWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#7o#^#o5pZ#7tVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#8Z#U#o5pZ#8`WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#8x#c#o5pZ#9PU%PPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#9hWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#:Q#c#o5pZ#:XU$}PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#:nV}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#d#;T#d#o5p~#;YWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#;r#i#o5p~#;yU$X~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_#<`Z}!O5p!Q![5p!c!}5p#R#S5p#T#Y5p#Y#Z#=R#Z#f5p#f#g#@W#g#k5p#k#l#@q#l#o5p_#=YW$fSYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z#=r#Z#o5pZ#=wWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#>a#h#o5pZ#>fWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#?O#Y#o5pZ#?TWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#?m#i#o5pZ#?tU$rRYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#@_U$UPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#@vWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#A`#c#o5pZ#AeWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#A}#h#o5pZ#BUU!WPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#BkX}!O5p!Q![5p!c!}5p#R#S5p#T#`5p#`#a#CW#a#i5p#i#j#Ei#j#o5pZ#C]VYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#Cr#U#o5pZ#CwWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n#Da#n#o5pZ#DfWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#EO#h#o5pZ#EVU!]PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#EnWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#FW#i#o5p~#F_U$`~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#FtV}!O5p!Q![5p!c!}5p#R#S5p#T#X5p#X#Y#GZ#Y#o5p~#G`^YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X#H[#X#`5p#`#a#My#a#e5p#e#f$$]#f#h5p#h#i$'`#i#o5p~#HaYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#IP#Y#i5p#i#j#LS#j#o5pZ#IUWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z#In#Z#o5pZ#IsWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#J]#^#o5pZ#JbWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#Jz#c#o5pZ#KPWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#Ki#Y#o5pZ#KpU%qPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#LXWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W#Lq#W#o5p~#LvWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#M`#Y#o5p~#MgU$x~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#NOVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#Ne#U#o5p~#NjWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$ S#i#o5p~$ XYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$ w#Y#]5p#]#^$#P#^#o5pZ$ |WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h$!f#h#o5pZ$!mU!_PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$#UWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d$#n#d#o5p~$#sWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c=j#c#o5p~$$bWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$$z#j#o5p~$%PWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$%i#^#o5p~$%nWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$&W#g#o5p~$&]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$&u#Y#o5p~$&|U$v~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$'eWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$'}#j#o5p~$(SWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$(l#g#o5p~$(qWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c$)Z#c#o5p~$)bU%V~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$)w[}!O5p!Q![5p!c!}5p#R#S5p#T#X5p#X#Y$*m#Y#c5p#c#d$-p#d#h5p#h#i$/g#i#j$5W#j#o5p~$*rWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a$+[#a#o5p~$+aWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$+y#Y#o5p~$,OWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W$,h#W#o5p~$,mWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$-V#i#o5p~$-^U$i~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$-uWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$._#g#o5p~$.dWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$.|#i#o5p~$/TU$l~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$/lYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X$0[#X#f5p#f#g$0u#g#o5pZ$0cU%QPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$0zYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$1j#^#i5p#i#j$3a#j#o5p~$1oWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c$2X#c#o5p~$2^WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Z5p#Z#[$2v#[#o5p~$2}U!U~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$3fWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W$4O#W#o5p~$4TWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$4m#i#o5p~$4tU%_~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$5]YYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V$5{#V#a5p#a#b$6f#b#o5pZ$6SUxPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$6mU%RPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$7SV}!O5p!Q![5p!c!}5p#R#S5p#T#f5p#f#g$7i#g#o5p~$7nYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!=R#j#m5p#m#n$8^#n#o5p~$8eU$Z~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$8zX}!O5p!Q![5p!c!}5p#R#S5p#T#b5p#b#c$9g#c#d5p#d#e$=v#e#o5pZ$9lWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X$:U#X#o5pZ$:ZWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$:s#Y#o5pZ$:xWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z$;b#Z#o5pZ$;gWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$e#X#o5p~$>jVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U$?P#U#o5p~$?UWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$?n#i#o5p~$?sWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$@]#Y#o5p~$@dU$b~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$@yU}!O5p!Q![5p!c!}5p#R#S5p#T#U$A]#U#o5pZ$AbWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a$Az#a#o5pZ$BPWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$Bi#j#o5pZ$BnWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$CW#Y#o5pZ$C_UzPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$CvO!n~~$C{O!o~", - tokenizers: [0, 1, 2, 3], - topRules: {"Query":[0,1]}, - tokenPrec: 2502 -}); - -// TODO: We could do better by climbing up the tree using `atNode.parentNode` to predict based on position as well. -// We could also refine the suggestions by creating datastructures based on the declarations in the schema, rather than blindly suggesting every label. -function suggestLabel(label) { - return { - label: label, - type: "label", - apply: label, - info: "label suggestion", - }; -} -function suggestVariable(varName) { - return { - label: varName, - type: "variable", - apply: varName, - info: "variable suggestion", - }; -} -function collectLabelSuggestions(context, tree, atNode) { - // This is a placeholder function. You will need to implement the logic to collect label suggestions. - // For now, we return an empty array. - var options = []; - tree.iterate({ - enter: (other) => { - if (other.name == "LABEL") { - let content = context.state.sliceDoc(other.from, other.to); - options.push(suggestLabel(content)); - } - } - }); - return options; -} -function collectVariableSuggestions(context, tree, atNode) { - var options = []; - tree.iterate({ - enter: (other) => { - if (other.name == "VAR") { - let content = context.state.sliceDoc(other.from, other.to); - options.push(suggestVariable(content)); - } - } - }); - return options; -} -// See: https://codemirror.net/examples/autocompletion/ and maybe the SQL / HTML Example there. -function autocompleteTypeQL(context) { - let tree = language.syntaxTree(context.state); - let currentNode = tree.resolveInner(context.pos, -1); // https://lezer.codemirror.net/docs/ref/#common.SyntaxNode - // We may have to walk the tree to find the most appropriate node to suggest things based on. - let options = null; - if (currentNode.name == "LABEL") { - options = collectLabelSuggestions(context, tree); - } - else if (currentNode.name == "VAR") { - options = collectVariableSuggestions(context, tree); - } - else ; - if (options != null) { - // And once we figure out, we have to create a list of completion objects - // It may be worth changing the grammar to be able to do this more easily, rather than replicate the original TypeQL grammar. - // https://codemirror.net/docs/ref/#autocomplete.Completion - return { - from: currentNode.from, - options: options, - // Docs: "regular expression that tells the extension that, as long as the updated input (the range between the result's from property and the completion point) matches that value, it can continue to use the list of completions." - validFor: /^(\w+)?$/ - }; - } - else { - return null; - } -} - -const TypeQLLanguage = language.LRLanguage.define({ - parser: parser.configure({ - props: [ - language.indentNodeProp.add({}), - language.foldNodeProp.add({ - QueryStage: language.foldInside - }), - highlight.styleTags({ - // See: https://lezer.codemirror.net/docs/ref/#highlight.tags - VAR: highlight.tags.variableName, - // Literals - STRINGLITERAL: highlight.tags.string, - INTEGERLITERAL: highlight.tags.number, - DOUBLELITERAL: highlight.tags.number, - BOOLEANLITERAL: highlight.tags.bool, - // Types - LABEL: highlight.tags.typeName, - BOOLEAN: highlight.tags.typeName, - INTEGER: highlight.tags.typeName, - DOUBLE: highlight.tags.typeName, - DECIMAL: highlight.tags.typeName, - DATETIMETZ: highlight.tags.typeName, - DATETIME: highlight.tags.typeName, - DATE: highlight.tags.typeName, - DURATION: highlight.tags.typeName, - STRING: highlight.tags.typeName, - // Keywords - ISA: highlight.tags.keyword, - HAS: highlight.tags.keyword, - LINKS: highlight.tags.keyword, - OWNS: highlight.tags.keyword, - RELATES: highlight.tags.keyword, - PLAYS: highlight.tags.keyword, - FUN: highlight.tags.keyword, - LET: highlight.tags.keyword, - FIRST: highlight.tags.keyword, - LAST: highlight.tags.keyword, - // Value type names? - // Stages - DEFINE: highlight.tags.heading1, - UNDEFINE: highlight.tags.heading1, - REDEFINE: highlight.tags.heading1, - MATCH: highlight.tags.heading1, - INSERT: highlight.tags.heading1, - DELETE: highlight.tags.heading1, - UPDATE: highlight.tags.heading1, - PUT: highlight.tags.heading1, - END: highlight.tags.heading1, - SELECT: highlight.tags.heading1, - REDUCE: highlight.tags.heading1, - SORT: highlight.tags.heading1, - OFFSET: highlight.tags.heading1, - LIMIT: highlight.tags.heading1, - REQUIRE: highlight.tags.heading1, - DISTINCT: highlight.tags.heading1, - GROUPBY: highlight.tags.heading1, - // SubPattern - OR: highlight.tags.controlOperator, - NOT: highlight.tags.controlOperator, - TRY: highlight.tags.controlOperator, - // Misc - Annotation: highlight.tags.meta, - LINECOMMENT: highlight.tags.lineComment, - }) - ] - }), - languageData: { - commentTokens: { line: "#" } - } -}); -function TypeQL() { - return new language.LanguageSupport(TypeQLLanguage, [ - TypeQLLanguage.data.of({ - autocomplete: autocompleteTypeQL - }), - ]); -} -// A Linter which flags syntax errors from: https://discuss.codemirror.net/t/showing-syntax-errors/3111/6 -function otherExampleLinter() { - return lint.linter((view) => { - const diagnostics = []; - language.syntaxTree(view.state).iterate({ - enter: n => { - if (n.type.isError) { - diagnostics.push({ - from: n.from, - to: n.to, - severity: "error", - message: "Syntax error.", - }); - } - }, - }); - return diagnostics; - }); -} - -exports.TypeQL = TypeQL; -exports.TypeQLLanguage = TypeQLLanguage; -exports.otherExampleLinter = otherExampleLinter; diff --git a/src/framework/codemirror-lang-typeql/index.d.cts b/src/framework/codemirror-lang-typeql/index.d.cts deleted file mode 100644 index 2bf6cd430..000000000 --- a/src/framework/codemirror-lang-typeql/index.d.cts +++ /dev/null @@ -1,5 +0,0 @@ -import { LRLanguage, LanguageSupport } from "@codemirror/language"; -declare const TypeQLLanguage: LRLanguage; -declare function TypeQL(): LanguageSupport; -declare function otherExampleLinter(): import("@codemirror/state").Extension; -export { TypeQLLanguage, TypeQL, otherExampleLinter }; diff --git a/src/framework/codemirror-lang-typeql/index.d.ts b/src/framework/codemirror-lang-typeql/index.d.ts deleted file mode 100644 index 2bf6cd430..000000000 --- a/src/framework/codemirror-lang-typeql/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { LRLanguage, LanguageSupport } from "@codemirror/language"; -declare const TypeQLLanguage: LRLanguage; -declare function TypeQL(): LanguageSupport; -declare function otherExampleLinter(): import("@codemirror/state").Extension; -export { TypeQLLanguage, TypeQL, otherExampleLinter }; diff --git a/src/framework/codemirror-lang-typeql/index.js b/src/framework/codemirror-lang-typeql/index.js deleted file mode 100644 index 54739ba37..000000000 --- a/src/framework/codemirror-lang-typeql/index.js +++ /dev/null @@ -1,192 +0,0 @@ -import { LRParser } from '@lezer/lr'; -import { syntaxTree, LRLanguage, indentNodeProp, foldNodeProp, foldInside, LanguageSupport } from '@codemirror/language'; -import { styleTags, tags } from '@lezer/highlight'; -import { linter } from '@codemirror/lint'; - -// This file was generated by lezer-generator. You probably shouldn't edit it. -const parser = LRParser.deserialize({ - version: 14, - states: "!+xOYQPOOO!ZQQO'#EPO!rQPO'#FiO!rQPO'#FkO!rQPO'#FmO!wQPO'#FoO#VQPO'#FtO#[QPO'#FwO#aQPO'#F{O#fQPO'#F}O#kQPO'#GPO#VQPO'#GRO#pQPO'#GTOOQO'#Fs'#FsOOQO'#EO'#EOOOQO'#H['#H[O$mQPO'#HSO$wQQO'#C`O%VQPO'#GpO&dQQO'#G|OOQO'#C_'#C_OOQO'#HR'#HROOQO'#C^'#C^Q&oQPOOO&tQPO'#EUOOQO'#El'#ElO'[QPO'#ElO(ZQPO'#EkO(bQQO'#EkOOQO'#ET'#ETO!ZQQO'#FcO(jQPO'#FdO(oQPO'#FfOOQO'#ES'#ESO(tQPO'#H]O(yQQO'#EROOQO,5:k,5:kO&yQPO'#EwO*PQPO'#HeO*UQPO,5P,5>POOQO-E;c-E;cO8_QPO'#ElO8mQPO'#EtO8pQPO'#F`O8uQPO,5;yO8}QPO,5<]O9SQSO,5<]OOQO,5>Q,5>QOOQO-E;d-E;dO9XQPO,5fQPO,5:UO>pQPO,5:UO>wQQO,5=dO>|QQO,5=eO?RQPO,5=fOOQO,5=g,5=gOOQO,5=_,5=_OOQO,5>W,5>WOOQO-E;j-E;jO?WQPO,5=lO?`QPO,5=lOOQO,5>X,5>XOOQO-E;k-E;kQYQPO,5>YOOQO-E;l-E;lOOQO,5:t,5:tO?gQSO,5:sO?rQSO,5:sO?}QWO1G0[O@fQWO,5;fOOQO,5;r,5;rOAYQPO,5;uOAqQPO,5;uOOQO,5;w,5;wOBSQPO,5;dOBeQPO,5;dOOQO1G0}1G0}OOQO,5;Z,5;ZOOQO,5;[,5;[OOQO,5;],5;]OBmQPO,5;_OOQO,5;_,5;_ODfQPO,5;aODpQPO,5;aOOQO,5;b,5;bOOQO,5;X,5;XODwQPO1G0qOEOQPO1G0qOEOQPO1G0qOEWQPO1G1iOE`QPO1G1jOEeQPO1G1lOEjQPO,5;`OEoQPO,5;zOEtQQO1G1eOFPQPO1G1eOOQO1G1e1G1eOOQO1G1w1G1wOFXQPO1G1wOOQO,5>R,5>ROF^QPO1G1|OOQO-E;e-E;eOOQO,5>S,5>SOOQO-E;f-E;fOOQO7+'i7+'iOFiQPO'#GWOFwQPO'#GWOOQO1G2]1G2]OOQO,5>T,5>TOOQO-E;g-E;gOOQO7+'u7+'uO#VQPO7+'uOF|QPO7+'uOGRQPO,59TOGWQPO,59TOGiQPO,59TO2^QQO,59TOGnQPO,59TOOQO-E;T-E;TOOQO,59m,59mOG|QPO1G.jOHbQPO1G.jOHbQPO1G.jOHjQPO1G.jOHqQPO,5:ZO#uQPO1G/sO2^QQO1G2pOHyQPO,5:POIOQQO1G/pOITQQO1G/pOIYQPO1G3OOOQO1G3P1G3POIaQQO1G3QOIfQPO1G3WOImQPO1G3WOImQPO1G3WOOQO1G3t1G3tOOQO,5=x,5=xOIuQSO1G0_OOQO-E;[-E;[OOQO'#Cy'#CyO?}QWO'#E`OOQO'#Eb'#EbOJQQPO'#EaOOQO'#E_'#E_OJkQQO'#E^OOQO7+%v7+%vOOQO'#E}'#E}O?}QWO'#E|OOQO1G1Q1G1QOOQO1G1a1G1aOOQO,5=},5=}OJxQPO1G1OOOQO-E;a-E;aO(bQQO1G0{O(bQQO1G0{OOQO,5={,5={OKZQPO7+&]OOQO-E;_-E;_OKbQPO7+&]OKjQPO'#HdOKoQPO7+'ROOQO7+'U7+'UOOQO7+'W7+'WOOQO1G0z1G0zOOQO1G1f1G1fOOQO,5=|,5=|OOQO7+'P7+'POKwQQO7+'POOQO-E;`-E;`OOQO7+'c7+'cPLSQPO'#HgOLXQPO,5OOOQO-E;b-E;bOOQO<V,5>VO!&nQQO1G2rOOQO-E;i-E;iO!&vQWO,5:}O!&}QPO,5:}OOQO7+&R7+&ROOQO7+)U7+)UOOQOAN=aAN=aPGnQPO'#HWO!'VQPO,5:aPHtQPO'#HYO!'[QPO,5:eO!'dQQO,5:hO!'nQPO,5:hOOQOAN>gAN>gO!'vQPO,5=OOOQO,5=P,5=PO!'{QPO,5=TO!(SQPO,5=TOOQO,5=Z,5=ZP2^QQO'#HkOOQO,5=y,5=yO!([QWO1G0iOOQO-E;]-E;]OOQO1G/{1G/{O!(cQQO1G0PO!(mQPO1G0POOQO1G0P1G0POOQO,5=u,5=uO!(uQQO1G0SOOQO-E;X-E;XOOQO1G2j1G2jOOQO,5>U,5>UO!)PQPO1G2oOOQO-E;h-E;hP?}QWO'#H_OOQO7+%k7+%kO!)WQQO7+%kP!#OQQO'#HZP9wQPO'#HjOOQO<T#V#WB[#W#XH}#X#Y!7Z#Y#Z!:|#Z#[!Cn#[#]!Gx#]#^!Ig#^#`5^#`#a#$u#a#b#1`#b#c#:k#c#d#<]#d#e#Bh#e#f5^#f#g#Fq#g#h$)t#h#i$7P#i#j$8w#j#k$@v#k#o5^#o#p$Cq#q#r$Cv~$pS&e~XY$kYZ$k]^$kpq$k~%PP!_!`%S~%XO#s~~%[VOr%Xrs%qs#O%X#O#P%v#P;'S%X;'S;=`&o<%lO%X~%vOo~~%yRO;'S%X;'S;=`&S;=`O%X~&VWOr%Xrs%qs#O%X#O#P%v#P;'S%X;'S;=`&o;=`<%l%X<%lO%X~&rP;=`<%l%X~&zS&f~OY&uZ;'S&u;'S;=`'W<%lO&u~'ZP;=`<%l&u~'aT}!O'p!Q!['p!c!}'p#R#S'p#T#o'p~'uT!f~}!O'p!Q!['p!c!}'p#R#S'p#T#o'p~(ZO#[~~(`Oe~~(eOi~~(jO#Y~~(oO#]~~(tOZ~Z(yQ#^Q!Q![)P!`!a)lX)WQpXgX!O!P)^!Q![)PX)aP!Q![)dX)iPpX!Q![)dP)qO!kP~)tP!O!P)w~)|Oh~~*RO#Z~Z*YRpXgX!O!P)^!Q![)P#l#m*cQ*fR!Q![*o!c!i*o#T#Z*oQ*tR#|Q!Q![*o!c!i*o#T#Z*o~+SO!g~~+XO$[~~+^P#v~!_!`+a~+fO#w~]+kP#OT!_!`+nW+sO#rW~+xP#t~!_!`+{~,QO#u~~,VO!}~~,YX#T#U,u#V#W-v#W#X/P#]#^0Q#_#`1e#f#g1v#g#h3P#i#j3t#j#k4i~,xP#U#V,{~-OP#g#h-R~-UP#h#i-X~-[P#f#g-_~-bP#T#U-e~-hP#V#W-k~-nP#h#i-q~-vO^~~-yP#T#U-|~.PQ#f#g.V#g#h.b~.YP#W#X.]~.bOd~~.eP#V#W.h~.kP#T#U.n~.qP#W#X.t~.wP#X#Y.z~/PO_~~/SP#]#^/V~/YP#g#h/]~/`P#h#i/c~/fP#]#^/i~/lP#b#c/o~/rP#V#W/u~/xP#h#i/{~0QO`~~0TP#b#c0W~0ZP#W#X0^~0aP#X#Y0d~0gP#d#e0j~0mP#X#Y0p~0sP#b#c0v~0yP#W#X0|~1PP#X#Y1S~1VP#b#c1Y~1]P#h#i1`~1eOa~~1hP#X#Y1k~1nP#m#n1q~1vOb~~1yQ#T#U2P#X#Y2h~2SP#b#c2V~2YP#Z#[2]~2`P#X#Y2c~2hOj~~2kP#Z#[2n~2qP#X#Y2t~2wP#l#m2z~3POq~~3SP#i#j3V~3YP#U#V3]~3`P#_#`3c~3fP#X#Y3i~3lP#m#n3o~3tOr~~3wP#b#c3z~3}P#]#^4Q~4TP#e#f4W~4ZP#i#j4^~4aP#X#Y4d~4iOc~~4lP#T#U4o~4rP#`#a4u~4xP#i#j4{~5OP#X#Y5R~5UP#g#h5X~5^Ot~Y5aT}!O5p!Q![5p!c!}5p#R#S5p#T#o5pY5uUYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pY6[T}!O6k!Q![6k!c!}6k#R#S6k#T#o6kY6pTYY}!O6k!Q![6k!c!}6k#R#S6k#T#o6k~7UO!Y~~7ZO!Z~~7`O#X~~7cW}!O5p!Q![5p!c!}5p#R#S5p#T#g5p#g#h7{#h#i9V#i#o5p_8SW!`PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W8l#W#o5p^8sU$nSYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~9[WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i9t#i#o5p~9yWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g:c#g#o5p~:hWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^;Q#^#o5p~;VWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V;o#V#o5p~;tWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j<^#j#o5p~WV}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#d>m#d#o5p~>rWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d?[#d#o5p~?aWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a?y#a#o5p~@OWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y@h#Y#o5p~@mVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#UAS#U#o5p~AXWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cAq#c#o5p~AxU|~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~B_V}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#dBt#d#o5p~ByYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cCi#c#i5p#i#jGW#j#o5p~CnWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iDW#i#o5p~D]VYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#UDr#U#o5p~DwWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^Ea#^#o5p~EfWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cFO#c#o5p~FTWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#hFm#h#o5p~FtU#y~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZG]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#cGu#c#o5pZGzWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iHd#i#o5pZHkU${PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~IQ^}!O5p!Q![5p!c!}5p#R#S5p#T#UI|#U#X5p#X#Y!!R#Y#]5p#]#^!+k#^#c5p#c#d!/z#d#i5p#i#j!2}#j#o5p~JRWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iJk#i#o5p~JpWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#YKY#Y#o5p~KaW!S~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iKy#i#o5p~LOWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^Lh#^#o5p~LmWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#bMV#b#o5p~M[WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#YMt#Y#o5p~M{U!R~YY}!ON_!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~NdWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#iN|#i#o5p~! RVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#n5p#n#o! h~! oU!Q~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!!W^YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W!#S#W#Y5p#Y#Z!&S#Z#`5p#`#a!(h#a#g5p#g#h!*|#h#o5p~!#XWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!#q#^#o5p~!#vWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#b!$`#b#o5p~!$eVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U!$z#U#o5p~!%PWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!%i#a#o5p~!%pU!P~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!&XWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!&q#^#o5pZ!&vWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!'`#c#o5pZ!'eWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!'}#Y#o5pZ!(UUTPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!(mWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!)V#Y#o5p~!)[WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!)t#i#o5p~!)yWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!*c#Y#o5p~!*jU$d~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p^!+RWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W8l#W#o5p~!+pWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!,Y#h#o5p~!,_WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!,w#i#o5p~!,|WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!-f#^#o5p~!-kWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!.T#c#o5p~!.YWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W!.r#W#o5p~!.wWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!/a#i#o5p~!/hU$p~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!0PWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!0i#j#o5p~!0nWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V!1W#V#o5p~!1]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!1u#a#o5p~!1zWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!2d#Y#o5p~!2kU!O~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!3SWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!3l#g#o5p~!3qVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U!4W#U#o5p~!4]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!4u#i#o5p~!4zWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!5d#^#o5p~!5iWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!6R#d#o5p~!6WWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!6p#c#o5p~!6wU!T~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!7^V}!O5p!Q![5p!c!}5p#R#S5p#T#b5p#b#c!7s#c#o5p~!7xYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X!8h#X#h5p#h#i!9R#i#o5p~!8oU%w~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!9WWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^!9p#^#o5p~!9uWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!:_#i#o5p~!:dWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n=j#n#o5p~!;P[}!O5p!Q![5p!c!}5p#R#S5p#T#U!;u#U#]5p#]#^!>Z#^#f5p#f#g!@o#g#i5p#i#j!Bf#j#o5p~!;zWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a!`WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!>x#g#o5pZ!>}WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!?g#h#o5pZ!?lWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!@U#i#o5pZ!@]U%ZPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!@tWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!A^#d#o5pZ!AcWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#a5p#a#b!A{#b#o5pZ!BSU%kPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~!BkWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c!CT#c#o5p~!C[U!b~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!CqV}!O5p!Q![5p!c!}5p#R#S5p#T#f5p#f#g!DW#g#o5pZ!D]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d!Du#d#o5pZ!DzWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!Ed#j#o5pZ!EiWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#d5p#d#e!FR#e#o5pZ!FWWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V!Fp#V#o5pZ!FuWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n!G_#n#o5pZ!GfU%TPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!G{U}!O5p!Q![5p!c!}5p#R#S5p#T#U!H_#U#o5pZ!HdWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!H|#h#o5pZ!ITU$OPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_!IjZ}!O5p!Q![5p!c!}5p#R#S5p#T#]5p#]#^!J]#^#b5p#b#c!Ke#c#g5p#g#h##p#h#o5pZ!JbWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X!Jz#X#o5pZ!KRU#{PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_!KlX#PSYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h!LX#h#i!Nm#i#o5pZ!L^WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y!Lv#Y#o5pZ!L{WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g!Me#g#o5pZ!MjWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i!NS#i#o5pZ!NZU$^RYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ!NrWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y# [#Y#o5pZ# aWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Z5p#Z#[# y#[#o5pZ#!OWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#!h#Y#o5pZ#!mWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g##V#g#o5pZ##^U}RYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ##uVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#$[#U#o5pZ#$cU#oPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#$xY}!O5p!Q![5p!c!}5p#R#S5p#T#U#%h#U#X5p#X#Y#)[#Y#]5p#]#^#*d#^#o5pZ#%mYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V#&]#V#g5p#g#h#(S#h#o5pZ#&bWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#&z#Y#o5pZ#'PWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a#'i#a#o5pZ#'pU#fPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#(XWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#(q#i#o5pZ#(xU%[PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#)aWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#)y#i#o5p~#*QU!y~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#*i]YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#_5p#_#`#+b#`#a5p#a#b#,j#b#c#.a#c#g5p#g#h#0W#h#o5p~#+gWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#,P#Y#o5p~#,WU#x~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#,oWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#-X#^#o5p~#-^WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#-v#i#o5p~#-}U$t~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#.fWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#_5p#_#`#/O#`#o5pZ#/TWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#/m#h#o5pZ#/tU$QPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#0]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#0u#i#o5pZ#0|U%SPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#1cY}!O5p!Q![5p!c!}5p#R#S5p#T#U#2R#U#X5p#X#Y#5W#Y#]5p#]#^#9c#^#o5p~#2WYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#2v#i#l5p#l#m#4m#m#o5p~#2{WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W#3e#W#o5p~#3jWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#[5p#[#]#4S#]#o5p~#4ZU!t~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#4tU$|PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#5]XYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#5x#U#W5p#W#X#7Q#X#o5pZ#5}WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#6g#c#o5pZ#6nU%OPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#7VWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#7o#^#o5pZ#7tVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#8Z#U#o5pZ#8`WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#8x#c#o5pZ#9PU%PPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#9hWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#:Q#c#o5pZ#:XU$}PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#:nV}!O5p!Q![5p!c!}5p#R#S5p#T#c5p#c#d#;T#d#o5p~#;YWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#;r#i#o5p~#;yU$X~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p_#<`Z}!O5p!Q![5p!c!}5p#R#S5p#T#Y5p#Y#Z#=R#Z#f5p#f#g#@W#g#k5p#k#l#@q#l#o5p_#=YW$fSYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z#=r#Z#o5pZ#=wWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#>a#h#o5pZ#>fWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#?O#Y#o5pZ#?TWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#?m#i#o5pZ#?tU$rRYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#@_U$UPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ#@vWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#A`#c#o5pZ#AeWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#A}#h#o5pZ#BUU!WPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#BkX}!O5p!Q![5p!c!}5p#R#S5p#T#`5p#`#a#CW#a#i5p#i#j#Ei#j#o5pZ#C]VYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#Cr#U#o5pZ#CwWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#m5p#m#n#Da#n#o5pZ#DfWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h#EO#h#o5pZ#EVU!]PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#EnWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i#FW#i#o5p~#F_U$`~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#FtV}!O5p!Q![5p!c!}5p#R#S5p#T#X5p#X#Y#GZ#Y#o5p~#G`^YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X#H[#X#`5p#`#a#My#a#e5p#e#f$$]#f#h5p#h#i$'`#i#o5p~#HaYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#IP#Y#i5p#i#j#LS#j#o5pZ#IUWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z#In#Z#o5pZ#IsWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^#J]#^#o5pZ#JbWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c#Jz#c#o5pZ#KPWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#Ki#Y#o5pZ#KpU%qPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#LXWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W#Lq#W#o5p~#LvWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y#M`#Y#o5p~#MgU$x~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~#NOVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U#Ne#U#o5p~#NjWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$ S#i#o5p~$ XYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$ w#Y#]5p#]#^$#P#^#o5pZ$ |WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#g5p#g#h$!f#h#o5pZ$!mU!_PYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$#UWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#c5p#c#d$#n#d#o5p~$#sWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c=j#c#o5p~$$bWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$$z#j#o5p~$%PWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$%i#^#o5p~$%nWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$&W#g#o5p~$&]WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$&u#Y#o5p~$&|U$v~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$'eWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$'}#j#o5p~$(SWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$(l#g#o5p~$(qWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c$)Z#c#o5p~$)bU%V~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$)w[}!O5p!Q![5p!c!}5p#R#S5p#T#X5p#X#Y$*m#Y#c5p#c#d$-p#d#h5p#h#i$/g#i#j$5W#j#o5p~$*rWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a$+[#a#o5p~$+aWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$+y#Y#o5p~$,OWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W$,h#W#o5p~$,mWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$-V#i#o5p~$-^U$i~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$-uWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#f5p#f#g$._#g#o5p~$.dWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$.|#i#o5p~$/TU$l~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$/lYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X$0[#X#f5p#f#g$0u#g#o5pZ$0cU%QPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$0zYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$1j#^#i5p#i#j$3a#j#o5p~$1oWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#b5p#b#c$2X#c#o5p~$2^WYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Z5p#Z#[$2v#[#o5p~$2}U!U~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$3fWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#V5p#V#W$4O#W#o5p~$4TWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$4m#i#o5p~$4tU%_~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$5]YYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U5p#U#V$5{#V#a5p#a#b$6f#b#o5pZ$6SUxPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$6mU%RPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$7SV}!O5p!Q![5p!c!}5p#R#S5p#T#f5p#f#g$7i#g#o5p~$7nYYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j!=R#j#m5p#m#n$8^#n#o5p~$8eU$Z~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$8zX}!O5p!Q![5p!c!}5p#R#S5p#T#b5p#b#c$9g#c#d5p#d#e$=v#e#o5pZ$9lWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#W5p#W#X$:U#X#o5pZ$:ZWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$:s#Y#o5pZ$:xWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#Y5p#Y#Z$;b#Z#o5pZ$;gWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#]5p#]#^$e#X#o5p~$>jVYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#U$?P#U#o5p~$?UWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#h5p#h#i$?n#i#o5p~$?sWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$@]#Y#o5p~$@dU$b~YY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5pZ$@yU}!O5p!Q![5p!c!}5p#R#S5p#T#U$A]#U#o5pZ$AbWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#`5p#`#a$Az#a#o5pZ$BPWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#i5p#i#j$Bi#j#o5pZ$BnWYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#X5p#X#Y$CW#Y#o5pZ$C_UzPYY}!O5p!Q![5p![!]6X!c!}5p#R#S5p#T#o5p~$CvO!n~~$C{O!o~", - tokenizers: [0, 1, 2, 3], - topRules: {"Query":[0,1]}, - tokenPrec: 2502 -}); - -// TODO: We could do better by climbing up the tree using `atNode.parentNode` to predict based on position as well. -// We could also refine the suggestions by creating datastructures based on the declarations in the schema, rather than blindly suggesting every label. -function suggestLabel(label) { - return { - label: label, - type: "label", - apply: label, - info: "label suggestion", - }; -} -function suggestVariable(varName) { - return { - label: varName, - type: "variable", - apply: varName, - info: "variable suggestion", - }; -} -function collectLabelSuggestions(context, tree, atNode) { - // This is a placeholder function. You will need to implement the logic to collect label suggestions. - // For now, we return an empty array. - var options = []; - tree.iterate({ - enter: (other) => { - if (other.name == "LABEL") { - let content = context.state.sliceDoc(other.from, other.to); - options.push(suggestLabel(content)); - } - } - }); - return options; -} -function collectVariableSuggestions(context, tree, atNode) { - var options = []; - tree.iterate({ - enter: (other) => { - if (other.name == "VAR") { - let content = context.state.sliceDoc(other.from, other.to); - options.push(suggestVariable(content)); - } - } - }); - return options; -} -// See: https://codemirror.net/examples/autocompletion/ and maybe the SQL / HTML Example there. -function autocompleteTypeQL(context) { - let tree = syntaxTree(context.state); - let currentNode = tree.resolveInner(context.pos, -1); // https://lezer.codemirror.net/docs/ref/#common.SyntaxNode - // We may have to walk the tree to find the most appropriate node to suggest things based on. - let options = null; - if (currentNode.name == "LABEL") { - options = collectLabelSuggestions(context, tree); - } - else if (currentNode.name == "VAR") { - options = collectVariableSuggestions(context, tree); - } - else ; - if (options != null) { - // And once we figure out, we have to create a list of completion objects - // It may be worth changing the grammar to be able to do this more easily, rather than replicate the original TypeQL grammar. - // https://codemirror.net/docs/ref/#autocomplete.Completion - return { - from: currentNode.from, - options: options, - // Docs: "regular expression that tells the extension that, as long as the updated input (the range between the result's from property and the completion point) matches that value, it can continue to use the list of completions." - validFor: /^(\w+)?$/ - }; - } - else { - return null; - } -} - -const TypeQLLanguage = LRLanguage.define({ - parser: parser.configure({ - props: [ - indentNodeProp.add({}), - foldNodeProp.add({ - QueryStage: foldInside - }), - styleTags({ - // See: https://lezer.codemirror.net/docs/ref/#highlight.tags - VAR: tags.variableName, - // Literals - STRINGLITERAL: tags.string, - INTEGERLITERAL: tags.number, - DOUBLELITERAL: tags.number, - BOOLEANLITERAL: tags.bool, - // Types - LABEL: tags.typeName, - BOOLEAN: tags.typeName, - INTEGER: tags.typeName, - DOUBLE: tags.typeName, - DECIMAL: tags.typeName, - DATETIMETZ: tags.typeName, - DATETIME: tags.typeName, - DATE: tags.typeName, - DURATION: tags.typeName, - STRING: tags.typeName, - // Keywords - ISA: tags.keyword, - HAS: tags.keyword, - LINKS: tags.keyword, - OWNS: tags.keyword, - RELATES: tags.keyword, - PLAYS: tags.keyword, - FUN: tags.keyword, - LET: tags.keyword, - FIRST: tags.keyword, - LAST: tags.keyword, - // Value type names? - // Stages - DEFINE: tags.heading1, - UNDEFINE: tags.heading1, - REDEFINE: tags.heading1, - MATCH: tags.heading1, - INSERT: tags.heading1, - DELETE: tags.heading1, - UPDATE: tags.heading1, - PUT: tags.heading1, - END: tags.heading1, - SELECT: tags.heading1, - REDUCE: tags.heading1, - SORT: tags.heading1, - OFFSET: tags.heading1, - LIMIT: tags.heading1, - REQUIRE: tags.heading1, - DISTINCT: tags.heading1, - GROUPBY: tags.heading1, - // SubPattern - OR: tags.controlOperator, - NOT: tags.controlOperator, - TRY: tags.controlOperator, - // Misc - Annotation: tags.meta, - LINECOMMENT: tags.lineComment, - }) - ] - }), - languageData: { - commentTokens: { line: "#" } - } -}); -function TypeQL() { - return new LanguageSupport(TypeQLLanguage, [ - TypeQLLanguage.data.of({ - autocomplete: autocompleteTypeQL - }), - ]); -} -// A Linter which flags syntax errors from: https://discuss.codemirror.net/t/showing-syntax-errors/3111/6 -function otherExampleLinter() { - return linter((view) => { - const diagnostics = []; - syntaxTree(view.state).iterate({ - enter: n => { - if (n.type.isError) { - diagnostics.push({ - from: n.from, - to: n.to, - severity: "error", - message: "Syntax error.", - }); - } - }, - }); - return diagnostics; - }); -} - -export { TypeQL, TypeQLLanguage, otherExampleLinter }; diff --git a/src/framework/codemirror-lang-typeql/index.ts b/src/framework/codemirror-lang-typeql/index.ts new file mode 100644 index 000000000..3a99878fc --- /dev/null +++ b/src/framework/codemirror-lang-typeql/index.ts @@ -0,0 +1,186 @@ + +import { parser } from "./generated/typeql.grammar.generated" +import { LRLanguage, LanguageSupport, indentNodeProp, foldNodeProp, foldInside, syntaxTree } from "@codemirror/language" +import { styleTags, tags as t } from "@lezer/highlight" +import { Diagnostic } from "@codemirror/lint"; +import { EditorView } from "@codemirror/view"; +import { linter } from '@codemirror/lint' +import { autocompletion, CompletionContext } from "@codemirror/autocomplete"; +import { NodePrefixAutoComplete } from "./complete" +import {TypeQLAutocompleteSchema} from "./typeQLAutocompleteSchema"; +import { SUGGESTION_MAP } from "./typeql_suggestions"; +import {Schema} from "../../service/schema-state.service"; + +export const TypeQLLanguage = LRLanguage.define({ + parser: parser.configure({ + props: [ + indentNodeProp.add({ + + }), + foldNodeProp.add({ + QueryStage: foldInside + }), + styleTags({ + // See: https://lezer.codemirror.net/docs/ref/#highlight.tags + + VAR: t.variableName, + + // Literals + STRINGLITERAL: t.string, + INTEGERLITERAL: t.number, + DOUBLELITERAL: t.number, + BOOLEANLITERAL: t.bool, + + // Types + LABEL: t.typeName, + + BOOLEAN: t.typeName, + INTEGER: t.typeName, + DOUBLE: t.typeName, + DECIMAL: t.typeName, + DATETIMETZ: t.typeName, + DATETIME: t.typeName, + DATE: t.typeName, + DURATION: t.typeName, + STRING: t.typeName, + + + // Keywords + ISA: t.keyword, + HAS: t.keyword, + LINKS: t.keyword, + OWNS: t.keyword, + RELATES: t.keyword, + PLAYS: t.keyword, + + FUN: t.keyword, + LET: t.keyword, + FIRST: t.keyword, + LAST: t.keyword, + + // Value type names? + + // Stages + DEFINE: t.heading1, + UNDEFINE: t.heading1, + REDEFINE: t.heading1, + + MATCH: t.heading1, + INSERT: t.heading1, + DELETE: t.heading1, + UPDATE: t.heading1, + PUT: t.heading1, + END: t.heading1, + + SELECT: t.heading1, + REDUCE: t.heading1, + SORT: t.heading1, + OFFSET: t.heading1, + LIMIT: t.heading1, + REQUIRE: t.heading1, + DISTINCT: t.heading1, + GROUPBY: t.heading1, + + // SubPattern + OR: t.controlOperator, + NOT: t.controlOperator, + TRY: t.controlOperator, + + // Misc + Annotation: t.meta, + LINECOMMENT: t.lineComment, + }) + ] + }), + languageData: { + commentTokens: { line: "#" } + } +}) + + +export function TypeQL() { + return new LanguageSupport(TypeQLLanguage, []) +} + +// A Linter which flags syntax errors from: https://discuss.codemirror.net/t/showing-syntax-errors/3111/6 +export function otherExampleLinter() { + return linter((view: EditorView) => { + const diagnostics: Diagnostic[] = []; + syntaxTree(view.state).iterate({ + enter: n => { + if (n.type.isError) { + diagnostics.push({ + from: n.from, + to: n.to, + severity: "error", + message: "Syntax error.", + }); + } + }, + }); + return diagnostics; + }); +} + +// Autocomplete +let typeqlAutocomplete = new NodePrefixAutoComplete( + SUGGESTION_MAP, + new TypeQLAutocompleteSchema({entities: {}, relations: {}, attributes: {}}, {entities: {}, relations: {}, attributes: {}}) +); + +function wrappedAutocomplete(context: CompletionContext) { + return typeqlAutocomplete.autocomplete(context); +} +export function typeqlAutocompleteExtension() { + return autocompletion({ override: [wrappedAutocomplete] }); +} + +export function updateAutocomleteSchemaFromDB(schema: Schema) { + typeqlAutocomplete.getState().updateFromDB(schema); +} + +// Manually run and collect the following results using _lastQueryAnswers +// match $default-owner owns $default-owned; limit 1; +// match $default-relation relates $default-related; limit 1; +// match $default-player plays $default-played; limit 1; +// match +// { $owner owns $owned; $relation is $default-relation; $related is $default-related; $player is $default-player; $played is $default-played; } or +// { $owner is $default-owner; $owned is $default-owned; $relation relates $related; $player is $default-player; $played is $default-played; } or +// { $owner is $default-owner; $owned is $default-owned; $relation is $default-relation; $related is $default-related; $player plays $played; }; +// and then call _updateSchemaFromDB(_lastQueryAnswers, _lastQueryAnswers, _lastQueryAnswers); +// +// // Or finer queries: +// // # match $owner owns $owned; +// // # match $relation relates $related; +// // # match $player plays $played; +// // And you have to set each argument separately +// +// function updateSchemaFromDB(ownsAnswers: ConceptRowAnswer[], relatesAnswers: ConceptRowAnswer[], playsAnswers: ConceptRowAnswer[]) { +// let builder = new SchemaBuilder(); +// ownsAnswers.forEach((answer) => { +// let data: ConceptRow = answer.data; +// let owner = (data["owner"] as Type).label; +// let owned = (data["owned"] as Type).label; +// builder.objectType(owner); +// builder.attributeType(owned); +// builder.recordOwns(owner, owned); +// }); +// relatesAnswers.forEach((answer) => { +// let data: ConceptRow = answer.data; +// let relation = (data["relation"] as Type).label; +// let related = (data["related"] as Type).label; +// builder.objectType(relation); +// builder.recordRelates(relation, related) +// }); +// playsAnswers.forEach((answer) => { +// let data: ConceptRow = answer.data; +// let player = (data["player"] as Type).label; +// let played = (data["played"] as Type).label; +// builder.objectType(player); +// builder.recordRelates(player, played) +// }) +// typeqlAutocomplete.getState().updateFromDB(builder.build()); +// } + +// (window as any)._updateSchemaFromDB = updateSchemaFromDB; +(window as any)._typeqlAutoComplete = typeqlAutocomplete; \ No newline at end of file diff --git a/src/framework/codemirror-lang-typeql/navigation.ts b/src/framework/codemirror-lang-typeql/navigation.ts new file mode 100644 index 000000000..f723fd6dd --- /dev/null +++ b/src/framework/codemirror-lang-typeql/navigation.ts @@ -0,0 +1,24 @@ +import {SyntaxNode} from "@lezer/common"; + +// path should be token ids +type TokenID = number; +export function nodesWithPath(root: SyntaxNode, path: TokenID[]) : SyntaxNode[] { + return nodesWithPathImpl([root], path, 0); +} + +function nodesWithPathImpl(from: SyntaxNode[], path: TokenID[], index: number) : SyntaxNode[] { + if (index >= path.length) { + return from; + } else { + let next = from.flatMap((node) => node.getChildren(path[index])); + return nodesWithPathImpl(next, path, index + 1) + } +} + +export function climbTill(from: SyntaxNode, till: TokenID): SyntaxNode | null { + let at: SyntaxNode | null = from; + while (at != null && at.type.id != till) { + at = at.parent; + } + return at; +} diff --git a/src/framework/codemirror-lang-typeql/typeQLAutocompleteSchema.ts b/src/framework/codemirror-lang-typeql/typeQLAutocompleteSchema.ts new file mode 100644 index 000000000..0db4eba84 --- /dev/null +++ b/src/framework/codemirror-lang-typeql/typeQLAutocompleteSchema.ts @@ -0,0 +1,213 @@ +import { Tree } from "@lezer/common"; +import { CompletionContext } from "@codemirror/autocomplete"; +import * as tokens from "./generated/typeql.grammar.generated.terms"; +import {nodesWithPath} from "./navigation"; +import { + Schema, + SchemaAttribute, + SchemaRole, + SchemaEntity, + SchemaRelation +} from "../../service/schema-state.service"; +type SchemaObject = SchemaEntity | SchemaRelation; + +type TypeLabel = string; + +function extractText(text: string, from: number, to: number): string { + return text.slice(from, to); +} + +export class TypeQLAutocompleteSchema { + fromDB: Schema; + fromEditor: Schema; // Partial because we don't care about subtypes, supertype or valueType + + constructor(fromDB: Schema, fromEditor: Schema) { + this.fromDB = fromDB; + this.fromEditor = fromEditor; + } + + updateFromDB(fromDB: Schema): void { + this.fromDB = fromDB; + } + + mayUpdateFromEditorState(context: CompletionContext, tree: Tree): void { + this.fromEditor = buildSchemafromTypeQL(context.state.sliceDoc(), tree); + } + + attributeTypes(): SchemaAttribute[] { + return record_values(this.fromDB.attributes).concat(record_values(this.fromEditor.attributes)); + } + + objectTypes(): SchemaObject[] { + return (this.entityTypes() as SchemaObject[]).concat(this.relationTypes() as SchemaObject[]); + } + + entityTypes(): SchemaEntity[] { + return record_values(this.fromDB.entities) + .concat(record_values(this.fromEditor.entities)); + } + + relationTypes(): SchemaRelation[] { + return record_values(this.fromDB.relations).concat(record_values(this.fromEditor.relations)); + } + + attributeType(type: TypeLabel): SchemaAttribute { + return this.fromDB.attributes[type] ?? + this.fromEditor.attributes[type]; + } + + objectType(type: TypeLabel): SchemaObject | null { + return this.entityType(type) ?? this.relationType(type); + } + + entityType(type: TypeLabel): SchemaEntity | null { + return this.fromDB.entities[type] ?? this.fromEditor.entities[type]; + } + + relationType(type: TypeLabel): SchemaRelation { + return this.fromDB.relations[type] ?? this.fromEditor.relations[type]; + } + + getOwns(label: TypeLabel): SchemaAttribute[] { + const objectType = this.objectType(label); + return objectType ? objectType.ownedAttributes : []; + } + + getPlays(label: TypeLabel): SchemaRole[] { + const objectType = this.objectType(label); + return objectType ? objectType.playedRoles : []; + } + getRelates(label: TypeLabel): SchemaRole[] { + const objectType = this.objectType(label); + return objectType ? objectType.playedRoles : []; + } +} + +export class SchemaBuilder { + schema: Schema; + constructor() { + this.schema = { entities: {}, relations:{}, attributes: {} }; + } + + entityType(label: TypeLabel): SchemaEntity { + if (!this.schema.entities[label]) { + this.schema.entities[label] = { + kind: "entityType", label, ownedAttributes: [], playedRoles: [], + subtypes: [], + }; + } + return this.schema.entities[label]; + } + + relationType(label: TypeLabel): SchemaRelation { + if (!this.schema.relations[label]) { + this.schema.relations[label] = { + kind: "relationType", label, ownedAttributes: [], playedRoles: [], relatedRoles: [], + subtypes: [], + }; + } + return this.schema.relations[label]; + } + + attributeType(label: TypeLabel): SchemaAttribute { + if (!this.schema.attributes[label]) { + this.schema.attributes[label] = { + kind: "attributeType", label, valueType: "string", + subtypes: [], + }; + } + return this.schema.attributes[label]; + } + + getObjectType(label: TypeLabel): SchemaObject | null{ + return this.schema.entities[label] ?? this.schema.relations[label]; + } + + recordOwns(type: TypeLabel, ownedType: TypeLabel): void { + const objectType = this.getObjectType(type)!; + const attributeType = this.attributeType(ownedType); + if (!objectType.ownedAttributes.includes(attributeType)) { + objectType.ownedAttributes.push(attributeType); + } + } + recordPlays(type: TypeLabel, playedType: TypeLabel): void { + const objectType = this.getObjectType(type)!; + let roleType: SchemaRole = { kind: "roleType", label: playedType }; + if (!objectType.playedRoles.includes(roleType)) { + objectType.playedRoles.push(roleType); + } + } + + recordRelates(type: TypeLabel, relatedType: TypeLabel): void { + const objectType = this.relationType(type); + let roleType: SchemaRole = { kind: "roleType", label: relatedType }; + if (!objectType.relatedRoles.includes(roleType)) { + objectType.relatedRoles.push(roleType); + } + } + + build(): Schema { + return this.schema; + } +} + + + +function buildSchemafromTypeQL(text: string, tree: Tree) : Schema { + let builder = new SchemaBuilder(); + // Extract all type declarations from the tree + let root = tree.topNode; + let definitionTypes = nodesWithPath(root, [tokens.QuerySchema, tokens.QueryDefine, tokens.Definables, tokens.Definable, tokens.DefinitionType]) + definitionTypes.forEach(node => { + let kindNode = node.getChild(tokens.KIND); + let labelNode = node.getChild(tokens.LABEL); + if (kindNode != null && labelNode != null) { + let kind = extractText(text, kindNode.from, kindNode.to); + let label = extractText(text, labelNode.from, labelNode.to); + switch (kind) { + case "entity": { + builder.entityType(label); + break; + } + case "relation": { + builder.relationType(label); + break; + } + case "attribute": { + builder.attributeType(label); + break; + } + } + } + }) + + + // Extract owns/relates/plays. Idk what to do with sub or annotations. + definitionTypes.forEach(node => { + let labelNode = node.getChild(tokens.LABEL); + if (labelNode == null) return; + let label = extractText(text, labelNode.from, labelNode.to); + nodesWithPath(node, [tokens.TypeCapability, tokens.TypeCapabilityBase]) + .map(typeCapabilityBaseNode => typeCapabilityBaseNode.firstChild) + .forEach(actualCapabilityNode => { + switch (actualCapabilityNode?.type.id) { + // We actually only want type-declarations for now. + case tokens.RelatesDeclaration: { + let roleTypeNode = actualCapabilityNode.firstChild!.nextSibling!; + let roleType = extractText(text, roleTypeNode.from, roleTypeNode.to); + builder.recordRelates(label, `${label}:${roleType}`); + break; + } + default: { + // Ignore other capabilities for now + break; + } + } + }); + }); + return builder.build(); +} + +function record_values(records: Record): Y[] { + return Object.entries(records).map(([_, value]) => value); +} diff --git a/src/framework/codemirror-lang-typeql/typeql.grammar b/src/framework/codemirror-lang-typeql/typeql.grammar new file mode 100644 index 000000000..3dc8c9a83 --- /dev/null +++ b/src/framework/codemirror-lang-typeql/typeql.grammar @@ -0,0 +1,778 @@ +// TypeQL grammar for lezer. run `pnpm run generate-grammar` to generate the parser and propagate these changes to typescript. + +// TODO: This is a direct translation of the original pest grammar (which is PEG iirc) +// It may be better to rewrite as much as possible so that it's better suited to the LR parser. +@top Query { QuerySchema | QueryPipelinePreambled (END SEMICOLON)? } +@skip { whitespace | linecomment } + +// Schema +QuerySchema { + QueryDefine | + QueryUndefine | + QueryRedefine +} + +QueryDefine { + DEFINE Definables +} + +QueryUndefine { + UNDEFINE Undefinables +} + +QueryRedefine { + REDEFINE Redefinables +} + +Definables { + (Definable SEMICOLON)+ +} + +Definable { + DefinitionType | + DefinitionFunction | + DefinitionStruct +} + +Undefinables { + (Undefinable SEMICOLON)+ +} + +Undefinable { + UndefineFrom | + // UndefineFunction | + UndefineStruct | + KIND LABEL +} + +Redefinables { + (Redefinable SEMICOLON)+ +} + +Redefinable { + RedefinableType | DefinitionFunction +} + +RedefinableType { + KIND? LABEL ((Annotations | TypeCapability) (COMMA TypeCapability)* COMMA?)? +} + +DefinitionType { + KIND? LABEL COMMA? ((Annotations | TypeCapability) (COMMA TypeCapability)* COMMA?)? +} + +TypeCapability { + TypeCapabilityBase Annotations? +} + +TypeCapabilityBase { + SubDeclaration | + ValueTypeDeclaration | + // AliasDeclaration | + OwnsDeclaration | + PlaysDeclaration | + RelatesDeclaration +} + +SubDeclaration { + SUB LABEL +} + +ValueTypeDeclaration { + VALUEKEYWORD ValueType +} + +// AliasDeclaration { +// ALIAS LABEL +// } + +OwnsDeclaration { + OWNS LABEL | + OWNS LabelList +} + +PlaysDeclaration { + PLAYS LABEL // | PLAYS LabelScoped +} + +RelatesDeclaration { + RELATES LABEL (AS LABEL)? | + RELATES LabelList (AS LabelList)? +} + + +DefinitionStruct { + STRUCT IDENTIFIER COLON DefinitionStructFields +} + +DefinitionStructFields { + DefinitionStructField (COMMA DefinitionStructField)* COMMA? +} + +DefinitionStructField { + IDENTIFIER VALUEKEYWORD StructFieldValueType +} + +StructFieldValueType { + ValueTypeOptional | ValueType +} + +ValueTypeOptional { + ValueType QUESTIONMARK +} + + +UndefineFrom { + UndefineAnnotationFromCapability | + UndefineAnnotationFromType | + UndefineCapability | + UndefineSpecialise +} + +UndefineAnnotationFromCapability { + AnnotationCategory FROM LABEL TypeCapabilityBase +} + +UndefineAnnotationFromType { + AnnotationCategory FROM LABEL +} + +UndefineCapability { + TypeCapabilityBase FROM LABEL +} + +UndefineSpecialise { + AS LABEL FROM LABEL RelatesDeclaration +} + +// UndefineFunction { +// FUN IDENTIFIER +// } + +UndefineStruct { + STRUCT IDENTIFIER +} + +LabelList { LABEL SQBRACKETOPEN SQBRACKETCLOSE } +NamedTypeAny { NamedTypeList | NamedType } +NamedTypeList { NamedType SQBRACKETOPEN SQBRACKETCLOSE } +NamedType { LABEL | ValueType } + +// Annotations +AnnotationCategory { + ANNOTATION_ABSTRACT + | ANNOTATION_CARD + | ANNOTATION_CASCADE + | ANNOTATION_DISTINCT + | ANNOTATION_INDEPENDENT + | ANNOTATION_KEY + | ANNOTATION_RANGE + | ANNOTATION_REGEX + | ANNOTATION_SUBKEY + | ANNOTATION_UNIQUE + | ANNOTATION_VALUES +} + + +Annotations { + Annotation+ +} + +Annotation { + ANNOTATION_ABSTRACT | + ANNOTATION_CASCADE | + ANNOTATION_DISTINCT | + ANNOTATION_INDEPENDENT | + ANNOTATION_KEY | + ANNOTATION_UNIQUE | + ANNOTATION_CARD PARENOPEN Cardinality PARENCLOSE | + ANNOTATION_RANGE PARENOPEN Range PARENCLOSE | + ANNOTATION_REGEX PARENOPEN STRINGLITERAL PARENCLOSE | + ANNOTATION_SUBKEY PARENOPEN IDENTIFIER PARENCLOSE | + ANNOTATION_VALUES PARENOPEN ValueLiteral (COMMA ValueLiteral)* COMMA? PARENCLOSE +} + +Cardinality { + INTEGERLITERAL DOUBLE_DOT INTEGERLITERAL? | + INTEGERLITERAL +} + +Range { + RangeBound DOUBLE_DOT RangeBound | + RangeBound DOUBLE_DOT | + DOUBLE_DOT RangeBound +} + +RangeBound { + ValueLiteral +} + + +// Pipelines +QueryPipelinePreambled { + Pipeline +// Preamble* Pipeline +} + +//Pipeline { +// QueryStage+ QueryStageTerminal? +//} + + +//Preamble { +// WITH DefinitionFunction +//} + +Pipeline { + QueryStage+ +} +QueryStage { + ClauseMatch | + ClauseInsert | + ClausePut | + ClauseUpdate | + ClauseDelete | + OperatorStream +} + +//QueryStageTerminal { +// ClauseFetch SEMICOLON +//} + + +// +// Stages +ClauseMatch { + MATCH Patterns +} + + +ClauseInsert { + INSERT (StatementThing SEMICOLON)+ +// INSERT (StatementThing SEMICOLON | StatementAssignment SEMICOLON)+ +} + +ClausePut { + PUT (StatementThing SEMICOLON)+ +} + +ClauseUpdate { + UPDATE (StatementThing SEMICOLON)+ +} + +ClauseDelete { + DELETE (StatementDeletable SEMICOLON)+ +} + +OperatorStream { + OperatorSelect | + OperatorSort | + OperatorDistinct | + OperatorOffset | + OperatorLimit | + OperatorRequire | + OperatorReduce +} + +OperatorSelect { + SELECT Vars SEMICOLON +} + +OperatorSort { + SORT VAROrder (COMMA VAROrder)* SEMICOLON +} + +OperatorOffset { + OFFSET INTEGERLITERAL SEMICOLON +} + +OperatorLimit { + LIMIT INTEGERLITERAL SEMICOLON +} + +OperatorRequire { + REQUIRE Vars SEMICOLON +} + +OperatorDistinct { + DISTINCT SEMICOLON +} + +OperatorReduce { + REDUCE ReduceAssign (COMMA ReduceAssign)* (GROUPBY Vars)? SEMICOLON +} + +// Stage arguments +Vars { + VAR (COMMA VAR)* COMMA? +} + +VAROrder { + VAR ORDER? +} + +ReduceAssign { + (VAR ASSIGN Reducer) +} + +Reducer { + COUNT (PARENOPEN VAR PARENCLOSE)? | + MAX PARENOPEN VAR PARENCLOSE | + MIN PARENOPEN VAR PARENCLOSE | + MEAN PARENOPEN VAR PARENCLOSE | + MEDIAN PARENOPEN VAR PARENCLOSE | + STD PARENOPEN VAR PARENCLOSE | + SUM PARENOPEN VAR PARENCLOSE | + LIST PARENOPEN VAR PARENCLOSE +} + +// Patterns +Patterns { + (Pattern SEMICOLON)+ +} + +Pattern { + Statement | + PatternDisjunction | + PatternConjunction | + PatternNegation | + PatternTry +} + +PatternConjunction { + CURLYOPEN Patterns CURLYCLOSE +} + +PatternDisjunction { + CURLYOPEN Patterns CURLYCLOSE (OR CURLYOPEN Patterns CURLYCLOSE)+ +} + +PatternNegation { + NOT CURLYOPEN Patterns CURLYCLOSE +} + +PatternTry { + TRY CURLYOPEN Patterns CURLYCLOSE +} + +StatementDeletable { + HAS? VAR OF VAR | + LINKS? Relation OF VAR | + VAR +} + +StatementType { + KIND? TypeRef (TypeConstraint (COMMA TypeConstraint)* COMMA?)? +} + +TypeConstraint { + TypeConstraintBase Annotations? +} + +TypeConstraintBase { + SubConstraint | + ValueTypeConstraint | + LabelConstraint | + OwnsConstraint | + RelatesConstraint | + PlaysConstraint +} + +SubConstraint { + SUB TypeRef +} + +ValueTypeConstraint { + VALUEKEYWORD ValueType +} + +LabelConstraint { + LABELKEYWORD LABEL // (LabelScoped | LABEL) +} + + +OwnsConstraint { + OWNS TypeRef | + OWNS TypeRefList +} + +RelatesConstraint { + RELATES TypeRef (AS TypeRef)? | + RELATES TypeRefList (AS TypeRefList)? +} + +PlaysConstraint { + PLAYS TypeRef +} + +Statement { + StatementAssignment | + // StatementSingle | + StatementType | + StatementThing +} + + +StatementThing { + VAR COMMA? ThingConstraintList + // ThingRelationAnonymous (COMMA? ThingConstraintList)? +} + +// ThingRelationAnonymous { +// TypeRef? Relation +// } + +ThingConstraintList { + ThingConstraint (COMMA ThingConstraint)* COMMA? +} + +ThingConstraint { + IsaConstraint | + IidConstraint | + HasConstraint | + LinksConstraint +} + +IsaConstraint { + ISA TypeRef (Expression | Comparison)? // | ExpressionStruct // | ValueLiteral is subsumed by Expression // | Relation is ambiguous with paranthesis expressions +} + +IidConstraint { + IID IID_VALUE +} + +HasConstraint { + HAS TypeRef VAR | + HAS TypeRef ValueLiteral | + HAS VAR +} + +LinksConstraint { + LINKS Relation +} + +Relation { + PARENOPEN RolePlayer (COMMA RolePlayer)* COMMA? PARENCLOSE +} + +RolePlayer { + TypeRefList COLON VAR | + TypeRef COLON VAR | + VAR +} + +TypeRefList { + TypeRef SQBRACKETOPEN SQBRACKETCLOSE +} + +TypeRef { + // LabelScoped | + LABEL | + VAR +} + +// LabelScoped { +// LABEL COLON LABEL +// } + + +ValueLiteral { + BOOLEANLITERAL | + STRINGLITERAL | + INTEGERLITERAL | + DOUBLELITERAL + // TODO +} + +ValueType { + BOOLEAN | + INTEGER | + DOUBLE | + DECIMAL | + DATETIMETZ | + DATETIME | + DATE | + DURATION | + STRING +} + +Comparison { + ComparisonOperator Expression +} + +ComparisonOperator { + EQUAL | NOT_EQUAL | GREATER | GREATER_EQUAL | LESS | LESS_EQUAL | LIKE | CONTAINS +} + +// Function +IDENTIFIER { LABEL } // hack + +DefinitionFunction { + FUN FunctionSignature COLON FunctionBlock +} + +FunctionSignature { + IDENTIFIER PARENOPEN FunctionArguments? PARENCLOSE ARROW FunctionOutput +} + + +FunctionArguments { + FunctionArgument (COMMA FunctionArgument)* COMMA? +} + +FunctionArgument { + VAR COLON NamedTypeAny +} + +FunctionOutput { + FunctionOutputStream | + FunctionOutputSingle +} + +FunctionOutputStream { + CURLYOPEN NamedTypeAny (COMMA NamedTypeAny)* COMMA? CURLYCLOSE +} + +FunctionOutputSingle { + NamedTypeAny (COMMA NamedTypeAny)* COMMA? +} + +FunctionBlock { + QueryStage+ ReturnStatement +} + +ReturnStatement { + RETURN (ReturnStream | ReturnSingle | ReturnReduce) +} + +ReturnStream { + CURLYOPEN Vars CURLYCLOSE +} + +ReturnSingle { + ReturnSingleSelector Vars +} + +ReturnSingleSelector { + FIRST | LAST +} + +ReturnReduce { + Reducer (COMMA Reducer)* COMMA? +} + +// Assignment +StatementAssignment { + LET AssignmentLeft (ASSIGN | IN) Expression +} + +AssignmentLeft { + VarsAssignment // | StructDestructor +} + +VarsAssignment { + VarAssignment (COMMA VarAssignment)* COMMA? +} + +VarAssignment { + VAR QUESTIONMARK? +} + +// StructDestructor { +// CURLYOPEN StructKey COLON StructDestructorValue CURLYCLOSE +// } + +// // StructDestructorValue { +// // VAR | StructDestructor +// // } + +// Expression +Expression { + ExpressionValue (ExpressionOperator ExpressionValue)* +} + +ExpressionValue { + // ExpressionListIndex | + ExpressionParenthesis | + FunctionCall | + ValueLiteral | + VAR +} + +FunctionCall { + FunctionName PARENOPEN FunctionCallArguments? PARENCLOSE +} + +FunctionName { + IDENTIFIER +} + +FunctionCallArguments { + Expression (COMMA Expression)* COMMA? +} +ExpressionOperator { + POWER | TIMES | DIVIDE | MODULO | PLUS | MINUS +} + +ExpressionParenthesis { + PARENOPEN Expression PARENCLOSE +} + +// ExpressionListIndex { +// VAR SQBRACKETOPEN Expression SQBRACKETCLOSE +// } + + +@tokens { + VAR { DOLLAR $[a-zA-Z_\-0-9]+ } + // IDENTIFIER { $[a-zA-Z_\-0-9]+ } // Consider splitting this into Label and LabelScoped + LABEL { $[a-zA-Z] $[a-zA-Z_\-0-9]+ (':' $[a-zA-Z_\-0-9]+)? } // Consider splitting this into Label and LabelScoped + DOLLAR { "$" } + + DISTINCT { "distinct" } + // + DEFINE { "define" } + UNDEFINE { "undefine" } + REDEFINE { "redefine" } + + ANNOTATION_ABSTRACT { "@abstract" } + ANNOTATION_CASCADE { "@cascade" } + ANNOTATION_DISTINCT { "@distinct" } + ANNOTATION_INDEPENDENT { "@independent" } + ANNOTATION_KEY { "@key" } + ANNOTATION_UNIQUE { "@unique" } + ANNOTATION_CARD { "@card" } + ANNOTATION_RANGE { "@range" } + ANNOTATION_REGEX { "@regex" } + ANNOTATION_SUBKEY { "@subkey" } + ANNOTATION_VALUES { "@values" } + + + // WITH { "with" } + MATCH { "match" } + INSERT { "insert" } + PUT { "put" } + UPDATE { "update" } + DELETE { "delete" } + SELECT { "select" } + SORT { "sort" } + OFFSET { "offset" } + LIMIT { "limit" } + REQUIRE { "require" } + // DISTINCT is already covered + REDUCE { "reduce" } + GROUPBY { "groupby" } + + END { "end" } + + COUNT { "count" } + MAX { "max" } + MIN { "min" } + MEAN { "mean" } + MEDIAN { "median" } + STD { "std" } + SUM { "sum" } + LIST { "list" } + ORDER { ASCENDINGORDER | DESCENDINGORDER } + ASCENDINGORDER { "asc" } + DESCENDINGORDER { "desc" } + + + // Patterns + OR { "or" } + NOT { "not" } + TRY { "try" } + + // Statements + HAS { "has" } + ISA { "isa" } + LINKS { "links" } + IID { "iid" } + + SUB { "sub" } + VALUEKEYWORD { "value" } + LABELKEYWORD { "label" } + OWNS { "owns" } + RELATES { "relates" } + PLAYS { "plays" } + + KIND { "entity" | "relation" | "attribute" } + + OF { "of" } + AS { "as" } + FROM { "from" } + + // ValueTypes + BOOLEAN { "boolean" } + INTEGER { "integer" } + DOUBLE { "double" } + DECIMAL { "decimal" } + DATETIMETZ { "datetime-tz" } + DATETIME { "datetime" } + DATE { "date" } + DURATION { "duration" } + STRING { "string" } + + // Functions & structs + FUN { "fun" } + ARROW { "->" } + STRUCT { "struct" } + RETURN { "return" } + + FIRST { "first" } + LAST { "last" } + + // Punctuation + SEMICOLON { ";" } + COMMA { "," } + COLON { ":" } + PARENOPEN { "(" } + PARENCLOSE { ")" } + CURLYOPEN { "{" } + CURLYCLOSE { "}" } + SQBRACKETOPEN { "[" } + SQBRACKETCLOSE { "]" } + DOUBLE_DOT { ".." } + QUESTIONMARK { "?" } + + // Expression operators + LET { "let" } + ASSIGN { "=" } + IN { "in" } + POWER { "^" } + TIMES { "*" } + DIVIDE { "/" } + MODULO { "%" } + PLUS { "+" } + MINUS { "-" } + + // Comparison operators + EQUAL { "==" } + NOT_EQUAL { "!=" } + GREATER { ">" } + GREATER_EQUAL { ">=" } + LESS { "<" } + LESS_EQUAL { "<=" } + LIKE { "like" } + CONTAINS { "contains" } + + //@precedence { ASCENDINGORDER, DESCENDINGORDER } + @precedence { + END, KIND, STRUCT, LET, FUN, RETURN, + BOOLEAN, INTEGER, STRING, DURATION, DOUBLE, DECIMAL, DATETIME, DATETIMETZ, DATE, + BOOLEANLITERAL, LIKE, CONTAINS, + NOT, TRY, DELETE, DISTINCT, MATCH, INSERT, PUT, UPDATE, SELECT, SORT, OFFSET, LIMIT, REQUIRE, REDUCE, + LABEL } // This causes some weird parses sometimes. + + // Literals + IID_VALUE { "0x" $[0-9a-fA-F]+ } + + BOOLEANLITERAL { "false" | "true" } + STRINGLITERAL { '"' (!["\\] | "\\" _)* '"' } + INTEGERLITERAL { "-"? $[0-9]+ } + DOUBLELITERAL { "-"? $[0-9]+('.' $[0-9]+)? } + @precedence { DOUBLELITERAL, INTEGERLITERAL } + + linecomment { "#" ![\n]* } + whitespace { $[ \t\n\r]+ } +} diff --git a/src/framework/codemirror-lang-typeql/typeql.grammar.d.ts b/src/framework/codemirror-lang-typeql/typeql.grammar.d.ts new file mode 100644 index 000000000..38f49b476 --- /dev/null +++ b/src/framework/codemirror-lang-typeql/typeql.grammar.d.ts @@ -0,0 +1,3 @@ +import {LRParser} from "@lezer/lr" + +export declare const parser: LRParser diff --git a/src/framework/codemirror-lang-typeql/typeql_suggestions.ts b/src/framework/codemirror-lang-typeql/typeql_suggestions.ts new file mode 100644 index 000000000..021d4ffd6 --- /dev/null +++ b/src/framework/codemirror-lang-typeql/typeql_suggestions.ts @@ -0,0 +1,184 @@ +import * as tokens from "./generated/typeql.grammar.generated.terms"; +import { CompletionContext, Completion } from "@codemirror/autocomplete"; +import {SyntaxNode, NodeType, Tree} from "@lezer/common" +import { SuggestionMap, SuffixOfPrefixSuggestion, suggest } from "./complete"; +import { TypeQLAutocompleteSchema } from "./typeQLAutocompleteSchema"; +import {climbTill, nodesWithPath} from "./navigation"; + +function findIsaConstraintLabelsForVar(context: CompletionContext, parseAt: SyntaxNode): string[] { + let parentStatementThing = climbTill(parseAt, tokens.StatementThing)!; + let varNode = parentStatementThing.getChild(tokens.VAR)!; + let varName = context.state.sliceDoc(varNode.from, varNode.to); + let pipelineNode = climbTill(parentStatementThing, tokens.Pipeline)!; + // TODO: Maybe consider disjunctions? + let statements = nodesWithPath(pipelineNode, [tokens.QueryStage, tokens.ClauseMatch, tokens.Patterns, tokens.Pattern, tokens.Statement, tokens.StatementThing]); + let relevantStatementThings = statements + .filter(n => { + let otherVarNode = n.getChild(tokens.VAR)!; + return context.state.sliceDoc(otherVarNode.from, otherVarNode.to) == varName; + }); + let possibleLabels = relevantStatementThings + .flatMap(n => nodesWithPath(n, [tokens.ThingConstraintList, tokens.ThingConstraint, tokens.IsaConstraint, tokens.TypeRef])) + .map(n => context.state.sliceDoc(n.from, n.to)); + // console.log(parentStatementThing, varNode, varName, pipelineNode, statements, relevantStatementThings, possibleLabels); + return possibleLabels; +} + +function suggestAttributeTypeForHas(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode, prefix: NodeType[], schema: TypeQLAutocompleteSchema): Completion[] { + let possibleLabels = findIsaConstraintLabelsForVar(context, parseAt); + if (possibleLabels.length > 0) { + return possibleLabels.flatMap(owner => schema.getOwns(owner)) + .map((attributeType) => suggest("AttributeType", attributeType.label)) + } else { + return suggestAttributeTypeLabels(context, tree, parseAt, climbedTo, prefix, schema); + } +} + +function suggestRoleTypeForLinks(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode, prefix: NodeType[], schema: TypeQLAutocompleteSchema): Completion[] { + let possibleLabels = findIsaConstraintLabelsForVar(context, parseAt); + if (possibleLabels.length > 0) { + return possibleLabels.flatMap(relationType => schema.getRelates(relationType)) + .map((roleType) => suggest("RoleType", roleType.label.split(":")[1])) + } else { + return suggestRelatedRoleTypeLabelsUnscoped(context, tree, parseAt, climbedTo, prefix, schema); + } +} + +function suggestAttributeTypeLabels(_context: CompletionContext, _tree: Tree, _parseAt: SyntaxNode, _climbedTo: SyntaxNode, _prefix: NodeType[], schema: TypeQLAutocompleteSchema): Completion[] { + return schema.attributeTypes().map((attributeType) => { return suggest("AttributeType", attributeType.label); }); +} + +function suggestObjectTypeLabels(_context: CompletionContext, _tree: Tree, _parseAt: SyntaxNode, _climbedTo: SyntaxNode, _prefix: NodeType[], schema: TypeQLAutocompleteSchema): Completion[] { + return schema.objectTypes().map((objectType) => suggest("ObjectType", objectType.label)); +} + +function suggestRoleTypesUnscopedForPlaysDeclaration(_context: CompletionContext, _tree: Tree, _parseAt: SyntaxNode, _climbedTo: SyntaxNode, _prefix: NodeType[], schema: TypeQLAutocompleteSchema): Completion[] { + return schema.objectTypes() + .flatMap((objectType) => objectType.playedRoles) + .map((role) => suggest("RoleType", role.label)); +} + +function suggestRelatedRoleTypeLabelsUnscoped(_context: CompletionContext, _tree: Tree, _parseAt: SyntaxNode, _climbedTo: SyntaxNode, _prefix: NodeType[], schema: TypeQLAutocompleteSchema): Completion[] { + return schema.relationTypes() + .flatMap((relation) => relation.relatedRoles) + .map((role) => suggest("RoleType", role.label.split(":")[1])); +} + +function suggestThingTypeLabels(context: CompletionContext, tree: Tree, parseAt: SyntaxNode, climbedTo: SyntaxNode, prefix: NodeType[], schema: TypeQLAutocompleteSchema): Completion[] { + return suggestAttributeTypeLabels(context, tree, parseAt, climbedTo, prefix, schema).concat( + suggestObjectTypeLabels(context, tree, parseAt, climbedTo, prefix, schema) + ); +} + +function suggestVariables(context: CompletionContext, tree: Tree, boost= 0): Completion[] { + let options: Completion[] = []; + tree.iterate({ + enter: (other: SyntaxNode) => { + if (other.type.id == tokens.VAR) { + let varName = context.state.sliceDoc(other.from, other.to); + options.push(suggest("variable", varName, boost)); + } + } + }); + return options; +} + +function suggestVariablesAt10(context: CompletionContext, tree: Tree): Completion[] { + return suggestVariables(context, tree, 10); +} + +function suggestVariablesAtMinus10(context: CompletionContext, tree: Tree): Completion[] { + return suggestVariables(context, tree, -10); +} + +function suggestThingConstraintKeywords(): Completion[] { + return ["isa", "has", "links"].map((constraintName) => { + return { + label: constraintName, + type: "thingConstraint", + apply: constraintName, + info: "Thing constraint keyword", + }; + }); +} +function suggestTypeConstraintKeywords(): Completion[] { + return ["sub", "owns", "relates", "plays"].map((constraintName) => { + return { + label: constraintName, + type: "typeConstraint", + apply: constraintName, + info: "Type constraint keyword", + }; + }); +} + +function suggestDefinedKeywords(_context: CompletionContext, _tree: Tree, _parseAt: SyntaxNode, _climbedTo: SyntaxNode, _prefix: NodeType[], _schema: TypeQLAutocompleteSchema): Completion[] { + return ["define", "redefine", "undefine"].map((keyword) => suggest("keyword", keyword, 1)); +} + +function suggestPipelineStages(_context: CompletionContext, _tree: Tree, _parseAt: SyntaxNode, _climbedTo: SyntaxNode, _prefix: NodeType[], _schema: TypeQLAutocompleteSchema): Completion[] { + return ["match", "insert", "delete", "update", "put", "select", "reduce", "sort", "limit", "offset", "end"].map((keyword) => suggest("keyword", keyword, 1)) +} + +function suggestKinds(_context: CompletionContext, _tree: Tree, _parseAt: SyntaxNode, _climbedTo: SyntaxNode, _prefix: NodeType[], _schema: TypeQLAutocompleteSchema): Completion[] { + return ["entity", "attribute", "relation"].map((keyword) => suggest("kind", keyword, 2)); +} + +function suggestNestedPatterns(_context: CompletionContext, _tree: Tree, _parseAt: SyntaxNode, _climbedTo: SyntaxNode, _prefix: NodeType[], _schema: TypeQLAutocompleteSchema): Completion[] { + return ["not {};", "{} or {};", "try {};"].map((keyword) => suggest("method", keyword, 2)); +} + +const SUFFIX_VAR_OR_COMMA = [[tokens.COMMA], [tokens.VAR]]; + + +// Will pick the first matching suffix. If you want to handle things manually, use an empty suffix. +const SUGGESTION_GROUP_FOR_THING_STATEMENTS: SuffixOfPrefixSuggestion[] = [ + { suffixes: SUFFIX_VAR_OR_COMMA, suggestions: [suggestThingConstraintKeywords] }, + { suffixes: [[tokens.HAS]], suggestions: [suggestAttributeTypeForHas, suggestVariablesAtMinus10] }, + { suffixes: [[tokens.ISA]], suggestions: [suggestThingTypeLabels, suggestVariablesAtMinus10] }, + { suffixes: [[tokens.HAS, tokens.TypeRef], [tokens.ISA, tokens.TypeRef]], suggestions: [suggestVariablesAtMinus10] }, +]; + +export const SUGGESTION_MAP: SuggestionMap = { + [tokens.LABEL]: [{ suffixes: [[]], suggestions: [suggestThingTypeLabels] }], + [tokens.VAR]: [{ suffixes: [[]], suggestions: [suggestVariablesAt10] }], + [tokens.Relation]: [ + {suffixes: [[tokens.PARENOPEN], [tokens.COMMA]], suggestions: [suggestRoleTypeForLinks, suggestVariablesAtMinus10]}, + {suffixes: [[tokens.COLON]], suggestions: [suggestVariablesAt10]}, + ], + [tokens.Statement]: [ + { suffixes: SUFFIX_VAR_OR_COMMA, suggestions: [suggestThingConstraintKeywords, suggestTypeConstraintKeywords] }, + { suffixes: [[tokens.HAS]], suggestions: [suggestAttributeTypeForHas, suggestVariablesAtMinus10] }, + { suffixes: [[tokens.ISA]], suggestions: [suggestThingTypeLabels, suggestVariablesAtMinus10] }, + { suffixes: [[tokens.HAS, tokens.TypeRef], [tokens.ISA, tokens.TypeRef]], suggestions: [suggestVariablesAtMinus10] }, + { suffixes: [[tokens.SEMICOLON, tokens.TypeRef]], suggestions: [suggestTypeConstraintKeywords] }, + { suffixes: [[tokens.OWNS]], suggestions: [suggestAttributeTypeLabels, suggestVariablesAtMinus10] }, + { suffixes: [[tokens.SUB]], suggestions: [suggestThingTypeLabels, suggestVariablesAtMinus10] }, + { suffixes: [[tokens.RELATES]], suggestions: [suggestRelatedRoleTypeLabelsUnscoped] }, + { suffixes: [[tokens.PLAYS]], suggestions: [suggestRoleTypesUnscopedForPlaysDeclaration] }, + ], + [tokens.ClauseMatch]: [ + { suffixes: [[tokens.MATCH, tokens.TypeRef]], suggestions: [suggestTypeConstraintKeywords] }, + { suffixes: [[tokens.MATCH]], suggestions: [suggestNestedPatterns, suggestVariablesAt10, suggestThingTypeLabels ] }, + ], + [tokens.ClauseInsert]: SUGGESTION_GROUP_FOR_THING_STATEMENTS, + [tokens.Query]: [ + { suffixes: [[tokens.QuerySchema]], suggestions: [suggestThingTypeLabels, suggestKinds] }, + { suffixes: [[tokens.QueryPipelinePreambled]], suggestions: [suggestNestedPatterns, suggestVariablesAt10, suggestPipelineStages ] }, + ], + + // Now some for define statements + [tokens.QuerySchema]: [ + { suffixes: [[tokens.DEFINE]], suggestions: [ suggestThingTypeLabels, suggestKinds] }, + { suffixes: [[tokens.DEFINE, tokens.LABEL]], suggestions: [ suggestTypeConstraintKeywords] }, + { suffixes: [[tokens.SEMICOLON, tokens.LABEL]], suggestions: [ suggestTypeConstraintKeywords] }, + ], + [tokens.Definable]: [ + { suffixes: [[tokens.COMMA], [tokens.KIND, tokens.LABEL]], suggestions: [ suggestTypeConstraintKeywords ] }, + { suffixes: [[tokens.OWNS]], suggestions: [suggestAttributeTypeLabels] }, + { suffixes: [[tokens.SUB]], suggestions: [suggestThingTypeLabels] }, + { suffixes: [ [tokens.PLAYS] ], suggestions: [ suggestRoleTypesUnscopedForPlaysDeclaration ] }, + { suffixes: [ [tokens.RELATES] ], suggestions: [ suggestRelatedRoleTypeLabelsUnscoped ] }, + ], + // TODO: Add any cases krishnan didn't think of. +}; diff --git a/src/framework/graph-visualiser/defaults.ts b/src/framework/graph-visualiser/defaults.ts index 757f34993..f389ee407 100644 --- a/src/framework/graph-visualiser/defaults.ts +++ b/src/framework/graph-visualiser/defaults.ts @@ -164,6 +164,9 @@ export const defaultSigmaSettings: Partial = { edgeProgramClasses: { curved: EdgeCurveProgram, }, + cameraPanBoundaries: { + tolerance: 1, + }, }; export const defaultForceSupervisorSettings: ForceLayoutSettings = { diff --git a/src/framework/graph-visualiser/index.ts b/src/framework/graph-visualiser/index.ts index bcd6dcfc8..041a52928 100644 --- a/src/framework/graph-visualiser/index.ts +++ b/src/framework/graph-visualiser/index.ts @@ -47,8 +47,8 @@ export class GraphVisualiser { handleQueryResult(res: ApiResponse) { if (isApiErrorResponse(res)) return; - if (res.ok.answerType == "conceptRows" && res.ok.query != null) { + (window as any)._lastQueryAnswers = res.ok.answers; // TODO: Remove once schema based autocomplete is stable. let converter = new StudioConverter(this.graph, res.ok.query, false, this.structureParameters, this.styleParameters); let logicalGraph = constructGraphFromRowsResult(res.ok); // In memory, not visualised convertLogicalGraphWith(logicalGraph, converter); diff --git a/src/framework/scroll-container/detect-scroll.directive.ts b/src/framework/scroll-container/detect-scroll.directive.ts new file mode 100644 index 000000000..35a40b228 --- /dev/null +++ b/src/framework/scroll-container/detect-scroll.directive.ts @@ -0,0 +1,12 @@ +import { Directive, HostBinding, HostListener } from "@angular/core"; + +@Directive({ + selector: "[detectScroll]" +}) +export class DetectScrollDirective { + @HostBinding("class.scrolled") scrolled = false; + + @HostListener("scroll", ["$event.target"]) onScroll(target: any) { + this.scrolled = target.scrollTop > 0; + } +} diff --git a/src/framework/typedb-driver/response.ts b/src/framework/typedb-driver/response.ts index 812cdcb95..725b47062 100644 --- a/src/framework/typedb-driver/response.ts +++ b/src/framework/typedb-driver/response.ts @@ -36,6 +36,7 @@ export interface ConceptRow { } export interface ConceptRowAnswer { + involvedBlocks: number[]; data: ConceptRow; } @@ -56,10 +57,7 @@ export interface OkQueryResponse extends QueryResponseBase { export interface ConceptRowsQueryResponse extends QueryResponseBase { answerType: "conceptRows"; - answers: { - involvedBlocks: number[]; - data: ConceptRow - }[]; + answers: ConceptRowAnswer[]; } export interface ConceptDocumentsQueryResponse extends QueryResponseBase { @@ -69,6 +67,8 @@ export interface ConceptDocumentsQueryResponse extends QueryResponseBase { export type QueryResponse = OkQueryResponse | ConceptRowsQueryResponse | ConceptDocumentsQueryResponse; +export type ApiOkResponse = { ok: OK_RES }; + export type ApiError = { code: string; message: string }; export interface ApiErrorResponse { @@ -80,9 +80,9 @@ export function isApiError(err: any): err is ApiError { return typeof err.code === "string" && typeof err.message === "string"; } -export type ApiResponse = { ok: OK_RES } | ApiErrorResponse; +export type ApiResponse = ApiOkResponse | ApiErrorResponse; -export function isOkResponse(res: ApiResponse): res is { ok: OK_RES } { +export function isOkResponse(res: ApiResponse): res is ApiOkResponse { return "ok" in res; } diff --git a/src/module/query/query-tool.component.html b/src/module/query/query-tool.component.html index 213f48177..2ca75846d 100644 --- a/src/module/query/query-tool.component.html +++ b/src/module/query/query-tool.component.html @@ -1,52 +1,35 @@
-
-
- -

History

+
+
+
+ +

Schema

+
-
-
    - @for (entry of state.history.entries; track entry) { -
  1. - - @if (isQueryRun(entry)) { - - - - } - - - -
  2. - } -
+
+
+ + + + + + + + + + + + + +
-
+
@@ -59,7 +42,7 @@

Query

@if (!codeEditorHidden) { - + }
@@ -154,5 +137,51 @@

Output

}
+
+
+ +

History

+
+
+
    + @for (entry of state.history.entries; track entry) { +
  1. + + @if (isQueryRun(entry)) { + + + + } + + + +
  2. + } +
+
+
diff --git a/src/module/query/query-tool.component.scss b/src/module/query/query-tool.component.scss index 2326cf790..33d4a0995 100644 --- a/src/module/query/query-tool.component.scss +++ b/src/module/query/query-tool.component.scss @@ -10,23 +10,8 @@ @use "typography"; @use "shapes"; -:host { - height: 100%; -} - -.query-page-rough { - height: 100%; - margin-top: 16px; - margin-bottom: 16px; - display: flex; - flex-direction: column; - gap: 16px; -} - .query-page { - height: 100%; - margin-top: 16px; - margin-bottom: 16px; + height: calc(100% - 86px); gap: 4px !important; ::ng-deep app-drag-handle span { @@ -66,6 +51,18 @@ @include shapes.light-source-gradient(primary.$purple, primary.$deep-purple); transition: background 0.1s linear; padding: 16px; + + &:has(.card-header-wrapper) { + padding: 0; + } +} + +.card-header-wrapper { + padding: 16px; + + .card-header { + margin-bottom: 0; + } } .card-header { @@ -76,6 +73,66 @@ height: 32px; } +.schema-pane { + display: flex; + flex-direction: column; +} + +.schema-container { + display: flex; + overflow: auto; + scrollbar-gutter: stable; + overscroll-behavior: contain; + + .gutter { + flex: 0 0 16px; + } + + &.scrolled { + border-top: 1px solid primary.$black-purple; + } + + mat-tree { + padding: 0; + box-sizing: border-box; + height: 100%; + margin: 0; + + .mat-mdc-icon-button { + margin: 0; + padding: 0; + width: 1em; + height: 1em; + } + + mat-tree-node[aria-level="3"] { + margin-left: 32px; + padding-left: 8px !important; /* overrides inline style from Angular Material */ + padding-right: 8px; + align-items: stretch; + border-bottom: 1px solid primary.$light-purple; + border-left: 1px solid primary.$light-purple; + border-right: 1px solid primary.$light-purple; + + button { + display: none; + } + + &:nth-child(odd) { + background: primary.$black-purple; + } + + &:nth-child(even) { + background: primary.$purple; + } + } + + mat-tree-node[aria-level="2"] + mat-tree-node[aria-level="3"] { + border-top: 1px solid primary.$light-purple; + } + } +} + mat-form-field { width: 100%; } diff --git a/src/module/query/query-tool.component.ts b/src/module/query/query-tool.component.ts index 6c3443a31..02884cbaa 100644 --- a/src/module/query/query-tool.component.ts +++ b/src/module/query/query-tool.component.ts @@ -12,16 +12,19 @@ import { MatButtonModule } from "@angular/material/button"; import { MatButtonToggleModule } from "@angular/material/button-toggle"; import { MatDividerModule } from "@angular/material/divider"; import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatIconModule } from "@angular/material/icon"; import { MatInputModule } from "@angular/material/input"; import { MatSortModule } from "@angular/material/sort"; import { MatTableModule } from "@angular/material/table"; +import { MatTreeModule } from "@angular/material/tree"; import { MatTooltipModule } from "@angular/material/tooltip"; import { RouterLink } from "@angular/router"; import { ResizableDirective } from "@hhangular/resizable"; import { distinctUntilChanged, filter, first, map, startWith } from "rxjs"; -import { otherExampleLinter, TypeQL } from "../../framework/codemirror-lang-typeql"; +import { otherExampleLinter, TypeQL, typeqlAutocompleteExtension } from "../../framework/codemirror-lang-typeql"; import { DriverAction, TransactionOperationAction, isQueryRun, isTransactionOperation } from "../../concept/action"; import { basicDark } from "../../framework/code-editor/theme"; +import { DetectScrollDirective } from "../../framework/scroll-container/detect-scroll.directive"; import { SpinnerComponent } from "../../framework/spinner/spinner.component"; import { RichTooltipDirective } from "../../framework/tooltip/rich-tooltip.directive"; import { AppData } from "../../service/app-data.service"; @@ -29,15 +32,19 @@ import { DriverState } from "../../service/driver-state.service"; import { QueryToolState } from "../../service/query-tool-state.service"; import { SnackbarService } from "../../service/snackbar.service"; import { PageScaffoldComponent } from "../scaffold/page/page-scaffold.component"; +import { SchemaTreeNodeComponent } from "./schema-tree-node/schema-tree-node.component"; +import {keymap} from "@codemirror/view"; +import {defaultKeymap} from "@codemirror/commands"; +import {startCompletion, completionKeymap} from "@codemirror/autocomplete"; @Component({ selector: "ts-query-tool", templateUrl: "query-tool.component.html", styleUrls: ["query-tool.component.scss"], imports: [ - RouterLink, AsyncPipe, PageScaffoldComponent, MatDividerModule, MatFormFieldModule, + RouterLink, AsyncPipe, PageScaffoldComponent, MatDividerModule, MatFormFieldModule, MatTreeModule, MatIconModule, MatInputModule, FormsModule, ReactiveFormsModule, MatButtonToggleModule, CodeEditor, ResizableDirective, - DatePipe, SpinnerComponent, MatTableModule, MatSortModule, MatTooltipModule, MatButtonModule, RichTooltipDirective, + DatePipe, SpinnerComponent, MatTableModule, MatSortModule, MatTooltipModule, MatButtonModule, RichTooltipDirective, SchemaTreeNodeComponent, DetectScrollDirective, ] }) export class QueryToolComponent implements OnInit, AfterViewInit, OnDestroy { @@ -49,8 +56,10 @@ export class QueryToolComponent implements OnInit, AfterViewInit, OnDestroy { readonly codeEditorTheme = basicDark; codeEditorHidden = true; - constructor(protected state: QueryToolState, public driver: DriverState, private appData: AppData, private snackbar: SnackbarService) { - } + constructor( + protected state: QueryToolState, public driver: DriverState, + private appData: AppData, private snackbar: SnackbarService + ) {} ngOnInit() { this.appData.viewState.setLastUsedTool("query"); @@ -67,6 +76,7 @@ export class QueryToolComponent implements OnInit, AfterViewInit, OnDestroy { ngAfterViewInit() { const articleWidth = this.articleRef.nativeElement.clientWidth; this.resizables.first.percent = (articleWidth * 0.15 + 100) / articleWidth * 100; + this.resizables.last.percent = (articleWidth * 0.15 + 100) / articleWidth * 100; this.graphViewRef.changes.pipe( map(x => x as QueryList>), startWith(this.graphViewRef), @@ -127,4 +137,6 @@ export class QueryToolComponent implements OnInit, AfterViewInit, OnDestroy { readonly JSON = JSON; readonly TypeQL = TypeQL; readonly linter = otherExampleLinter; + readonly typeqlAutocompleteExtension = typeqlAutocompleteExtension; + readonly codeEditorKeymap = keymap.of([...defaultKeymap, {key: "Alt-Space", run: startCompletion, preventDefault: true}]); } diff --git a/src/module/query/schema-tree-node/schema-tree-node.component.html b/src/module/query/schema-tree-node/schema-tree-node.component.html new file mode 100644 index 000000000..249729e45 --- /dev/null +++ b/src/module/query/schema-tree-node/schema-tree-node.component.html @@ -0,0 +1,24 @@ +@switch (data.nodeKind) { + @case ("root") { + {{ data.label }} + } + @case ("concept") { + {{ data.concept.label }}@if (data.concept.kind === "attributeType") {, value {{ data.concept.valueType }}} + } + @case ("link") { + @switch (data.linkKind) { + @case ("sub") { +

sub {{ data.supertype.label }}

+ } + @case ("owns") { +

owns {{ data.ownedAttribute.label }}, value {{ data.ownedAttribute.valueType }}

+ } + @case ("relates") { +

relates {{ data.role.label }}

+ } + @case ("plays") { +

plays {{ data.role.label }}

+ } + } + } +} diff --git a/src/module/query/schema-tree-node/schema-tree-node.component.scss b/src/module/query/schema-tree-node/schema-tree-node.component.scss new file mode 100644 index 000000000..5b0296d87 --- /dev/null +++ b/src/module/query/schema-tree-node/schema-tree-node.component.scss @@ -0,0 +1,259 @@ +/*!/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +@use "media"; +@use "primary"; +@use "secondary"; +@use "typography"; +@use "shapes"; + +.query-page-rough { + height: 100%; + margin-top: 16px; + margin-bottom: 16px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.query-page { + height: 100%; + margin-top: 16px; + margin-bottom: 16px; + gap: 4px !important; + + ::ng-deep app-drag-handle span { + display: none; + } +} + +.tp-bento-container { + gap: 16px; +} + +.tool-windows { + flex: 0 0 360px; +} + +.main-panes { + flex: 1; + gap: 4px !important; + + @media (max-width: media.$max-width-mobile) { + margin-left: -8px; + } +} + +.history-pane, .query-pane, .run-pane { + display: flex; + flex-direction: column; +} + +.run-pane mat-form-field { + height: 100%; +} + +.card { + @include shapes.standard-border; + border-radius: shapes.$border-radius-panel; + @include shapes.light-source-gradient(primary.$purple, primary.$deep-purple); + transition: background 0.1s linear; + padding: 16px; +} + +.card-header { + display: flex; + gap: 16px; + align-items: center; + margin-bottom: 12px; + height: 32px; +} + +mat-form-field { + width: 100%; +} + +button { + i.fa-play { + margin: 0; + } + + &:enabled i.fa-play { + color: secondary.$deep-green; + } +} + +.query-text-box { + resize: none; + font-family: "Monaco" !important; + font-size: 14px !important; + font-weight: 400 !important; + line-height: 22px !important; + letter-spacing: 0.02em !important; +} + +.code-editor-container { + height: 100%; + overflow: auto; + border: 1px solid primary.$light-purple; + background: primary.$black-purple; + + code-editor { + height: 100%; + } +} + +.answers-outer-container { + height: 100%; + width: 100%; + border: 1px solid primary.$light-purple; + background: primary.$black-purple; + position: relative; +} + +.answers-container { + position: absolute; + inset: 0; + overflow: auto; +} + +.answers-text-container { + mat-form-field { + flex: 1; + display: flex; + flex-direction: column; + } + + ::ng-deep .mdc-text-field, + ::ng-deep .mat-mdc-form-field-flex, + ::ng-deep .mat-mdc-form-field-infix { + width: 100%; + height: 100%; + --mdc-outlined-text-field-container-shape: 0; + } + + ::ng-deep .mdc-notched-outline { + display: none; + } + + ::ng-deep .mdc-text-field { + --mdc-outlined-text-field-input-text-color: #{secondary.$pink}; + } + + ::ng-deep .mat-mdc-form-field:not(.form-field-dense) .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix { + padding: 4px 2px 4px 6px; + } + + .answers-text-box { + height: 100% !important; + resize: none; + @include typography.code; + } +} + +.status-text-container { + width: 100%; + height: 100%; + display: flex; +} + +.status-text { + @include typography.p1; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 80%; + text-align: center; + z-index: 10; /* Ensure it's above the canvas elements */ + white-space: normal !important; + overflow-wrap: break-word; + padding: 10px; + margin: 0; +} + +.answers-placeholder-container { + height: 100%; + overflow: auto; + border: 3px dashed primary.$light-purple; + border-radius: shapes.$border-radius-panel; + display: flex; + justify-content: center; + align-items: center; + background: primary.$black-purple; + --mat-button-filled-container-height: 40px; +} + +#structureView { + width: 100%; + height: 100%; + display: flex; +} + +.history-pane { + @media (max-width: media.$max-width-mobile) { + display: none; + } + + .history-container { + height: 100%; + width: 100%; + overflow: auto; + } + + ol li { + list-style: none; + margin-top: 8px; + + aside { + display: flex; + align-items: center; + + .bullet { + margin: 0 4px; + } + + tp-spinner { + width: unset; + } + + .action-status { + margin-left: 8px; + } + + .action-status i { + margin-left: 6px; + font-size: 12px; + + &.fa-xmark { + color: #{primary.$red}; + cursor: pointer; + } + } + } + + .mat-divider { + margin-top: 8px; + } + } + + mat-form-field { + margin-top: 4px; + + ::ng-deep .mdc-notched-outline { + + } + + textarea { + resize: none; + @include typography.code; + } + } + + .transaction-operation-type { + min-width: 140px; + } +} diff --git a/src/module/query/schema-tree-node/schema-tree-node.component.ts b/src/module/query/schema-tree-node/schema-tree-node.component.ts new file mode 100644 index 000000000..f5e7e3d0b --- /dev/null +++ b/src/module/query/schema-tree-node/schema-tree-node.component.ts @@ -0,0 +1,33 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { Component, Input } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { MatButtonModule } from "@angular/material/button"; +import { MatButtonToggleModule } from "@angular/material/button-toggle"; +import { MatDividerModule } from "@angular/material/divider"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatIconModule } from "@angular/material/icon"; +import { MatInputModule } from "@angular/material/input"; +import { MatSortModule } from "@angular/material/sort"; +import { MatTableModule } from "@angular/material/table"; +import { MatTreeModule } from "@angular/material/tree"; +import { MatTooltipModule } from "@angular/material/tooltip"; +import { SchemaTreeNode } from "../../../service/query-tool-state.service"; + +@Component({ + selector: "ts-schema-tree-node", + templateUrl: "schema-tree-node.component.html", + styleUrls: ["schema-tree-node.component.scss"], + imports: [ + MatDividerModule, MatFormFieldModule, MatTreeModule, MatIconModule, + MatInputModule, FormsModule, ReactiveFormsModule, MatButtonToggleModule, + MatTableModule, MatSortModule, MatTooltipModule, MatButtonModule, + ] +}) +export class SchemaTreeNodeComponent { + @Input({ required: true }) data!: SchemaTreeNode; +} diff --git a/src/module/schema/schema-tool.component.scss b/src/module/schema/schema-tool.component.scss index 9dcd7f2c2..db43a3eff 100644 --- a/src/module/schema/schema-tool.component.scss +++ b/src/module/schema/schema-tool.component.scss @@ -10,14 +10,8 @@ @use "typography"; @use "shapes"; -:host { - height: 100%; -} - .schema-page { - height: 100%; - margin-top: 16px; - margin-bottom: 16px; + height: calc(100% - 86px); gap: 4px !important; ::ng-deep app-drag-handle span { diff --git a/src/service/driver-state.service.ts b/src/service/driver-state.service.ts index a7c756245..fae90dec2 100644 --- a/src/service/driver-state.service.ts +++ b/src/service/driver-state.service.ts @@ -13,7 +13,7 @@ import { ConnectionConfig, databasesSortedByName, DEFAULT_DATABASE_NAME } from " import { Transaction } from "../concept/transaction"; import { TypeDBHttpDriver } from "../framework/typedb-driver"; import { Database } from "../framework/typedb-driver/database"; -import { ApiResponse, isApiErrorResponse, isOkResponse, QueryResponse, VersionResponse } from "../framework/typedb-driver/response"; +import { ApiOkResponse, ApiResponse, isApiErrorResponse, isOkResponse, QueryResponse, VersionResponse } from "../framework/typedb-driver/response"; import { requireValue } from "../framework/util/observable"; import { INTERNAL_ERROR } from "../framework/util/strings"; import { AppData } from "./app-data.service"; @@ -309,7 +309,7 @@ export class DriverState { ), lockId); } - runBackgroundReadQueries(queries: string[]): Observable> { + runBackgroundReadQueries(queries: string[]): Observable> { const driver = this.requireDriver(`${this.constructor.name}.${this.query.name} > ${this.requireDriver.name}`); const databaseName = this.requireDatabase(`${this.constructor.name}.${this.openTransaction.name} > ${this.requireDatabase.name}`).name; return fromPromise(driver.openTransaction(databaseName, "read")).pipe( diff --git a/src/service/query-tool-state.service.ts b/src/service/query-tool-state.service.ts index 1c548fe0d..42a91c744 100644 --- a/src/service/query-tool-state.service.ts +++ b/src/service/query-tool-state.service.ts @@ -17,7 +17,10 @@ import { Concept, Value } from "../framework/typedb-driver/concept"; import { ApiResponse, ConceptDocument, ConceptRow, isApiErrorResponse, QueryResponse } from "../framework/typedb-driver/response"; import { INTERNAL_ERROR } from "../framework/util/strings"; import { DriverState } from "./driver-state.service"; +import { SchemaState, Schema, SchemaAttribute, SchemaRole, SchemaConcept } from "./schema-state.service"; import { SnackbarService } from "./snackbar.service"; +import { FlatTreeControl } from "@angular/cdk/tree"; +import { MatTreeFlatDataSource, MatTreeFlattener } from "@angular/material/tree"; export type OutputType = "raw" | "log" | "table" | "graph"; @@ -34,11 +37,12 @@ export class QueryToolState { queryControl = new FormControl("", {nonNullable: true}); outputTypeControl = new FormControl("log" as OutputType, { nonNullable: true }); outputTypes: OutputType[] = ["log", "table", "graph", "raw"]; - readonly history = new HistoryWindowState(this.driver); + readonly schemaWindow = new SchemaWindowState(this.schema); readonly logOutput = new LogOutputState(); readonly tableOutput = new TableOutputState(); readonly graphOutput = new GraphOutputState(); readonly rawOutput = new RawOutputState(); + readonly history = new HistoryWindowState(this.driver); answersOutputEnabled = true; readonly runDisabledReason$ = combineLatest( [this.driver.status$, this.driver.database$, this.driver.autoTransactionEnabled$, this.driver.transaction$, this.queryControl.valueChanges.pipe(startWith(this.queryControl.value))] @@ -53,7 +57,7 @@ export class QueryToolState { readonly outputDisabledReason$ = this.driver.status$.pipe(map(x => x === "connected" ? null : NO_SERVER_CONNECTED)); readonly outputDisabled$ = this.outputDisabledReason$.pipe(map(x => x != null)); - constructor(private driver: DriverState, private snackbar: SnackbarService) { + constructor(private driver: DriverState, private schema: SchemaState, private snackbar: SnackbarService) { (window as any)["queryToolState"] = this; this.outputDisabled$.subscribe((disabled) => { if (disabled) this.outputTypeControl.disable(); @@ -131,6 +135,113 @@ export class QueryToolState { } } +export type SchemaTreeNodeKind = "root" | "concept" | "link"; + +export interface SchemaTreeNodeBase { + nodeKind: SchemaTreeNodeKind; + children?: SchemaTreeNode[]; +} + +export interface SchemaTreeRootNode extends SchemaTreeNodeBase { + nodeKind: "root"; + label: string; + children: SchemaTreeConceptNode[]; +} + +export interface SchemaTreeConceptNode extends SchemaTreeNodeBase { + nodeKind: "concept"; + concept: SchemaConcept; + children: SchemaTreeLinkNode[]; +} + +export type SchemaTreeLinkKind = "sub" | "owns" | "plays" | "relates"; + +export interface SchemaTreeLinkNodeBase extends SchemaTreeNodeBase { + nodeKind: "link"; + linkKind: SchemaTreeLinkKind; +} + +export interface SchemaTreeSubLinkNode extends SchemaTreeLinkNodeBase { + linkKind: "sub"; + supertype: SchemaConcept; +} + +export interface SchemaTreeOwnsLinkNode extends SchemaTreeLinkNodeBase { + linkKind: "owns"; + ownedAttribute: SchemaAttribute; +} + +export interface SchemaTreePlaysLinkNode extends SchemaTreeLinkNodeBase { + linkKind: "plays"; + role: SchemaRole; +} + +export interface SchemaTreeRelatesLinkNode extends SchemaTreeLinkNodeBase { + linkKind: "relates"; + role: SchemaRole; +} + +export type SchemaTreeLinkNode = SchemaTreeSubLinkNode | SchemaTreeOwnsLinkNode | SchemaTreePlaysLinkNode | SchemaTreeRelatesLinkNode; + +export type SchemaTreeNode = SchemaTreeRootNode | SchemaTreeConceptNode | SchemaTreeLinkNode; + +export class SchemaWindowState { + dataSource: SchemaTreeRootNode[] = []; + + constructor(public schemaState: SchemaState) { + schemaState.value$.subscribe(schema => { + this.populateDataSources(schema); + }); + } + + hasChild = (_: number, node: SchemaTreeNode) => !!node.children?.length; + + childrenAccessor = (node: SchemaTreeNode) => node.children ?? []; + + private populateDataSources(schema: Schema | null) { + if (!schema) { + this.dataSource.length = 0; + return; + } + this.dataSource = [{ + nodeKind: "root", + label: "Entities", + children: Object.values(schema.entities).sort((a, b) => a.label.localeCompare(b.label)).map(x => ({ + nodeKind: "concept", + concept: x, + children: ([ + ...(x.supertype ? [{ nodeKind: "link", linkKind: "sub", supertype: x.supertype }] : []), + ...x.ownedAttributes.map(y => ({ nodeKind: "link", linkKind: "owns", ownedAttribute: y })), + ...x.playedRoles.map(y => ({ nodeKind: "link", linkKind: "plays", role: y })), + ] as SchemaTreeLinkNode[]), + })), + }, { + nodeKind: "root", + label: "Relations", + children: Object.values(schema.relations).sort((a, b) => a.label.localeCompare(b.label)).map(x => ({ + nodeKind: "concept", + concept: x, + children: ([ + ...(x.supertype ? [{ nodeKind: "link", linkKind: "sub", supertype: x.supertype }] : []), + ...x.relatedRoles.map(y => ({ nodeKind: "link", linkKind: "relates", role: y })), + ...x.ownedAttributes.map(y => ({ nodeKind: "link", linkKind: "owns", ownedAttribute: y })), + ...x.playedRoles.map(y => ({ nodeKind: "link", linkKind: "plays", role: y })), + ] as SchemaTreeLinkNode[]), + })), + }, { + nodeKind: "root", + label: "Attributes", + children: Object.values(schema.attributes).sort((a, b) => a.label.localeCompare(b.label)).map(x => ({ + nodeKind: "concept", + concept: x, + children: ([ + ...(x.supertype ? [{ nodeKind: "link", linkKind: "sub", supertype: x.supertype }] : []), + ] as SchemaTreeLinkNode[]), + })), + }]; + } +} + export class HistoryWindowState { readonly entries: DriverAction[] = []; @@ -500,603 +611,3 @@ export class RawOutputState { this.control.patchValue(``); } } - -/* -class QueryRunner constructor( - val transactionState: TransactionState, // TODO: restrict in the future, when TypeDB 3.0 answers return complete info - private val notificationSrv: NotificationService, - private val preferenceSrv: PreferenceService, - private val queries: String, - private val onComplete: () -> Unit -) { - - sealed class Response { - - object Done : Response() - - data class Message(val type: Type, val text: String) : Response() { - enum class Type { INFO, SUCCESS, ERROR, TYPEQL } - } - - data class Value(val value: com.vaticle.typedb.driver.api.concept.value.Value?) : Response() - - sealed class Stream : Response() { - - val queue = LinkedBlockingQueue>() - - class ConceptMapGroups : Stream() - class ValueGroups : Stream() - class JSONs : Stream() - class ConceptMaps constructor(val source: Source) : Stream() { - enum class Source { INSERT, UPDATE, GET } - } - } -} - - companion object { - const val RESULT_ = "## Result> " - const val ERROR_ = "## Error> " - const val RUNNING_ = "## Running> " - const val COMPLETED = "## Completed" - const val TERMINATED = "## Terminated" - const val DEFINE_QUERY = "Define query:" - const val DEFINE_QUERY_SUCCESS = "Define query successfully defined new types in the schema." - const val UNDEFINE_QUERY = "Undefine query:" - const val UNDEFINE_QUERY_SUCCESS = "Undefine query successfully undefined types in the schema." - const val DELETE_QUERY = "Delete query:" - const val DELETE_QUERY_SUCCESS = "Delete query successfully deleted things from the database." - const val INSERT_QUERY = "Insert query:" - const val INSERT_QUERY_SUCCESS = "Insert query successfully inserted new things to the database:" - const val INSERT_QUERY_NO_RESULT = "Insert query did not insert any new thing to the database." - const val UPDATE_QUERY = "Update query:" - const val UPDATE_QUERY_SUCCESS = "Update query successfully updated things in the databases:" - const val UPDATE_QUERY_NO_RESULT = "Update query did not update any thing in the databases." - const val GET_QUERY = "Get query:" - const val GET_QUERY_SUCCESS = "Get query successfully matched concepts in the database:" - const val GET_QUERY_NO_RESULT = "Get query did not match any concepts in the database." - const val GET_AGGREGATE_QUERY = "Get Aggregate query:" - const val GET_AGGREGATE_QUERY_SUCCESS = "Get Aggregate query successfully calculated:" - const val GET_GROUP_QUERY = "Get Group query:" - const val GET_GROUP_QUERY_SUCCESS = "Get Group query successfully matched concept groups in the database:" - const val GET_GROUP_QUERY_NO_RESULT = "Get Group query did not match any concept groups in the database." - const val GET_GROUP_AGGREGATE_QUERY = "Get Group Aggregate query:" - const val GET_GROUP_AGGREGATE_QUERY_SUCCESS = - "Get Group Aggregate query successfully aggregated matched concept groups in the database:" - const val GET_GROUP_AGGREGATE_QUERY_NO_RESULT = - "Get Group Aggregate query did not match any concept groups to aggregate in the database." - const val FETCH_QUERY = "Fetch query:" - const val FETCH_QUERY_SUCCESS = "Fetch query successfully retrieved data from the database:" - const val FETCH_QUERY_NO_RESULT = "Fetch query did not retrieve any data from the database." - - private const val COUNT_DOWN_LATCH_PERIOD_MS: Long = 50 - private val LOGGER = KotlinLogging.logger {} - } - - var startTime: Long? = null - var endTime: Long? = null - val responses = LinkedBlockingQueue() - val isConsumed: Boolean get() = consumerLatch.count == 0L - val isRunning = AtomicBoolean(false) -private val consumerLatch = CountDownLatch(1) -private val coroutines = CoroutineScope(Dispatchers.Default) -private val hasStopSignal get() = transactionState.hasStopSignalAtomic -private val transaction get() = transactionState.transaction!! -private val onClose = LinkedBlockingQueue<() -> Unit>() - - fun onClose(function: () -> Unit) = onClose.put(function) - - fun setConsumed() = consumerLatch.countDown() - -private fun collectEmptyLine() = collectMessage(INFO, "") - -private fun collectMessage(type: Response.Message.Type, string: String) { - responses.put(Response.Message(type, string)) - } - - internal fun launch() = coroutines.launchAndHandle(notificationSrv, LOGGER) { - try { - isRunning.set(true) - startTime = System.currentTimeMillis() - runQueries(TypeQL.parseQueries(queries).toList()) - } catch (e: Exception) { - collectEmptyLine() - collectMessage(ERROR, ERROR_ + e.message) - } finally { - endTime = System.currentTimeMillis() - isRunning.set(false) - responses.add(Response.Done) - var isConsumed: Boolean - if (!hasStopSignal.state) { - do { - isConsumed = consumerLatch.count == 0L - if (!isConsumed) delay(COUNT_DOWN_LATCH_PERIOD_MS) - } while (!isConsumed && !hasStopSignal.state) - } - onComplete() - } - } - -private fun runQueries(queries: List) = queries.forEach { query -> - if (hasStopSignal.state) return@forEach - when (query) { - is TypeQLDefine -> runDefineQuery(query) - is TypeQLUndefine -> runUndefineQuery(query) - is TypeQLDelete -> runDeleteQuery(query) - is TypeQLInsert -> runInsertQuery(query) - is TypeQLUpdate -> runUpdateQuery(query) - is TypeQLGet -> runGetQuery(query) - is TypeQLGet.Aggregate -> runGetAggregateQuery(query) - is TypeQLGet.Group -> runGetGroupQuery(query) - is TypeQLGet.Group.Aggregate -> runGetGroupAggregateQuery(query) - is TypeQLFetch -> runFetchQuery(query) - else -> throw IllegalStateException("Unrecognised TypeQL query") - } - } - -private fun runDefineQuery(query: TypeQLDefine) = runUnitQuery( - name = DEFINE_QUERY, - successMsg = DEFINE_QUERY_SUCCESS, - queryStr = query.toString() - ) { transaction.query().define(query).resolve() } - -private fun runUndefineQuery(query: TypeQLUndefine) = runUnitQuery( - name = UNDEFINE_QUERY, - successMsg = UNDEFINE_QUERY_SUCCESS, - queryStr = query.toString() - ) { transaction.query().undefine(query).resolve() } - -private fun runDeleteQuery(query: TypeQLDelete) = runUnitQuery( - name = DELETE_QUERY, - successMsg = DELETE_QUERY_SUCCESS, - queryStr = query.toString() - ) { transaction.query().delete(query).resolve() } - -private fun runInsertQuery(query: TypeQLInsert) = runStreamingQuery( - name = INSERT_QUERY, - successMsg = INSERT_QUERY_SUCCESS, - noResultMsg = INSERT_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.ConceptMaps(INSERT) - ) { transaction.query().insert(query, transactionState.defaultTypeDBOptions().prefetch(true)) } - -private fun runUpdateQuery(query: TypeQLUpdate) = runStreamingQuery( - name = UPDATE_QUERY, - successMsg = UPDATE_QUERY_SUCCESS, - noResultMsg = UPDATE_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.ConceptMaps(UPDATE) - ) { transaction.query().update(query, transactionState.defaultTypeDBOptions().prefetch(true)) } - -private fun runGetQuery(query: TypeQLGet) = runStreamingQuery( - name = GET_QUERY, - successMsg = GET_QUERY_SUCCESS, - noResultMsg = GET_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.ConceptMaps(GET) - ) { - if (query.modifiers().limit().isPresent) { - transaction.query().get(query) - } else { - val queryWithLimit = TypeQLGet.Limited(query, preferenceSrv.getQueryLimit) - transaction.query().get(queryWithLimit) - } - } - -private fun runGetAggregateQuery(query: TypeQLGet.Aggregate) { - printQueryStart(GET_AGGREGATE_QUERY, query.toString()) - val result = transaction.query().get(query).resolve().orElse(null) - collectEmptyLine() - collectMessage(SUCCESS, RESULT_ + GET_AGGREGATE_QUERY_SUCCESS) - responses.put(Response.Value(result)) - } - -private fun runGetGroupQuery(query: TypeQLGet.Group) = runStreamingQuery( - name = GET_GROUP_QUERY, - successMsg = GET_GROUP_QUERY_SUCCESS, - noResultMsg = GET_GROUP_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.ConceptMapGroups() - ) { transaction.query().get(query) } - -private fun runGetGroupAggregateQuery(query: TypeQLGet.Group.Aggregate) = runStreamingQuery( - name = GET_GROUP_AGGREGATE_QUERY, - successMsg = GET_GROUP_AGGREGATE_QUERY_SUCCESS, - noResultMsg = GET_GROUP_AGGREGATE_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.ValueGroups() - ) { transaction.query().get(query) } - -private fun runFetchQuery(query: TypeQLFetch) = runStreamingQuery( - name = FETCH_QUERY, - successMsg = FETCH_QUERY_SUCCESS, - noResultMsg = FETCH_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.JSONs() - ) { - if (query.modifiers().limit().isPresent) { - transaction.query().fetch(query) - } else { - val queryWithLimit = TypeQLFetch.Limited(query, preferenceSrv.getQueryLimit) - transaction.query().fetch(queryWithLimit) - } - } - -private fun runUnitQuery(name: String, successMsg: String, queryStr: String, queryFn: () -> Unit) { - printQueryStart(name, queryStr) - queryFn() - collectEmptyLine() - collectMessage(SUCCESS, RESULT_ + successMsg) - } - -private fun runStreamingQuery( - name: String, - successMsg: String, - noResultMsg: String, - queryStr: String, - stream: Response.Stream, - queryFn: () -> Stream -) { - printQueryStart(name, queryStr) - collectResponseStream(queryFn(), successMsg, noResultMsg, stream) - } - -private fun printQueryStart(name: String, queryStr: String) { - collectEmptyLine() - collectMessage(INFO, RUNNING_ + name) - collectMessage(TYPEQL, queryStr) - } - -private fun collectResponseStream( - results: Stream, - successMsg: String, - noResultMsg: String, - stream: Response.Stream -) { - var started = false - var error = false - try { - collectEmptyLine() - results.peek { - if (started) return@peek - collectMessage(SUCCESS, RESULT_ + successMsg) - responses.put(stream) - started = true - }.forEach { - if (hasStopSignal.state) return@forEach - stream.queue.put(Either.first(it)) - } - } catch (e: Exception) { - collectMessage(ERROR, ERROR_ + e.message) - error = true - } finally { - if (started) stream.queue.put(Either.second(Response.Done)) - if (error || hasStopSignal.state) collectMessage(ERROR, TERMINATED) - else if (started) collectMessage(INFO, COMPLETED) - else collectMessage(SUCCESS, RESULT_ + noResultMsg) - } - } - - fun close() { - hasStopSignal.set(true) - onClose.forEach { it() } - } -} - */ - -/* -class QueryRunner( - val transactionState: TransactionState, // TODO: restrict in the future, when TypeDB 3.0 answers return complete info - private val notificationSrv: NotificationService, - private val preferenceSrv: PreferenceService, - private val queries: String, - private val onComplete: () -> Unit -) { - - sealed class Response { - - object Done : Response() - - data class Message(val type: Type, val text: String) : Response() { - enum class Type { INFO, SUCCESS, ERROR, TYPEQL } - } - - data class Value(val value: com.typedb.driver.api.concept.value.Value) : Response() - - sealed class Stream : Response() { - - val queue = LinkedBlockingQueue>() - - class JSONs : Stream() - class ConceptRows : Stream() - } -} - - companion object { - const val RESULT_ = "## Result> " - const val ERROR_ = "## Error> " - const val RUNNING_ = "## Running> " - const val COMPLETED = "## Completed" - const val TERMINATED = "## Terminated" - const val QUERY = "Query:" - const val QUERY_SUCCESS = "Success." - const val QUERY_NO_RESULT = "Query returned no results." - const val DEFINE_QUERY = "Define query:" - const val DEFINE_QUERY_SUCCESS = "Define query successfully defined new types in the schema." - const val UNDEFINE_QUERY = "Undefine query:" - const val UNDEFINE_QUERY_SUCCESS = "Undefine query successfully undefined types in the schema." - const val DELETE_QUERY = "Delete query:" - const val DELETE_QUERY_SUCCESS = "Delete query successfully deleted things from the database." - const val INSERT_QUERY = "Insert query:" - const val INSERT_QUERY_SUCCESS = "Insert query successfully inserted new things to the database:" - const val INSERT_QUERY_NO_RESULT = "Insert query did not insert any new thing to the database." - const val UPDATE_QUERY = "Update query:" - const val UPDATE_QUERY_SUCCESS = "Update query successfully updated things in the databases:" - const val UPDATE_QUERY_NO_RESULT = "Update query did not update any thing in the databases." - const val GET_QUERY = "Get query:" - const val GET_QUERY_SUCCESS = "Get query successfully matched concepts in the database:" - const val GET_QUERY_NO_RESULT = "Get query did not match any concepts in the database." - const val GET_AGGREGATE_QUERY = "Get Aggregate query:" - const val GET_AGGREGATE_QUERY_SUCCESS = "Get Aggregate query successfully calculated:" - const val GET_GROUP_QUERY = "Get Group query:" - const val GET_GROUP_QUERY_SUCCESS = "Get Group query successfully matched concept groups in the database:" - const val GET_GROUP_QUERY_NO_RESULT = "Get Group query did not match any concept groups in the database." - const val GET_GROUP_AGGREGATE_QUERY = "Get Group Aggregate query:" - const val GET_GROUP_AGGREGATE_QUERY_SUCCESS = - "Get Group Aggregate query successfully aggregated matched concept groups in the database:" - const val GET_GROUP_AGGREGATE_QUERY_NO_RESULT = - "Get Group Aggregate query did not match any concept groups to aggregate in the database." - const val FETCH_QUERY = "Fetch query:" - const val FETCH_QUERY_SUCCESS = "Fetch query successfully retrieved data from the database:" - const val FETCH_QUERY_NO_RESULT = "Fetch query did not retrieve any data from the database." - - private const val COUNT_DOWN_LATCH_PERIOD_MS: Long = 50 - private val LOGGER = KotlinLogging.logger {} - } - - var startTime: Long? = null - var endTime: Long? = null - val responses = LinkedBlockingQueue() - val isConsumed: Boolean get() = consumerLatch.count == 0L - val isRunning = AtomicBoolean(false) -private val consumerLatch = CountDownLatch(1) -private val coroutines = CoroutineScope(Dispatchers.Default) -private val hasStopSignal get() = transactionState.hasStopSignal -private val transaction get() = transactionState.transaction!! -private val onClose = LinkedBlockingQueue<() -> Unit>() - - fun onClose(function: () -> Unit) = onClose.put(function) - - fun setConsumed() = consumerLatch.countDown() - -private fun collectEmptyLine() = collectMessage(INFO, "") - -private fun collectMessage(type: Response.Message.Type, string: String) { - responses.put(Response.Message(type, string)) - } - - internal fun launch() = coroutines.launchAndHandle(notificationSrv, LOGGER) { - try { - isRunning.set(true) - startTime = System.currentTimeMillis() - runQuery(queries) -// runQueries(TypeQL.parseQueries(queries).collect(Collectors.toList())) - } catch (e: Exception) { - collectEmptyLine() - collectMessage(ERROR, ERROR_ + e.message) - } finally { - endTime = System.currentTimeMillis() - isRunning.set(false) - responses.add(Response.Done) - var isConsumed: Boolean - if (!hasStopSignal) { - do { - isConsumed = consumerLatch.count == 0L - if (!isConsumed) delay(COUNT_DOWN_LATCH_PERIOD_MS) - } while (!isConsumed && !hasStopSignal) - } - onComplete() - } - } - -private fun runQuery(query: String) { - if (hasStopSignal) return - - collectEmptyLine() - collectMessage(INFO, RUNNING_) - collectMessage(TYPEQL, query) - - val answer = transaction.query(query).resolve() - - if (answer.isOk) { - collectEmptyLine() - collectMessage(SUCCESS, RESULT_ + QUERY_SUCCESS) - return - } else if (answer.isConceptRows) { - val streamRaw = answer.asConceptRows().stream() - val stream = Response.Stream.ConceptRows() - - var started = false - var error = false - try { - collectEmptyLine() - streamRaw.peek { - if (started) return@peek - collectMessage(SUCCESS, RESULT_ + QUERY_SUCCESS) - responses.put(stream) - started = true - }.forEach { - if (hasStopSignal) return@forEach - stream.queue.put(Either.first(it)) - } - } catch (e: Exception) { - collectMessage(ERROR, ERROR_ + e.message) - error = true - } finally { - if (started) stream.queue.put(Either.second(Response.Done)) - if (error || hasStopSignal) collectMessage(ERROR, TERMINATED) - else if (started) collectMessage(INFO, COMPLETED) - else collectMessage(SUCCESS, RESULT_ + QUERY_NO_RESULT) - } - } else if (answer.isConceptDocuments) { - val streamRaw = answer.asConceptDocuments().stream() - val stream = Response.Stream.JSONs() - - var started = false - var error = false - try { - collectEmptyLine() - streamRaw.peek { - if (started) return@peek - collectMessage(SUCCESS, RESULT_ + QUERY_SUCCESS) - responses.put(stream) - started = true - }.forEach { - if (hasStopSignal) return@forEach - stream.queue.put(Either.first(it)) - } - } catch (e: Exception) { - collectMessage(ERROR, ERROR_ + e.message) - error = true - } finally { - if (started) stream.queue.put(Either.second(Response.Done)) - if (error || hasStopSignal) collectMessage(ERROR, TERMINATED) - else if (started) collectMessage(INFO, COMPLETED) - else collectMessage(SUCCESS, RESULT_ + QUERY_SUCCESS) - } - } else throw IllegalArgumentException() - } - -private fun runDefineQuery(query: TypeQLDefine) = runUnitQuery( - name = DEFINE_QUERY, - successMsg = DEFINE_QUERY_SUCCESS, - queryStr = query.toString() - ) { transaction.query(query.toString()).resolve() } - -private fun runUndefineQuery(query: TypeQLUndefine) = runUnitQuery( - name = UNDEFINE_QUERY, - successMsg = UNDEFINE_QUERY_SUCCESS, - queryStr = query.toString() - ) { transaction.query(query.toString()).resolve() } - -private fun runDeleteQuery(query: TypeQLDelete) = runUnitQuery( - name = DELETE_QUERY, - successMsg = DELETE_QUERY_SUCCESS, - queryStr = query.toString() - ) { transaction.query(query.toString()).resolve() } - -private fun runInsertQuery(query: TypeQLInsert) = runStreamingQuery( - name = INSERT_QUERY, - successMsg = INSERT_QUERY_SUCCESS, - noResultMsg = INSERT_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.ConceptRows() - ) { transaction.query(query.toString()).resolve().asConceptRows().stream() } // TODO: prefetch = true option - -private fun runUpdateQuery(query: TypeQLUpdate) = runStreamingQuery( - name = UPDATE_QUERY, - successMsg = UPDATE_QUERY_SUCCESS, - noResultMsg = UPDATE_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.ConceptRows() - ) { transaction.query(query.toString()).resolve().asConceptRows().stream() } - -private fun runGetQuery(query: TypeQLGet) = runStreamingQuery( - name = GET_QUERY, - successMsg = GET_QUERY_SUCCESS, - noResultMsg = GET_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.ConceptRows() - ) { -// if (query.modifiers().limit().isPresent) { - transaction.query(query.toString()).resolve().asConceptRows().stream() -// } else { -// val queryWithLimit = TypeQLGet.Limited(query, preferenceSrv.getQueryLimit) -// transaction.query().get(queryWithLimit) -// } - } - -private fun runGetAggregateQuery(query: TypeQLGet.Aggregate) { - collectMessage(INFO, "runGetAggregateQuery: unsupported") -// printQueryStart(GET_AGGREGATE_QUERY, query.toString()) -// val result = transaction.query(query.toString()).resolve() -// collectEmptyLine() -// collectMessage(SUCCESS, RESULT_ + GET_AGGREGATE_QUERY_SUCCESS) -// responses.put(Response.Value(result)) - } - -private fun runFetchQuery(query: TypeQLFetch) = runStreamingQuery( - name = FETCH_QUERY, - successMsg = FETCH_QUERY_SUCCESS, - noResultMsg = FETCH_QUERY_NO_RESULT, - queryStr = query.toString(), - stream = Response.Stream.JSONs() - ) { -// if (query.modifiers().limit().isPresent) { - transaction.query(query.toString()).resolve().asConceptDocuments().stream() -// } else { -// val queryWithLimit = TypeQLFetch.Limited(query, preferenceSrv.getQueryLimit) -// transaction.query().fetch(queryWithLimit) -// } - } - -private fun runUnitQuery(name: String, successMsg: String, queryStr: String, queryFn: () -> Unit) { - printQueryStart(name, queryStr) - queryFn() - collectEmptyLine() - collectMessage(SUCCESS, RESULT_ + successMsg) - } - -private fun runStreamingQuery( - name: String, - successMsg: String, - noResultMsg: String, - queryStr: String, - stream: Response.Stream, - queryFn: () -> Stream -) { - printQueryStart(name, queryStr) - collectResponseStream(queryFn(), successMsg, noResultMsg, stream) - } - -private fun printQueryStart(name: String, queryStr: String) { - collectEmptyLine() - collectMessage(INFO, RUNNING_ + name) - collectMessage(TYPEQL, queryStr) - } - -private fun collectResponseStream( - results: Stream, - successMsg: String, - noResultMsg: String, - stream: Response.Stream -) { - var started = false - var error = false - try { - collectEmptyLine() - results.peek { - if (started) return@peek - collectMessage(SUCCESS, RESULT_ + successMsg) - responses.put(stream) - started = true - }.forEach { - if (hasStopSignal) return@forEach - stream.queue.put(Either.first(it)) - } - } catch (e: Exception) { - collectMessage(ERROR, ERROR_ + e.message) - error = true - } finally { - if (started) stream.queue.put(Either.second(Response.Done)) - if (error || hasStopSignal) collectMessage(ERROR, TERMINATED) - else if (started) collectMessage(INFO, COMPLETED) - else collectMessage(SUCCESS, RESULT_ + noResultMsg) - } - } - - fun close() { - transactionState.sendStopSignal() - onClose.forEach { it() } - } -} - */ diff --git a/src/service/schema-state.service.ts b/src/service/schema-state.service.ts index 52db2e8ae..4e84499fe 100644 --- a/src/service/schema-state.service.ts +++ b/src/service/schema-state.service.ts @@ -12,9 +12,11 @@ import { createSigmaRenderer, GraphVisualiser } from "../framework/graph-visuali import { defaultSigmaSettings } from "../framework/graph-visualiser/defaults"; import { newVisualGraph } from "../framework/graph-visualiser/graph"; import { Layouts } from "../framework/graph-visualiser/layouts"; -import { ApiResponse, isApiErrorResponse, QueryResponse } from "../framework/typedb-driver/response"; +import { AttributeType, EntityType, RelationType, RoleType, Type } from "../framework/typedb-driver/concept"; +import { ApiOkResponse, ApiResponse, ConceptRowsQueryResponse, isApiErrorResponse, QueryResponse } from "../framework/typedb-driver/response"; import { DriverState } from "./driver-state.service"; import { SnackbarService } from "./snackbar.service"; +import {updateAutocomleteSchemaFromDB} from "../framework/codemirror-lang-typeql"; const NO_SERVER_CONNECTED = `No server connected`; const NO_DATABASE_SELECTED = `No database selected`; @@ -22,20 +24,51 @@ const NO_DATABASE_SELECTED = `No database selected`; const schemaQueries = { typeHierarchy: `match { $t sub! $supertype; } or {$t sub $supertype; $t is $supertype; };`, ownedAttributes: `match { $t owns $attr; not { $t sub! $sown; $sown owns $attr; }; };`, - roleplayers: `match { $t relates $related; not { $t sub! $srel; $srel relates $related; }; };`, - playableRoles: `match { $t plays $played; not { $t sub! $splay; $splay plays $played; }; };`, + relatedRoles: `match { $t relates $related; not { $t sub! $srel; $srel relates $related; }; };`, + playedRoles: `match { $t plays $played; not { $t sub! $splay; $splay plays $played; }; };`, } as const satisfies Record; const schemaQueriesList = Object.values(schemaQueries); type VisualiserStatus = "ok" | "running" | "noAnswers" | "error"; +export interface SchemaEntity extends EntityType { + supertype?: SchemaEntity; + subtypes: SchemaEntity[]; + ownedAttributes: SchemaAttribute[]; + playedRoles: SchemaRole[]; +} + +export interface SchemaRelation extends RelationType { + supertype?: SchemaRelation; + subtypes: SchemaRelation[]; + ownedAttributes: SchemaAttribute[]; + playedRoles: SchemaRole[]; + relatedRoles: SchemaRole[]; +} + +export interface SchemaAttribute extends AttributeType { + supertype?: SchemaAttribute; + subtypes: SchemaAttribute[]; +} + +export type SchemaConcept = SchemaEntity | SchemaRelation | SchemaAttribute; + +export type SchemaRole = RoleType; + +export interface Schema { + entities: Record; + relations: Record; + attributes: Record; +} + @Injectable({ providedIn: "root", }) export class SchemaState { readonly visualiser = new VisualiserState(); - queryResponses$ = new BehaviorSubject[] | null>(null); + queryResponses$ = new BehaviorSubject[] | null>(null); + readonly value$ = new BehaviorSubject(null); isRefreshing = false; readonly refreshDisabledReason$ = combineLatest([this.driver.status$, this.driver.database$]).pipe(map(([status, db]) => { if (status !== "connected") return NO_SERVER_CONNECTED; @@ -45,12 +78,20 @@ export class SchemaState { readonly refreshEnabled$ = this.refreshDisabledReason$.pipe(map(x => x == null)); constructor(private driver: DriverState, private snackbar: SnackbarService) { - (window as any)["schemaToolState"] = this; + (window as any)["schemaState"] = this; this.driver.database$.pipe( distinctUntilChanged((x, y) => x?.name === y?.name) ).subscribe(() => { this.refresh(); }); + this.queryResponses$.subscribe(data => { + this.push(data); + }); + this.value$.subscribe(schema => { + if (schema != null) { + updateAutocomleteSchemaFromDB(schema) + } + }) } refresh() { @@ -67,18 +108,32 @@ export class SchemaState { } this.initialiseOutput(); - const responses: ApiResponse[] = []; + const responses: ApiOkResponse[] = []; this.isRefreshing = true; this.driver.runBackgroundReadQueries(schemaQueriesList).pipe( finalize(() => { this.isRefreshing = false; }) ).subscribe({ - next: (res) => { responses.push(res); }, + next: (res) => { + if (res.ok.answerType !== `conceptRows`) throw `Unexpected answerType: '${res.ok.answerType}' (expected 'conceptRows')`; + responses.push(res as ApiOkResponse); + }, error: (err) => { this.handleQueryError(err); }, complete: () => { this.queryResponses$.next(responses); }, }); }); } + push(data: ApiOkResponse[] | null) { + if (!data) { + this.value$.next(null); + return; + } + + const schemaBuilder = new SchemaBuilder(data); + const schema = schemaBuilder.build(); + this.value$.next(schema); + } + private initialiseOutput() { this.visualiser.destroy(); this.visualiser.status = "running"; @@ -113,6 +168,224 @@ export class SchemaState { } } +function entityOf(entityType: EntityType): SchemaEntity { + return { + kind: entityType.kind, + label: entityType.label, + supertype: undefined, + subtypes: [], + ownedAttributes: [], + playedRoles: [], + }; +} + +function relationOf(relationType: RelationType): SchemaRelation { + return { + kind: relationType.kind, + label: relationType.label, + supertype: undefined, + subtypes: [], + ownedAttributes: [], + playedRoles: [], + relatedRoles: [], + }; +} + +function attributeOf(attributeType: AttributeType): SchemaAttribute { + return { + kind: attributeType.kind, + label: attributeType.label, + supertype: undefined, + subtypes: [], + valueType: attributeType.valueType, + }; +} + +class SchemaBuilder { + readonly typeHierarchy: ConceptRowsQueryResponse; + readonly ownedAttributes: ConceptRowsQueryResponse; + readonly relatedRoles: ConceptRowsQueryResponse; + readonly playedRoles: ConceptRowsQueryResponse; + readonly entityTypes = {} as Record; + readonly relationTypes = {} as Record; + readonly attributeTypes = {} as Record; + + constructor(data: ApiOkResponse[]) { + const [typeHierarchy, ownedAttributes, relatedRoles, playedRoles] = data.map(x => x.ok); + this.typeHierarchy = typeHierarchy; + this.ownedAttributes = ownedAttributes; + this.relatedRoles = relatedRoles; + this.playedRoles = playedRoles; + } + + build(): Schema { + this.populateConcepts(); + this.buildTypeHierarchy(); + this.attachOwnedAttributes(); + this.attachPlayedRoles(); + this.attachRelatedRoles(); + return { + entities: this.entityTypes, + relations: this.relationTypes, + attributes: this.attributeTypes, + }; + } + + private populateConcepts() { + for (const answer of this.typeHierarchy.answers) { + const [type, supertype] = [answer.data["t"], answer.data["supertype"]]; + if (!type || !supertype) throw this.unexpectedTypeHierarchyAnswer(answer); + switch (type.kind) { + case "entityType": + this.entityTypes[type.label] = this.entityTypes[type.label] ?? entityOf(type); + break; + case "relationType": + this.relationTypes[type.label] = this.relationTypes[type.label] ?? relationOf(type); + break; + case "attributeType": + this.attributeTypes[type.label] = this.attributeTypes[type.label] ?? attributeOf(type); + break; + case "roleType": + continue; + default: + throw this.unexpectedTypeHierarchyAnswer(answer); + } + } + } + + private buildTypeHierarchy() { + for (const answer of this.typeHierarchy.answers) { + const [type, supertype] = [answer.data["t"], answer.data["supertype"]] as Type[]; + if (type.label === supertype.label) continue; + let node: SchemaConcept; + let supernode: SchemaConcept; + switch (type.kind) { + case "entityType": + node = this.expectEntityType(type.label); + supernode = this.expectEntityType(supertype.label); + break; + case "relationType": + node = this.expectRelationType(type.label); + supernode = this.expectRelationType(supertype.label); + break; + case "attributeType": + node = this.expectAttributeType(type.label); + supernode = this.expectAttributeType(supertype.label); + break; + case "roleType": + continue; + default: + throw this.unexpectedTypeHierarchyAnswer(answer); + } + node.supertype = supernode; + (supernode.subtypes as SchemaConcept[]).push(node); + } + } + + private attachOwnedAttributes() { + for (const answer of this.ownedAttributes.answers) { + const [ownerType, ownedAttr] = [answer.data["t"], answer.data["attr"]]; + if (!ownerType || !ownedAttr || ownedAttr.kind !== "attributeType") throw this.unexpectedOwnedAttributesAnswer(answer); + let ownerNode: SchemaConcept; + const ownedAttrNode: SchemaAttribute = this.expectAttributeType(ownedAttr.label); + switch (ownerType.kind) { + case "entityType": + ownerNode = this.expectEntityType(ownerType.label); + break; + case "relationType": + ownerNode = this.expectRelationType(ownerType.label); + break; + default: + throw this.unexpectedOwnedAttributesAnswer(answer); + } + this.propagateOwnedAttributes(ownerNode, ownedAttrNode); + } + } + + private propagateOwnedAttributes(ownerNode: SchemaEntity | SchemaRelation, ownedAttrNode: SchemaAttribute) { + ownerNode.ownedAttributes.push(ownedAttrNode); + for (const ownerSubnode of ownerNode.subtypes) { + this.propagateOwnedAttributes(ownerSubnode, ownedAttrNode); + } + } + + private attachRelatedRoles() { + for (const answer of this.relatedRoles.answers) { + const [rel, role] = [answer.data["t"], answer.data["related"]]; + if (!rel || !role || rel.kind !== "relationType" || role.kind !== "roleType") throw this.unexpectedRoleplayersAnswer(answer); + const relNode: SchemaRelation = this.expectRelationType(rel.label); + this.propagateRelatedRoles(relNode, role); + } + } + + private propagateRelatedRoles(relNode: SchemaRelation, role: RoleType) { + relNode.relatedRoles.push(role); + for (const relSubnode of relNode.subtypes) { + this.propagateRelatedRoles(relSubnode, role); + } + } + + private attachPlayedRoles() { + for (const answer of this.playedRoles.answers) { + const [obj, role] = [answer.data["t"], answer.data["played"]]; + if (!obj || !role || role.kind !== "roleType") throw this.unexpectedPlayedRolesAnswer(answer); + let objNode: SchemaEntity | SchemaRelation; + switch (obj.kind) { + case "entityType": + objNode = this.expectEntityType(obj.label); + break; + case "relationType": + objNode = this.expectRelationType(obj.label); + break; + default: + throw this.unexpectedPlayedRolesAnswer(answer); + } + this.propagatePlayedRoles(objNode, role); + } + } + + private propagatePlayedRoles(objNode: SchemaEntity | SchemaRelation, role: RoleType) { + objNode.playedRoles.push(role); + for (const objSubnode of objNode.subtypes) { + this.propagatePlayedRoles(objSubnode, role); + } + } + + private expectEntityType(label: string): SchemaEntity { + const type = this.entityTypes[label]; + if (!type) throw `Missing expected entity type in schema with label '${label}'`; + return type; + } + + private expectRelationType(label: string): SchemaRelation { + const type = this.relationTypes[label]; + if (!type) throw `Missing expected relation type in schema with label '${label}'`; + return type; + } + + private expectAttributeType(label: string): SchemaAttribute { + const type = this.attributeTypes[label]; + if (!type) throw `Missing expected attribute type in schema with label '${label}'`; + return type; + } + + private unexpectedTypeHierarchyAnswer(answer: ConceptRowsQueryResponse["answers"][number]) { + return `Unexpected type hierarchy answer: ${JSON.stringify(answer.data)}`; + } + + private unexpectedOwnedAttributesAnswer(answer: ConceptRowsQueryResponse["answers"][number]) { + return `Unexpected owned attributes answer: ${JSON.stringify(answer.data)}`; + } + + private unexpectedPlayedRolesAnswer(answer: ConceptRowsQueryResponse["answers"][number]) { + return `Unexpected played roles answer: ${JSON.stringify(answer.data)}`; + } + + private unexpectedRoleplayersAnswer(answer: ConceptRowsQueryResponse["answers"][number]) { + return `Unexpected related roles answer: ${JSON.stringify(answer.data)}`; + } +} + export class VisualiserState { status: VisualiserStatus = "ok"; diff --git a/styles/base.scss b/styles/base.scss index fc8dc302c..abf16247a 100644 --- a/styles/base.scss +++ b/styles/base.scss @@ -509,7 +509,7 @@ button i + span, button span + i { .action-bar { flex: 0 0 49px; display: flex; - margin: 16px 0 0 0; + padding: 19px 0; position: sticky; top: 0; background-color: primary.$black-purple; diff --git a/styles/material.scss b/styles/material.scss index ddb83ccae..f45367e6f 100644 --- a/styles/material.scss +++ b/styles/material.scss @@ -809,4 +809,32 @@ body { container-color: secondary.$mid-deep-grey, container-shape: shapes.$border-radius, )); + + .mdc-tooltip { + --mdc-plain-tooltip-supporting-text-size: 14px; + --mdc-plain-tooltip-supporting-line-height: 22px; + --mdc-plain-tooltip-supporting-text-tracking: var(--body-letter-spacing); + --mdc-plain-tooltip-container-color: #{secondary.$mid-deep-grey}; + --mdc-plain-tooltip-container-shape: #{shapes.$border-radius}; + } + + .mdc-tooltip__surface { + padding: 6px 10px; + } + + /* Tree */ + @include mat.tree-overrides(( + container-background-color: transparent, + node-min-height: 30px, + node-text-size: 14px, + node-text-weight: typography.$regular, + )); + + mat-tree-node[aria-level="2"] { + padding-left: 16px !important; /* overrides inline style from Angular Material */ + } + + mat-tree-node[mattreenodetoggle] { + cursor: pointer; + } }