From 7d1313c9c1d1a97b38ad4fb295f922adfcae130f Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 18 May 2022 13:40:04 -0400 Subject: [PATCH 01/24] initial toolbox button styling based on scratch colors --- src/css/index.css | 4 ++-- src/css/toolbox.css | 4 +++- src/docs/add-var.json | 5 ++++- src/docs/add.json | 5 ++++- src/docs/and.json | 5 ++++- src/docs/assign-add.json | 5 ++++- src/docs/assign-div.json | 5 ++++- src/docs/assign-mult.json | 5 ++++- src/docs/assign-sub.json | 5 ++++- src/docs/assign.json | 5 ++++- src/docs/break.json | 5 ++++- src/docs/choice.json | 5 ++++- src/docs/comp-eq.json | 5 ++++- src/docs/comp-gt.json | 5 ++++- src/docs/comp-gte.json | 5 ++++- src/docs/comp-lt.json | 5 ++++- src/docs/comp-lte.json | 5 ++++- src/docs/comp-ne.json | 5 ++++- src/docs/div.json | 5 ++++- src/docs/elif.json | 5 ++++- src/docs/else.json | 5 ++++- src/docs/f-str-item.json | 5 ++++- src/docs/f-str.json | 5 ++++- src/docs/false.json | 5 ++++- src/docs/find.json | 5 ++++- src/docs/floor-div.json | 5 ++++- src/docs/for.json | 5 ++++- src/docs/if.json | 5 ++++- src/docs/import.json | 5 ++++- src/docs/in.json | 5 ++++- src/docs/input.json | 5 ++++- src/docs/join.json | 5 ++++- src/docs/len.json | 5 ++++- src/docs/list-append.json | 5 ++++- src/docs/list-element-assign.json | 5 ++++- src/docs/list-index.json | 5 ++++- src/docs/list-item.json | 5 ++++- src/docs/list-literal.json | 5 ++++- src/docs/mod.json | 5 ++++- src/docs/mult.json | 5 ++++- src/docs/not-in.json | 5 ++++- src/docs/not.json | 5 ++++- src/docs/num.json | 5 ++++- src/docs/or.json | 5 ++++- src/docs/print.json | 5 ++++- src/docs/randint.json | 5 ++++- src/docs/range.json | 5 ++++- src/docs/replace.json | 5 ++++- src/docs/split.json | 5 ++++- src/docs/str.json | 5 ++++- src/docs/sub.json | 5 ++++- src/docs/to-int.json | 5 ++++- src/docs/to-str.json | 5 ++++- src/docs/true.json | 5 ++++- src/docs/while.json | 5 ++++- src/editor/toolbox.ts | 10 ++++++++-- 56 files changed, 225 insertions(+), 58 deletions(-) diff --git a/src/css/index.css b/src/css/index.css index 5b21493..d773f75 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -33,7 +33,7 @@ body { } .button { - color: #0d0c22; + color: white; cursor: pointer; border-radius: 4px; display: flex; @@ -49,7 +49,7 @@ body { width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; - border: solid 2px #fff; + border: none; } .button.button-invalid { diff --git a/src/css/toolbox.css b/src/css/toolbox.css index 41f0395..2115d1a 100644 --- a/src/css/toolbox.css +++ b/src/css/toolbox.css @@ -51,6 +51,7 @@ hole1 { box-shadow: 0px 0px 1px 1px #0000005e inset; margin: 0px 3px; display: inline-block; + background-color:white; } hole2 { @@ -60,6 +61,7 @@ hole2 { border: dashed 1px #c7c7c7; min-width: 12px; height: 16px; + background-color: white; } #user-variables { @@ -628,4 +630,4 @@ hole2 { .cascaded-menu-extra-item .inline-var { color: #aa5bc8; -} +} \ No newline at end of file diff --git a/src/docs/add-var.json b/src/docs/add-var.json index 9e6d8a1..ffcc615 100644 --- a/src/docs/add-var.json +++ b/src/docs/add-var.json @@ -25,5 +25,8 @@ ] } ], - "search-queries": ["create new variable", "variable", "var"] + "search-queries": ["create new variable", "variable", "var"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/add.json b/src/docs/add.json index 6b00a7b..065d4aa 100644 --- a/src/docs/add.json +++ b/src/docs/add.json @@ -16,5 +16,8 @@ "example": "a = \"hello\"\nb = \"world\"\nprint((a + \" \" + b))" } ], - "search-queries": ["add", "sum"] + "search-queries": ["add", "sum"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/and.json b/src/docs/and.json index 490f1cf..57f05cd 100644 --- a/src/docs/and.json +++ b/src/docs/and.json @@ -16,5 +16,8 @@ "example": "a = 5\nb = 7\nc=2\n\nif ((a > c) and (b > c)):\n\tprint(\"Both expressions are true.\")" } ], - "search-queries": ["and", "both", "and operator"] + "search-queries": ["and", "both", "and operator"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/assign-add.json b/src/docs/assign-add.json index 1ba8cad..2a599d8 100644 --- a/src/docs/assign-add.json +++ b/src/docs/assign-add.json @@ -11,5 +11,8 @@ "example": "a = 2\na += 2\nprint(a)" } ], - "search-queries": ["add to variable"] + "search-queries": ["add to variable"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/assign-div.json b/src/docs/assign-div.json index 694d7bc..169fdea 100644 --- a/src/docs/assign-div.json +++ b/src/docs/assign-div.json @@ -11,5 +11,8 @@ "example": "a = 10\na /= 2\nprint(a)" } ], - "search-queries": ["divide variable"] + "search-queries": ["divide variable"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/assign-mult.json b/src/docs/assign-mult.json index 300bd01..7d7e1e1 100644 --- a/src/docs/assign-mult.json +++ b/src/docs/assign-mult.json @@ -11,5 +11,8 @@ "example": "a = 5\na *= 2\nprint(a)" } ], - "search-queries": ["multiply variable"] + "search-queries": ["multiply variable"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/assign-sub.json b/src/docs/assign-sub.json index 01082c0..b2e4f0f 100644 --- a/src/docs/assign-sub.json +++ b/src/docs/assign-sub.json @@ -11,5 +11,8 @@ "example": "a = 5\na -= 2\nprint(a)" } ], - "search-queries": ["subtract from variable", "deduct from variable"] + "search-queries": ["subtract from variable", "deduct from variable"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/assign.json b/src/docs/assign.json index 2e7265f..58c2ad9 100644 --- a/src/docs/assign.json +++ b/src/docs/assign.json @@ -11,5 +11,8 @@ "example": "a = 123\nprint(a)\na=321\nprint(a)" } ], - "search-queries": ["set", "assign", "assign variable", "set variable", "update value variable"] + "search-queries": ["set", "assign", "assign variable", "set variable", "update value variable"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/break.json b/src/docs/break.json index 20918e7..377ee8d 100644 --- a/src/docs/break.json +++ b/src/docs/break.json @@ -40,5 +40,8 @@ ] } ], - "search-queries": ["exit", "loop", "break"] + "search-queries": ["exit", "loop", "break"], + "styles":{ + "backgroundColor": "#FFAB19" + } } diff --git a/src/docs/choice.json b/src/docs/choice.json index d08ceb4..dca2002 100644 --- a/src/docs/choice.json +++ b/src/docs/choice.json @@ -11,5 +11,8 @@ "example": "from random import choice\n\nif choice([1, 2, 3, 4, 5, 6]) == 6:\n\tprint(\"Rolled Six!\")" } ], - "search-queries": ["choice", "random choice", "choose randomly from list", "select randomly from array"] + "search-queries": ["choice", "random choice", "choose randomly from list", "select randomly from array"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/comp-eq.json b/src/docs/comp-eq.json index 1bf5b5e..d221cf9 100644 --- a/src/docs/comp-eq.json +++ b/src/docs/comp-eq.json @@ -16,5 +16,8 @@ "example": "a = 5\nb = 5\nwhile a == b:\n\ta += 1\nprint(a)\nprint(a)" } ], - "search-queries": ["compare equal", "check equal", "are equal"] + "search-queries": ["compare equal", "check equal", "are equal"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/comp-gt.json b/src/docs/comp-gt.json index 4ed677e..f33c3fe 100644 --- a/src/docs/comp-gt.json +++ b/src/docs/comp-gt.json @@ -16,5 +16,8 @@ "example": "a = 1\nb = 5\nwhile b > a:\n\ta += 1\nprint(a)\nprint(a)" } ], - "search-queries": ["compare greater than", "compare", "check greater than"] + "search-queries": ["compare greater than", "compare", "check greater than"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/comp-gte.json b/src/docs/comp-gte.json index 32365d5..902e5d5 100644 --- a/src/docs/comp-gte.json +++ b/src/docs/comp-gte.json @@ -16,5 +16,8 @@ "example": "a = 1\nb = 5\nwhile b >= a:\n\ta += 1\nprint(a)\nprint(a)" } ], - "search-queries": ["compare greater than or equal", "compare", "check greater than or equal"] + "search-queries": ["compare greater than or equal", "compare", "check greater than or equal"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/comp-lt.json b/src/docs/comp-lt.json index aa5a4a2..647e71a 100644 --- a/src/docs/comp-lt.json +++ b/src/docs/comp-lt.json @@ -16,5 +16,8 @@ "example": "a = 1\nb = 5\nwhile a < b:\n\ta += 1\nprint(a)\nprint(a)" } ], - "search-queries": ["compare", "compare less than", "check less than"] + "search-queries": ["compare", "compare less than", "check less than"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/comp-lte.json b/src/docs/comp-lte.json index e48781c..974b237 100644 --- a/src/docs/comp-lte.json +++ b/src/docs/comp-lte.json @@ -16,5 +16,8 @@ "example": "a = 1\nb = 5\nwhile a <= b:\n\ta += 1\nprint(a)\nprint(a)" } ], - "search-queries": ["less than or equal", "compare", "compare less than or equal", "check"] + "search-queries": ["less than or equal", "compare", "compare less than or equal", "check"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/comp-ne.json b/src/docs/comp-ne.json index 010d510..d90c1ad 100644 --- a/src/docs/comp-ne.json +++ b/src/docs/comp-ne.json @@ -16,5 +16,8 @@ "example": "a = 1\nb = 5\nwhile a != b:\n\ta += 1\nprint(a)\nprint(a)" } ], - "search-queries": ["not equal", "compare not equal", "not equal to", "are not equal"] + "search-queries": ["not equal", "compare not equal", "not equal to", "are not equal"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/div.json b/src/docs/div.json index e813b39..cafd721 100644 --- a/src/docs/div.json +++ b/src/docs/div.json @@ -11,5 +11,8 @@ "example": "a = 2\nb = 10\nprint((b / a))" } ], - "search-queries": ["divide", "division", "divide numbers"] + "search-queries": ["divide", "division", "divide numbers"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/elif.json b/src/docs/elif.json index 250c7b4..b26c103 100644 --- a/src/docs/elif.json +++ b/src/docs/elif.json @@ -34,5 +34,8 @@ "id": "elif-after-elif" } ], - "search-queries": ["else if", "else", "else condition", "choose", "path", "elif"] + "search-queries": ["else if", "else", "else condition", "choose", "path", "elif"], + "styles":{ + "backgroundColor": "#FFAB19" + } } diff --git a/src/docs/else.json b/src/docs/else.json index 77e7d6b..f5567cd 100644 --- a/src/docs/else.json +++ b/src/docs/else.json @@ -11,5 +11,8 @@ "example": "a = 2\nif a > 3:\n\tprint(3)\nelif a > 4:\n\tprint(4)\nelif a == 5:\n\tprint(5)\nelif a > 6:\n\tprint(6)\nelse:\n\tprint(\"None of the above are true.\")" } ], - "search-queries": ["else", "otherwise", "if"] + "search-queries": ["else", "otherwise", "if"], + "styles":{ + "backgroundColor": "#FFAB19" + } } diff --git a/src/docs/f-str-item.json b/src/docs/f-str-item.json index 74cbc7c..ab0e305 100644 --- a/src/docs/f-str-item.json +++ b/src/docs/f-str-item.json @@ -11,5 +11,8 @@ "example": "age = 16\nname = \"Alex\"\nprint(f'My name is {name} and I am {age} years old')" } ], - "search-queries": ["formatted string item", "formatted text item"] + "search-queries": ["formatted string item", "formatted text item"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/f-str.json b/src/docs/f-str.json index c11d663..2789590 100644 --- a/src/docs/f-str.json +++ b/src/docs/f-str.json @@ -11,5 +11,8 @@ "example": "age = 16\nname = \"Alex\"\nprint(f'My name is {name} and I am {age} years old')" } ], - "search-queries": ["formatted string", "formatted text"] + "search-queries": ["formatted string", "formatted text"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/false.json b/src/docs/false.json index 523b0e9..1550d4c 100644 --- a/src/docs/false.json +++ b/src/docs/false.json @@ -11,5 +11,8 @@ "example": "if False :\n\tprint(\"will not print\")\n\nif True :\n\tprint(\"will print\")" } ], - "search-queries": ["false", "boolean"] + "search-queries": ["false", "boolean"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/find.json b/src/docs/find.json index b1df3e7..2d4945d 100644 --- a/src/docs/find.json +++ b/src/docs/find.json @@ -11,5 +11,8 @@ "example": "name = \"Zimmer\"\nindexOfE = name.find(\"e\")\nprint(indexOfE)" } ], - "search-queries": ["search text", "find text", "find string"] + "search-queries": ["search text", "find text", "find string"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/floor-div.json b/src/docs/floor-div.json index 0a88a50..500e33d 100644 --- a/src/docs/floor-div.json +++ b/src/docs/floor-div.json @@ -11,5 +11,8 @@ "example": "a = 2\nb = 7\nprint((b / a))" } ], - "search-queries": ["divide numbers", "divide integer", "divide floor"] + "search-queries": ["divide numbers", "divide integer", "divide floor"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/for.json b/src/docs/for.json index cb499e9..eb09cf7 100644 --- a/src/docs/for.json +++ b/src/docs/for.json @@ -58,5 +58,8 @@ "id": "loop-chars-of-str" } ], - "search-queries": ["for", "loop", "repeat", "go through", "iterate", "list"] + "search-queries": ["for", "loop", "repeat", "go through", "iterate", "list"], + "styles":{ + "backgroundColor": "#411b8c" + } } diff --git a/src/docs/if.json b/src/docs/if.json index 73bad14..0d6d494 100644 --- a/src/docs/if.json +++ b/src/docs/if.json @@ -25,5 +25,8 @@ "id": "check-val-variable" } ], - "search-queries": ["if", "condition", "conditional", "choose", "path"] + "search-queries": ["if", "condition", "conditional", "choose", "path"], + "styles":{ + "backgroundColor": "#FFAB19" + } } diff --git a/src/docs/import.json b/src/docs/import.json index 14c5e5a..24a5dab 100644 --- a/src/docs/import.json +++ b/src/docs/import.json @@ -16,5 +16,8 @@ "example": "from random import randint\nprint(randint(1, 6))" } ], - "search-queries": ["import module", "import random"] + "search-queries": ["import module", "import random"], + "styles":{ + "backgroundColor": "#FF4D6A" + } } diff --git a/src/docs/in.json b/src/docs/in.json index 41412d9..d0c5ed8 100644 --- a/src/docs/in.json +++ b/src/docs/in.json @@ -11,5 +11,8 @@ "example": "from random import randint\na = randint(1, 10)\nb = [1, 2, 3, 4, 5]\nif a in b:\n\tprint(\"a is inside b\")\n\nif a not in b:\n\tprint(\"a is not inside b\")" } ], - "search-queries": ["in list", "inside list", "is inside list", "check inside list", "check list includes"] + "search-queries": ["in list", "inside list", "is inside list", "check inside list", "check list includes"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/input.json b/src/docs/input.json index aa2b664..dcc4cde 100644 --- a/src/docs/input.json +++ b/src/docs/input.json @@ -11,5 +11,8 @@ "example": "name = input(\"Hi! What’s your name?\")\nprint(name)" } ], - "search-queries": ["input", "prompt", "text", "user", "ask input", "ask user"] + "search-queries": ["input", "prompt", "text", "user", "ask input", "ask user"], + "styles":{ + "backgroundColor": "#5CB1D6" + } } diff --git a/src/docs/join.json b/src/docs/join.json index 01b55a3..7e59b47 100644 --- a/src/docs/join.json +++ b/src/docs/join.json @@ -11,5 +11,8 @@ "example": "names = [\"Anna\", \"John\", \"Peter\"]\njoinedNames = \"-\".join(names)\nprint(joinedNames)" } ], - "search-queries": ["join string", "join text"] + "search-queries": ["join string", "join text"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/len.json b/src/docs/len.json index 4fdd027..dada84d 100644 --- a/src/docs/len.json +++ b/src/docs/len.json @@ -16,5 +16,8 @@ "example": "items = [1, 2, 3]\na = len(items)\nprint(a)" } ], - "search-queries": ["length of list", "length of string", "length of text"] + "search-queries": ["length of list", "length of string", "length of text"], + "styles":{ + "backgroundColor": "#5CB1D6" + } } diff --git a/src/docs/list-append.json b/src/docs/list-append.json index adb57ff..2f8ca50 100644 --- a/src/docs/list-append.json +++ b/src/docs/list-append.json @@ -23,5 +23,8 @@ "add to end list", "append to end list", "insert end list" - ] + ], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/list-element-assign.json b/src/docs/list-element-assign.json index b36a6f9..e426b91 100644 --- a/src/docs/list-element-assign.json +++ b/src/docs/list-element-assign.json @@ -21,5 +21,8 @@ "change value list index", "update item at index", "set value list index" - ] + ], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/list-index.json b/src/docs/list-index.json index d33b82b..ac7a46e 100644 --- a/src/docs/list-index.json +++ b/src/docs/list-index.json @@ -16,5 +16,8 @@ "example": "a = [1, 2, 3]\nif a[1] == 2:\n\tprint(\"The second element of the list is equal to 2.\")" } ], - "search-queries": ["access list item", "access list element", "access list element at index"] + "search-queries": ["access list item", "access list element", "access list element at index"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/list-item.json b/src/docs/list-item.json index 760e789..9d33fc1 100644 --- a/src/docs/list-item.json +++ b/src/docs/list-item.json @@ -11,5 +11,8 @@ "example": "a = []\nb = [1, 2]\nc = [1, 2, 3]" } ], - "search-queries": ["comma", "list item"] + "search-queries": ["comma", "list item"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/list-literal.json b/src/docs/list-literal.json index 3ed86bb..4888558 100644 --- a/src/docs/list-literal.json +++ b/src/docs/list-literal.json @@ -16,5 +16,8 @@ "example": "a = [\"cat\", \"dog\", \"parrot\"]\nb = [1, \"cat\", true]\nc = [1, [1, \"dog\"], \"cat\", [true]]" } ], - "search-queries": ["empty list", "create empty list", "create list", "array", "create empty array"] + "search-queries": ["empty list", "create empty list", "create list", "array", "create empty array"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/mod.json b/src/docs/mod.json index 1401770..5cc8863 100644 --- a/src/docs/mod.json +++ b/src/docs/mod.json @@ -11,5 +11,8 @@ "example": "print((29 % 5)) # prints 4\nprint((28 % 5)) # prints 3\nprint((27 % 5)) # prints 2\nprint((26 % 5)) # prints 1\nprint((25 % 5)) # prints 0" } ], - "search-queries": ["modulo", "remainder"] + "search-queries": ["modulo", "remainder"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/mult.json b/src/docs/mult.json index 77602a0..1d02664 100644 --- a/src/docs/mult.json +++ b/src/docs/mult.json @@ -11,5 +11,8 @@ "example": "a = 2\nb = 5\nprint((b * a))" } ], - "search-queries": ["multiply numbers", "multiply"] + "search-queries": ["multiply numbers", "multiply"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/not-in.json b/src/docs/not-in.json index ddb815f..69df0a7 100644 --- a/src/docs/not-in.json +++ b/src/docs/not-in.json @@ -11,5 +11,8 @@ "example": "from random import randint\na = randint(1, 10)\nb = [1, 2, 3, 4, 5]\nif a in b:\n\tprint(\"a is inside b\")\n\nif a not in b:\n\tprint(\"a is not inside b\")" } ], - "search-queries": ["not in", "not inside list", "not within list", "does not include"] + "search-queries": ["not in", "not inside list", "not within list", "does not include"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/not.json b/src/docs/not.json index bdf6b8d..1aeadd9 100644 --- a/src/docs/not.json +++ b/src/docs/not.json @@ -11,5 +11,8 @@ "example": "a = 2\nb = 2\nif not (a == b):\n\tprint(\"Will not print.\")\n\nif not (b != a):\n\tprint(\"b is equal to a\")" } ], - "search-queries": ["negate", "not", "logical", "boolean", "opposite"] + "search-queries": ["negate", "not", "logical", "boolean", "opposite"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/num.json b/src/docs/num.json index 6289373..d4816be 100644 --- a/src/docs/num.json +++ b/src/docs/num.json @@ -10,5 +10,8 @@ "id": "ex-num", "example": "age = 21\nprint(age)\nprint(21)" } - ] + ], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/or.json b/src/docs/or.json index bd74815..1cb1b7f 100644 --- a/src/docs/or.json +++ b/src/docs/or.json @@ -16,5 +16,8 @@ "example": "a = 5\nb = 7\nc=2\n\nif ((a > c) and (b < c)):\n\tprint(\"Only the first expression is true.\")" } ], - "search-queries": ["conditional", "or", "logical", "boolean", "one of"] + "search-queries": ["conditional", "or", "logical", "boolean", "one of"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/print.json b/src/docs/print.json index 46aa743..bcc989f 100644 --- a/src/docs/print.json +++ b/src/docs/print.json @@ -16,5 +16,8 @@ "example": "a = \"abc\"\nprint(a)" } ], - "search-queries": ["output", "say", "print", "print output", "console", "write", "see output"] + "search-queries": ["output", "say", "print", "print output", "console", "write", "see output"], + "styles":{ + "backgroundColor": "#9966FF" + } } diff --git a/src/docs/randint.json b/src/docs/randint.json index 2055944..b08fbe5 100644 --- a/src/docs/randint.json +++ b/src/docs/randint.json @@ -11,5 +11,8 @@ "example": "a = randint(0, 10)\nprint(a)" } ], - "search-queries": ["random number", "random integer", "randint", "random between"] + "search-queries": ["random number", "random integer", "randint", "random between"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/range.json b/src/docs/range.json index 7e28f37..f4c5b80 100644 --- a/src/docs/range.json +++ b/src/docs/range.json @@ -11,5 +11,8 @@ "example": "for i in range(10):\n\tprint(i)" } ], - "search-queries": ["range", "sequence", "iterate", "for loop"] + "search-queries": ["range", "sequence", "iterate", "for loop"], + "styles":{ + "backgroundColor": "#2f116b" + } } diff --git a/src/docs/replace.json b/src/docs/replace.json index de6d013..e81cd69 100644 --- a/src/docs/replace.json +++ b/src/docs/replace.json @@ -11,5 +11,8 @@ "example": "name = \"John\"\nname = name.replace(\"oh\", \"a\")\nprint(name)" } ], - "search-queries": ["replace", "replace text", "replace string"] + "search-queries": ["replace", "replace text", "replace string"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/split.json b/src/docs/split.json index 59cc25d..c38f305 100644 --- a/src/docs/split.json +++ b/src/docs/split.json @@ -11,5 +11,8 @@ "example": "name = \"Split-this-text-on-hyphen\"\nsplitName = name.split(\"-\")\nprint(splitName)" } ], - "search-queries": ["split", "split text", "split string"] + "search-queries": ["split", "split text", "split string"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/str.json b/src/docs/str.json index f483e50..72cc829 100644 --- a/src/docs/str.json +++ b/src/docs/str.json @@ -11,5 +11,8 @@ "example": "fruit = \"apple\"\nprint(fruit)\nprint(\"apple\")" } ], - "search-queries": ["create text", "create string", "empty string", "empty text"] + "search-queries": ["create text", "create string", "empty string", "empty text"], + "styles":{ + "backgroundColor": "#FF8C1A" + } } diff --git a/src/docs/sub.json b/src/docs/sub.json index 40170f4..1dbf92c 100644 --- a/src/docs/sub.json +++ b/src/docs/sub.json @@ -11,5 +11,8 @@ "example": "a = 20\nb = 10\nprint((a - b))" } ], - "search-queries": ["subtract numbers", "subtraction", "deduct"] + "search-queries": ["subtract numbers", "subtraction", "deduct"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/to-int.json b/src/docs/to-int.json index 6d0b786..7296977 100644 --- a/src/docs/to-int.json +++ b/src/docs/to-int.json @@ -11,5 +11,8 @@ "example": "a = \"2\"\nb = \"5\"\ntext_add = a + b\nnumber_add = int(a) + int(b)" } ], - "search-queries": ["integer cast", "number convert", "convert to number", "convert to integer"] + "search-queries": ["integer cast", "number convert", "convert to number", "convert to integer"], + "styles":{ + "backgroundColor": "#a239bf" + } } diff --git a/src/docs/to-str.json b/src/docs/to-str.json index 623fe52..c921ec9 100644 --- a/src/docs/to-str.json +++ b/src/docs/to-str.json @@ -20,5 +20,8 @@ "example": "a = 2\nc = \"2\"\nprint(str(a) == c)" } ], - "search-queries": ["string cast", "text convert", "convert to string", "convert to text"] + "search-queries": ["string cast", "text convert", "convert to string", "convert to text"], + "styles":{ + "backgroundColor": "#a239bf" + } } diff --git a/src/docs/true.json b/src/docs/true.json index d025075..b55124c 100644 --- a/src/docs/true.json +++ b/src/docs/true.json @@ -11,5 +11,8 @@ "example": "if True :\n\tprint(\"will print\")\n\nif False :\n\tprint(\"will not print\")" } ], - "search-queries": ["true", "boolean"] + "search-queries": ["true", "boolean"], + "styles":{ + "backgroundColor": "#59C059" + } } diff --git a/src/docs/while.json b/src/docs/while.json index a85b0b7..af8d896 100644 --- a/src/docs/while.json +++ b/src/docs/while.json @@ -46,5 +46,8 @@ "condition", "conditional", "repeat condition" - ] + ], + "styles":{ + "backgroundColor": "#FFAB19" + } } diff --git a/src/editor/toolbox.ts b/src/editor/toolbox.ts index 9bfd2b4..2e8c985 100644 --- a/src/editor/toolbox.ts +++ b/src/editor/toolbox.ts @@ -222,11 +222,12 @@ export class ToolboxController { export class ToolboxButton { container: HTMLDivElement; - constructor(text: string, domId?: string, code?: CodeConstruct) { + constructor(text: string, domId?: string, btnColor?: string, code?: CodeConstruct) { this.container = document.createElement("div"); this.container.classList.add("var-button-container"); const button = document.createElement("div"); + button.style.backgroundColor = btnColor; button.classList.add("button"); if (!(code instanceof Expression) && !(code instanceof Modifier)) { @@ -256,7 +257,12 @@ export class ToolboxButton { } static createToolboxButtonFromJsonObj(action: EditCodeAction) { - return new ToolboxButton(action.optionName, action.cssId, action.getCode()); + return new ToolboxButton( + action.optionName, + action.cssId, + action.documentation.styles.backgroundColor, + action.getCode() + ); } divButtonVisualMode(insertionType: InsertionType) { From b5e3320aee4bb651e96b6e55166ef698a955a4cf Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 18 May 2022 13:46:11 -0400 Subject: [PATCH 02/24] updated range.json color --- src/docs/range.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/range.json b/src/docs/range.json index f4c5b80..7439910 100644 --- a/src/docs/range.json +++ b/src/docs/range.json @@ -13,6 +13,6 @@ ], "search-queries": ["range", "sequence", "iterate", "for loop"], "styles":{ - "backgroundColor": "#2f116b" + "backgroundColor": "#411b8c" } } From 449215f91ce75db6dc81fbd753656eae093a8a65 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 18 May 2022 15:45:25 -0400 Subject: [PATCH 03/24] adding commas --- src/editor/action-executor.ts | 38 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index 0c91359..516c43b 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -33,7 +33,7 @@ import { ValueOperationExpr, VarAssignmentStmt, VariableReferenceExpr, - VarOperationStmt + VarOperationStmt, } from "../syntax-tree/ast"; import { rebuildBody, replaceInBody } from "../syntax-tree/body"; import { Callback, CallbackType } from "../syntax-tree/callback"; @@ -49,7 +49,7 @@ import { Tooltip, TYPE_MISMATCH_ANY, TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR, - TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR + TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR, } from "../syntax-tree/consts"; import { Module } from "../syntax-tree/module"; import { Reference } from "../syntax-tree/scope"; @@ -1218,31 +1218,33 @@ export class ActionExecutor { } case EditActionType.DeleteBinaryOperatorItem: { - if(!(context.token.rootNode instanceof BinaryOperatorExpr)) break; - - let leftOperand = context.token.rootNode.getLeftOperand() - let rightOperand = context.token.rootNode.getRightOperand() - - if(leftOperand === context.token){ - this.replaceCode(context.token.rootNode, rightOperand) - } - else{ - this.replaceCode(context.token.rootNode, leftOperand) + if (!(context.token.rootNode instanceof BinaryOperatorExpr)) break; + + let leftOperand = context.token.rootNode.getLeftOperand(); + let rightOperand = context.token.rootNode.getRightOperand(); + + if (leftOperand === context.token) { + this.replaceCode(context.token.rootNode, rightOperand); + } else { + this.replaceCode(context.token.rootNode, leftOperand); } break; } - case EditActionType.DeleteFunctionCallExpr:{ + case EditActionType.DeleteFunctionCallExpr: { this.deleteCode(context.token.rootNode); break; } - case EditActionType.DeleteFunctionCallExprItem:{ + case EditActionType.DeleteFunctionCallExprItem: { let rootNode = context.token.rootNode; let replacementTkn; - if(!(rootNode instanceof FunctionCallExpr)) break; - for(let i=0; i Date: Tue, 24 May 2022 10:09:18 -0400 Subject: [PATCH 04/24] color coding blocks v1 --- src/editor/action-executor.ts | 4373 +++++++++++---------- src/editor/action-filter.ts | 5 +- src/editor/consts.ts | 397 +- src/editor/event-router.ts | 31 +- src/editor/toolbox.ts | 3 +- src/messages/messages.ts | 15 +- src/suggestions/suggestions-controller.ts | 5 +- src/syntax-tree/ast.ts | 107 +- 8 files changed, 2631 insertions(+), 2305 deletions(-) diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index 516c43b..5851c1a 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -1,2163 +1,2210 @@ -import { Position, Range } from "monaco-editor"; -import { ErrorMessage } from "../messages/error-msg-generator"; -import { ConstructHighlight, ScopeHighlight } from "../messages/messages"; -import { - AssignmentModifier, - AutocompleteTkn, - BinaryOperatorExpr, - CodeConstruct, - EditableTextTkn, - ElseStatement, - EmptyLineStmt, - EmptyOperatorTkn, - Expression, - FormattedStringCurlyBracketsExpr, - FormattedStringExpr, - FunctionCallExpr, - IdentifierTkn, - IfStatement, - Importable, - ImportStatement, - ListAccessModifier, - ListLiteralExpression, - LiteralValExpr, - MethodCallModifier, - Modifier, - NonEditableTkn, - OperatorTkn, - Statement, - TemporaryStmt, - Token, - TypedEmptyExpr, - UnaryOperatorExpr, - ValueOperationExpr, - VarAssignmentStmt, - VariableReferenceExpr, - VarOperationStmt, -} from "../syntax-tree/ast"; -import { rebuildBody, replaceInBody } from "../syntax-tree/body"; -import { Callback, CallbackType } from "../syntax-tree/callback"; -import { - addClassToDraftModeResolutionButton, - AutoCompleteType, - BuiltInFunctions, - getOperatorCategory, - IgnoreConversionRecord, - PythonKeywords, - StringRegex, - TAB_SPACES, - Tooltip, - TYPE_MISMATCH_ANY, - TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR, - TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR, -} from "../syntax-tree/consts"; -import { Module } from "../syntax-tree/module"; -import { Reference } from "../syntax-tree/scope"; -import { TypeChecker } from "../syntax-tree/type-checker"; -import { getUserFriendlyType, isImportable } from "../utilities/util"; -import { LogEvent, Logger, LogType } from "./../logger/analytics"; -import { BinaryOperator, DataType, InsertionType } from "./../syntax-tree/consts"; -import { EditCodeAction } from "./action-filter"; -import { Actions, EditActionType, InsertActionType } from "./consts"; -import { EditAction } from "./data-types"; -import { Context, UpdatableContext } from "./focus"; - -export class ActionExecutor { - module: Module; - - constructor(module: Module) { - this.module = module; - } - - execute(action: EditAction, providedContext?: Context, e?: KeyboardEvent): boolean { - const pressedKey = e?.key; - let context = providedContext ? providedContext : this.module.focus.getContext(); - - let preventDefaultEvent = true; - let flashGreen = false; - - if (action?.data?.autocompleteData) flashGreen = true; - - let { eventType, eventData } = this.getLogEventSource(action?.data?.source); - - switch (action.type) { - case EditActionType.OpenAutocomplete: { - const autocompleteTkn = new AutocompleteTkn( - action.data.firstChar, - action.data.autocompleteType, - action.data.validMatches - ); - - autocompleteTkn.subscribe( - CallbackType.change, - new Callback( - (() => { - if (!this.module.menuController.isMenuOpen()) { - this.openAutocompleteMenu(action.data.validMatches); - } - - this.updateAutocompleteMenu(autocompleteTkn); - - if ( - this.module.menuController.hasNoSuggestions() && - autocompleteTkn.left != autocompleteTkn.getParentStatement().left && - !autocompleteTkn.message - ) { - const message = this.module.messageController.addHoverMessage( - autocompleteTkn, - {}, - `Did you want to create a text message? use double quotes before and after the text, like this: "your desired text"` - ); - - const button = message.createButton("convert to text"); - button.addEventListener("click", () => { - this.module.executer.execute( - new EditAction(EditActionType.ConvertAutocompleteToString, { - token: autocompleteTkn, - source: { type: "draft-mode" }, - }), - this.module.focus.getContext() - ); - }); - } - }).bind(this) - ) - ); - - this.openAutocompleteMenu(action.data.validMatches); - - switch (action.data.autocompleteType) { - case AutoCompleteType.StartOfLine: - this.replaceEmptyStatement(context.lineStatement, new TemporaryStmt(autocompleteTkn)); - - break; - - case AutoCompleteType.AtEmptyOperatorHole: - case AutoCompleteType.AtExpressionHole: - this.insertToken(context, autocompleteTkn); - - break; - - case AutoCompleteType.RightOfExpression: - this.insertToken(context, autocompleteTkn, { toRight: true }); - - break; - case AutoCompleteType.LeftOfExpression: - this.insertToken(context, autocompleteTkn, { toLeft: true }); - - break; - } - - this.module.editor.cursor.setSelection(null); - const match = autocompleteTkn.isTerminatingMatch(); - - if (match) { - this.performMatchAction(match, autocompleteTkn); - } else { - let highlight = new ConstructHighlight(this.module.editor, autocompleteTkn, [230, 235, 255, 0.7]); - - autocompleteTkn.subscribe( - CallbackType.delete, - new Callback(() => { - if (highlight) { - highlight.removeFromDOM(); - highlight = null; - } - }) - ); - } - - break; - } - - case EditActionType.InsertElseStatement: { - const newStatement = new ElseStatement(action.data.hasCondition); - - if (action.data.outside) { - // when the else is being inserted outside - const elseRoot = context.lineStatement.rootNode as Module | Statement; - newStatement.rootNode = elseRoot; - newStatement.indexInRoot = context.lineStatement.indexInRoot; - newStatement.body.push(new EmptyLineStmt(newStatement, 0)); - - replaceInBody(elseRoot, newStatement.indexInRoot, newStatement); - rebuildBody(elseRoot, newStatement.indexInRoot, context.lineStatement.lineNumber); - this.module.editor.executeEdits(this.getBoundaries(context.lineStatement), newStatement); - } else { - // when being inserted inside - const curStmtRoot = context.lineStatement.rootNode as Statement; - const elseRoot = curStmtRoot.rootNode as Module | Statement; - newStatement.rootNode = elseRoot; - newStatement.indexInRoot = curStmtRoot.indexInRoot + 1; - - // indent back and place all of the code below it as its child - const toMoveStatements = curStmtRoot.body.splice( - context.lineStatement.indexInRoot, - curStmtRoot.body.length - context.lineStatement.indexInRoot - ); - - // remove the empty line statement - toMoveStatements.splice(0, 1)[0]; - - if (toMoveStatements.length == 0) newStatement.body.push(new EmptyLineStmt(newStatement, 0)); - const providedLeftPos = new Position( - context.lineStatement.lineNumber, - context.lineStatement.left - TAB_SPACES - ); - newStatement.build(providedLeftPos); - - this.module.editor.executeEdits( - this.getBoundaries(context.lineStatement, { selectIndent: true }), - newStatement - ); - - const topReferences = new Array(); - const bottomReferences = new Array(); - - for (const ref of curStmtRoot.scope.references) { - if (ref.statement.indexInRoot > context.lineStatement.indexInRoot) { - bottomReferences.push(ref); - } else topReferences.push(ref); - } - - if (bottomReferences.length > 0) { - curStmtRoot.scope.references = topReferences; - newStatement.scope.references = bottomReferences; - } - - for (const [i, stmt] of toMoveStatements.entries()) { - stmt.rootNode = newStatement; - stmt.indexInRoot = i; - newStatement.body.push(stmt); - } - - newStatement.init(providedLeftPos); - newStatement.rootNode = elseRoot; - newStatement.indexInRoot = newStatement.indexInRoot; - this.module.addStatementToBody( - elseRoot, - newStatement, - newStatement.indexInRoot, - providedLeftPos.lineNumber - ); - } - - if (flashGreen) this.flashGreen(newStatement); - - let scopeHighlight = new ScopeHighlight(this.module.editor, newStatement); - eventData.code = "else-statement"; - - break; - } - - case EditActionType.InsertExpression: { - this.insertExpression(context, action.data?.expression); - - if (flashGreen) this.flashGreen(action.data?.expression); - - eventData.code = action.data?.expression?.getRenderText(); - - break; - } - - case EditActionType.InsertStatement: { - const statement = action.data?.statement as Statement; - - this.replaceEmptyStatement(context.lineStatement, statement); - - if (flashGreen) this.flashGreen(action.data?.statement); - - if (statement.hasBody()) { - let scopeHighlight = new ScopeHighlight(this.module.editor, statement); - } - - eventData.code = action.data?.statement?.getRenderText(); - - break; - } - - case EditActionType.InsertVarAssignStatement: { - //TODO: Might want to change back to use the case above if no new logic is added - const statement = action.data?.statement; - - const id = action.data?.autocompleteData?.identifier?.trim(); - - if (statement instanceof VarAssignmentStmt && id) statement.setIdentifier(id); - - this.replaceEmptyStatement(context.lineStatement, action.data?.statement as Statement); - - if (flashGreen) this.flashGreen(action.data?.statement); - - eventData.code = "var-assignment"; - eventData.id = id; - - break; - } - - case EditActionType.InsertUnaryOperator: { - if (action.data?.replace) { - this.insertExpression( - context, - new UnaryOperatorExpr(action.data.operator, (context.token as TypedEmptyExpr).type[0]) - ); - } else if (action.data?.wrap) { - const expr = context.expressionToRight as Expression; - - const initialBoundary = this.getBoundaries(expr); - const root = expr.rootNode as Statement; - - const newCode = new UnaryOperatorExpr( - action.data.operator, - expr.returns, - expr.returns, - expr.rootNode, - expr.indexInRoot - ); - - newCode.setOperand(expr); - root.tokens[newCode.indexInRoot] = newCode; - root.rebuild(root.getLeftPosition(), 0); - - this.module.editor.executeEdits(initialBoundary, newCode); - this.module.focus.updateContext({ - positionToMove: newCode.tokens[1].getLeftPosition(), - }); - } - - eventData.code = action.data.operator; - - break; - } - - case EditActionType.DeleteNextToken: { - if (context.expressionToRight instanceof OperatorTkn) { - this.replaceCode( - context.expressionToRight, - new EmptyOperatorTkn(" ", context.expressionToRight, context.expressionToRight.indexInRoot) - ); - } else if (this.module.validator.atBeginningOfValOperation(context)) { - this.deleteCode(context.expressionToRight.rootNode); - } else if (context.expressionToRight instanceof Modifier) { - this.deleteModifier(context.expressionToRight, { deleting: true }); - } else this.deleteCode(context.expressionToRight); - - break; - } - - case EditActionType.DeletePrevToken: { - if (context.expressionToLeft instanceof OperatorTkn) { - this.replaceCode( - context.expressionToLeft, - new EmptyOperatorTkn(" ", context.expressionToLeft, context.expressionToLeft.indexInRoot) - ); - } else if ( - context.expressionToLeft instanceof VariableReferenceExpr && - context.expressionToLeft.rootNode instanceof VarOperationStmt - ) { - this.deleteCode(context.expressionToLeft.rootNode, { statement: true }); - } else if (context.expressionToLeft instanceof Modifier) this.deleteModifier(context.expressionToLeft); - else this.deleteCode(context.expressionToLeft); - - break; - } - - case EditActionType.DeleteStatement: { - this.deleteCode(context.lineStatement, { statement: true }); - - break; - } - - case EditActionType.DeleteMultiLineStatement: { - if ( - context.lineStatement instanceof IfStatement || - (context.lineStatement instanceof ElseStatement && context.lineStatement.hasCondition) - ) { - const elseStatementsAfterIf = []; - - for ( - let i = context.lineStatement.indexInRoot + 1; - i < context.lineStatement.rootNode.body.length; - i++ - ) { - const line = context.lineStatement.rootNode.body[i]; - - if (line instanceof ElseStatement) elseStatementsAfterIf.push(line); - else break; - } - - for (const elseStmt of elseStatementsAfterIf) { - this.module.messageController.addHoverMessage( - elseStmt, - null, - "add if before the first else, or delete this." - ); - } - } - - while (context.lineStatement.body.length > 0) { - this.module.editor.indentRecursively( - context.lineStatement.body[context.lineStatement.body.length - 1], - { backward: true } - ); - this.module.indentBackStatement(context.lineStatement.body[context.lineStatement.body.length - 1]); - } - - this.deleteCode(context.lineStatement, { statement: true }); - - break; - } - - case EditActionType.DeleteCurLine: { - this.module.deleteLine(context.lineStatement); - let range: Range; - - if (action.data?.pressedBackspace) { - const lineAbove = this.module.focus.getStatementAtLineNumber(context.lineStatement.lineNumber - 1); - this.module.focus.updateContext({ - positionToMove: new Position(lineAbove.lineNumber, lineAbove.right), - }); - range = new Range( - context.lineStatement.lineNumber, - context.lineStatement.left, - lineAbove.lineNumber, - lineAbove.right - ); - } else { - range = new Range( - context.lineStatement.lineNumber, - context.lineStatement.left, - context.lineStatement.lineNumber + 1, - context.lineStatement.left - ); - } - - this.module.editor.executeEdits(range, null, ""); - - break; - } - - case EditActionType.DeleteSelectedModifier: { - this.deleteModifier(context.token.rootNode as Modifier, { deleting: true }); - - break; - } - - case EditActionType.DeletePrevLine: { - const prevLine = this.module.focus.getStatementAtLineNumber(context.lineStatement.lineNumber - 1); - - if (prevLine.left != context.lineStatement.left) { - this.module.editor.indentRecursively(context.lineStatement, { backward: false }); - this.module.indentForwardStatement(context.lineStatement); - } - - const deleteRange = new Range( - prevLine.lineNumber, - prevLine.left, - prevLine.lineNumber + 1, - prevLine.left - ); - this.module.deleteLine(prevLine); - this.module.editor.executeEdits(deleteRange, null, ""); - - break; - } - - case EditActionType.IndentBackwardsIfStmt: { - const root = context.lineStatement.rootNode as Statement | Module; - - const toIndentStatements = new Array(); - - for (let i = context.lineStatement.indexInRoot; i < root.body.length; i++) { - toIndentStatements.push(root.body[i]); - } - - for (const stmt of toIndentStatements.reverse()) { - this.module.editor.indentRecursively(stmt, { backward: true }); - this.module.indentBackStatement(stmt); - } - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.DeleteBackMultiLines: { - for ( - let i = context.lineStatement.rootNode.body.length - 1; - i >= context.lineStatement.indexInRoot; - i-- - ) { - this.module.editor.indentRecursively(context.lineStatement.rootNode.body[i], { backward: true }); - this.module.indentBackStatement(context.lineStatement.rootNode.body[i]); - } - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.IndentBackwards: { - this.module.editor.indentRecursively(context.lineStatement, { backward: true }); - this.module.indentBackStatement(context.lineStatement); - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.IndentForwardsIfStmt: { - const root = context.lineStatement.rootNode as Statement | Module; - - const toIndentStatements = new Array(); - - for (let i = context.lineStatement.indexInRoot; i < root.body.length; i++) { - toIndentStatements.push(root.body[i]); - - if (i + 1 < root.body.length && !(root.body[i + 1] instanceof ElseStatement)) break; - } - - for (const stmt of toIndentStatements) { - this.module.editor.indentRecursively(stmt, { backward: false }); - this.module.indentForwardStatement(stmt); - } - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.IndentForwards: { - this.module.editor.indentRecursively(context.lineStatement, { backward: false }); - this.module.indentForwardStatement(context.lineStatement); - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.InsertEmptyLine: { - const newEmptyLine = this.module.insertEmptyLine(); - this.module.focus.fireOnNavOffCallbacks(context.lineStatement, newEmptyLine); - - break; - } - - case EditActionType.SelectPrevToken: { - this.module.focus.navigateLeft(); - - break; - } - - case EditActionType.SelectNextToken: { - this.module.focus.navigateRight(); - - break; - } - - case EditActionType.InsertFormattedStringItem: { - const cursorPos = this.module.editor.monaco.getPosition(); - const selectedText = this.module.editor.monaco.getSelection(); - const editableToken = this.module.focus.getTextEditableItem(context); - const token = editableToken.getToken(); - const formattedStringExpr = token.rootNode as FormattedStringExpr; - - const leftText = token.text.substring(0, cursorPos.column - token.left); - const rightText = token.text.substring(cursorPos.column - token.left, token.right); - - const leftToken = token; - leftToken.text = leftText; - const rightToken = new EditableTextTkn("", StringRegex, formattedStringExpr, token.indexInRoot + 1); - rightToken.text = rightText; - - formattedStringExpr.tokens.splice(token.indexInRoot + 1, 0, ...[rightToken]); - - formattedStringExpr.rebuild(formattedStringExpr.getLeftPosition(), 0); - - let rightTokenRange = new Range( - rightToken.getLineNumber(), - rightToken.left, - rightToken.getLineNumber(), - rightToken.right - ); - - this.module.editor.executeEdits(rightTokenRange, rightToken); - - const fStringToken = new FormattedStringCurlyBracketsExpr(formattedStringExpr, token.indexInRoot + 1); - - formattedStringExpr.tokens.splice(token.indexInRoot + 1, 0, ...[fStringToken]); - - formattedStringExpr.rebuild(formattedStringExpr.getLeftPosition(), 0); - - let editRange: Range; - - if (selectedText.startColumn != selectedText.endColumn) { - editRange = new Range( - cursorPos.lineNumber, - selectedText.startColumn, - cursorPos.lineNumber, - selectedText.endColumn - ); - } else { - editRange = new Range( - cursorPos.lineNumber, - cursorPos.column, - cursorPos.lineNumber, - cursorPos.column - ); - } - - this.module.editor.executeEdits(editRange, fStringToken); - this.module.focus.updateContext({ tokenToSelect: fStringToken.tokens[1] }); - eventData.code = "f-string-item"; - - break; - } - - case EditActionType.DeleteFStringCurlyBrackets: { - const fStringToRemove = action.data.item as FormattedStringCurlyBracketsExpr; - - const root = fStringToRemove.rootNode; - - const tokenBefore = root.tokens[fStringToRemove.indexInRoot - 1] as EditableTextTkn; - const tokenAfter = root.tokens[fStringToRemove.indexInRoot + 1] as EditableTextTkn; - - const indexToReplace = tokenBefore.indexInRoot; - - const newToken = new EditableTextTkn( - tokenBefore.text + tokenAfter.text, - StringRegex, - root, - fStringToRemove.indexInRoot - 1 - ); - - const focusPos = new Position(tokenBefore.getLineNumber(), tokenBefore.right); - - const replaceRange = new Range( - tokenAfter.getLineNumber(), - tokenAfter.right, - tokenBefore.getLineNumber(), - tokenBefore.left - ); - - this.module.removeItem(fStringToRemove); - this.module.removeItem(tokenAfter); - this.module.removeItem(tokenBefore); - - root.tokens.splice(indexToReplace, 0, newToken); - - root.rebuild(root.getLeftPosition(), 0); - - this.module.editor.executeEdits(replaceRange, newToken); - this.module.focus.updateContext({ positionToMove: focusPos }); - - break; - } - - case EditActionType.InsertChar: { - const cursorPos = this.module.editor.monaco.getPosition(); - const selectedText = this.module.editor.monaco.getSelection(); - const editableToken = this.module.focus.getTextEditableItem(context); - const editableText = editableToken.getEditableText(); - const token = editableToken.getToken(); - let newText = ""; - - if ((pressedKey == "{" || pressedKey == "}") && token.rootNode instanceof FormattedStringExpr) { - this.execute( - new EditAction(EditActionType.InsertFormattedStringItem, { source: { type: "autocomplete" } }) - ); - - break; - } - - if (token instanceof IdentifierTkn && token.isEmptyIdentifier()) { - const curText = ""; - newText = curText + pressedKey; - } else { - const curText = editableText.split(""); - curText.splice( - cursorPos.column - token.left, - Math.abs(selectedText.startColumn - selectedText.endColumn), - pressedKey - ); - - newText = curText.join(""); - } - - this.validateIdentifier(context, newText); - - let editRange: Range; - - if (selectedText.startColumn != selectedText.endColumn) { - editRange = new Range( - cursorPos.lineNumber, - selectedText.startColumn, - cursorPos.lineNumber, - selectedText.endColumn - ); - } else if ( - context.tokenToRight?.isTextEditable && - context.tokenToRight instanceof IdentifierTkn && - context.tokenToRight.isEmpty - ) { - editRange = new Range( - cursorPos.lineNumber, - context.tokenToRight.left, - cursorPos.lineNumber, - context.tokenToRight.right - ); - } else { - editRange = new Range( - cursorPos.lineNumber, - cursorPos.column, - cursorPos.lineNumber, - cursorPos.column - ); - } - - if (editableToken instanceof AutocompleteTkn) { - let match = editableToken.checkMatch(pressedKey); - - if (match) { - this.performMatchAction(match, editableToken); - - break; - } - - match = editableToken.isInsertableTerminatingMatch(pressedKey); - - if (match) { - this.performMatchAction(match, editableToken); - - this.execute(this.module.eventRouter.getKeyAction(e)); - - break; - } - } - - if (editableToken.setEditedText(newText)) this.module.editor.executeEdits(editRange, null, pressedKey); - - break; - } - - case EditActionType.DeleteStringLiteral: { - this.deleteCode(context.tokenToLeft.rootNode); - - break; - } - - case EditActionType.DeletePrevChar: - case EditActionType.DeleteNextChar: { - const cursorPos = this.module.editor.monaco.getPosition(); - const selectedText = this.module.editor.monaco.getSelection(); - const editableToken = this.module.focus.getTextEditableItem(context); - const token = editableToken.getToken(); - - let newText = ""; - - const curText = editableToken.getEditableText().split(""); - const toDeleteItems = - selectedText.startColumn == selectedText.endColumn - ? 1 - : Math.abs(selectedText.startColumn - selectedText.endColumn); - - const toDeletePos = action.type == EditActionType.DeleteNextChar ? 0 : 1; - - curText.splice( - Math.min( - cursorPos.column - token.left - toDeletePos, - selectedText.startColumn - token.left - toDeletePos - ), - toDeleteItems - ); - - newText = curText.join(""); - - this.validateIdentifier(context, newText); - - // check if it needs to turn back into a hole: - if (newText.length == 0) { - let removableExpr: CodeConstruct = null; - - if (context.expression instanceof LiteralValExpr) { - removableExpr = context.expression; - } else if (context.token instanceof AutocompleteTkn) { - removableExpr = context.token; - } else if (context.expressionToLeft instanceof LiteralValExpr) { - removableExpr = context.expressionToLeft; - } else if (context.tokenToLeft instanceof AutocompleteTkn) { - removableExpr = context.tokenToLeft; - } else if (context.expressionToRight instanceof LiteralValExpr) { - removableExpr = context.expressionToRight; - } else if (context.tokenToRight instanceof AutocompleteTkn) { - removableExpr = context.tokenToRight; - } - - if (removableExpr != null) { - if ( - removableExpr instanceof AutocompleteTkn && - removableExpr.rootNode instanceof TemporaryStmt - ) { - this.deleteCode(removableExpr.rootNode, { statement: true }); - } else if ( - removableExpr instanceof AutocompleteTkn && - removableExpr.autocompleteType == AutoCompleteType.AtEmptyOperatorHole - ) { - this.replaceCode( - removableExpr, - new EmptyOperatorTkn(" ", removableExpr.rootNode, removableExpr.indexInRoot) - ); - } else if ( - removableExpr instanceof AutocompleteTkn && - (removableExpr.autocompleteType == AutoCompleteType.RightOfExpression || - removableExpr.autocompleteType == AutoCompleteType.LeftOfExpression) - ) { - this.deleteAutocompleteToken(removableExpr); - } else this.deleteCode(removableExpr); - - break; - } - - let identifier: IdentifierTkn = null; - if (context.tokenToLeft instanceof IdentifierTkn) { - identifier = context.tokenToLeft; - } else if (context.tokenToRight instanceof IdentifierTkn) { - identifier = context.tokenToRight; - } else if (context.token instanceof IdentifierTkn) { - identifier = context.token; - } - - if (identifier != null) { - // reset identifier: - identifier.text = " "; - identifier.isEmpty = true; - - // change editor - this.module.editor.executeEdits( - new Range(cursorPos.lineNumber, identifier.left, cursorPos.lineNumber, identifier.right), - null, - " " - ); - - // rebuild ast - context.lineStatement.build(context.lineStatement.getLeftPosition()); - this.module.focus.updateContext({ tokenToSelect: identifier }); - - break; - } - } - - if (editableToken.setEditedText(newText)) { - let editRange = new Range( - cursorPos.lineNumber, - cursorPos.column, - cursorPos.lineNumber, - cursorPos.column - ); - - if (selectedText.startColumn != selectedText.endColumn) { - editRange = new Range( - cursorPos.lineNumber, - selectedText.startColumn, - cursorPos.lineNumber, - selectedText.endColumn - 1 - ); - } - - this.module.editor.executeEdits(editRange, null, ""); - preventDefaultEvent = false; - } - - break; - } - - case EditActionType.InsertAssignmentModifier: { - if (context.expressionToLeft.rootNode instanceof VarOperationStmt) { - const varOpStmt = context.expressionToLeft.rootNode; - - if ( - action.data.modifier instanceof AssignmentModifier && - context.expressionToLeft instanceof VariableReferenceExpr - ) { - if (context.expressionToLeft.rootNode.draftModeEnabled) { - this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); - } - const initialBoundary = this.getBoundaries(context.expressionToLeft); - - const varAssignStmt = new VarAssignmentStmt( - "", - context.expressionToLeft.identifier, - varOpStmt.rootNode, - varOpStmt.indexInRoot - ); - - replaceInBody(varOpStmt.rootNode, varOpStmt.indexInRoot, varAssignStmt); - - this.module.editor.executeEdits(initialBoundary, varAssignStmt); - this.module.focus.updateContext(varAssignStmt.getInitialFocus()); - - if (flashGreen) this.flashGreen(varAssignStmt); - } else { - if ( - context.expressionToLeft instanceof VariableReferenceExpr && - context.expressionToLeft.rootNode.draftModeEnabled - ) { - this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); - } - - varOpStmt.appendModifier(action.data.modifier); - varOpStmt.rebuild(varOpStmt.getLeftPosition(), 0); - - this.module.editor.insertAtCurPos([action.data.modifier]); - this.module.focus.updateContext(action.data.modifier.getInitialFocus()); - - if (flashGreen) this.flashGreen(action.data.modifier); - } - } - - eventData.code = action.data.modifier.getRenderText(); - - break; - } - - case EditActionType.InsertModifier: { - const modifier = action.data.modifier as Modifier; - - if (context.expressionToLeft instanceof Modifier) { - if (context.expressionToLeft.rootNode instanceof ValueOperationExpr) { - const valOprExpr = context.expressionToLeft.rootNode; - const valOprExprRoot = valOprExpr.rootNode as Statement; - - let replacementResult = valOprExpr.rootNode.checkInsertionAtHole( - valOprExpr.indexInRoot, - modifier.returns - ); - - const holeTypes = valOprExpr.rootNode.typeOfHoles[valOprExpr.indexInRoot]; - - if (replacementResult.insertionType !== InsertionType.Invalid) { - valOprExpr.appendModifier(modifier); - valOprExprRoot.rebuild(valOprExprRoot.getLeftPosition(), 0); - - this.module.editor.insertAtCurPos([modifier]); - this.module.focus.updateContext(modifier.getInitialFocus()); - - if (replacementResult.insertionType == InsertionType.DraftMode) - this.module.openDraftMode( - valOprExpr, - TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR( - valOprExpr.getKeyword(), - holeTypes, - valOprExpr.returns - ), - [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton( - valOprExpr.getKeyword(), - this.module, - valOprExpr - ); - }), - ] - ); - } - - if (valOprExpr.rootNode instanceof Statement) valOprExpr.rootNode.onInsertInto(valOprExpr); - } - } else if ( - context.expressionToLeft instanceof VariableReferenceExpr && - context.expressionToLeft.rootNode instanceof VarOperationStmt - ) { - if (context.expressionToLeft.rootNode.draftModeEnabled) { - this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); - } - const varOpStmt = context.expressionToLeft.rootNode; - - varOpStmt.appendModifier(modifier); - varOpStmt.rebuild(varOpStmt.getLeftPosition(), 0); - - this.module.editor.insertAtCurPos([modifier]); - this.module.focus.updateContext(modifier.getInitialFocus()); - - if (modifier instanceof MethodCallModifier && modifier.returns !== DataType.Void) { - //TODO: PropertyAccessModifier should also be included here once we have them - this.module.openDraftMode( - varOpStmt, - "This statement has no effect since the value it returns is not stored anywhere.", - [] - ); //TODO: Offer fixes? - } - } else { - const exprToLeftRoot = context.expressionToLeft.rootNode as Statement; - const exprToLeftIndexInRoot = context.expressionToLeft.indexInRoot; - - if (modifier instanceof ListAccessModifier) { - modifier.returns = TypeChecker.getElementTypeFromListType(context.expressionToLeft.returns); - - if (!modifier.returns) modifier.returns = DataType.Any; - } - - const replacementResult = exprToLeftRoot.checkInsertionAtHole( - context.expressionToLeft.indexInRoot, - modifier.returns - ); - const holeDataTypes = exprToLeftRoot.typeOfHoles[context.expressionToLeft.indexInRoot]; - - const valOprExpr = new ValueOperationExpr( - context.expressionToLeft, - [modifier], - context.expressionToLeft.rootNode, - context.expressionToLeft.indexInRoot - ); - - if (valOprExpr.rootNode instanceof Statement) valOprExpr.rootNode.onInsertInto(valOprExpr); - - context.expressionToLeft.indexInRoot = 0; - context.expressionToLeft.rootNode = valOprExpr; - - if (replacementResult.insertionType !== InsertionType.Invalid) { - this.module.closeConstructDraftRecord(context.expressionToLeft); - - exprToLeftRoot.tokens[exprToLeftIndexInRoot] = valOprExpr; - exprToLeftRoot.rebuild(exprToLeftRoot.getLeftPosition(), 0); - - this.module.editor.insertAtCurPos([modifier]); - this.module.focus.updateContext(modifier.getInitialFocus()); - - if (replacementResult.insertionType == InsertionType.DraftMode) { - if (valOprExpr.returns === DataType.Any) { - this.module.openDraftMode( - valOprExpr, - TYPE_MISMATCH_ANY(holeDataTypes, valOprExpr.returns), - [ - new IgnoreConversionRecord( - "", - null, - null, - "", - null, - Tooltip.IgnoreWarning - ).getConversionButton("", this.module, valOprExpr), - ] - ); - } else { - this.module.openDraftMode( - valOprExpr, - TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR( - valOprExpr.getKeyword(), - holeDataTypes, - valOprExpr.returns - ), - [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton( - valOprExpr.getKeyword(), - this.module, - valOprExpr - ); - }), - ] - ); - } - } - } - } - - if (flashGreen) this.flashGreen(action.data.modifier); - eventData.code = action.data.modifier.getRenderText(); - - break; - } - - case EditActionType.InsertBinaryOperator: { - let binExpr: BinaryOperatorExpr; - - if (action.data.toRight) { - binExpr = this.replaceWithBinaryOp(action.data.operator, context.expressionToLeft, { - toLeft: true, - }); - } else if (action.data.toLeft) { - binExpr = this.replaceWithBinaryOp(action.data.operator, context.expressionToRight, { - toRight: true, - }); - } else if (action.data.replace) { - binExpr = new BinaryOperatorExpr(action.data.operator, (context.token as TypedEmptyExpr).type[0]); - this.insertExpression(context, binExpr); - } - - if (flashGreen) this.flashGreen(binExpr); - eventData.code = action.data.operator; - - break; - } - - case EditActionType.WrapExpressionWithItem: { - // both lists and str work on any, so the first step of validation is always OK. - - const initialBoundary = this.getBoundaries(context.expressionToRight); - const expr = context.expressionToRight as Expression; - const indexInRoot = expr.indexInRoot; - const root = expr.rootNode as Statement; - - const newCode = action.data.expression as Expression; - newCode.indexInRoot = expr.indexInRoot; - newCode.rootNode = expr.rootNode; - - const isValidRootInsertion = - newCode.returns == DataType.Any || - root.typeOfHoles[indexInRoot].indexOf(newCode.returns) >= 0 || - root.typeOfHoles[indexInRoot] == DataType.Any; - - let replaceIndex: number = 0; - - for (const [i, token] of newCode.tokens.entries()) { - if (token instanceof TypedEmptyExpr) { - replaceIndex = i; - - break; - } - } - - if (isValidRootInsertion) { - this.module.closeConstructDraftRecord(root.tokens[indexInRoot]); - } - - newCode.tokens[replaceIndex] = context.expressionToRight; - context.expressionToRight.indexInRoot = replaceIndex; - context.expressionToRight.rootNode = newCode; - root.tokens[indexInRoot] = newCode; - root.rebuild(root.getLeftPosition(), 0); - this.module.editor.executeEdits(initialBoundary, newCode); - this.module.focus.updateContext({ - positionToMove: new Position(newCode.lineNumber, newCode.right), - }); - - if (newCode.rootNode instanceof BinaryOperatorExpr) { - newCode.rootNode.onInsertInto(newCode); - newCode.rootNode.validateTypes(this.module); - } else if (newCode.rootNode instanceof Statement) { - newCode.rootNode.onInsertInto(newCode); - } - - if (!isValidRootInsertion) { - this.module.closeConstructDraftRecord(expr); - this.module.openDraftMode(newCode, "DEBUG THIS", []); - } - - if (flashGreen) this.flashGreen(newCode); - eventData.code = action.data.expression.getRenderText(); - eventData.wrap = true; - - break; - } - - case EditActionType.ConvertAutocompleteToString: { - const autocompleteToken = action.data.token as AutocompleteTkn; - const literalValExpr = new LiteralValExpr( - DataType.String, - autocompleteToken.text, - autocompleteToken.rootNode as Expression | Statement, - autocompleteToken.indexInRoot - ); - - autocompleteToken.draftModeEnabled = false; - this.deleteCode(autocompleteToken); - this.insertExpression(this.module.focus.getContext(), literalValExpr); - - eventData.code = "double-quote"; - eventData.wrap = true; - - break; - } - - case EditActionType.InsertEmptyList: { - const newLiteral = new ListLiteralExpression(); - this.insertExpression(context, newLiteral); - - if (flashGreen) this.flashGreen(newLiteral); - eventData.code = "empty-list"; - - break; - } - - case EditActionType.InsertEmptyListItem: { - if (action.data.toRight) { - const code = [new NonEditableTkn(", "), new TypedEmptyExpr([DataType.Any])]; - this.insertEmptyListItem(context.tokenToRight, context.tokenToRight.indexInRoot, code); - this.module.editor.insertAtCurPos(code); - this.module.focus.updateContext({ tokenToSelect: code[1] }); - - if (flashGreen) this.flashGreen(code[1]); - } else if (action.data.toLeft) { - const code = [new TypedEmptyExpr([DataType.Any]), new NonEditableTkn(", ")]; - this.insertEmptyListItem(context.tokenToLeft, context.tokenToLeft.indexInRoot + 1, code); - this.module.editor.insertAtCurPos(code); - this.module.focus.updateContext({ tokenToSelect: code[0] }); - - if (flashGreen) this.flashGreen(code[0]); - } - eventData.code = "list-item-comma"; - - break; - } - - case EditActionType.DeleteListItem: { - if (action.data.toRight) { - const items = this.module.removeItems(context.token.rootNode, context.token.indexInRoot, 2); - this.module.editor.executeEdits(this.getCascadedBoundary(items), null, ""); - } else if (action.data.toLeft) { - const items = this.module.removeItems(context.token.rootNode, context.token.indexInRoot - 1, 2); - this.module.editor.executeEdits(this.getCascadedBoundary(items), null, ""); - } - - break; - } - - case EditActionType.DeleteBinaryOperator: { - this.deleteCode(context.token.rootNode); - break; - } - - case EditActionType.DeleteBinaryOperatorItem: { - if (!(context.token.rootNode instanceof BinaryOperatorExpr)) break; - - let leftOperand = context.token.rootNode.getLeftOperand(); - let rightOperand = context.token.rootNode.getRightOperand(); - - if (leftOperand === context.token) { - this.replaceCode(context.token.rootNode, rightOperand); - } else { - this.replaceCode(context.token.rootNode, leftOperand); - } - break; - } - - case EditActionType.DeleteFunctionCallExpr: { - this.deleteCode(context.token.rootNode); - break; - } - - case EditActionType.DeleteFunctionCallExprItem: { - let rootNode = context.token.rootNode; - let replacementTkn; - if (!(rootNode instanceof FunctionCallExpr)) break; - for (let i = 0; i < rootNode.tokens.length; i++) { - if ( - !(rootNode.tokens[i] instanceof TypedEmptyExpr) && - !(rootNode.tokens[i] instanceof NonEditableTkn) - ) { - replacementTkn = rootNode.tokens[i]; - } - } - this.replaceCode(rootNode, replacementTkn); - break; - } - - case EditActionType.InsertImportFromDraftMode: { - let currContext = context; - this.module.editor.monaco.setPosition(new Position(1, 1)); - this.module.editor.cursor.setSelection(null); - this.module.insertEmptyLine(); - this.module.editor.monaco.setPosition(new Position(1, 1)); - this.module.editor.cursor.setSelection(null); - currContext = this.module.focus.getContext(); - - const stmt = new ImportStatement(action.data?.moduleName, action.data?.itemName); - const insertAction = new EditCodeAction( - "from --- import --- :", - "add-import-btn", - () => stmt, - InsertActionType.InsertStatement, - {}, - null, - [" "], - "import", - null - ); - - insertAction.performAction(this, this.module.eventRouter, currContext, { type: "draft-mode" }); - eventData.code = stmt.getRenderText(); - - break; - } - case EditActionType.InsertMemberCallConversion: - case EditActionType.InsertMemberAccessConversion: { - const root = action.data.codeToReplace; - this.module.focus.updateContext( - new UpdatableContext(null, action.data.codeToReplace.getRightPosition()) - ); - this.execute( - new EditAction(EditActionType.InsertModifier, { - source: action?.data?.source, - modifier: Actions.instance() - .actionsList.find((element) => element.cssId == action.data.conversionConstructId) - .getCodeFunction() as Modifier, - }), - this.module.focus.getContext() - ); - - this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); - - if (root instanceof Expression) root.validateTypes(this.module); - - eventData.code = action.data.codeToReplace.getRenderText(); - - break; - } - case EditActionType.InsertFunctionConversion: - case EditActionType.InsertTypeCast: - case EditActionType.InsertComparisonConversion: { - const root = action.data.codeToReplace; - this.deleteCode(action.data.codeToReplace, { - statement: null, - replaceType: action.data.typeToConvertTo, - }); - this.insertExpression( - this.module.focus.getContext(), - Actions.instance() - .actionsList.find((element) => element.cssId == action.data.conversionConstructId) - .getCodeFunction() as Expression - ); - action.data.codeToReplace.draftModeEnabled = false; - this.insertExpression(this.module.focus.getContext(), action.data.codeToReplace as Expression); - this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); - - if (root instanceof Expression) root.validateTypes(this.module); - eventData.code = action.data.codeToReplace.getRenderText(); - - break; - } - - case EditActionType.SelectClosestTokenAbove: { - this.module.focus.navigateUp(); - - break; - } - - case EditActionType.SelectClosestTokenBelow: { - this.module.focus.navigateDown(); - - break; - } - - case EditActionType.MoveCursorLeft: - preventDefaultEvent = false; - - break; - - case EditActionType.MoveCursorRight: - preventDefaultEvent = false; - - break; - - case EditActionType.SelectLeft: - preventDefaultEvent = false; - break; - - case EditActionType.SelectRight: - preventDefaultEvent = false; - break; - - case EditActionType.SelectToStart: - preventDefaultEvent = false; - break; - - case EditActionType.SelectToEnd: - preventDefaultEvent = false; - break; - - case EditActionType.Copy: - preventDefaultEvent = false; - break; - - case EditActionType.InsertLiteral: { - const newLiteral = new LiteralValExpr(action.data?.literalType, action.data?.initialValue); - this.insertExpression(context, newLiteral); - - if (flashGreen) this.flashGreen(newLiteral); - - if (action.data?.source?.type === "keyboard") { - eventType = LogType.InsertCode; - eventData.source = "keyboard"; - eventData.code = `literal-${getUserFriendlyType(newLiteral.returns)}`; - } - - break; - } - - case EditActionType.OpenValidInsertMenu: - this.openAutocompleteMenu( - this.module.actionFilter - .getProcessedInsertionsList() - .filter((item) => item.insertionResult.insertionType != InsertionType.Invalid) - ); - this.styleAutocompleteMenu(context.position); - - break; - - //TODO: Remove later - case EditActionType.OpenValidInsertMenuSingleLevel: - if (!this.module.menuController.isMenuOpen()) { - //TODO: Make this work with ActionFilter - //const suggestions = this.module.getAllValidInsertsList(focusedNode); - //this.module.menuController.buildSingleLevelConstructCategoryMenu(suggestions); - } else this.module.menuController.removeMenus(); - - break; - - case EditActionType.SelectMenuSuggestionAbove: - this.module.menuController.focusOptionAbove(); - - break; - - case EditActionType.SelectMenuSuggestionBelow: - this.module.menuController.focusOptionBelow(); - - break; - - case EditActionType.SelectMenuSuggestion: - this.module.menuController.selectFocusedOption(); - - break; - - case EditActionType.CloseValidInsertMenu: - this.module.menuController.removeMenus(); - - break; - - case EditActionType.OpenSubMenu: - this.module.menuController.openSubMenu(); - - break; - - case EditActionType.CloseSubMenu: - this.module.menuController.closeSubMenu(); - - break; - - case EditActionType.CloseDraftMode: - this.deleteCode(action.data.codeNode); - - break; - - case EditActionType.None: { - preventDefaultEvent = true; - - break; - } - - case EditActionType.InsertOperatorTkn: { - this.replaceCode(context.tokenToLeft, action.data.operator); - - if (context.tokenToLeft.rootNode instanceof BinaryOperatorExpr) { - const root = context.tokenToLeft.rootNode; - root.operator = action.data.operator.operator; - root.operatorCategory = getOperatorCategory(root.operator); - - if (root.getLeftOperand() instanceof TypedEmptyExpr) { - root.updateTypeOfEmptyOperandOnOperatorChange("left"); - } - - if (root.getRightOperand() instanceof TypedEmptyExpr) { - root.updateTypeOfEmptyOperandOnOperatorChange("right"); - } - } - - if (flashGreen) this.flashGreen(action.data.operator); - eventData.code = action.data.operator.getRenderText(); - - break; - } - - case EditActionType.DeleteUnconvertibleOperandWarning: { - if (action.data.codeToDelete.draftModeEnabled) - this.module.closeConstructDraftRecord(action.data.codeToDelete); - this.deleteCode(action.data.codeToDelete); - - //TODO: Eventually this if statement should go as all constructs will have this method - if ( - action.data.rootExpression instanceof Expression || - action.data.rootExpression instanceof ListAccessModifier - ) - action.data.rootExpression.validateTypes(this.module); - - break; - } - } - - if (eventData && eventType) Logger.Instance().queueEvent(new LogEvent(eventType, eventData)); - - this.module.editor.monaco.focus(); - - return preventDefaultEvent; - } - - createVarReference(buttonId: string): VariableReferenceExpr { - const identifier = document.getElementById(buttonId).innerText; - const dataType = this.module.variableController.getVariableTypeNearLine( - this.module.focus.getFocusedStatement().scope ?? - ( - this.module.focus.getStatementAtLineNumber(this.module.editor.monaco.getPosition().lineNumber) - .rootNode as Statement | Module - ).scope, - this.module.editor.monaco.getPosition().lineNumber, - identifier - ); - - return new VariableReferenceExpr(identifier, dataType, buttonId); - } - - insertVariableReference(buttonId: string, source: {}, providedContext?: Context, autocompleteData?: {}) { - let context = providedContext ? providedContext : this.module.focus.getContext(); - - let { eventType, eventData } = this.getLogEventSource(source); - - if (this.module.validator.onBeginningOfLine(context)) { - const varRef = this.createVarReference(buttonId); - const stmt = new VarOperationStmt(varRef); - this.replaceEmptyStatement(context.lineStatement, stmt); - - const availableActions = this.module.actionFilter - .getProcessedInsertionsList() - .filter( - (action) => - action.insertionResult.insertionType !== InsertionType.Invalid && - (action.insertActionType === InsertActionType.InsertAssignmentModifier || - action.insertActionType === InsertActionType.InsertAugmentedAssignmentModifier) - ); - - this.module.openDraftMode( - stmt, - "Variable references should not be used on empty lines. Try converting it to an assignment statement instead!", - (() => { - const buttons = []; - - for (const action of availableActions) { - const button = document.createElement("div"); - addClassToDraftModeResolutionButton(button, stmt); - - const text = `${varRef.identifier}${action.optionName}`.replace(/---/g, ""); - button.innerHTML = text; - - const modifier = action.getCode(); - button.addEventListener("click", () => { - this.module.closeConstructDraftRecord(stmt); - this.module.executer.execute( - new EditAction(EditActionType.InsertAssignmentModifier, { - codeToReplace: stmt, - replacementConstructCssId: action.cssId, - modifier: modifier, - source: { type: "draft-mode" }, - }), - this.module.focus.getContext() - ); - this.flashGreen(modifier.rootNode as Statement); - }); - - buttons.push(button); - } - - return buttons; - })() - ); - - if (autocompleteData) { - this.flashGreen(stmt); - } - - eventData.code = varRef.getRenderText(); - } else if (this.module.validator.atEmptyExpressionHole(context)) { - const expr = this.createVarReference(buttonId); - this.insertExpression(context, expr); - - if (autocompleteData) { - this.flashGreen(expr); - } - - eventData.code = expr.getRenderText(); - } - - if (eventData && eventType) Logger.Instance().queueEvent(new LogEvent(eventType, eventData)); - } - - getLogEventSource(source: any): { eventType: LogType; eventData: any } { - let eventType: LogType; - let eventData: any = null; - - if (source) { - eventData = {}; - - switch (source.type) { - case "toolbox": - eventType = LogType.InsertCode; - eventData.source = "toolbox"; - - break; - - case "autocomplete": - eventType = LogType.InsertCode; - eventData.source = "autocomplete"; - - break; - - case "autocomplete-menu": - eventType = LogType.InsertCode; - eventData = { - source: "autocomplete-menu", - precision: source.precision, - length: source.length, - }; - - break; - - case "defined-variables": - eventType = LogType.InsertCode; - eventData.source = "defined-vars-toolbox"; - - break; - - case "draft-mode": - eventType = LogType.InsertCode; - eventData.source = "draft-mode"; - - break; - } - } - - return { eventType, eventData }; - } - - deleteAutocompleteOnMatch(context: Context): Context { - let token: AutocompleteTkn; - - if (context.token instanceof AutocompleteTkn) token = context.token; - if (context.tokenToLeft instanceof AutocompleteTkn) token = context.tokenToLeft; - if (context.tokenToRight instanceof AutocompleteTkn) token = context.tokenToRight; - - if (token) { - switch (token.autocompleteType) { - case AutoCompleteType.RightOfExpression: - case AutoCompleteType.LeftOfExpression: - this.deleteAutocompleteToken(token); - - break; - - case AutoCompleteType.StartOfLine: - if (token.rootNode instanceof TemporaryStmt) { - this.deleteCode(token.rootNode, { - statement: true, - }); - } else { - this.deleteCode(token); - } - - break; - - case AutoCompleteType.AtExpressionHole: - this.deleteCode(token, {}); - - break; - } - } - - return this.module.focus.getContext(); - } - - private flashGreen(code: CodeConstruct) { - if (code) { - let highlight = new ConstructHighlight(this.module.editor, code, [109, 242, 162, 1]); - - setTimeout(() => { - if (highlight) { - highlight.changeHighlightColour([255, 255, 255, 0]); - - setTimeout(() => { - highlight.removeFromDOM(); - highlight = null; - }, 500); - } - }, 1); - } - } - - private insertEmptyListItem(focusedCode: CodeConstruct, index: number, items: Array) { - if (focusedCode instanceof Token || focusedCode instanceof Expression) { - const root = focusedCode.rootNode; - - if (root instanceof Statement && root.tokens.length > 0) { - root.tokens.splice(index, 0, ...items); - - for (let i = 0; i < root.tokens.length; i++) { - root.tokens[i].indexInRoot = i; - root.tokens[i].rootNode = root; - } - - root.rebuild(root.getLeftPosition(), 0); - } - } - } - - private performMatchAction(match: EditCodeAction, token: AutocompleteTkn) { - if ( - match.insertActionType == InsertActionType.InsertNewVariableStmt && - (Object.keys(PythonKeywords).indexOf(token.text.trim()) >= 0 || - Object.keys(BuiltInFunctions).indexOf(token.text.trim()) >= 0) - ) { - // TODO: can insert an interesting warning - return; - } - - let length = 0; - if (match.insertActionType == InsertActionType.InsertNewVariableStmt) length = token.text.length + 1; - else length = match.matchString.length + 1; - - match.performAction( - this, - this.module.eventRouter, - this.module.focus.getContext(), - { type: "autocomplete", precision: "1", length }, - { - identifier: token.text, - } - ); - } - - private insertToken(context: Context, code: Token, { toLeft = false, toRight = false } = {}) { - if (context.token instanceof TypedEmptyExpr || context.token instanceof EmptyOperatorTkn) { - if (context.expression != null) { - const root = context.expression.rootNode as Statement; - root.replace(code, context.expression.indexInRoot); - } else if (context.token != null) { - const root = context.token.rootNode as Statement; - root.replace(code, context.token.indexInRoot); - } - - const range = new Range( - context.position.lineNumber, - context.token.left, - context.position.lineNumber, - context.token.right - ); - - this.module.editor.executeEdits(range, code); - } else if (toRight && context.expressionToLeft != null) { - const root = context.expressionToLeft.rootNode; - code.rootNode = root; - root.tokens.splice(context.expressionToLeft.indexInRoot + 1, 0, code); - root.rebuild(root.getLeftPosition(), 0); - this.module.editor.insertAtCurPos([code]); - } else if (toLeft && context.expressionToRight != null) { - const root = context.expressionToRight.rootNode; - code.rootNode = root; - root.tokens.splice(context.expressionToRight.indexInRoot, 0, code); - root.rebuild(root.getLeftPosition(), 0); - this.module.editor.insertAtCurPos([code]); - } - } - - private insertExpression(context: Context, code: Expression) { - // type checks -- different handling based on type of code construct - // focusedNode.returns != code.returns would work, but we need more context to get the right error message - if (context.token instanceof TypedEmptyExpr) { - const root = context.token.rootNode; - let insertionResult = root.typeValidateInsertionIntoHole(code, context.token); - - if (insertionResult.insertionType != InsertionType.Invalid) { - if (root instanceof Statement) { - root.onInsertInto(code); - } - - if (context.token.message && context.selected) { - //TODO: This should only be closed if the current insertion would fix the current draft mode. Currently we don't know if that is the case. - this.module.messageController.removeMessageFromConstruct(context.token); - } - - // replaces expression with the newly inserted expression - const expr = code as Expression; - - this.module.replaceFocusedExpression(expr); - - const range = new Range( - context.position.lineNumber, - context.token.left, - context.position.lineNumber, - context.token.right - ); - - this.module.editor.executeEdits(range, expr); - - //TODO: This should probably run only if the insert above was successful, we cannot assume that it was - if (!context.token.message) { - const newContext = code.getInitialFocus(); - this.module.focus.updateContext(newContext); - } - } - - if (root instanceof BinaryOperatorExpr) { - root.validateTypes(this.module); - } else if (insertionResult.insertionType == InsertionType.DraftMode) { - this.module.openDraftMode(code, insertionResult.message, [ - ...insertionResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton(code.getKeyword(), this.module, code); - }), - ]); - } else if (isImportable(code)) { - //TODO: This needs to run regardless of what happens above. But for that we need nested draft modes. It should not be a case within the same if block - //The current problem is that a construct can only have a single draft mode on it. This is mostly ok since we often reinsert the construct when fixing a draft mode - //and the reinsertion triggers another draft mode if necessary. But this does not happen for importables because they are not reinserted on a fix so we might lose some - //draft modes this way. - - //A quick fix for now would be to just trigger reinsertion. Otherwise we need a mechanism for having multiple draft modes. I have a commit on a separate branch for that. - //Converting them to a linked list seems to make the most sense. - this.checkImports(code, insertionResult.insertionType); - } - } - } - - private checkImports(insertedCode: Importable, currentInsertionType: InsertionType) { - if (currentInsertionType === InsertionType.Invalid) return; - - const insertionType = insertedCode.validateImportOnInsertion(this.module, currentInsertionType); - if (insertionType === InsertionType.DraftMode && insertedCode instanceof Statement) { - this.module.openImportDraftMode(insertedCode); - } - } - - private openAutocompleteMenu(inserts: EditCodeAction[]) { - if (!this.module.menuController.isMenuOpen()) { - inserts = inserts.filter((insert) => insert.insertionResult.insertionType !== InsertionType.Invalid); - this.module.menuController.buildSingleLevelMenu(inserts); - } else this.module.menuController.removeMenus(); - } - - private replaceEmptyStatement(emptyLine: Statement, statement: Statement) { - const root = emptyLine.rootNode as Statement | Module; - - replaceInBody(root, emptyLine.indexInRoot, statement); - - if (root instanceof Statement) root.notify(CallbackType.replace); - - var range = new Range(emptyLine.lineNumber, statement.left, emptyLine.lineNumber, statement.right); - - if (emptyLine.message) this.module.messageController.removeMessageFromConstruct(emptyLine); - - if (isImportable(statement)) { - this.checkImports(statement, InsertionType.Valid); - } - - this.module.editor.executeEdits(range, statement); - this.module.focus.updateContext(statement.getInitialFocus()); - } - - private replaceWithBinaryOp( - op: BinaryOperator, - expr: Expression, - { toLeft = false, toRight = false } - ): BinaryOperatorExpr { - if (expr instanceof Modifier) expr = expr.rootNode as Expression; - - const initialBoundary = this.getBoundaries(expr); - const root = expr.rootNode as Statement; - const index = expr.indexInRoot; - - const newCode = new BinaryOperatorExpr( - op, - expr.returns, // is not that important, will be replaced in the constructor based on the operator. - root, - expr.indexInRoot - ); - - const curOperand = toLeft ? newCode.getLeftOperand() : newCode.getRightOperand(); - const otherOperand = toLeft ? newCode.getRightOperand() : newCode.getLeftOperand(); - const insertionResult = newCode.typeValidateInsertionIntoHole(expr, curOperand as TypedEmptyExpr); - - /** - * Special cases - * - * if (--- + (--- + ---)|): --> attempting to insert a comparator or binary boolean operation should fail - */ - if (insertionResult.insertionType === InsertionType.Valid) { - const replacementResult = expr.canReplaceWithConstruct(newCode); - - // this can never go into draft mode - if (replacementResult.insertionType !== InsertionType.Invalid) { - if (root.tokens[index].draftModeEnabled) this.module.closeConstructDraftRecord(root.tokens[index]); - - if (toLeft) newCode.replaceLeftOperand(expr); - else newCode.replaceRightOperand(expr); - - expr.indexInRoot = curOperand.indexInRoot; - expr.rootNode = newCode; - - root.tokens[index] = newCode; - //TODO: Call onInsertInto() on this line - root.rebuild(root.getLeftPosition(), 0); - - this.module.editor.executeEdits(initialBoundary, newCode); - this.module.focus.updateContext({ - tokenToSelect: newCode.tokens[otherOperand.indexInRoot], - }); - - if (replacementResult.insertionType !== InsertionType.DraftMode && expr.draftModeEnabled) { - this.module.closeConstructDraftRecord(expr); - } else if (root instanceof BinaryOperatorExpr) { - root.validateTypes(this.module); - } else if (replacementResult.insertionType === InsertionType.DraftMode) { - this.module.openDraftMode(newCode, replacementResult.message, [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton(newCode.getRenderText(), this.module, newCode); - }), - ]); - } - - if (newCode.rootNode instanceof Statement) newCode.rootNode.onInsertInto(newCode); - - return newCode; - } - } - } - - private getCascadedBoundary(codes: Array): Range { - if (codes.length > 1) { - const lineNumber = codes[0].getLineNumber(); - - return new Range(lineNumber, codes[0].left, lineNumber, codes[codes.length - 1].right); - } else return this.getBoundaries(codes[0]); - } - - private getBoundaries(code: CodeConstruct, { selectIndent = false } = {}): Range { - const lineNumber = code.getLineNumber(); - - if (code instanceof Statement && code.hasBody()) { - const stmtStack = new Array(); - stmtStack.unshift(...code.body); - let endLineNumber = 0; - let endColumn = 0; - - while (stmtStack.length > 0) { - const curStmt = stmtStack.pop(); - - if (curStmt instanceof Statement && curStmt.hasBody()) stmtStack.unshift(...curStmt.body); - - if (endLineNumber < curStmt.lineNumber) { - endLineNumber = curStmt.lineNumber; - endColumn = curStmt.right; - } - } - - return new Range(lineNumber, code.left, endLineNumber, endColumn); - } else if (code instanceof Statement || code instanceof Token) { - if (selectIndent) { - return new Range(lineNumber, code.left - TAB_SPACES, lineNumber, code.right); - } else return new Range(lineNumber, code.left, lineNumber, code.right); - } - } - - private deleteModifier(mod: Modifier, { deleting = false } = {}) { - // TODO: this will be a prototype version of the code. needs to be cleaned and iterated on -> - // e.g. merge the operations for VarOperationStmt and ValueOperationExpr - - // TODO: if deleting, should not move cursor - const removeRange = this.getBoundaries(mod); - const rootOfExprToLeft = mod.rootNode; - - rootOfExprToLeft.tokens.splice(mod.indexInRoot, 1); - this.module.recursiveNotify(mod, CallbackType.delete); - - this.module.closeConstructDraftRecord(rootOfExprToLeft); - - let built = false; - let positionToMove: Position; - - if (rootOfExprToLeft.tokens.length == 1) { - // only a val or var-ref is remaining: - if (rootOfExprToLeft instanceof ValueOperationExpr) { - rootOfExprToLeft.updateReturnType(); - - let replacementResult = rootOfExprToLeft.rootNode.checkInsertionAtHole( - rootOfExprToLeft.indexInRoot, - rootOfExprToLeft.returns - ); - - if (replacementResult.insertionType == InsertionType.DraftMode) { - const ref = rootOfExprToLeft.getVarRef(); - if (ref instanceof VariableReferenceExpr) { - const line = this.module.focus.getContext().lineStatement; - - const varType = this.module.variableController.getVariableTypeNearLine( - line.rootNode instanceof Module ? this.module.scope : line.scope, - line.lineNumber, - ref.identifier, - false - ); - - let expectedTypes = rootOfExprToLeft.rootNode.typeOfHoles[rootOfExprToLeft.indexInRoot]; - const currentAllowedTypes = rootOfExprToLeft.rootNode.getCurrentAllowedTypesOfHole( - rootOfExprToLeft.indexInRoot, - false - ); - - if (currentAllowedTypes.length > 0) { - expectedTypes = currentAllowedTypes; - } - - this.module.openDraftMode( - rootOfExprToLeft, - TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR(ref.identifier, varType, expectedTypes), - [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton( - ref.identifier, - this.module, - rootOfExprToLeft - ); - }), - ] - ); - } else { - let expectedTypes = rootOfExprToLeft.rootNode.typeOfHoles[rootOfExprToLeft.indexInRoot]; - - const currentAllowedTypes = rootOfExprToLeft.rootNode.getCurrentAllowedTypesOfHole( - rootOfExprToLeft.indexInRoot, - false - ); - - if (currentAllowedTypes.length > 0) { - expectedTypes = currentAllowedTypes; - } - - this.module.openDraftMode( - ref, - TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR( - ref.getKeyword(), - ref.returns, - expectedTypes - ), - [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton( - ref.getKeyword(), - this.module, - rootOfExprToLeft - ); - }), - ] - ); - } - } - const value = rootOfExprToLeft.tokens[0]; - rootOfExprToLeft.rootNode.tokens[rootOfExprToLeft.indexInRoot] = value; - value.rootNode = rootOfExprToLeft.rootNode; - value.indexInRoot = rootOfExprToLeft.indexInRoot; - - rootOfExprToLeft.rootNode.rebuild(rootOfExprToLeft.rootNode.getLeftPosition(), 0); - positionToMove = new Position(value.getLineNumber(), value.right); - built = true; - } - } - - if (!built) { - rootOfExprToLeft.rebuild(rootOfExprToLeft.getLeftPosition(), 0); - positionToMove = new Position(rootOfExprToLeft.getLineNumber(), rootOfExprToLeft.right); - } - - this.module.editor.executeEdits(removeRange, null, ""); - if (!deleting) { - this.module.focus.updateContext({ - positionToMove, - }); - } - } - - private deleteAutocompleteToken(token: Token) { - const range = this.getBoundaries(token); - const root = token.rootNode as Statement; - root.tokens.splice(token.indexInRoot, 1); - - root.rebuild(root.getLeftPosition(), 0); - token.notify(CallbackType.delete); - - this.module.editor.executeEdits(range, null, ""); - } - - private replaceCode(code: CodeConstruct, replace: CodeConstruct) { - const replacementRange = this.getBoundaries(code); - const root = code.rootNode; - - if (root instanceof Statement) { - root.tokens.splice(code.indexInRoot, 1, replace); - - this.module.recursiveNotify(code, CallbackType.delete); - - for (let i = 0; i < root.tokens.length; i++) { - root.tokens[i].indexInRoot = i; - root.tokens[i].rootNode = root; - } - - root.rebuild(root.getLeftPosition(), 0); - - this.module.editor.executeEdits(replacementRange, replace); - - if (replace instanceof Token && replace.isEmpty) { - this.module.focus.updateContext({ tokenToSelect: replace }); - } else this.module.focus.updateContext({ positionToMove: replace.getRightPosition() }); - } - } - - private deleteCode(code: CodeConstruct, { statement = false, replaceType = null } = {}) { - const root = code.rootNode; - const replacementRange = this.getBoundaries(code); - let replacement: CodeConstruct; - - if (statement) replacement = this.module.removeStatement(code as Statement); - else replacement = this.module.replaceItemWTypedEmptyExpr(code, replaceType); - - this.module.editor.executeEdits(replacementRange, replacement); - this.module.focus.updateContext({ tokenToSelect: replacement }); - - if (root instanceof Expression) root.validateTypes(this.module); - } - - private validateIdentifier(context: Context, identifierText: string) { - let focusedNode = null; - - if (context.token && context.selected && context.token instanceof IdentifierTkn) { - focusedNode = context.token; - } else if (context.tokenToLeft && context.tokenToLeft instanceof IdentifierTkn) { - focusedNode = context.tokenToLeft; - } else if (context.tokenToRight && context.tokenToRight instanceof IdentifierTkn) { - focusedNode = context.tokenToRight; - } - - if ( - focusedNode instanceof IdentifierTkn || - context.tokenToLeft instanceof IdentifierTkn || - context.tokenToRight instanceof IdentifierTkn - ) { - if (Object.keys(PythonKeywords).indexOf(identifierText) > -1) { - this.module.messageController.addPopUpMessage( - focusedNode, - { identifier: identifierText }, - ErrorMessage.identifierIsKeyword - ); - } else if (Object.keys(BuiltInFunctions).indexOf(identifierText) > -1) { - this.module.messageController.addPopUpMessage( - focusedNode, - { identifier: identifierText }, - ErrorMessage.identifierIsBuiltInFunc - ); - } - } - } - - private updateAutocompleteMenu(autocompleteTkn: AutocompleteTkn) { - this.module.menuController.updateMenuOptions(autocompleteTkn.getEditableText()); - this.module.menuController.updatePosition( - this.module.menuController.getNewMenuPositionFromCode(autocompleteTkn) - ); - } - - private styleAutocompleteMenu(pos: Position) { - this.module.menuController.styleMenuOptions(); - this.module.menuController.updatePosition(this.module.menuController.getNewMenuPositionFromPosition(pos)); - } -} +import { Position, Range } from "monaco-editor"; +import { ErrorMessage } from "../messages/error-msg-generator"; +import { ConstructHighlight, ScopeHighlight } from "../messages/messages"; +import { + AssignmentModifier, + AutocompleteTkn, + BinaryOperatorExpr, + CodeConstruct, + EditableTextTkn, + ElseStatement, + EmptyLineStmt, + EmptyOperatorTkn, + Expression, + FormattedStringCurlyBracketsExpr, + FormattedStringExpr, + FunctionCallExpr, + IdentifierTkn, + IfStatement, + Importable, + ImportStatement, + ListAccessModifier, + ListLiteralExpression, + LiteralValExpr, + MethodCallModifier, + Modifier, + NonEditableTkn, + OperatorTkn, + Statement, + TemporaryStmt, + Token, + TypedEmptyExpr, + UnaryOperatorExpr, + ValueOperationExpr, + VarAssignmentStmt, + VariableReferenceExpr, + VarOperationStmt, +} from "../syntax-tree/ast"; +import { rebuildBody, replaceInBody } from "../syntax-tree/body"; +import { Callback, CallbackType } from "../syntax-tree/callback"; +import { + addClassToDraftModeResolutionButton, + AutoCompleteType, + BuiltInFunctions, + getOperatorCategory, + IgnoreConversionRecord, + PythonKeywords, + StringRegex, + TAB_SPACES, + Tooltip, + TYPE_MISMATCH_ANY, + TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR, + TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR, +} from "../syntax-tree/consts"; +import { Module } from "../syntax-tree/module"; +import { Reference } from "../syntax-tree/scope"; +import { TypeChecker } from "../syntax-tree/type-checker"; +import { getUserFriendlyType, isImportable } from "../utilities/util"; +import { LogEvent, Logger, LogType } from "./../logger/analytics"; +import { BinaryOperator, DataType, InsertionType } from "./../syntax-tree/consts"; +import { EditCodeAction } from "./action-filter"; +import { Actions, Docs, EditActionType, InsertActionType } from "./consts"; +import { EditAction } from "./data-types"; +import { Context, UpdatableContext } from "./focus"; + +export class ActionExecutor { + module: Module; + + constructor(module: Module) { + this.module = module; + } + + execute(action: EditAction, providedContext?: Context, e?: KeyboardEvent): boolean { + const pressedKey = e?.key; + let context = providedContext ? providedContext : this.module.focus.getContext(); + + let preventDefaultEvent = true; + let flashGreen = false; + + if (action?.data?.autocompleteData) flashGreen = true; + + let { eventType, eventData } = this.getLogEventSource(action?.data?.source); + + switch (action.type) { + case EditActionType.OpenAutocomplete: { + const autocompleteTkn = new AutocompleteTkn( + action.data.firstChar, + action.data.autocompleteType, + action.data.validMatches + ); + + autocompleteTkn.subscribe( + CallbackType.change, + new Callback( + (() => { + if (!this.module.menuController.isMenuOpen()) { + this.openAutocompleteMenu(action.data.validMatches); + } + + this.updateAutocompleteMenu(autocompleteTkn); + + if ( + this.module.menuController.hasNoSuggestions() && + autocompleteTkn.left != autocompleteTkn.getParentStatement().left && + !autocompleteTkn.message + ) { + const message = this.module.messageController.addHoverMessage( + autocompleteTkn, + {}, + `Did you want to create a text message? use double quotes before and after the text, like this: "your desired text"` + ); + + const button = message.createButton("convert to text"); + button.addEventListener("click", () => { + this.module.executer.execute( + new EditAction(EditActionType.ConvertAutocompleteToString, { + token: autocompleteTkn, + source: { type: "draft-mode" }, + }), + this.module.focus.getContext() + ); + }); + } + }).bind(this) + ) + ); + + this.openAutocompleteMenu(action.data.validMatches); + + switch (action.data.autocompleteType) { + case AutoCompleteType.StartOfLine: + this.replaceEmptyStatement(context.lineStatement, new TemporaryStmt(autocompleteTkn)); + + break; + + case AutoCompleteType.AtEmptyOperatorHole: + case AutoCompleteType.AtExpressionHole: + this.insertToken(context, autocompleteTkn); + + break; + + case AutoCompleteType.RightOfExpression: + this.insertToken(context, autocompleteTkn, { toRight: true }); + + break; + case AutoCompleteType.LeftOfExpression: + this.insertToken(context, autocompleteTkn, { toLeft: true }); + + break; + } + + this.module.editor.cursor.setSelection(null); + const match = autocompleteTkn.isTerminatingMatch(); + + if (match) { + this.performMatchAction(match, autocompleteTkn); + } else { + let highlight = new ConstructHighlight(this.module.editor, autocompleteTkn, [230, 235, 255, 0.7]); + + autocompleteTkn.subscribe( + CallbackType.delete, + new Callback(() => { + if (highlight) { + highlight.removeFromDOM(); + highlight = null; + } + }) + ); + } + + break; + } + + case EditActionType.InsertElseStatement: { + const newStatement = new ElseStatement(Docs.ElseDocs.styles.backgroundColor, action.data.hasCondition); + + if (action.data.outside) { + // when the else is being inserted outside + const elseRoot = context.lineStatement.rootNode as Module | Statement; + newStatement.rootNode = elseRoot; + newStatement.indexInRoot = context.lineStatement.indexInRoot; + newStatement.body.push(new EmptyLineStmt(newStatement, 0)); + + replaceInBody(elseRoot, newStatement.indexInRoot, newStatement); + rebuildBody(elseRoot, newStatement.indexInRoot, context.lineStatement.lineNumber); + this.module.editor.executeEdits(this.getBoundaries(context.lineStatement), newStatement); + } else { + // when being inserted inside + const curStmtRoot = context.lineStatement.rootNode as Statement; + const elseRoot = curStmtRoot.rootNode as Module | Statement; + newStatement.rootNode = elseRoot; + newStatement.indexInRoot = curStmtRoot.indexInRoot + 1; + + // indent back and place all of the code below it as its child + const toMoveStatements = curStmtRoot.body.splice( + context.lineStatement.indexInRoot, + curStmtRoot.body.length - context.lineStatement.indexInRoot + ); + + // remove the empty line statement + toMoveStatements.splice(0, 1)[0]; + + if (toMoveStatements.length == 0) newStatement.body.push(new EmptyLineStmt(newStatement, 0)); + const providedLeftPos = new Position( + context.lineStatement.lineNumber, + context.lineStatement.left - TAB_SPACES + ); + newStatement.build(providedLeftPos); + + this.module.editor.executeEdits( + this.getBoundaries(context.lineStatement, { selectIndent: true }), + newStatement + ); + + const topReferences = new Array(); + const bottomReferences = new Array(); + + for (const ref of curStmtRoot.scope.references) { + if (ref.statement.indexInRoot > context.lineStatement.indexInRoot) { + bottomReferences.push(ref); + } else topReferences.push(ref); + } + + if (bottomReferences.length > 0) { + curStmtRoot.scope.references = topReferences; + newStatement.scope.references = bottomReferences; + } + + for (const [i, stmt] of toMoveStatements.entries()) { + stmt.rootNode = newStatement; + stmt.indexInRoot = i; + newStatement.body.push(stmt); + } + + newStatement.init(providedLeftPos); + newStatement.rootNode = elseRoot; + newStatement.indexInRoot = newStatement.indexInRoot; + this.module.addStatementToBody( + elseRoot, + newStatement, + newStatement.indexInRoot, + providedLeftPos.lineNumber + ); + } + + if (flashGreen) this.flashGreen(newStatement); + + let scopeHighlight = new ScopeHighlight(this.module.editor, newStatement, newStatement.color); + eventData.code = "else-statement"; + + break; + } + + case EditActionType.InsertExpression: { + const expression = action.data?.expression as Expression; + + this.insertExpression(context, action.data?.expression); + + if (flashGreen) this.flashGreen(action.data?.expression); + + this.setTokenColor(action.data?.expression, expression.color); + + eventData.code = action.data?.expression?.getRenderText(); + + break; + } + + case EditActionType.InsertStatement: { + const statement = action.data?.statement as Statement; + + this.replaceEmptyStatement(context.lineStatement, statement); + + if (flashGreen) this.flashGreen(action.data?.statement); + + if (statement.hasBody()) { + let scopeHighlight = new ScopeHighlight(this.module.editor, statement, statement.color); + } else { + this.setTokenColor(action.data?.statement, statement.color); + } + + eventData.code = action.data?.statement?.getRenderText(); + + break; + } + + case EditActionType.InsertVarAssignStatement: { + //TODO: Might want to change back to use the case above if no new logic is added + const statement = action.data?.statement; + + const id = action.data?.autocompleteData?.identifier?.trim(); + + if (statement instanceof VarAssignmentStmt && id) statement.setIdentifier(id); + + this.replaceEmptyStatement(context.lineStatement, action.data?.statement as Statement); + + if (flashGreen) this.flashGreen(action.data?.statement); + + eventData.code = "var-assignment"; + eventData.id = id; + + break; + } + + case EditActionType.InsertUnaryOperator: { + if (action.data?.replace) { + this.insertExpression( + context, + new UnaryOperatorExpr( + Docs.NotDocs.styles.backgroundColor, + action.data.operator, + (context.token as TypedEmptyExpr).type[0] + ) + ); + } else if (action.data?.wrap) { + const expr = context.expressionToRight as Expression; + + const initialBoundary = this.getBoundaries(expr); + const root = expr.rootNode as Statement; + + const newCode = new UnaryOperatorExpr( + Docs.NotDocs.styles.backgroundColor, + action.data.operator, + expr.returns, + expr.returns, + expr.rootNode, + expr.indexInRoot + ); + + newCode.setOperand(expr); + root.tokens[newCode.indexInRoot] = newCode; + root.rebuild(root.getLeftPosition(), 0); + + this.module.editor.executeEdits(initialBoundary, newCode); + this.module.focus.updateContext({ + positionToMove: newCode.tokens[1].getLeftPosition(), + }); + } + + eventData.code = action.data.operator; + + break; + } + + case EditActionType.DeleteNextToken: { + if (context.expressionToRight instanceof OperatorTkn) { + this.replaceCode( + context.expressionToRight, + new EmptyOperatorTkn(" ", context.expressionToRight, context.expressionToRight.indexInRoot) + ); + } else if (this.module.validator.atBeginningOfValOperation(context)) { + this.deleteCode(context.expressionToRight.rootNode); + } else if (context.expressionToRight instanceof Modifier) { + this.deleteModifier(context.expressionToRight, { deleting: true }); + } else this.deleteCode(context.expressionToRight); + + break; + } + + case EditActionType.DeletePrevToken: { + if (context.expressionToLeft instanceof OperatorTkn) { + this.replaceCode( + context.expressionToLeft, + new EmptyOperatorTkn(" ", context.expressionToLeft, context.expressionToLeft.indexInRoot) + ); + } else if ( + context.expressionToLeft instanceof VariableReferenceExpr && + context.expressionToLeft.rootNode instanceof VarOperationStmt + ) { + this.deleteCode(context.expressionToLeft.rootNode, { statement: true }); + } else if (context.expressionToLeft instanceof Modifier) this.deleteModifier(context.expressionToLeft); + else this.deleteCode(context.expressionToLeft); + + break; + } + + case EditActionType.DeleteStatement: { + this.deleteCode(context.lineStatement, { statement: true }); + + break; + } + + case EditActionType.DeleteMultiLineStatement: { + if ( + context.lineStatement instanceof IfStatement || + (context.lineStatement instanceof ElseStatement && context.lineStatement.hasCondition) + ) { + const elseStatementsAfterIf = []; + + for ( + let i = context.lineStatement.indexInRoot + 1; + i < context.lineStatement.rootNode.body.length; + i++ + ) { + const line = context.lineStatement.rootNode.body[i]; + + if (line instanceof ElseStatement) elseStatementsAfterIf.push(line); + else break; + } + + for (const elseStmt of elseStatementsAfterIf) { + this.module.messageController.addHoverMessage( + elseStmt, + null, + "add if before the first else, or delete this." + ); + } + } + + while (context.lineStatement.body.length > 0) { + this.module.editor.indentRecursively( + context.lineStatement.body[context.lineStatement.body.length - 1], + { backward: true } + ); + this.module.indentBackStatement(context.lineStatement.body[context.lineStatement.body.length - 1]); + } + + this.deleteCode(context.lineStatement, { statement: true }); + + break; + } + + case EditActionType.DeleteCurLine: { + this.module.deleteLine(context.lineStatement); + let range: Range; + + if (action.data?.pressedBackspace) { + const lineAbove = this.module.focus.getStatementAtLineNumber(context.lineStatement.lineNumber - 1); + this.module.focus.updateContext({ + positionToMove: new Position(lineAbove.lineNumber, lineAbove.right), + }); + range = new Range( + context.lineStatement.lineNumber, + context.lineStatement.left, + lineAbove.lineNumber, + lineAbove.right + ); + } else { + range = new Range( + context.lineStatement.lineNumber, + context.lineStatement.left, + context.lineStatement.lineNumber + 1, + context.lineStatement.left + ); + } + + this.module.editor.executeEdits(range, null, ""); + + break; + } + + case EditActionType.DeleteSelectedModifier: { + this.deleteModifier(context.token.rootNode as Modifier, { deleting: true }); + + break; + } + + case EditActionType.DeletePrevLine: { + const prevLine = this.module.focus.getStatementAtLineNumber(context.lineStatement.lineNumber - 1); + + if (prevLine.left != context.lineStatement.left) { + this.module.editor.indentRecursively(context.lineStatement, { backward: false }); + this.module.indentForwardStatement(context.lineStatement); + } + + const deleteRange = new Range( + prevLine.lineNumber, + prevLine.left, + prevLine.lineNumber + 1, + prevLine.left + ); + this.module.deleteLine(prevLine); + this.module.editor.executeEdits(deleteRange, null, ""); + + break; + } + + case EditActionType.IndentBackwardsIfStmt: { + const root = context.lineStatement.rootNode as Statement | Module; + + const toIndentStatements = new Array(); + + for (let i = context.lineStatement.indexInRoot; i < root.body.length; i++) { + toIndentStatements.push(root.body[i]); + } + + for (const stmt of toIndentStatements.reverse()) { + this.module.editor.indentRecursively(stmt, { backward: true }); + this.module.indentBackStatement(stmt); + } + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.DeleteBackMultiLines: { + for ( + let i = context.lineStatement.rootNode.body.length - 1; + i >= context.lineStatement.indexInRoot; + i-- + ) { + this.module.editor.indentRecursively(context.lineStatement.rootNode.body[i], { backward: true }); + this.module.indentBackStatement(context.lineStatement.rootNode.body[i]); + } + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.IndentBackwards: { + this.module.editor.indentRecursively(context.lineStatement, { backward: true }); + this.module.indentBackStatement(context.lineStatement); + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.IndentForwardsIfStmt: { + const root = context.lineStatement.rootNode as Statement | Module; + + const toIndentStatements = new Array(); + + for (let i = context.lineStatement.indexInRoot; i < root.body.length; i++) { + toIndentStatements.push(root.body[i]); + + if (i + 1 < root.body.length && !(root.body[i + 1] instanceof ElseStatement)) break; + } + + for (const stmt of toIndentStatements) { + this.module.editor.indentRecursively(stmt, { backward: false }); + this.module.indentForwardStatement(stmt); + } + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.IndentForwards: { + this.module.editor.indentRecursively(context.lineStatement, { backward: false }); + this.module.indentForwardStatement(context.lineStatement); + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.InsertEmptyLine: { + const newEmptyLine = this.module.insertEmptyLine(); + this.module.focus.fireOnNavOffCallbacks(context.lineStatement, newEmptyLine); + + break; + } + + case EditActionType.SelectPrevToken: { + this.module.focus.navigateLeft(); + + break; + } + + case EditActionType.SelectNextToken: { + this.module.focus.navigateRight(); + + break; + } + + case EditActionType.InsertFormattedStringItem: { + const cursorPos = this.module.editor.monaco.getPosition(); + const selectedText = this.module.editor.monaco.getSelection(); + const editableToken = this.module.focus.getTextEditableItem(context); + const token = editableToken.getToken(); + const formattedStringExpr = token.rootNode as FormattedStringExpr; + + const leftText = token.text.substring(0, cursorPos.column - token.left); + const rightText = token.text.substring(cursorPos.column - token.left, token.right); + + const leftToken = token; + leftToken.text = leftText; + const rightToken = new EditableTextTkn("", StringRegex, formattedStringExpr, token.indexInRoot + 1); + rightToken.text = rightText; + + formattedStringExpr.tokens.splice(token.indexInRoot + 1, 0, ...[rightToken]); + + formattedStringExpr.rebuild(formattedStringExpr.getLeftPosition(), 0); + + let rightTokenRange = new Range( + rightToken.getLineNumber(), + rightToken.left, + rightToken.getLineNumber(), + rightToken.right + ); + + this.module.editor.executeEdits(rightTokenRange, rightToken); + + const fStringToken = new FormattedStringCurlyBracketsExpr( + Docs.NotDocs.styles.backgroundColor, + formattedStringExpr, + token.indexInRoot + 1 + ); + + formattedStringExpr.tokens.splice(token.indexInRoot + 1, 0, ...[fStringToken]); + + formattedStringExpr.rebuild(formattedStringExpr.getLeftPosition(), 0); + + let editRange: Range; + + if (selectedText.startColumn != selectedText.endColumn) { + editRange = new Range( + cursorPos.lineNumber, + selectedText.startColumn, + cursorPos.lineNumber, + selectedText.endColumn + ); + } else { + editRange = new Range( + cursorPos.lineNumber, + cursorPos.column, + cursorPos.lineNumber, + cursorPos.column + ); + } + + this.module.editor.executeEdits(editRange, fStringToken); + this.module.focus.updateContext({ tokenToSelect: fStringToken.tokens[1] }); + eventData.code = "f-string-item"; + + break; + } + + case EditActionType.DeleteFStringCurlyBrackets: { + const fStringToRemove = action.data.item as FormattedStringCurlyBracketsExpr; + + const root = fStringToRemove.rootNode; + + const tokenBefore = root.tokens[fStringToRemove.indexInRoot - 1] as EditableTextTkn; + const tokenAfter = root.tokens[fStringToRemove.indexInRoot + 1] as EditableTextTkn; + + const indexToReplace = tokenBefore.indexInRoot; + + const newToken = new EditableTextTkn( + tokenBefore.text + tokenAfter.text, + StringRegex, + root, + fStringToRemove.indexInRoot - 1 + ); + + const focusPos = new Position(tokenBefore.getLineNumber(), tokenBefore.right); + + const replaceRange = new Range( + tokenAfter.getLineNumber(), + tokenAfter.right, + tokenBefore.getLineNumber(), + tokenBefore.left + ); + + this.module.removeItem(fStringToRemove); + this.module.removeItem(tokenAfter); + this.module.removeItem(tokenBefore); + + root.tokens.splice(indexToReplace, 0, newToken); + + root.rebuild(root.getLeftPosition(), 0); + + this.module.editor.executeEdits(replaceRange, newToken); + this.module.focus.updateContext({ positionToMove: focusPos }); + + break; + } + + case EditActionType.InsertChar: { + const cursorPos = this.module.editor.monaco.getPosition(); + const selectedText = this.module.editor.monaco.getSelection(); + const editableToken = this.module.focus.getTextEditableItem(context); + const editableText = editableToken.getEditableText(); + const token = editableToken.getToken(); + let newText = ""; + + if ((pressedKey == "{" || pressedKey == "}") && token.rootNode instanceof FormattedStringExpr) { + this.execute( + new EditAction(EditActionType.InsertFormattedStringItem, { source: { type: "autocomplete" } }) + ); + + break; + } + + if (token instanceof IdentifierTkn && token.isEmptyIdentifier()) { + const curText = ""; + newText = curText + pressedKey; + } else { + const curText = editableText.split(""); + curText.splice( + cursorPos.column - token.left, + Math.abs(selectedText.startColumn - selectedText.endColumn), + pressedKey + ); + + newText = curText.join(""); + } + + this.validateIdentifier(context, newText); + + let editRange: Range; + + if (selectedText.startColumn != selectedText.endColumn) { + editRange = new Range( + cursorPos.lineNumber, + selectedText.startColumn, + cursorPos.lineNumber, + selectedText.endColumn + ); + } else if ( + context.tokenToRight?.isTextEditable && + context.tokenToRight instanceof IdentifierTkn && + context.tokenToRight.isEmpty + ) { + editRange = new Range( + cursorPos.lineNumber, + context.tokenToRight.left, + cursorPos.lineNumber, + context.tokenToRight.right + ); + } else { + editRange = new Range( + cursorPos.lineNumber, + cursorPos.column, + cursorPos.lineNumber, + cursorPos.column + ); + } + + if (editableToken instanceof AutocompleteTkn) { + let match = editableToken.checkMatch(pressedKey); + + if (match) { + this.performMatchAction(match, editableToken); + + break; + } + + match = editableToken.isInsertableTerminatingMatch(pressedKey); + + if (match) { + this.performMatchAction(match, editableToken); + + this.execute(this.module.eventRouter.getKeyAction(e)); + + break; + } + } + + if (editableToken.setEditedText(newText)) this.module.editor.executeEdits(editRange, null, pressedKey); + + break; + } + + case EditActionType.DeleteStringLiteral: { + this.deleteCode(context.tokenToLeft.rootNode); + + break; + } + + case EditActionType.DeletePrevChar: + case EditActionType.DeleteNextChar: { + const cursorPos = this.module.editor.monaco.getPosition(); + const selectedText = this.module.editor.monaco.getSelection(); + const editableToken = this.module.focus.getTextEditableItem(context); + const token = editableToken.getToken(); + + let newText = ""; + + const curText = editableToken.getEditableText().split(""); + const toDeleteItems = + selectedText.startColumn == selectedText.endColumn + ? 1 + : Math.abs(selectedText.startColumn - selectedText.endColumn); + + const toDeletePos = action.type == EditActionType.DeleteNextChar ? 0 : 1; + + curText.splice( + Math.min( + cursorPos.column - token.left - toDeletePos, + selectedText.startColumn - token.left - toDeletePos + ), + toDeleteItems + ); + + newText = curText.join(""); + + this.validateIdentifier(context, newText); + + // check if it needs to turn back into a hole: + if (newText.length == 0) { + let removableExpr: CodeConstruct = null; + + if (context.expression instanceof LiteralValExpr) { + removableExpr = context.expression; + } else if (context.token instanceof AutocompleteTkn) { + removableExpr = context.token; + } else if (context.expressionToLeft instanceof LiteralValExpr) { + removableExpr = context.expressionToLeft; + } else if (context.tokenToLeft instanceof AutocompleteTkn) { + removableExpr = context.tokenToLeft; + } else if (context.expressionToRight instanceof LiteralValExpr) { + removableExpr = context.expressionToRight; + } else if (context.tokenToRight instanceof AutocompleteTkn) { + removableExpr = context.tokenToRight; + } + + if (removableExpr != null) { + if ( + removableExpr instanceof AutocompleteTkn && + removableExpr.rootNode instanceof TemporaryStmt + ) { + this.deleteCode(removableExpr.rootNode, { statement: true }); + } else if ( + removableExpr instanceof AutocompleteTkn && + removableExpr.autocompleteType == AutoCompleteType.AtEmptyOperatorHole + ) { + this.replaceCode( + removableExpr, + new EmptyOperatorTkn(" ", removableExpr.rootNode, removableExpr.indexInRoot) + ); + } else if ( + removableExpr instanceof AutocompleteTkn && + (removableExpr.autocompleteType == AutoCompleteType.RightOfExpression || + removableExpr.autocompleteType == AutoCompleteType.LeftOfExpression) + ) { + this.deleteAutocompleteToken(removableExpr); + } else this.deleteCode(removableExpr); + + break; + } + + let identifier: IdentifierTkn = null; + if (context.tokenToLeft instanceof IdentifierTkn) { + identifier = context.tokenToLeft; + } else if (context.tokenToRight instanceof IdentifierTkn) { + identifier = context.tokenToRight; + } else if (context.token instanceof IdentifierTkn) { + identifier = context.token; + } + + if (identifier != null) { + // reset identifier: + identifier.text = " "; + identifier.isEmpty = true; + + // change editor + this.module.editor.executeEdits( + new Range(cursorPos.lineNumber, identifier.left, cursorPos.lineNumber, identifier.right), + null, + " " + ); + + // rebuild ast + context.lineStatement.build(context.lineStatement.getLeftPosition()); + this.module.focus.updateContext({ tokenToSelect: identifier }); + + break; + } + } + + if (editableToken.setEditedText(newText)) { + let editRange = new Range( + cursorPos.lineNumber, + cursorPos.column, + cursorPos.lineNumber, + cursorPos.column + ); + + if (selectedText.startColumn != selectedText.endColumn) { + editRange = new Range( + cursorPos.lineNumber, + selectedText.startColumn, + cursorPos.lineNumber, + selectedText.endColumn - 1 + ); + } + + this.module.editor.executeEdits(editRange, null, ""); + preventDefaultEvent = false; + } + + break; + } + + case EditActionType.InsertAssignmentModifier: { + if (context.expressionToLeft.rootNode instanceof VarOperationStmt) { + const varOpStmt = context.expressionToLeft.rootNode; + + if ( + action.data.modifier instanceof AssignmentModifier && + context.expressionToLeft instanceof VariableReferenceExpr + ) { + if (context.expressionToLeft.rootNode.draftModeEnabled) { + this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); + } + const initialBoundary = this.getBoundaries(context.expressionToLeft); + + const varAssignStmt = new VarAssignmentStmt( + Docs.AddVarDocs.styles.backgroundColor, + "", + context.expressionToLeft.identifier, + varOpStmt.rootNode, + varOpStmt.indexInRoot + ); + + replaceInBody(varOpStmt.rootNode, varOpStmt.indexInRoot, varAssignStmt); + + this.module.editor.executeEdits(initialBoundary, varAssignStmt); + this.module.focus.updateContext(varAssignStmt.getInitialFocus()); + + if (flashGreen) this.flashGreen(varAssignStmt); + } else { + if ( + context.expressionToLeft instanceof VariableReferenceExpr && + context.expressionToLeft.rootNode.draftModeEnabled + ) { + this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); + } + + varOpStmt.appendModifier(action.data.modifier); + varOpStmt.rebuild(varOpStmt.getLeftPosition(), 0); + + this.module.editor.insertAtCurPos([action.data.modifier]); + this.module.focus.updateContext(action.data.modifier.getInitialFocus()); + + if (flashGreen) this.flashGreen(action.data.modifier); + } + } + + eventData.code = action.data.modifier.getRenderText(); + + break; + } + + case EditActionType.InsertModifier: { + const modifier = action.data.modifier as Modifier; + + if (context.expressionToLeft instanceof Modifier) { + if (context.expressionToLeft.rootNode instanceof ValueOperationExpr) { + const valOprExpr = context.expressionToLeft.rootNode; + const valOprExprRoot = valOprExpr.rootNode as Statement; + + let replacementResult = valOprExpr.rootNode.checkInsertionAtHole( + valOprExpr.indexInRoot, + modifier.returns + ); + + const holeTypes = valOprExpr.rootNode.typeOfHoles[valOprExpr.indexInRoot]; + + if (replacementResult.insertionType !== InsertionType.Invalid) { + valOprExpr.appendModifier(modifier); + valOprExprRoot.rebuild(valOprExprRoot.getLeftPosition(), 0); + + this.module.editor.insertAtCurPos([modifier]); + this.module.focus.updateContext(modifier.getInitialFocus()); + + if (replacementResult.insertionType == InsertionType.DraftMode) + this.module.openDraftMode( + valOprExpr, + TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR( + valOprExpr.getKeyword(), + holeTypes, + valOprExpr.returns + ), + [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton( + valOprExpr.getKeyword(), + this.module, + valOprExpr + ); + }), + ] + ); + } + + if (valOprExpr.rootNode instanceof Statement) valOprExpr.rootNode.onInsertInto(valOprExpr); + } + } else if ( + context.expressionToLeft instanceof VariableReferenceExpr && + context.expressionToLeft.rootNode instanceof VarOperationStmt + ) { + if (context.expressionToLeft.rootNode.draftModeEnabled) { + this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); + } + const varOpStmt = context.expressionToLeft.rootNode; + + varOpStmt.appendModifier(modifier); + varOpStmt.rebuild(varOpStmt.getLeftPosition(), 0); + + this.module.editor.insertAtCurPos([modifier]); + this.module.focus.updateContext(modifier.getInitialFocus()); + + if (modifier instanceof MethodCallModifier && modifier.returns !== DataType.Void) { + //TODO: PropertyAccessModifier should also be included here once we have them + this.module.openDraftMode( + varOpStmt, + "This statement has no effect since the value it returns is not stored anywhere.", + [] + ); //TODO: Offer fixes? + } + } else { + const exprToLeftRoot = context.expressionToLeft.rootNode as Statement; + const exprToLeftIndexInRoot = context.expressionToLeft.indexInRoot; + + if (modifier instanceof ListAccessModifier) { + modifier.returns = TypeChecker.getElementTypeFromListType(context.expressionToLeft.returns); + + if (!modifier.returns) modifier.returns = DataType.Any; + } + + const replacementResult = exprToLeftRoot.checkInsertionAtHole( + context.expressionToLeft.indexInRoot, + modifier.returns + ); + const holeDataTypes = exprToLeftRoot.typeOfHoles[context.expressionToLeft.indexInRoot]; + + const valOprExpr = new ValueOperationExpr( + context.expressionToLeft, + [modifier], + context.expressionToLeft.rootNode, + context.expressionToLeft.indexInRoot + ); + + if (valOprExpr.rootNode instanceof Statement) valOprExpr.rootNode.onInsertInto(valOprExpr); + + context.expressionToLeft.indexInRoot = 0; + context.expressionToLeft.rootNode = valOprExpr; + + if (replacementResult.insertionType !== InsertionType.Invalid) { + this.module.closeConstructDraftRecord(context.expressionToLeft); + + exprToLeftRoot.tokens[exprToLeftIndexInRoot] = valOprExpr; + exprToLeftRoot.rebuild(exprToLeftRoot.getLeftPosition(), 0); + + this.module.editor.insertAtCurPos([modifier]); + this.module.focus.updateContext(modifier.getInitialFocus()); + + if (replacementResult.insertionType == InsertionType.DraftMode) { + if (valOprExpr.returns === DataType.Any) { + this.module.openDraftMode( + valOprExpr, + TYPE_MISMATCH_ANY(holeDataTypes, valOprExpr.returns), + [ + new IgnoreConversionRecord( + "", + null, + null, + "", + null, + Tooltip.IgnoreWarning + ).getConversionButton("", this.module, valOprExpr), + ] + ); + } else { + this.module.openDraftMode( + valOprExpr, + TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR( + valOprExpr.getKeyword(), + holeDataTypes, + valOprExpr.returns + ), + [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton( + valOprExpr.getKeyword(), + this.module, + valOprExpr + ); + }), + ] + ); + } + } + } + } + + if (flashGreen) this.flashGreen(action.data.modifier); + eventData.code = action.data.modifier.getRenderText(); + + break; + } + + case EditActionType.InsertBinaryOperator: { + let binExpr: BinaryOperatorExpr; + + if (action.data.toRight) { + binExpr = this.replaceWithBinaryOp(action.data.operator, context.expressionToLeft, { + toLeft: true, + }); + } else if (action.data.toLeft) { + binExpr = this.replaceWithBinaryOp(action.data.operator, context.expressionToRight, { + toRight: true, + }); + } else if (action.data.replace) { + binExpr = new BinaryOperatorExpr( + Docs.AddDocs.styles.backgroundColor, + action.data.operator, + (context.token as TypedEmptyExpr).type[0] + ); + this.insertExpression(context, binExpr); + } + + if (flashGreen) this.flashGreen(binExpr); + eventData.code = action.data.operator; + + break; + } + + case EditActionType.WrapExpressionWithItem: { + // both lists and str work on any, so the first step of validation is always OK. + + const initialBoundary = this.getBoundaries(context.expressionToRight); + const expr = context.expressionToRight as Expression; + const indexInRoot = expr.indexInRoot; + const root = expr.rootNode as Statement; + + const newCode = action.data.expression as Expression; + newCode.indexInRoot = expr.indexInRoot; + newCode.rootNode = expr.rootNode; + + const isValidRootInsertion = + newCode.returns == DataType.Any || + root.typeOfHoles[indexInRoot].indexOf(newCode.returns) >= 0 || + root.typeOfHoles[indexInRoot] == DataType.Any; + + let replaceIndex: number = 0; + + for (const [i, token] of newCode.tokens.entries()) { + if (token instanceof TypedEmptyExpr) { + replaceIndex = i; + + break; + } + } + + if (isValidRootInsertion) { + this.module.closeConstructDraftRecord(root.tokens[indexInRoot]); + } + + newCode.tokens[replaceIndex] = context.expressionToRight; + context.expressionToRight.indexInRoot = replaceIndex; + context.expressionToRight.rootNode = newCode; + root.tokens[indexInRoot] = newCode; + root.rebuild(root.getLeftPosition(), 0); + this.module.editor.executeEdits(initialBoundary, newCode); + this.module.focus.updateContext({ + positionToMove: new Position(newCode.lineNumber, newCode.right), + }); + + if (newCode.rootNode instanceof BinaryOperatorExpr) { + newCode.rootNode.onInsertInto(newCode); + newCode.rootNode.validateTypes(this.module); + } else if (newCode.rootNode instanceof Statement) { + newCode.rootNode.onInsertInto(newCode); + } + + if (!isValidRootInsertion) { + this.module.closeConstructDraftRecord(expr); + this.module.openDraftMode(newCode, "DEBUG THIS", []); + } + + if (flashGreen) this.flashGreen(newCode); + eventData.code = action.data.expression.getRenderText(); + eventData.wrap = true; + + break; + } + + case EditActionType.ConvertAutocompleteToString: { + const autocompleteToken = action.data.token as AutocompleteTkn; + const literalValExpr = new LiteralValExpr( + Docs.StrDocs.styles.backgroundColor, + DataType.String, + autocompleteToken.text, + autocompleteToken.rootNode as Expression | Statement, + autocompleteToken.indexInRoot + ); + + autocompleteToken.draftModeEnabled = false; + this.deleteCode(autocompleteToken); + this.insertExpression(this.module.focus.getContext(), literalValExpr); + + eventData.code = "double-quote"; + eventData.wrap = true; + + break; + } + + case EditActionType.InsertEmptyList: { + const newLiteral = new ListLiteralExpression(Docs.ListLiteralDocs.styles.backgroundColor); + this.insertExpression(context, newLiteral); + + if (flashGreen) this.flashGreen(newLiteral); + eventData.code = "empty-list"; + + break; + } + + case EditActionType.InsertEmptyListItem: { + if (action.data.toRight) { + const code = [new NonEditableTkn(", "), new TypedEmptyExpr([DataType.Any])]; + this.insertEmptyListItem(context.tokenToRight, context.tokenToRight.indexInRoot, code); + this.module.editor.insertAtCurPos(code); + this.module.focus.updateContext({ tokenToSelect: code[1] }); + + if (flashGreen) this.flashGreen(code[1]); + } else if (action.data.toLeft) { + const code = [new TypedEmptyExpr([DataType.Any]), new NonEditableTkn(", ")]; + this.insertEmptyListItem(context.tokenToLeft, context.tokenToLeft.indexInRoot + 1, code); + this.module.editor.insertAtCurPos(code); + this.module.focus.updateContext({ tokenToSelect: code[0] }); + + if (flashGreen) this.flashGreen(code[0]); + } + eventData.code = "list-item-comma"; + + break; + } + + case EditActionType.DeleteListItem: { + if (action.data.toRight) { + const items = this.module.removeItems(context.token.rootNode, context.token.indexInRoot, 2); + this.module.editor.executeEdits(this.getCascadedBoundary(items), null, ""); + } else if (action.data.toLeft) { + const items = this.module.removeItems(context.token.rootNode, context.token.indexInRoot - 1, 2); + this.module.editor.executeEdits(this.getCascadedBoundary(items), null, ""); + } + + break; + } + + case EditActionType.DeleteBinaryOperator: { + this.deleteCode(context.token.rootNode); + break; + } + + case EditActionType.DeleteBinaryOperatorItem: { + if (!(context.token.rootNode instanceof BinaryOperatorExpr)) break; + + let leftOperand = context.token.rootNode.getLeftOperand(); + let rightOperand = context.token.rootNode.getRightOperand(); + + if (leftOperand === context.token) { + this.replaceCode(context.token.rootNode, rightOperand); + } else { + this.replaceCode(context.token.rootNode, leftOperand); + } + break; + } + + case EditActionType.DeleteFunctionCallExpr: { + this.deleteCode(context.token.rootNode); + break; + } + + case EditActionType.DeleteFunctionCallExprItem: { + let rootNode = context.token.rootNode; + let replacementTkn; + if (!(rootNode instanceof FunctionCallExpr)) break; + for (let i = 0; i < rootNode.tokens.length; i++) { + if ( + !(rootNode.tokens[i] instanceof TypedEmptyExpr) && + !(rootNode.tokens[i] instanceof NonEditableTkn) + ) { + replacementTkn = rootNode.tokens[i]; + } + } + this.replaceCode(rootNode, replacementTkn); + break; + } + + case EditActionType.InsertImportFromDraftMode: { + let currContext = context; + this.module.editor.monaco.setPosition(new Position(1, 1)); + this.module.editor.cursor.setSelection(null); + this.module.insertEmptyLine(); + this.module.editor.monaco.setPosition(new Position(1, 1)); + this.module.editor.cursor.setSelection(null); + currContext = this.module.focus.getContext(); + + const stmt = new ImportStatement( + Docs.ImportDocs.styles.backgroundColor, + action.data?.moduleName, + action.data?.itemName + ); + const insertAction = new EditCodeAction( + "from --- import --- :", + "add-import-btn", + () => stmt, + InsertActionType.InsertStatement, + {}, + null, + [" "], + "import", + null + ); + + insertAction.performAction(this, this.module.eventRouter, currContext, { type: "draft-mode" }); + eventData.code = stmt.getRenderText(); + + break; + } + case EditActionType.InsertMemberCallConversion: + case EditActionType.InsertMemberAccessConversion: { + const root = action.data.codeToReplace; + this.module.focus.updateContext( + new UpdatableContext(null, action.data.codeToReplace.getRightPosition()) + ); + this.execute( + new EditAction(EditActionType.InsertModifier, { + source: action?.data?.source, + modifier: Actions.instance() + .actionsList.find((element) => element.cssId == action.data.conversionConstructId) + .getCodeFunction() as Modifier, + }), + this.module.focus.getContext() + ); + + this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); + + if (root instanceof Expression) root.validateTypes(this.module); + + eventData.code = action.data.codeToReplace.getRenderText(); + + break; + } + case EditActionType.InsertFunctionConversion: + case EditActionType.InsertTypeCast: + case EditActionType.InsertComparisonConversion: { + const root = action.data.codeToReplace; + this.deleteCode(action.data.codeToReplace, { + statement: null, + replaceType: action.data.typeToConvertTo, + }); + this.insertExpression( + this.module.focus.getContext(), + Actions.instance() + .actionsList.find((element) => element.cssId == action.data.conversionConstructId) + .getCodeFunction() as Expression + ); + action.data.codeToReplace.draftModeEnabled = false; + this.insertExpression(this.module.focus.getContext(), action.data.codeToReplace as Expression); + this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); + + if (root instanceof Expression) root.validateTypes(this.module); + eventData.code = action.data.codeToReplace.getRenderText(); + + break; + } + + case EditActionType.SelectClosestTokenAbove: { + this.module.focus.navigateUp(); + + break; + } + + case EditActionType.SelectClosestTokenBelow: { + this.module.focus.navigateDown(); + + break; + } + + case EditActionType.MoveCursorLeft: + preventDefaultEvent = false; + + break; + + case EditActionType.MoveCursorRight: + preventDefaultEvent = false; + + break; + + case EditActionType.SelectLeft: + preventDefaultEvent = false; + break; + + case EditActionType.SelectRight: + preventDefaultEvent = false; + break; + + case EditActionType.SelectToStart: + preventDefaultEvent = false; + break; + + case EditActionType.SelectToEnd: + preventDefaultEvent = false; + break; + + case EditActionType.Copy: + preventDefaultEvent = false; + break; + + case EditActionType.InsertLiteral: { + let color: string; + if (action.data?.literalType == DataType.String) { + color = Docs.StrDocs.styles.backgroundColor; + } else if (action.data?.literalType == DataType.Number) { + color = Docs.NumDocs.styles.backgroundColor; + } else if (action.data?.literalType == DataType.Number) { + color = Docs.NumDocs.styles.backgroundColor; + } else if (action.data?.literalType == DataType.Boolean && action.data?.initialValue == "True") { + color = Docs.TrueDocs.styles.backgroundColor; + } else if (action.data?.literalType == DataType.Boolean && action.data?.initialValue == "False") { + color = Docs.FalseDocs.styles.backgroundColor; + } + const newLiteral = new LiteralValExpr(color, action.data?.literalType, action.data?.initialValue); + this.insertExpression(context, newLiteral); + + if (flashGreen) this.flashGreen(newLiteral); + + if (action.data?.source?.type === "keyboard") { + eventType = LogType.InsertCode; + eventData.source = "keyboard"; + eventData.code = `literal-${getUserFriendlyType(newLiteral.returns)}`; + } + + break; + } + + case EditActionType.OpenValidInsertMenu: + this.openAutocompleteMenu( + this.module.actionFilter + .getProcessedInsertionsList() + .filter((item) => item.insertionResult.insertionType != InsertionType.Invalid) + ); + this.styleAutocompleteMenu(context.position); + + break; + + //TODO: Remove later + case EditActionType.OpenValidInsertMenuSingleLevel: + if (!this.module.menuController.isMenuOpen()) { + //TODO: Make this work with ActionFilter + //const suggestions = this.module.getAllValidInsertsList(focusedNode); + //this.module.menuController.buildSingleLevelConstructCategoryMenu(suggestions); + } else this.module.menuController.removeMenus(); + + break; + + case EditActionType.SelectMenuSuggestionAbove: + this.module.menuController.focusOptionAbove(); + + break; + + case EditActionType.SelectMenuSuggestionBelow: + this.module.menuController.focusOptionBelow(); + + break; + + case EditActionType.SelectMenuSuggestion: + this.module.menuController.selectFocusedOption(); + + break; + + case EditActionType.CloseValidInsertMenu: + this.module.menuController.removeMenus(); + + break; + + case EditActionType.OpenSubMenu: + this.module.menuController.openSubMenu(); + + break; + + case EditActionType.CloseSubMenu: + this.module.menuController.closeSubMenu(); + + break; + + case EditActionType.CloseDraftMode: + this.deleteCode(action.data.codeNode); + + break; + + case EditActionType.None: { + preventDefaultEvent = true; + + break; + } + + case EditActionType.InsertOperatorTkn: { + this.replaceCode(context.tokenToLeft, action.data.operator); + + if (context.tokenToLeft.rootNode instanceof BinaryOperatorExpr) { + const root = context.tokenToLeft.rootNode; + root.operator = action.data.operator.operator; + root.operatorCategory = getOperatorCategory(root.operator); + + if (root.getLeftOperand() instanceof TypedEmptyExpr) { + root.updateTypeOfEmptyOperandOnOperatorChange("left"); + } + + if (root.getRightOperand() instanceof TypedEmptyExpr) { + root.updateTypeOfEmptyOperandOnOperatorChange("right"); + } + } + + if (flashGreen) this.flashGreen(action.data.operator); + eventData.code = action.data.operator.getRenderText(); + + break; + } + + case EditActionType.DeleteUnconvertibleOperandWarning: { + if (action.data.codeToDelete.draftModeEnabled) + this.module.closeConstructDraftRecord(action.data.codeToDelete); + this.deleteCode(action.data.codeToDelete); + + //TODO: Eventually this if statement should go as all constructs will have this method + if ( + action.data.rootExpression instanceof Expression || + action.data.rootExpression instanceof ListAccessModifier + ) + action.data.rootExpression.validateTypes(this.module); + + break; + } + } + + if (eventData && eventType) Logger.Instance().queueEvent(new LogEvent(eventType, eventData)); + + this.module.editor.monaco.focus(); + + return preventDefaultEvent; + } + + createVarReference(buttonId: string): VariableReferenceExpr { + const identifier = document.getElementById(buttonId).innerText; + const dataType = this.module.variableController.getVariableTypeNearLine( + this.module.focus.getFocusedStatement().scope ?? + ( + this.module.focus.getStatementAtLineNumber(this.module.editor.monaco.getPosition().lineNumber) + .rootNode as Statement | Module + ).scope, + this.module.editor.monaco.getPosition().lineNumber, + identifier + ); + + return new VariableReferenceExpr(identifier, dataType, buttonId); + } + + insertVariableReference(buttonId: string, source: {}, providedContext?: Context, autocompleteData?: {}) { + let context = providedContext ? providedContext : this.module.focus.getContext(); + + let { eventType, eventData } = this.getLogEventSource(source); + + if (this.module.validator.onBeginningOfLine(context)) { + const varRef = this.createVarReference(buttonId); + const stmt = new VarOperationStmt(varRef); + this.replaceEmptyStatement(context.lineStatement, stmt); + + const availableActions = this.module.actionFilter + .getProcessedInsertionsList() + .filter( + (action) => + action.insertionResult.insertionType !== InsertionType.Invalid && + (action.insertActionType === InsertActionType.InsertAssignmentModifier || + action.insertActionType === InsertActionType.InsertAugmentedAssignmentModifier) + ); + + this.module.openDraftMode( + stmt, + "Variable references should not be used on empty lines. Try converting it to an assignment statement instead!", + (() => { + const buttons = []; + + for (const action of availableActions) { + const button = document.createElement("div"); + addClassToDraftModeResolutionButton(button, stmt); + + const text = `${varRef.identifier}${action.optionName}`.replace(/---/g, ""); + button.innerHTML = text; + + const modifier = action.getCode(); + button.addEventListener("click", () => { + this.module.closeConstructDraftRecord(stmt); + this.module.executer.execute( + new EditAction(EditActionType.InsertAssignmentModifier, { + codeToReplace: stmt, + replacementConstructCssId: action.cssId, + modifier: modifier, + source: { type: "draft-mode" }, + }), + this.module.focus.getContext() + ); + this.flashGreen(modifier.rootNode as Statement); + }); + + buttons.push(button); + } + + return buttons; + })() + ); + + if (autocompleteData) { + this.flashGreen(stmt); + } + + eventData.code = varRef.getRenderText(); + } else if (this.module.validator.atEmptyExpressionHole(context)) { + const expr = this.createVarReference(buttonId); + this.insertExpression(context, expr); + + if (autocompleteData) { + this.flashGreen(expr); + } + + eventData.code = expr.getRenderText(); + } + + if (eventData && eventType) Logger.Instance().queueEvent(new LogEvent(eventType, eventData)); + } + + getLogEventSource(source: any): { eventType: LogType; eventData: any } { + let eventType: LogType; + let eventData: any = null; + + if (source) { + eventData = {}; + + switch (source.type) { + case "toolbox": + eventType = LogType.InsertCode; + eventData.source = "toolbox"; + + break; + + case "autocomplete": + eventType = LogType.InsertCode; + eventData.source = "autocomplete"; + + break; + + case "autocomplete-menu": + eventType = LogType.InsertCode; + eventData = { + source: "autocomplete-menu", + precision: source.precision, + length: source.length, + }; + + break; + + case "defined-variables": + eventType = LogType.InsertCode; + eventData.source = "defined-vars-toolbox"; + + break; + + case "draft-mode": + eventType = LogType.InsertCode; + eventData.source = "draft-mode"; + + break; + } + } + + return { eventType, eventData }; + } + + deleteAutocompleteOnMatch(context: Context): Context { + let token: AutocompleteTkn; + + if (context.token instanceof AutocompleteTkn) token = context.token; + if (context.tokenToLeft instanceof AutocompleteTkn) token = context.tokenToLeft; + if (context.tokenToRight instanceof AutocompleteTkn) token = context.tokenToRight; + + if (token) { + switch (token.autocompleteType) { + case AutoCompleteType.RightOfExpression: + case AutoCompleteType.LeftOfExpression: + this.deleteAutocompleteToken(token); + + break; + + case AutoCompleteType.StartOfLine: + if (token.rootNode instanceof TemporaryStmt) { + this.deleteCode(token.rootNode, { + statement: true, + }); + } else { + this.deleteCode(token); + } + + break; + + case AutoCompleteType.AtExpressionHole: + this.deleteCode(token, {}); + + break; + } + } + + return this.module.focus.getContext(); + } + + private flashGreen(code: CodeConstruct) { + if (code) { + let highlight = new ConstructHighlight(this.module.editor, code, [109, 242, 162, 1]); + + setTimeout(() => { + if (highlight) { + highlight.changeHighlightColour([255, 255, 255, 0]); + + setTimeout(() => { + highlight.removeFromDOM(); + highlight = null; + }, 500); + } + }, 1); + } + } + + private setTokenColor(code: CodeConstruct, color: string) { + const aRgbHex = color.substring(1).match(/.{1,2}/g); + const aRgb = [parseInt(aRgbHex[0], 16), parseInt(aRgbHex[1], 16), parseInt(aRgbHex[2], 16)]; + + if (code) { + let highlight = new ConstructHighlight(this.module.editor, code, [aRgb[0], aRgb[1], aRgb[2], 1]); + } + } + + private insertEmptyListItem(focusedCode: CodeConstruct, index: number, items: Array) { + if (focusedCode instanceof Token || focusedCode instanceof Expression) { + const root = focusedCode.rootNode; + + if (root instanceof Statement && root.tokens.length > 0) { + root.tokens.splice(index, 0, ...items); + + for (let i = 0; i < root.tokens.length; i++) { + root.tokens[i].indexInRoot = i; + root.tokens[i].rootNode = root; + } + + root.rebuild(root.getLeftPosition(), 0); + } + } + } + + private performMatchAction(match: EditCodeAction, token: AutocompleteTkn) { + if ( + match.insertActionType == InsertActionType.InsertNewVariableStmt && + (Object.keys(PythonKeywords).indexOf(token.text.trim()) >= 0 || + Object.keys(BuiltInFunctions).indexOf(token.text.trim()) >= 0) + ) { + // TODO: can insert an interesting warning + return; + } + + let length = 0; + if (match.insertActionType == InsertActionType.InsertNewVariableStmt) length = token.text.length + 1; + else length = match.matchString.length + 1; + + match.performAction( + this, + this.module.eventRouter, + this.module.focus.getContext(), + { type: "autocomplete", precision: "1", length }, + { + identifier: token.text, + } + ); + } + + private insertToken(context: Context, code: Token, { toLeft = false, toRight = false } = {}) { + if (context.token instanceof TypedEmptyExpr || context.token instanceof EmptyOperatorTkn) { + if (context.expression != null) { + const root = context.expression.rootNode as Statement; + root.replace(code, context.expression.indexInRoot); + } else if (context.token != null) { + const root = context.token.rootNode as Statement; + root.replace(code, context.token.indexInRoot); + } + + const range = new Range( + context.position.lineNumber, + context.token.left, + context.position.lineNumber, + context.token.right + ); + + this.module.editor.executeEdits(range, code); + } else if (toRight && context.expressionToLeft != null) { + const root = context.expressionToLeft.rootNode; + code.rootNode = root; + root.tokens.splice(context.expressionToLeft.indexInRoot + 1, 0, code); + root.rebuild(root.getLeftPosition(), 0); + this.module.editor.insertAtCurPos([code]); + } else if (toLeft && context.expressionToRight != null) { + const root = context.expressionToRight.rootNode; + code.rootNode = root; + root.tokens.splice(context.expressionToRight.indexInRoot, 0, code); + root.rebuild(root.getLeftPosition(), 0); + this.module.editor.insertAtCurPos([code]); + } + } + + private insertExpression(context: Context, code: Expression) { + // type checks -- different handling based on type of code construct + // focusedNode.returns != code.returns would work, but we need more context to get the right error message + if (context.token instanceof TypedEmptyExpr) { + const root = context.token.rootNode; + let insertionResult = root.typeValidateInsertionIntoHole(code, context.token); + + if (insertionResult.insertionType != InsertionType.Invalid) { + if (root instanceof Statement) { + root.onInsertInto(code); + } + + if (context.token.message && context.selected) { + //TODO: This should only be closed if the current insertion would fix the current draft mode. Currently we don't know if that is the case. + this.module.messageController.removeMessageFromConstruct(context.token); + } + + // replaces expression with the newly inserted expression + const expr = code as Expression; + + this.module.replaceFocusedExpression(expr); + + const range = new Range( + context.position.lineNumber, + context.token.left, + context.position.lineNumber, + context.token.right + ); + + this.module.editor.executeEdits(range, expr); + + //TODO: This should probably run only if the insert above was successful, we cannot assume that it was + if (!context.token.message) { + const newContext = code.getInitialFocus(); + this.module.focus.updateContext(newContext); + } + } + + if (root instanceof BinaryOperatorExpr) { + root.validateTypes(this.module); + } else if (insertionResult.insertionType == InsertionType.DraftMode) { + this.module.openDraftMode(code, insertionResult.message, [ + ...insertionResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton(code.getKeyword(), this.module, code); + }), + ]); + } else if (isImportable(code)) { + //TODO: This needs to run regardless of what happens above. But for that we need nested draft modes. It should not be a case within the same if block + //The current problem is that a construct can only have a single draft mode on it. This is mostly ok since we often reinsert the construct when fixing a draft mode + //and the reinsertion triggers another draft mode if necessary. But this does not happen for importables because they are not reinserted on a fix so we might lose some + //draft modes this way. + + //A quick fix for now would be to just trigger reinsertion. Otherwise we need a mechanism for having multiple draft modes. I have a commit on a separate branch for that. + //Converting them to a linked list seems to make the most sense. + this.checkImports(code, insertionResult.insertionType); + } + } + } + + private checkImports(insertedCode: Importable, currentInsertionType: InsertionType) { + if (currentInsertionType === InsertionType.Invalid) return; + + const insertionType = insertedCode.validateImportOnInsertion(this.module, currentInsertionType); + if (insertionType === InsertionType.DraftMode && insertedCode instanceof Statement) { + this.module.openImportDraftMode(insertedCode); + } + } + + private openAutocompleteMenu(inserts: EditCodeAction[]) { + if (!this.module.menuController.isMenuOpen()) { + inserts = inserts.filter((insert) => insert.insertionResult.insertionType !== InsertionType.Invalid); + this.module.menuController.buildSingleLevelMenu(inserts); + } else this.module.menuController.removeMenus(); + } + + private replaceEmptyStatement(emptyLine: Statement, statement: Statement) { + const root = emptyLine.rootNode as Statement | Module; + + replaceInBody(root, emptyLine.indexInRoot, statement); + + if (root instanceof Statement) root.notify(CallbackType.replace); + + var range = new Range(emptyLine.lineNumber, statement.left, emptyLine.lineNumber, statement.right); + + if (emptyLine.message) this.module.messageController.removeMessageFromConstruct(emptyLine); + + if (isImportable(statement)) { + this.checkImports(statement, InsertionType.Valid); + } + + this.module.editor.executeEdits(range, statement); + this.module.focus.updateContext(statement.getInitialFocus()); + } + + private replaceWithBinaryOp( + op: BinaryOperator, + expr: Expression, + { toLeft = false, toRight = false } + ): BinaryOperatorExpr { + if (expr instanceof Modifier) expr = expr.rootNode as Expression; + + const initialBoundary = this.getBoundaries(expr); + const root = expr.rootNode as Statement; + const index = expr.indexInRoot; + + const newCode = new BinaryOperatorExpr( + Docs.AddDocs.styles.backgroundColor, + op, + expr.returns, // is not that important, will be replaced in the constructor based on the operator. + root, + expr.indexInRoot + ); + + const curOperand = toLeft ? newCode.getLeftOperand() : newCode.getRightOperand(); + const otherOperand = toLeft ? newCode.getRightOperand() : newCode.getLeftOperand(); + const insertionResult = newCode.typeValidateInsertionIntoHole(expr, curOperand as TypedEmptyExpr); + + /** + * Special cases + * + * if (--- + (--- + ---)|): --> attempting to insert a comparator or binary boolean operation should fail + */ + if (insertionResult.insertionType === InsertionType.Valid) { + const replacementResult = expr.canReplaceWithConstruct(newCode); + + // this can never go into draft mode + if (replacementResult.insertionType !== InsertionType.Invalid) { + if (root.tokens[index].draftModeEnabled) this.module.closeConstructDraftRecord(root.tokens[index]); + + if (toLeft) newCode.replaceLeftOperand(expr); + else newCode.replaceRightOperand(expr); + + expr.indexInRoot = curOperand.indexInRoot; + expr.rootNode = newCode; + + root.tokens[index] = newCode; + //TODO: Call onInsertInto() on this line + root.rebuild(root.getLeftPosition(), 0); + + this.module.editor.executeEdits(initialBoundary, newCode); + this.module.focus.updateContext({ + tokenToSelect: newCode.tokens[otherOperand.indexInRoot], + }); + + if (replacementResult.insertionType !== InsertionType.DraftMode && expr.draftModeEnabled) { + this.module.closeConstructDraftRecord(expr); + } else if (root instanceof BinaryOperatorExpr) { + root.validateTypes(this.module); + } else if (replacementResult.insertionType === InsertionType.DraftMode) { + this.module.openDraftMode(newCode, replacementResult.message, [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton(newCode.getRenderText(), this.module, newCode); + }), + ]); + } + + if (newCode.rootNode instanceof Statement) newCode.rootNode.onInsertInto(newCode); + + return newCode; + } + } + } + + private getCascadedBoundary(codes: Array): Range { + if (codes.length > 1) { + const lineNumber = codes[0].getLineNumber(); + + return new Range(lineNumber, codes[0].left, lineNumber, codes[codes.length - 1].right); + } else return this.getBoundaries(codes[0]); + } + + private getBoundaries(code: CodeConstruct, { selectIndent = false } = {}): Range { + const lineNumber = code.getLineNumber(); + + if (code instanceof Statement && code.hasBody()) { + const stmtStack = new Array(); + stmtStack.unshift(...code.body); + let endLineNumber = 0; + let endColumn = 0; + + while (stmtStack.length > 0) { + const curStmt = stmtStack.pop(); + + if (curStmt instanceof Statement && curStmt.hasBody()) stmtStack.unshift(...curStmt.body); + + if (endLineNumber < curStmt.lineNumber) { + endLineNumber = curStmt.lineNumber; + endColumn = curStmt.right; + } + } + + return new Range(lineNumber, code.left, endLineNumber, endColumn); + } else if (code instanceof Statement || code instanceof Token) { + if (selectIndent) { + return new Range(lineNumber, code.left - TAB_SPACES, lineNumber, code.right); + } else return new Range(lineNumber, code.left, lineNumber, code.right); + } + } + + private deleteModifier(mod: Modifier, { deleting = false } = {}) { + // TODO: this will be a prototype version of the code. needs to be cleaned and iterated on -> + // e.g. merge the operations for VarOperationStmt and ValueOperationExpr + + // TODO: if deleting, should not move cursor + const removeRange = this.getBoundaries(mod); + const rootOfExprToLeft = mod.rootNode; + + rootOfExprToLeft.tokens.splice(mod.indexInRoot, 1); + this.module.recursiveNotify(mod, CallbackType.delete); + + this.module.closeConstructDraftRecord(rootOfExprToLeft); + + let built = false; + let positionToMove: Position; + + if (rootOfExprToLeft.tokens.length == 1) { + // only a val or var-ref is remaining: + if (rootOfExprToLeft instanceof ValueOperationExpr) { + rootOfExprToLeft.updateReturnType(); + + let replacementResult = rootOfExprToLeft.rootNode.checkInsertionAtHole( + rootOfExprToLeft.indexInRoot, + rootOfExprToLeft.returns + ); + + if (replacementResult.insertionType == InsertionType.DraftMode) { + const ref = rootOfExprToLeft.getVarRef(); + if (ref instanceof VariableReferenceExpr) { + const line = this.module.focus.getContext().lineStatement; + + const varType = this.module.variableController.getVariableTypeNearLine( + line.rootNode instanceof Module ? this.module.scope : line.scope, + line.lineNumber, + ref.identifier, + false + ); + + let expectedTypes = rootOfExprToLeft.rootNode.typeOfHoles[rootOfExprToLeft.indexInRoot]; + const currentAllowedTypes = rootOfExprToLeft.rootNode.getCurrentAllowedTypesOfHole( + rootOfExprToLeft.indexInRoot, + false + ); + + if (currentAllowedTypes.length > 0) { + expectedTypes = currentAllowedTypes; + } + + this.module.openDraftMode( + rootOfExprToLeft, + TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR(ref.identifier, varType, expectedTypes), + [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton( + ref.identifier, + this.module, + rootOfExprToLeft + ); + }), + ] + ); + } else { + let expectedTypes = rootOfExprToLeft.rootNode.typeOfHoles[rootOfExprToLeft.indexInRoot]; + + const currentAllowedTypes = rootOfExprToLeft.rootNode.getCurrentAllowedTypesOfHole( + rootOfExprToLeft.indexInRoot, + false + ); + + if (currentAllowedTypes.length > 0) { + expectedTypes = currentAllowedTypes; + } + + this.module.openDraftMode( + ref, + TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR( + ref.getKeyword(), + ref.returns, + expectedTypes + ), + [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton( + ref.getKeyword(), + this.module, + rootOfExprToLeft + ); + }), + ] + ); + } + } + const value = rootOfExprToLeft.tokens[0]; + rootOfExprToLeft.rootNode.tokens[rootOfExprToLeft.indexInRoot] = value; + value.rootNode = rootOfExprToLeft.rootNode; + value.indexInRoot = rootOfExprToLeft.indexInRoot; + + rootOfExprToLeft.rootNode.rebuild(rootOfExprToLeft.rootNode.getLeftPosition(), 0); + positionToMove = new Position(value.getLineNumber(), value.right); + built = true; + } + } + + if (!built) { + rootOfExprToLeft.rebuild(rootOfExprToLeft.getLeftPosition(), 0); + positionToMove = new Position(rootOfExprToLeft.getLineNumber(), rootOfExprToLeft.right); + } + + this.module.editor.executeEdits(removeRange, null, ""); + if (!deleting) { + this.module.focus.updateContext({ + positionToMove, + }); + } + } + + private deleteAutocompleteToken(token: Token) { + const range = this.getBoundaries(token); + const root = token.rootNode as Statement; + root.tokens.splice(token.indexInRoot, 1); + + root.rebuild(root.getLeftPosition(), 0); + token.notify(CallbackType.delete); + + this.module.editor.executeEdits(range, null, ""); + } + + private replaceCode(code: CodeConstruct, replace: CodeConstruct) { + const replacementRange = this.getBoundaries(code); + const root = code.rootNode; + + if (root instanceof Statement) { + root.tokens.splice(code.indexInRoot, 1, replace); + + this.module.recursiveNotify(code, CallbackType.delete); + + for (let i = 0; i < root.tokens.length; i++) { + root.tokens[i].indexInRoot = i; + root.tokens[i].rootNode = root; + } + + root.rebuild(root.getLeftPosition(), 0); + + this.module.editor.executeEdits(replacementRange, replace); + + if (replace instanceof Token && replace.isEmpty) { + this.module.focus.updateContext({ tokenToSelect: replace }); + } else this.module.focus.updateContext({ positionToMove: replace.getRightPosition() }); + } + } + + private deleteCode(code: CodeConstruct, { statement = false, replaceType = null } = {}) { + const root = code.rootNode; + const replacementRange = this.getBoundaries(code); + let replacement: CodeConstruct; + + if (statement) replacement = this.module.removeStatement(code as Statement); + else replacement = this.module.replaceItemWTypedEmptyExpr(code, replaceType); + + this.module.editor.executeEdits(replacementRange, replacement); + this.module.focus.updateContext({ tokenToSelect: replacement }); + + if (root instanceof Expression) root.validateTypes(this.module); + } + + private validateIdentifier(context: Context, identifierText: string) { + let focusedNode = null; + + if (context.token && context.selected && context.token instanceof IdentifierTkn) { + focusedNode = context.token; + } else if (context.tokenToLeft && context.tokenToLeft instanceof IdentifierTkn) { + focusedNode = context.tokenToLeft; + } else if (context.tokenToRight && context.tokenToRight instanceof IdentifierTkn) { + focusedNode = context.tokenToRight; + } + + if ( + focusedNode instanceof IdentifierTkn || + context.tokenToLeft instanceof IdentifierTkn || + context.tokenToRight instanceof IdentifierTkn + ) { + if (Object.keys(PythonKeywords).indexOf(identifierText) > -1) { + this.module.messageController.addPopUpMessage( + focusedNode, + { identifier: identifierText }, + ErrorMessage.identifierIsKeyword + ); + } else if (Object.keys(BuiltInFunctions).indexOf(identifierText) > -1) { + this.module.messageController.addPopUpMessage( + focusedNode, + { identifier: identifierText }, + ErrorMessage.identifierIsBuiltInFunc + ); + } + } + } + + private updateAutocompleteMenu(autocompleteTkn: AutocompleteTkn) { + this.module.menuController.updateMenuOptions(autocompleteTkn.getEditableText()); + this.module.menuController.updatePosition( + this.module.menuController.getNewMenuPositionFromCode(autocompleteTkn) + ); + } + + private styleAutocompleteMenu(pos: Position) { + this.module.menuController.styleMenuOptions(); + this.module.menuController.updatePosition(this.module.menuController.getNewMenuPositionFromPosition(pos)); + } +} diff --git a/src/editor/action-filter.ts b/src/editor/action-filter.ts index bf69cd0..86f6927 100644 --- a/src/editor/action-filter.ts +++ b/src/editor/action-filter.ts @@ -43,6 +43,7 @@ export class ActionFilter { action.getCodeFunction, action.insertActionType, action.insertData, + action.documentation, action.validateAction(this.module.validator, context), action.terminatingChars, action.matchString, @@ -81,6 +82,7 @@ export class ActionFilter { null, {}, new InsertionResult(varRecord[1], "MESSAGE BASED ON INSERTION TYPE", []), //TODO: Need to actually check what the insertion type is and populate the insertion result accordingly + null, [""], varStmt.getIdentifier(), null, @@ -323,6 +325,7 @@ export class EditCodeAction extends UserAction { getCodeFunction: () => Statement | Expression, insertActionType: InsertActionType, insertData: any = {}, + documentation: any, insertionResult: InsertionResult, terminatingChars: string[], matchString: string, @@ -335,7 +338,7 @@ export class EditCodeAction extends UserAction { getCodeFunction, insertActionType, insertData, - null, + documentation, terminatingChars, matchString, matchRegex, diff --git a/src/editor/consts.ts b/src/editor/consts.ts index 4379a21..888eebd 100644 --- a/src/editor/consts.ts +++ b/src/editor/consts.ts @@ -75,7 +75,7 @@ import { ValueOperationExpr, VarAssignmentStmt, VarOperationStmt, - WhileStatement + WhileStatement, } from "../syntax-tree/ast"; import { AugmentedAssignmentOperator, @@ -83,7 +83,7 @@ import { DataType, IdentifierRegex, NumberRegex, - UnaryOperator + UnaryOperator, } from "../syntax-tree/consts"; import { Module } from "../syntax-tree/module"; import { EditCodeAction } from "./action-filter"; @@ -289,6 +289,61 @@ export enum InsertActionType { InsertOperatorTkn, } +export const Docs: any = { + AddVarDocs, + AddDocs, + AndDocs, + AssignAddDocs, + AssignDivDocs, + AssignMultDocs, + AssignSubDocs, + AssignDocs, + BreakDocs, + RandChoiceDocs, + CompEqDocs, + CompGtDocs, + CompGteDocs, + CompLtDocs, + CompLteDocs, + CompNeDocs, + DivDocs, + ElifDocs, + ElseDocs, + FStringItemDocs, + FStringDocs, + FalseDocs, + FindDocs, + FloorDivDocs, + ForDocs, + IfDocs, + ImportDocs, + InDocs, + InputDocs, + JoinDocs, + LenDocs, + ListAppendDocs, + ListIndexDocs, + ListItemDocs, + ListLiteralDocs, + ModDocs, + MultDocs, + NotInDocs, + NotDocs, + NumDocs, + OrDocs, + PrintDocs, + RandintDocs, + RangeDocs, + ReplaceDocs, + SplitDocs, + StrDocs, + SubDocs, + CastToIntDocs, + CastToStrDocs, + TrueDocs, + WhileDocs, +}; + export class Actions { private static inst: Actions; actionsList: Array; @@ -300,7 +355,10 @@ export class Actions { const PrintStmt = new EditCodeAction( "print(---)", "add-print-btn", - () => new FunctionCallStmt("print", [new Argument([DataType.Any], "item", false)]), + () => + new FunctionCallStmt(PrintDocs.styles.backgroundColor, "print", [ + new Argument([DataType.Any], "item", false), + ]), InsertActionType.InsertPrintFunctionStmt, {}, PrintDocs, @@ -314,6 +372,7 @@ export class Actions { "add-randint-btn", () => new FunctionCallExpr( + RandintDocs.styles.backgroundColor, "randint", [new Argument([DataType.Number], "start", false), new Argument([DataType.Number], "end", false)], DataType.Number, @@ -334,6 +393,7 @@ export class Actions { "add-choice-btn", () => new FunctionCallExpr( + RandChoiceDocs.styles.backgroundColor, "choice", [new Argument([DataType.AnyList], "choices", false)], DataType.Any, @@ -352,7 +412,13 @@ export class Actions { const RangeExpr = new EditCodeAction( "range(---)", "add-range-btn", - () => new FunctionCallExpr("range", [new Argument([DataType.Number], "end", false)], DataType.NumberList), + () => + new FunctionCallExpr( + RangeDocs.styles.backgroundColor, + "range", + [new Argument([DataType.Number], "end", false)], + DataType.NumberList + ), InsertActionType.InsertExpression, {}, RangeDocs, @@ -366,6 +432,7 @@ export class Actions { "add-len-btn", () => new FunctionCallExpr( + LenDocs.styles.backgroundColor, "len", [ new Argument( @@ -393,7 +460,13 @@ export class Actions { const InputExpr = new EditCodeAction( "input(---)", "add-input-btn", - () => new FunctionCallExpr("input", [new Argument([DataType.String], "prompt", true)], DataType.String), + () => + new FunctionCallExpr( + InputDocs.styles.backgroundColor, + "input", + [new Argument([DataType.String], "prompt", true)], + DataType.String + ), InsertActionType.InsertInputExpr, {}, InputDocs, @@ -405,7 +478,7 @@ export class Actions { const StringLiteralExpr = new EditCodeAction( '""', "add-str-btn", - () => new LiteralValExpr(DataType.String, ""), + () => new LiteralValExpr(StrDocs.styles.backgroundColor, DataType.String, ""), InsertActionType.InsertLiteral, { literalType: DataType.String, @@ -420,7 +493,7 @@ export class Actions { const FormattedStringLiteralExpr = new EditCodeAction( "f''", "add-f-str-literal-btn", - () => new FormattedStringExpr(""), + () => new FormattedStringExpr(FStringDocs.styles.backgroundColor, ""), InsertActionType.InsertExpression, {}, FStringDocs, @@ -432,7 +505,7 @@ export class Actions { const FormattedStringItem = new EditCodeAction( "{}", "add-f-str-item-btn", - () => new FormattedStringCurlyBracketsExpr(), + () => new FormattedStringCurlyBracketsExpr(FStringDocs.styles.backgroundColor), InsertActionType.InsertFormattedStringItem, {}, FStringItemDocs, @@ -444,7 +517,7 @@ export class Actions { const NumberLiteralExpr = new EditCodeAction( "0", "add-num-btn", - () => new LiteralValExpr(DataType.Number, "0"), + () => new LiteralValExpr(NumDocs.styles.backgroundColor, DataType.Number, "0"), InsertActionType.InsertExpression, { literalType: DataType.Number, @@ -459,7 +532,7 @@ export class Actions { const BooleanTrueLiteralExpr = new EditCodeAction( "True", "add-true-btn", - () => new LiteralValExpr(DataType.Boolean, "True"), + () => new LiteralValExpr(TrueDocs.styles.backgroundColor, DataType.Boolean, "True"), InsertActionType.InsertExpression, { literalType: DataType.Boolean, @@ -475,7 +548,7 @@ export class Actions { const BooleanFalseLiteralExpr = new EditCodeAction( "False", "add-false-btn", - () => new LiteralValExpr(DataType.Boolean, "False"), + () => new LiteralValExpr(FalseDocs.styles.backgroundColor, DataType.Boolean, "False"), InsertActionType.InsertLiteral, { literalType: DataType.Boolean, @@ -491,7 +564,7 @@ export class Actions { const BinAddExpr = new EditCodeAction( "--- + ---", "add-bin-add-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.Add, DataType.Number), //NOTE: For + this will be reassigned in the constructor + () => new BinaryOperatorExpr(AddDocs.styles.backgroundColor, BinaryOperator.Add, DataType.Number), //NOTE: For + this will be reassigned in the constructor InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.Add, @@ -505,7 +578,7 @@ export class Actions { const BinSubExpr = new EditCodeAction( "--- - ---", "add-bin-sub-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.Subtract, DataType.Number), + () => new BinaryOperatorExpr(SubDocs.styles.backgroundColor, BinaryOperator.Subtract, DataType.Number), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.Subtract, @@ -519,7 +592,7 @@ export class Actions { const BinMultExpr = new EditCodeAction( "--- * ---", "add-bin-mul-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.Multiply, DataType.Number), + () => new BinaryOperatorExpr(MultDocs.styles.backgroundColor, BinaryOperator.Multiply, DataType.Number), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.Multiply, @@ -533,7 +606,7 @@ export class Actions { const BinDivExpr = new EditCodeAction( "--- / ---", "add-bin-div-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.Divide, DataType.Number), + () => new BinaryOperatorExpr(DivDocs.styles.backgroundColor, BinaryOperator.Divide, DataType.Number), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.Divide, @@ -547,7 +620,7 @@ export class Actions { const BinFloorDivExpr = new EditCodeAction( "--- // ---", "add-bin-floor-div-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.FloorDiv, DataType.Number), + () => new BinaryOperatorExpr(FloorDivDocs.styles.backgroundColor, BinaryOperator.FloorDiv, DataType.Number), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.FloorDiv, @@ -561,7 +634,7 @@ export class Actions { const BinModExpr = new EditCodeAction( "--- % ---", "add-bin-mod-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.Mod, DataType.Number), + () => new BinaryOperatorExpr(ModDocs.styles.backgroundColor, BinaryOperator.Mod, DataType.Number), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.Mod, @@ -575,7 +648,7 @@ export class Actions { const InOperatorTkn = new EditCodeAction( "in", "add-in-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.In), + () => new OperatorTkn(InDocs.styles.backgroundColor, BinaryOperator.In), InsertActionType.InsertOperatorTkn, {}, InDocs, @@ -587,7 +660,7 @@ export class Actions { const NotInOperatorTkn = new EditCodeAction( "not in", "add-not-in-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.NotIn), + () => new OperatorTkn(NotInDocs.styles.backgroundColor, BinaryOperator.NotIn), InsertActionType.InsertOperatorTkn, {}, NotInDocs, @@ -599,7 +672,7 @@ export class Actions { const AddOperatorTkn = new EditCodeAction( "+", "add-add-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.Add), + () => new OperatorTkn(AddDocs.styles.backgroundColor, BinaryOperator.Add), InsertActionType.InsertOperatorTkn, {}, AddDocs, @@ -611,7 +684,7 @@ export class Actions { const SubOperatorTkn = new EditCodeAction( "-", "add-sub-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.Subtract), + () => new OperatorTkn(SubDocs.styles.backgroundColor, BinaryOperator.Subtract), InsertActionType.InsertOperatorTkn, {}, SubDocs, @@ -623,7 +696,7 @@ export class Actions { const MultOperatorTkn = new EditCodeAction( "*", "add-mult-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.Multiply), + () => new OperatorTkn(MultDocs.styles.backgroundColor, BinaryOperator.Multiply), InsertActionType.InsertOperatorTkn, {}, MultDocs, @@ -635,7 +708,7 @@ export class Actions { const DivOperatorTkn = new EditCodeAction( "/", "add-div-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.Divide), + () => new OperatorTkn(DivDocs.styles.backgroundColor, BinaryOperator.Divide), InsertActionType.InsertOperatorTkn, {}, DivDocs, @@ -647,10 +720,10 @@ export class Actions { const FloorDivOperatorTkn = new EditCodeAction( "//", "add-floor-div-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.FloorDiv), + () => new OperatorTkn(FloorDivDocs.styles.backgroundColor, BinaryOperator.FloorDiv), InsertActionType.InsertOperatorTkn, {}, - DivDocs, + FloorDivDocs, ["/"], "/", null @@ -659,10 +732,10 @@ export class Actions { const ModOperatorTkn = new EditCodeAction( "%", "add-mod-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.Mod), + () => new OperatorTkn(ModDocs.styles.backgroundColor, BinaryOperator.Mod), InsertActionType.InsertOperatorTkn, {}, - DivDocs, + ModDocs, ["%"], "", null @@ -671,7 +744,7 @@ export class Actions { const BinAndExpr = new EditCodeAction( "--- and ---", "add-bin-and-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.And, DataType.Boolean), + () => new BinaryOperatorExpr(AndDocs.styles.backgroundColor, BinaryOperator.And, DataType.Boolean), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.And, @@ -685,7 +758,7 @@ export class Actions { const BinOrExpr = new EditCodeAction( "--- or ---", "add-bin-or-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.Or, DataType.Boolean), + () => new BinaryOperatorExpr(OrDocs.styles.backgroundColor, BinaryOperator.Or, DataType.Boolean), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.Or, @@ -699,7 +772,7 @@ export class Actions { const AndOperatorTkn = new EditCodeAction( "and", "add-and-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.And), + () => new OperatorTkn(AndDocs.styles.backgroundColor, BinaryOperator.And), InsertActionType.InsertOperatorTkn, {}, AndDocs, @@ -711,7 +784,7 @@ export class Actions { const OrOperatorTkn = new EditCodeAction( "or", "add-or-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.Or), + () => new OperatorTkn(OrDocs.styles.backgroundColor, BinaryOperator.Or), InsertActionType.InsertOperatorTkn, {}, OrDocs, @@ -723,7 +796,7 @@ export class Actions { const BinCompEqExpr = new EditCodeAction( "--- == ---", "add-comp-eq-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.Equal, DataType.Boolean), + () => new BinaryOperatorExpr(CompEqDocs.styles.backgroundColor, BinaryOperator.Equal, DataType.Boolean), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.Equal, @@ -737,7 +810,7 @@ export class Actions { const BinCompNeqExpr = new EditCodeAction( "--- != ---", "add-comp-neq-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.NotEqual, DataType.Boolean), + () => new BinaryOperatorExpr(CompNeDocs.styles.backgroundColor, BinaryOperator.NotEqual, DataType.Boolean), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.NotEqual, @@ -751,7 +824,7 @@ export class Actions { const BinCompLtExpr = new EditCodeAction( "--- < ---", "add-comp-lt-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.LessThan, DataType.Boolean), + () => new BinaryOperatorExpr(CompLtDocs.styles.backgroundColor, BinaryOperator.LessThan, DataType.Boolean), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.LessThan, @@ -766,7 +839,12 @@ export class Actions { const BinCompLteExpr = new EditCodeAction( "--- <= ---", "add-comp-lte-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.LessThanEqual, DataType.Boolean), + () => + new BinaryOperatorExpr( + CompLteDocs.styles.backgroundColor, + BinaryOperator.LessThanEqual, + DataType.Boolean + ), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.LessThanEqual, @@ -780,7 +858,8 @@ export class Actions { const BinCompGtExpr = new EditCodeAction( "--- > ---", "add-comp-gt-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.GreaterThan, DataType.Boolean), + () => + new BinaryOperatorExpr(CompGtDocs.styles.backgroundColor, BinaryOperator.GreaterThan, DataType.Boolean), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.GreaterThan, @@ -795,7 +874,12 @@ export class Actions { const BinCompGteExpr = new EditCodeAction( "--- >= ---", "add-comp-gte-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.GreaterThanEqual, DataType.Boolean), + () => + new BinaryOperatorExpr( + CompGteDocs.styles.backgroundColor, + BinaryOperator.GreaterThanEqual, + DataType.Boolean + ), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.GreaterThanEqual, @@ -809,7 +893,7 @@ export class Actions { const EqOperatorTkn = new EditCodeAction( "==", "add-comp-eq-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.Equal), + () => new OperatorTkn(CompEqDocs.styles.backgroundColor, BinaryOperator.Equal), InsertActionType.InsertOperatorTkn, {}, CompEqDocs, @@ -821,7 +905,7 @@ export class Actions { const NeqOperatorTkn = new EditCodeAction( "!=", "add-comp-neq-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.NotEqual), + () => new OperatorTkn(CompNeDocs.styles.backgroundColor, BinaryOperator.NotEqual), InsertActionType.InsertOperatorTkn, {}, CompNeDocs, @@ -833,7 +917,7 @@ export class Actions { const GtOperatorTkn = new EditCodeAction( ">", "add-comp-gt-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.GreaterThan), + () => new OperatorTkn(CompNeDocs.styles.backgroundColor, BinaryOperator.GreaterThan), InsertActionType.InsertOperatorTkn, {}, CompNeDocs, @@ -845,7 +929,7 @@ export class Actions { const LtOperatorTkn = new EditCodeAction( "<", "add-comp-lt-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.LessThan), + () => new OperatorTkn(CompNeDocs.styles.backgroundColor, BinaryOperator.LessThan), InsertActionType.InsertOperatorTkn, {}, CompNeDocs, @@ -857,7 +941,7 @@ export class Actions { const GteOperatorTkn = new EditCodeAction( ">=", "add-comp-gte-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.GreaterThanEqual), + () => new OperatorTkn(CompNeDocs.styles.backgroundColor, BinaryOperator.GreaterThanEqual), InsertActionType.InsertOperatorTkn, {}, CompNeDocs, @@ -869,7 +953,7 @@ export class Actions { const LteOperatorTkn = new EditCodeAction( "<=", "add-comp-lte-op-tkn-btn", - () => new OperatorTkn(BinaryOperator.LessThanEqual), + () => new OperatorTkn(CompNeDocs.styles.backgroundColor, BinaryOperator.LessThanEqual), InsertActionType.InsertOperatorTkn, {}, CompNeDocs, @@ -881,7 +965,7 @@ export class Actions { const BinInExpr = new EditCodeAction( "--- in ---", "add-bin-in-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.In, DataType.Boolean), + () => new BinaryOperatorExpr(InDocs.styles.backgroundColor, BinaryOperator.In, DataType.Boolean), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.In, @@ -895,7 +979,7 @@ export class Actions { const BinNotInExpr = new EditCodeAction( "--- not in ---", "add-bin-not-in-expr-btn", - () => new BinaryOperatorExpr(BinaryOperator.NotIn, DataType.Boolean), + () => new BinaryOperatorExpr(NotInDocs.styles.backgroundColor, BinaryOperator.NotIn, DataType.Boolean), InsertActionType.InsertBinaryExpr, { operator: BinaryOperator.NotIn, @@ -909,7 +993,7 @@ export class Actions { const UnaryNotExpr = new EditCodeAction( "not ---", "add-unary-not-expr-btn", - () => new UnaryOperatorExpr(UnaryOperator.Not, DataType.Boolean), + () => new UnaryOperatorExpr(NotDocs.styles.backgroundColor, UnaryOperator.Not, DataType.Boolean), InsertActionType.InsertUnaryExpr, { operator: UnaryOperator.Not, @@ -926,6 +1010,7 @@ export class Actions { "add-find-method-call-btn", () => new MethodCallModifier( + FindDocs.styles.backgroundColor, "find", [new Argument([DataType.String], "item", false)], DataType.Number, @@ -942,7 +1027,7 @@ export class Actions { const WhileStmt = new EditCodeAction( "while --- :", "add-while-expr-btn", - () => new WhileStatement(), + () => new WhileStatement(WhileDocs.styles.backgroundColor), InsertActionType.InsertStatement, {}, WhileDocs, @@ -955,7 +1040,7 @@ export class Actions { "break", "add-break-stmt-btn", () => - new KeywordStmt("break", null, null, (context: Context) => { + new KeywordStmt(BreakDocs.styles.backgroundColor, "break", null, null, (context: Context) => { let parent = context.lineStatement.rootNode as Statement | Module; while ( @@ -980,7 +1065,7 @@ export class Actions { const IfStmt = new EditCodeAction( "if --- :", "add-if-expr-btn", - () => new IfStatement(), + () => new IfStatement(IfDocs.styles.backgroundColor), InsertActionType.InsertStatement, {}, IfDocs, @@ -992,7 +1077,7 @@ export class Actions { const ElifStmt = new EditCodeAction( "elif --- :", "add-elif-expr-btn", - () => new ElseStatement(true), + () => new ElseStatement(ElifDocs.styles.backgroundColor, true), InsertActionType.InsertElifStmt, {}, ElifDocs, @@ -1004,7 +1089,7 @@ export class Actions { const ElseStmt = new EditCodeAction( "else :", "add-else-expr-btn", - () => new ElseStatement(false), + () => new ElseStatement(ElseDocs.styles.backgroundColor, false), InsertActionType.InsertElseStmt, {}, ElseDocs, @@ -1016,7 +1101,7 @@ export class Actions { const ForStmt = new EditCodeAction( "for -- in --- :", "add-for-expr-btn", - () => new ForStatement(), + () => new ForStatement(ForDocs.styles.backgroundColor), InsertActionType.InsertStatement, {}, ForDocs, @@ -1028,7 +1113,7 @@ export class Actions { const ImportStmt = new EditCodeAction( "from --- import ---", "add-import-btn", - () => new ImportStatement(), + () => new ImportStatement(ImportDocs.styles.backgroundColor), InsertActionType.InsertStatement, {}, ImportDocs, @@ -1040,7 +1125,7 @@ export class Actions { const ImportRandintStmt = new EditCodeAction( "from random import randint", "add-import-randint-btn", - () => new ImportStatement("random", "randint"), + () => new ImportStatement(ImportDocs.styles.backgroundColor, "random", "randint"), InsertActionType.InsertStatement, {}, ImportDocs, @@ -1052,7 +1137,7 @@ export class Actions { const ImportChoiceStmt = new EditCodeAction( "from random import choice", "add-import-choice-btn", - () => new ImportStatement("random", "choice"), + () => new ImportStatement(ImportDocs.styles.backgroundColor, "random", "choice"), InsertActionType.InsertStatement, {}, ImportDocs, @@ -1064,7 +1149,7 @@ export class Actions { const ListLiteralExpr = new EditCodeAction( "[]", "add-list-literal-btn", - () => new ListLiteralExpression(), + () => new ListLiteralExpression(ListLiteralDocs.styles.backgroundColor), InsertActionType.InsertListLiteral, {}, ListLiteralDocs, @@ -1076,7 +1161,7 @@ export class Actions { const ListCommaItem = new EditCodeAction( ", ---", "add-list-item-btn", - () => new ListComma(), + () => new ListComma(ListItemDocs.styles.backgroundColor), InsertActionType.InsertListItem, {}, ListItemDocs, @@ -1088,7 +1173,7 @@ export class Actions { const ListIndexAccessor = new EditCodeAction( "[---]", "add-list-index-btn", - () => new ListAccessModifier(), + () => new ListAccessModifier(ListIndexDocs.styles.backgroundColor), InsertActionType.InsertListIndexAccessor, {}, ListIndexDocs, @@ -1100,7 +1185,7 @@ export class Actions { const AssignmentMod = new EditCodeAction( "= ---", "add-assign-mod-btn", - () => new AssignmentModifier(), + () => new AssignmentModifier(AssignDocs.styles.backgroundColor), InsertActionType.InsertAssignmentModifier, {}, AssignDocs, @@ -1112,7 +1197,8 @@ export class Actions { const AugAddAssignmentMod = new EditCodeAction( "+= ---", "add-aug-assign-add-mod-btn", - () => new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Add), + () => + new AugmentedAssignmentModifier(AssignAddDocs.styles.backgroundColor, AugmentedAssignmentOperator.Add), InsertActionType.InsertAugmentedAssignmentModifier, {}, AssignAddDocs, @@ -1124,7 +1210,11 @@ export class Actions { const AugSubAssignmentMod = new EditCodeAction( "-= ---", "add-aug-assign-sub-mod-btn", - () => new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Subtract), + () => + new AugmentedAssignmentModifier( + AssignSubDocs.styles.backgroundColor, + AugmentedAssignmentOperator.Subtract + ), InsertActionType.InsertAugmentedAssignmentModifier, {}, AssignSubDocs, @@ -1136,7 +1226,11 @@ export class Actions { const AugMulAssignmentMod = new EditCodeAction( "*= ---", "add-aug-assign-mul-mod-btn", - () => new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Multiply), + () => + new AugmentedAssignmentModifier( + AssignMultDocs.styles.backgroundColor, + AugmentedAssignmentOperator.Multiply + ), InsertActionType.InsertAugmentedAssignmentModifier, {}, AssignMultDocs, @@ -1148,7 +1242,11 @@ export class Actions { const AugDivAssignmentMod = new EditCodeAction( "/= ---", "add-aug-assign-div-mod-btn", - () => new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Divide), + () => + new AugmentedAssignmentModifier( + AssignDivDocs.styles.backgroundColor, + AugmentedAssignmentOperator.Divide + ), InsertActionType.InsertAugmentedAssignmentModifier, {}, AssignDivDocs, @@ -1162,6 +1260,7 @@ export class Actions { "add-list-append-stmt-btn", () => new MethodCallModifier( + ListAppendDocs.styles.backgroundColor, "append", [new Argument([DataType.Any], "object", false)], DataType.Void, @@ -1180,6 +1279,7 @@ export class Actions { "add-replace-method-call-btn", () => new MethodCallModifier( + ReplaceDocs.styles.backgroundColor, "replace", [new Argument([DataType.String], "old", false), new Argument([DataType.String], "new", false)], DataType.String, @@ -1198,6 +1298,7 @@ export class Actions { "add-join-method-call-btn", () => new MethodCallModifier( + JoinDocs.styles.backgroundColor, "join", [ new Argument( @@ -1234,6 +1335,7 @@ export class Actions { "add-split-method-call-btn", () => new MethodCallModifier( + SplitDocs.styles.backgroundColor, "split", [new Argument([DataType.String], "sep", false)], DataType.StringList, @@ -1250,7 +1352,13 @@ export class Actions { const CastStrExpr = new EditCodeAction( "str(---)", "add-cast-str-btn", - () => new FunctionCallExpr("str", [new Argument([DataType.Any], "value", false)], DataType.String), + () => + new FunctionCallExpr( + CastToStrDocs.styles.backgroundColor, + "str", + [new Argument([DataType.Any], "value", false)], + DataType.String + ), InsertActionType.InsertCastStrExpr, {}, CastToStrDocs, @@ -1262,7 +1370,13 @@ export class Actions { const CastIntExpr = new EditCodeAction( "int(---)", "add-cast-int-btn", - () => new FunctionCallExpr("int", [new Argument([DataType.String], "value", false)], DataType.Number), + () => + new FunctionCallExpr( + CastToIntDocs.styles.backgroundColor, + "int", + [new Argument([DataType.String], "value", false)], + DataType.Number + ), InsertActionType.InsertCastStrExpr, {}, CastToIntDocs, @@ -1274,7 +1388,7 @@ export class Actions { const VarAssignStmt = new EditCodeAction( "var = ---", "add-var-btn", - () => new VarAssignmentStmt(), + () => new VarAssignmentStmt(AddVarDocs.styles.backgroundColor), InsertActionType.InsertNewVariableStmt, {}, AddVarDocs, @@ -1361,16 +1475,29 @@ export class Actions { this.varActionsMap = new Map>([ [ DataType.Boolean, - [new VarAction(() => new VarAssignmentStmt(), "set {VAR_ID} to new value", "Set Value")], + [ + new VarAction( + () => new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor), + "set {VAR_ID} to new value", + "Set Value" + ), + ], ], [ DataType.Number, [ - new VarAction(() => new VarAssignmentStmt(), "set {VAR_ID} to new value", "Set Value"), + new VarAction( + () => new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor), + "set {VAR_ID} to new value", + "Set Value" + ), new VarAction( () => new VarOperationStmt(null, [ - new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Add), + new AugmentedAssignmentModifier( + AssignAddDocs.styles.backgroundColor, + AugmentedAssignmentOperator.Add + ), ]), "add value to {VAR_ID}", "Update Value" @@ -1378,7 +1505,10 @@ export class Actions { new VarAction( () => new VarOperationStmt(null, [ - new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Subtract), + new AugmentedAssignmentModifier( + AssignSubDocs.styles.backgroundColor, + AugmentedAssignmentOperator.Subtract + ), ]), "subtract value from {VAR_ID}", "Update Value" @@ -1386,7 +1516,10 @@ export class Actions { new VarAction( () => new VarOperationStmt(null, [ - new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Multiply), + new AugmentedAssignmentModifier( + AssignMultDocs.styles.backgroundColor, + AugmentedAssignmentOperator.Multiply + ), ]), "multiply {VAR_ID} by value", "Update Value" @@ -1394,7 +1527,10 @@ export class Actions { new VarAction( () => new VarOperationStmt(null, [ - new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Divide), + new AugmentedAssignmentModifier( + AssignDivDocs.styles.backgroundColor, + AugmentedAssignmentOperator.Divide + ), ]), "divide {VAR_ID} by value", "Update Value" @@ -1404,20 +1540,32 @@ export class Actions { [ DataType.String, [ - new VarAction(() => new VarAssignmentStmt(), "set {VAR_ID} to new value", "Set Value"), + new VarAction( + () => new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor), + "set {VAR_ID} to new value", + "Set Value" + ), new VarAction( () => new VarOperationStmt(null, [ - new AugmentedAssignmentModifier(AugmentedAssignmentOperator.Add), + new AugmentedAssignmentModifier( + AssignAddDocs.styles.backgroundColor, + AugmentedAssignmentOperator.Add + ), ]), "add text to {VAR_ID}", "Update Value" ), - new VarAction(() => new ForStatement(), "loop through characters of {VAR_ID}", "Loops"), + new VarAction( + () => new ForStatement(ForDocs.styles.backgroundColor), + "loop through characters of {VAR_ID}", + "Loops" + ), new VarAction( () => new ValueOperationExpr(null, [ new MethodCallModifier( + SplitDocs.styles.backgroundColor, "split", [new Argument([DataType.String], "sep", false)], DataType.StringList, @@ -1431,6 +1579,7 @@ export class Actions { () => new ValueOperationExpr(null, [ new MethodCallModifier( + JoinDocs.styles.backgroundColor, "join", [ new Argument( @@ -1455,6 +1604,7 @@ export class Actions { () => new ValueOperationExpr(null, [ new MethodCallModifier( + ReplaceDocs.styles.backgroundColor, "replace", [ new Argument([DataType.String], "old", false), @@ -1471,6 +1621,7 @@ export class Actions { () => new ValueOperationExpr(null, [ new MethodCallModifier( + FindDocs.styles.backgroundColor, "find", [new Argument([DataType.String], "item", false)], DataType.Number, @@ -1486,7 +1637,11 @@ export class Actions { DataType.AnyList, [ new VarAction( - () => new VarOperationStmt(null, [new ListAccessModifier(), new AssignmentModifier()]), + () => + new VarOperationStmt(null, [ + new ListAccessModifier(ListIndexDocs.styles.backgroundColor), + new AssignmentModifier(AssignDocs.styles.backgroundColor), + ]), "set an index in {VAR_ID} to value", "Update List" ), @@ -1494,6 +1649,7 @@ export class Actions { () => new VarOperationStmt(null, [ new MethodCallModifier( + ListAppendDocs.styles.backgroundColor, "append", [new Argument([DataType.Any], "object", false)], DataType.Void, @@ -1503,10 +1659,21 @@ export class Actions { "append value to list {VAR_ID}", "Update List" ), - new VarAction(() => new VarAssignmentStmt(), "set {VAR_ID} to new value", "Update List"), - new VarAction(() => new ForStatement(), "loop through items of {VAR_ID}", "Loops"), new VarAction( - () => new ValueOperationExpr(null, [new ListAccessModifier()]), + () => new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor), + "set {VAR_ID} to new value", + "Update List" + ), + new VarAction( + () => new ForStatement(ForDocs.styles.backgroundColor), + "loop through items of {VAR_ID}", + "Loops" + ), + new VarAction( + () => + new ValueOperationExpr(null, [ + new ListAccessModifier(ListIndexDocs.styles.backgroundColor), + ]), "get item from {VAR_ID} at index", "Get Value" ), @@ -1516,7 +1683,11 @@ export class Actions { DataType.Boolean, [ new VarAction( - () => new VarOperationStmt(null, [new ListAccessModifier(), new AssignmentModifier()]), + () => + new VarOperationStmt(null, [ + new ListAccessModifier(ListIndexDocs.styles.backgroundColor), + new AssignmentModifier(AssignDocs.styles.backgroundColor), + ]), "set an index in {VAR_ID} to value", "Update List" ), @@ -1524,6 +1695,7 @@ export class Actions { () => new VarOperationStmt(null, [ new MethodCallModifier( + ListAppendDocs.styles.backgroundColor, "append", [new Argument([DataType.Any], "object", false)], DataType.Void, @@ -1533,10 +1705,21 @@ export class Actions { "append value to list {VAR_ID}", "Update List" ), - new VarAction(() => new VarAssignmentStmt(), "set {VAR_ID} to new value", "Update List"), - new VarAction(() => new ForStatement(), "loop through items of {VAR_ID}", "Loops"), new VarAction( - () => new ValueOperationExpr(null, [new ListAccessModifier()]), + () => new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor), + "set {VAR_ID} to new value", + "Update List" + ), + new VarAction( + () => new ForStatement(ForDocs.styles.backgroundColor), + "loop through items of {VAR_ID}", + "Loops" + ), + new VarAction( + () => + new ValueOperationExpr(null, [ + new ListAccessModifier(ListIndexDocs.styles.backgroundColor), + ]), "get item from {VAR_ID} at index", "Get Value" ), @@ -1546,7 +1729,11 @@ export class Actions { DataType.NumberList, [ new VarAction( - () => new VarOperationStmt(null, [new ListAccessModifier(), new AssignmentModifier()]), + () => + new VarOperationStmt(null, [ + new ListAccessModifier(ListIndexDocs.styles.backgroundColor), + new AssignmentModifier(AssignDocs.styles.backgroundColor), + ]), "set an index in {VAR_ID} to value", "Update List" ), @@ -1554,6 +1741,7 @@ export class Actions { () => new VarOperationStmt(null, [ new MethodCallModifier( + ListAppendDocs.styles.backgroundColor, "append", [new Argument([DataType.Any], "object", false)], DataType.Void, @@ -1563,10 +1751,21 @@ export class Actions { "add value to list {VAR_ID}", "Update List" ), - new VarAction(() => new VarAssignmentStmt(), "set {VAR_ID} to new value", "Update List"), - new VarAction(() => new ForStatement(), "loop through items of {VAR_ID}", "Loops"), new VarAction( - () => new ValueOperationExpr(null, [new ListAccessModifier()]), + () => new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor), + "set {VAR_ID} to new value", + "Update List" + ), + new VarAction( + () => new ForStatement(ForDocs.styles.backgroundColor), + "loop through items of {VAR_ID}", + "Loops" + ), + new VarAction( + () => + new ValueOperationExpr(null, [ + new ListAccessModifier(ListIndexDocs.styles.backgroundColor), + ]), "get item from {VAR_ID} at index", "Get Value" ), @@ -1576,7 +1775,11 @@ export class Actions { DataType.StringList, [ new VarAction( - () => new VarOperationStmt(null, [new ListAccessModifier(), new AssignmentModifier()]), + () => + new VarOperationStmt(null, [ + new ListAccessModifier(ListIndexDocs.styles.backgroundColor), + new AssignmentModifier(AssignDocs.styles.backgroundColor), + ]), "set an index in {VAR_ID} to value", "Update List" ), @@ -1584,6 +1787,7 @@ export class Actions { () => new VarOperationStmt(null, [ new MethodCallModifier( + ListAppendDocs.styles.backgroundColor, "append", [new Argument([DataType.Any], "object", false)], DataType.Void, @@ -1593,10 +1797,21 @@ export class Actions { "append value to list {VAR_ID}", "Update List" ), - new VarAction(() => new VarAssignmentStmt(), "set {VAR_ID} to new value", "Update List"), - new VarAction(() => new ForStatement(), "loop through items of {VAR_ID}", "Loops"), new VarAction( - () => new ValueOperationExpr(null, [new ListAccessModifier()]), + () => new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor), + "set {VAR_ID} to new value", + "Update List" + ), + new VarAction( + () => new ForStatement(ForDocs.styles.backgroundColor), + "loop through items of {VAR_ID}", + "Loops" + ), + new VarAction( + () => + new ValueOperationExpr(null, [ + new ListAccessModifier(ListIndexDocs.styles.backgroundColor), + ]), "get item from {VAR_ID} at index", "Get Value" ), diff --git a/src/editor/event-router.ts b/src/editor/event-router.ts index f52e133..204a5f3 100644 --- a/src/editor/event-router.ts +++ b/src/editor/event-router.ts @@ -3,7 +3,7 @@ import * as ast from "../syntax-tree/ast"; import { Module } from "../syntax-tree/module"; import { AutoCompleteType, DataType, IdentifierRegex, InsertionType } from "./../syntax-tree/consts"; import { EditCodeAction } from "./action-filter"; -import { Actions, EditActionType, InsertActionType, KeyPress } from "./consts"; +import { Actions, Docs, EditActionType, InsertActionType, KeyPress } from "./consts"; import { EditAction } from "./data-types"; import { Context } from "./focus"; @@ -143,7 +143,7 @@ export class EventRouter { if ( inTextEditMode && !(context.tokenToLeft instanceof ast.NonEditableTkn) && - !this.module.validator.onBeginningOfLine(context) + !this.module.validator.onBeginningOfLine(context) ) { if (e.ctrlKey) return new EditAction(EditActionType.DeleteToStart); else return new EditAction(EditActionType.DeletePrevChar); @@ -159,8 +159,7 @@ export class EventRouter { return new EditAction(EditActionType.DeleteStringLiteral); } else if (this.module.validator.canMoveLeftOnEmptyMultilineStatement(context)) { return new EditAction(EditActionType.SelectPrevToken); - } - else if (this.module.validator.canDeletePrevStatement(context)) { + } else if (this.module.validator.canDeletePrevStatement(context)) { return new EditAction(EditActionType.DeleteStatement); } else if (this.module.validator.canDeletePrevMultiLineStatement(context)) { return new EditAction(EditActionType.DeleteMultiLineStatement); @@ -186,20 +185,18 @@ export class EventRouter { return new EditAction(EditActionType.DeleteListItem, { toRight: true, }); - } else if(this.module.validator.isBinaryOperatorExprTknEmpty(context)){ - if(this.module.validator.isAllBinaryOperatorExprTknsEmpty(context)){ - return new EditAction(EditActionType.DeleteBinaryOperator) + } else if (this.module.validator.isBinaryOperatorExprTknEmpty(context)) { + if (this.module.validator.isAllBinaryOperatorExprTknsEmpty(context)) { + return new EditAction(EditActionType.DeleteBinaryOperator); } - - return new EditAction(EditActionType.DeleteBinaryOperatorItem) - } - else if(this.module.validator.isFunctionCallExprTknEmpty(context)){ - if(this.module.validator.isAllFunctionCallExprTknsEmpty(context)){ - return new EditAction(EditActionType.DeleteFunctionCallExpr) + + return new EditAction(EditActionType.DeleteBinaryOperatorItem); + } else if (this.module.validator.isFunctionCallExprTknEmpty(context)) { + if (this.module.validator.isAllFunctionCallExprTknsEmpty(context)) { + return new EditAction(EditActionType.DeleteFunctionCallExpr); } - return new EditAction(EditActionType.DeleteFunctionCallExprItem) - } - else if (this.module.validator.shouldDeleteVarAssignmentOnHole(context)) { + return new EditAction(EditActionType.DeleteFunctionCallExprItem); + } else if (this.module.validator.shouldDeleteVarAssignmentOnHole(context)) { return new EditAction(EditActionType.DeleteStatement); } else if (this.module.validator.shouldDeleteHole(context)) { return new EditAction(EditActionType.DeleteSelectedModifier); @@ -593,7 +590,7 @@ export class EventRouter { case InsertActionType.InsertListLiteral: { if (this.module.validator.atLeftOfExpression(context)) { return new EditAction(EditActionType.WrapExpressionWithItem, { - expression: new ast.ListLiteralExpression(), + expression: new ast.ListLiteralExpression(Docs.ListLiteralDocs.styles.backgroundColor), source, }); } else if (this.module.validator.atEmptyExpressionHole(context)) { diff --git a/src/editor/toolbox.ts b/src/editor/toolbox.ts index 2e8c985..492d859 100644 --- a/src/editor/toolbox.ts +++ b/src/editor/toolbox.ts @@ -177,7 +177,8 @@ export class ToolboxController { const staticDummySpace = document.getElementById("static-toolbox-dummy-space"); const toolboxCategories = Actions.instance().toolboxCategories; - + const hello = Actions.instance().actionsMap; + console.log(hello); for (const constructGroup of toolboxCategories) { if (constructGroup) { let categoryDiv; diff --git a/src/messages/messages.ts b/src/messages/messages.ts index cf40d73..178bb41 100644 --- a/src/messages/messages.ts +++ b/src/messages/messages.ts @@ -127,6 +127,7 @@ export class ConstructHighlight extends CodeHighlight { constructor(editor: Editor, codeToHighlight: CodeConstruct, rgbColour: [number, number, number, number]) { super(editor, codeToHighlight); this.changeHighlightColour(rgbColour); + this.domElement.style.outline = "1px solid black"; } protected createDomElement() { @@ -636,14 +637,14 @@ export class ScopeHighlight { */ private callbacks: Map; - constructor(editor: Editor, statement: Statement) { + constructor(editor: Editor, statement: Statement, color: string) { this.statement = statement; this.selection = this.statement.getSelection(); this.editor = editor; this.callbacks = new Map(); - this.createDomElement(); + this.createDomElement(color); ScopeHighlight.idCounter++; this.headerElement.id = `scope-header-${ScopeHighlight.idPrefix}-${ScopeHighlight.idCounter}`; this.bodyElement.id = `scope-body-${ScopeHighlight.idPrefix}-${ScopeHighlight.idCounter}`; @@ -690,14 +691,18 @@ export class ScopeHighlight { /** * Construct the DOM element for this visual. */ - protected createDomElement(): void { + protected createDomElement(color: string): void { this.headerElement = document.createElement("div"); this.headerElement.classList.add("scope-header-highlight"); - this.headerElement.style.backgroundColor = "rgba(75, 200, 255, 0.125)"; + // this.headerElement.style.backgroundColor = "rgba(75, 200, 255, 0.125)"; + this.headerElement.style.backgroundColor = color; + this.headerElement.style.outline = "1px solid black"; this.bodyElement = document.createElement("div"); this.bodyElement.classList.add("scope-body-highlight"); - this.bodyElement.style.backgroundColor = "rgba(75, 200, 255, 0.125)"; + // this.bodyElement.style.backgroundColor = "rgba(75, 200, 255, 0.125)"; + this.bodyElement.style.backgroundColor = color; + this.bodyElement.style.outline = "1px solid black"; this.updateDimensions(); diff --git a/src/suggestions/suggestions-controller.ts b/src/suggestions/suggestions-controller.ts index a664b5b..f7305a2 100644 --- a/src/suggestions/suggestions-controller.ts +++ b/src/suggestions/suggestions-controller.ts @@ -1,6 +1,6 @@ import { Position } from "monaco-editor"; import { EditCodeAction } from "../editor/action-filter"; -import { Actions, InsertActionType } from "../editor/consts"; +import { Actions, Docs, InsertActionType } from "../editor/consts"; import { Editor } from "../editor/editor"; import { EDITOR_DOM_ID } from "../editor/toolbox"; import { Validator } from "../editor/validator"; @@ -835,7 +835,8 @@ export class MenuController { .length === 0 ) { substringMatchRanges = [[[0, optionText.length - 1]]]; //It will always exactly match the user input. - editAction.getCode = () => new VarAssignmentStmt("", optionText); + editAction.getCode = () => + new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor, "", optionText); editAction.trimSpacesBeforeTermChar = true; } // for displaying the correct identifier for the ---[---] = --- option diff --git a/src/syntax-tree/ast.ts b/src/syntax-tree/ast.ts index 13c5136..96ca54f 100644 --- a/src/syntax-tree/ast.ts +++ b/src/syntax-tree/ast.ts @@ -1,6 +1,6 @@ import { Position, Selection } from "monaco-editor"; import { EditCodeAction, InsertionResult } from "../editor/action-filter"; -import { ConstructName, EditActionType } from "../editor/consts"; +import { ConstructName, Docs, EditActionType } from "../editor/consts"; import { EditAction } from "../editor/data-types"; import { DraftRecord } from "../editor/draft"; import { Context, UpdatableContext } from "../editor/focus"; @@ -201,6 +201,7 @@ export abstract class Statement implements CodeConstruct { right: number; rootNode: Statement | Module = null; indexInRoot: number; + color: string; body = new Array(); scope: Scope = null; tokens = new Array(); @@ -970,9 +971,11 @@ export class WhileStatement extends Statement { scope: Scope; private conditionIndex: number; - constructor(root?: CodeConstruct | Module, indexInRoot?: number) { + constructor(color: string, root?: CodeConstruct | Module, indexInRoot?: number) { super(); + this.color = color; + this.tokens.push(new NonEditableTkn("while ", this, this.tokens.length)); this.conditionIndex = this.tokens.length; this.tokens.push(new TypedEmptyExpr([DataType.Boolean], this, this.tokens.length)); @@ -995,9 +998,11 @@ export class WhileStatement extends Statement { export class IfStatement extends Statement { private conditionIndex: number; - constructor(root?: CodeConstruct | Module, indexInRoot?: number) { + constructor(color: string, root?: CodeConstruct | Module, indexInRoot?: number) { super(); + this.color = color; + this.tokens.push(new NonEditableTkn("if ", this, this.tokens.length)); this.conditionIndex = this.tokens.length; this.tokens.push(new TypedEmptyExpr([DataType.Boolean], this, this.tokens.length)); @@ -1021,8 +1026,10 @@ export class ElseStatement extends Statement { private conditionIndex: number; hasCondition: boolean = false; - constructor(hasCondition: boolean, root?: IfStatement, indexInRoot?: number) { + constructor(color: string, hasCondition: boolean, root?: IfStatement, indexInRoot?: number) { super(); + + this.color = color; this.hasCondition = hasCondition; if (hasCondition) { @@ -1058,9 +1065,11 @@ export class ElseStatement extends Statement { export class ImportStatement extends Statement { private moduleNameIndex: number = -1; private itemNameIndex: number = -1; - constructor(moduleName: string = "", itemName: string = "") { + constructor(color: string, moduleName: string = "", itemName: string = "") { super(); + this.color = color; + this.tokens.push(new NonEditableTkn("from ", this, this.tokens.length)); this.moduleNameIndex = this.tokens.length; this.tokens.push(new EditableTextTkn(moduleName, new RegExp("^[a-zA-Z]*$"), this, this.tokens.length)); @@ -1123,10 +1132,11 @@ export class ForStatement extends Statement implements VariableContainer { //TODO: Statements should not have a data type? dataType = DataType.Any; - constructor(root?: CodeConstruct | Module, indexInRoot?: number) { + constructor(color: string, root?: CodeConstruct | Module, indexInRoot?: number) { super(); this.buttonId = ""; + this.color = color; this.tokens.push(new NonEditableTkn("for ", this, this.tokens.length)); this.identifierIndex = this.tokens.length; @@ -1155,7 +1165,7 @@ export class ForStatement extends Statement implements VariableContainer { this.hasEmptyToken = true; - this.loopVar = new VarAssignmentStmt(); + this.loopVar = new VarAssignmentStmt(Docs.AddVarDocs.styles.backgroundColor); this.loopVar.rootNode = this; this.subscribe( @@ -1414,9 +1424,10 @@ export class VarAssignmentStmt extends Statement implements VariableContainer { codeConstructName = ConstructName.VarAssignment; private oldIdentifier: string; - constructor(buttonId?: string, id?: string, root?: Statement | Module, indexInRoot?: number) { + constructor(color: string, buttonId?: string, id?: string, root?: Statement | Module, indexInRoot?: number) { super(); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; @@ -1852,9 +1863,10 @@ export class ListAccessModifier extends Modifier { leftExprTypes = [DataType.AnyList]; private indexOfIndexTkn: number; - constructor(root?: ValueOperationExpr | VarOperationStmt, indexInRoot?: number) { + constructor(color: string, root?: ValueOperationExpr | VarOperationStmt, indexInRoot?: number) { super(); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; @@ -1969,6 +1981,7 @@ export class MethodCallModifier extends Modifier { returns: DataType; constructor( + color: string, functionName: string, args: Array, returns: DataType, @@ -1978,6 +1991,7 @@ export class MethodCallModifier extends Modifier { ) { super(); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; @@ -2054,9 +2068,10 @@ export class AssignmentModifier extends Modifier { rootNode: VarOperationStmt; simpleInvalidTooltip = Tooltip.InvalidAugmentedAssignment; - constructor(root?: VarOperationStmt, indexInRoot?: number) { + constructor(color: string, root?: VarOperationStmt, indexInRoot?: number) { super(); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; @@ -2086,9 +2101,10 @@ export class AugmentedAssignmentModifier extends Modifier { private operation: AugmentedAssignmentOperator; simpleInvalidTooltip = Tooltip.InvalidAugmentedAssignment; - constructor(operation: AugmentedAssignmentOperator, root?: VarOperationStmt, indexInRoot?: number) { + constructor(color: string, operation: AugmentedAssignmentOperator, root?: VarOperationStmt, indexInRoot?: number) { super(); + this.color = color; this.operation = operation; this.rootNode = root; @@ -2131,6 +2147,7 @@ export class FunctionCallExpr extends Expression implements Importable { requiredModule: string; constructor( + color: string, functionName: string, args: Array, returns: DataType, @@ -2140,6 +2157,7 @@ export class FunctionCallExpr extends Expression implements Importable { ) { super(returns); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; this.functionName = functionName; @@ -2305,6 +2323,7 @@ export class FunctionCallStmt extends Statement implements Importable { requiredModule: string; constructor( + color: string, functionName: string, args: Array, root?: Statement | Module, @@ -2313,6 +2332,7 @@ export class FunctionCallStmt extends Statement implements Importable { ) { super(); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; this.functionName = functionName; @@ -2449,6 +2469,7 @@ export class KeywordStmt extends Statement { validator: (context: Context) => boolean; constructor( + color: string, keyword, root?: Statement | Expression, indexInRoot?: number, @@ -2456,6 +2477,7 @@ export class KeywordStmt extends Statement { ) { super(); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; this.validator = validator; @@ -2547,9 +2569,16 @@ export class BinaryOperatorExpr extends Expression { static originalReturnTypeAdd = DataType.Any; static originalReturnTypeArithmetic = DataType.Number; - constructor(operator: BinaryOperator, returns: DataType, root?: Statement | Expression, indexInRoot?: number) { + constructor( + color: string, + operator: BinaryOperator, + returns: DataType, + root?: Statement | Expression, + indexInRoot?: number + ) { super(returns); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; this.operator = operator; @@ -2569,7 +2598,9 @@ export class BinaryOperatorExpr extends Expression { this.typeOfHoles[this.tokens.length - 1] = [DataType.Number, DataType.String, ...ListTypes]; this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.keywordIndex = this.tokens.length; - this.tokens.push(new OperatorTkn(operator, this, this.tokens.length)); + this.tokens.push( + new OperatorTkn(Docs.AddDocs.styles.backgroundColor, operator, this, this.tokens.length) + ); this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.rightOperandIndex = this.tokens.length; this.tokens.push( @@ -2584,7 +2615,9 @@ export class BinaryOperatorExpr extends Expression { this.typeOfHoles[this.tokens.length - 1] = [DataType.Number, DataType.String, ...ListTypes]; this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.keywordIndex = this.tokens.length; - this.tokens.push(new OperatorTkn(operator, this, this.tokens.length)); + this.tokens.push( + new OperatorTkn(Docs.AddDocs.styles.backgroundColor, operator, this, this.tokens.length) + ); this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.rightOperandIndex = this.tokens.length; this.tokens.push(new TypedEmptyExpr([returns], this, this.tokens.length)); @@ -2595,7 +2628,7 @@ export class BinaryOperatorExpr extends Expression { this.typeOfHoles[this.tokens.length - 1] = [DataType.Number]; this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.keywordIndex = this.tokens.length; - this.tokens.push(new OperatorTkn(operator, this, this.tokens.length)); + this.tokens.push(new OperatorTkn(Docs.AddDocs.styles.backgroundColor, operator, this, this.tokens.length)); this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.rightOperandIndex = this.tokens.length; this.tokens.push(new TypedEmptyExpr([DataType.Number], this, this.tokens.length)); @@ -2608,7 +2641,7 @@ export class BinaryOperatorExpr extends Expression { this.typeOfHoles[this.tokens.length - 1] = [DataType.Boolean]; this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.keywordIndex = this.tokens.length; - this.tokens.push(new OperatorTkn(operator, this, this.tokens.length)); + this.tokens.push(new OperatorTkn(Docs.AddDocs.styles.backgroundColor, operator, this, this.tokens.length)); this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.rightOperandIndex = this.tokens.length; this.tokens.push(new TypedEmptyExpr([DataType.Boolean], this, this.tokens.length)); @@ -2622,7 +2655,9 @@ export class BinaryOperatorExpr extends Expression { this.typeOfHoles[this.tokens.length - 1] = [DataType.Any]; this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.keywordIndex = this.tokens.length; - this.tokens.push(new OperatorTkn(operator, this, this.tokens.length)); + this.tokens.push( + new OperatorTkn(Docs.AddDocs.styles.backgroundColor, operator, this, this.tokens.length) + ); this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.rightOperandIndex = this.tokens.length; this.tokens.push(new TypedEmptyExpr([DataType.Any], this, this.tokens.length)); @@ -2632,7 +2667,9 @@ export class BinaryOperatorExpr extends Expression { this.typeOfHoles[this.tokens.length - 1] = [DataType.Any]; this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.keywordIndex = this.tokens.length; - this.tokens.push(new OperatorTkn(operator, this, this.tokens.length)); + this.tokens.push( + new OperatorTkn(Docs.AddDocs.styles.backgroundColor, operator, this, this.tokens.length) + ); this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.rightOperandIndex = this.tokens.length; this.tokens.push( @@ -2660,7 +2697,9 @@ export class BinaryOperatorExpr extends Expression { this.typeOfHoles[this.tokens.length - 1] = [DataType.Number, DataType.String]; this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.keywordIndex = this.tokens.length; - this.tokens.push(new OperatorTkn(operator, this, this.tokens.length)); + this.tokens.push( + new OperatorTkn(Docs.AddDocs.styles.backgroundColor, operator, this, this.tokens.length) + ); this.tokens.push(new NonEditableTkn(" ", this, this.tokens.length)); this.rightOperandIndex = this.tokens.length; this.tokens.push(new TypedEmptyExpr([DataType.Number, DataType.String], this, this.tokens.length)); @@ -3380,6 +3419,7 @@ export class UnaryOperatorExpr extends Expression { private operandIndex: number; constructor( + color: string, operator: UnaryOperator, returns: DataType, operatesOn: DataType = DataType.Any, @@ -3388,6 +3428,7 @@ export class UnaryOperatorExpr extends Expression { ) { super(returns); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; this.operator = operator; @@ -3496,9 +3537,16 @@ export class OperatorTkn extends Modifier { operator: UnaryOperator | BinaryOperator; operatorCategory: OperatorCategory; - constructor(operator: UnaryOperator | BinaryOperator, root?: Statement | Expression, indexInRoot?: number) { + constructor( + color: string, + operator: UnaryOperator | BinaryOperator, + root?: Statement | Expression, + indexInRoot?: number + ) { super(); + this.color = color; + this.tokens.push(new NonEditableTkn(operator, this, this.tokens.length)); this.operator = operator; @@ -3516,9 +3564,11 @@ export class OperatorTkn extends Modifier { export class LiteralValExpr extends Expression { valueTokenIndex: number = 0; - constructor(returns: DataType, value?: string, root?: Statement | Expression, indexInRoot?: number) { + constructor(color: string, returns: DataType, value?: string, root?: Statement | Expression, indexInRoot?: number) { super(returns); + this.color = color; + switch (returns) { case DataType.String: { this.tokens.push(new NonEditableTkn('"', this, this.tokens.length)); @@ -3585,9 +3635,11 @@ export class LiteralValExpr extends Expression { export class FormattedStringExpr extends Expression { valueTokenIndex: number = 0; - constructor(value?: string, root?: Statement | Expression, indexInRoot?: number) { + constructor(color: string, value?: string, root?: Statement | Expression, indexInRoot?: number) { super(DataType.String); + this.color = color; + this.tokens.push(new NonEditableTkn("f", this, this.tokens.length)); this.tokens.push(new NonEditableTkn("'", this, this.tokens.length)); this.tokens.push(new EditableTextTkn(value == undefined ? "" : value, StringRegex, this, this.tokens.length)); @@ -3618,9 +3670,11 @@ export class FormattedStringExpr extends Expression { export class FormattedStringCurlyBracketsExpr extends Expression { valueTokenIndex: number = 0; - constructor(root?: Statement | Expression, indexInRoot?: number) { + constructor(color: string, root?: Statement | Expression, indexInRoot?: number) { super(DataType.String); + this.color = color; + this.tokens.push(new NonEditableTkn("{", this, this.tokens.length)); this.tokens.push(new TypedEmptyExpr([DataType.Any], this, this.tokens.length)); this.tokens.push(new NonEditableTkn("}", this, this.tokens.length)); @@ -3641,9 +3695,10 @@ export class FormattedStringCurlyBracketsExpr extends Expression { } export class ListLiteralExpression extends Expression { - constructor(root?: Statement | Expression, indexInRoot?: number) { + constructor(color: string, root?: Statement | Expression, indexInRoot?: number) { super(DataType.AnyList); + this.color = color; this.rootNode = root; this.indexInRoot = indexInRoot; @@ -3756,9 +3811,11 @@ export class ListLiteralExpression extends Expression { } export class ListComma extends Expression { - constructor() { + constructor(color: string) { super(DataType.Void); + this.color = color; + this.simpleInvalidTooltip = Tooltip.InvalidInsertListComma; } From febfacc0ef8e2c172c36935b8627780f9fc9a5a4 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 24 May 2022 10:28:28 -0400 Subject: [PATCH 05/24] pulled changes from master --- .vscode/settings.json | 4 +- src/editor/action-executor.ts | 35 +++---------- src/editor/consts.ts | 6 +-- src/editor/event-router.ts | 37 ++++++++++---- src/editor/validator.ts | 92 ++++++++++++++++++++--------------- 5 files changed, 94 insertions(+), 80 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b788cb..f165b92 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,7 @@ "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": true - } + }, + "editor.tabSize": 4 + } diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index 5851c1a..13e445d 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -13,7 +13,6 @@ import { Expression, FormattedStringCurlyBracketsExpr, FormattedStringExpr, - FunctionCallExpr, IdentifierTkn, IfStatement, Importable, @@ -1233,44 +1232,26 @@ export class ActionExecutor { break; } - case EditActionType.DeleteBinaryOperator: { + case EditActionType.DeleteRootNode: { this.deleteCode(context.token.rootNode); break; } - case EditActionType.DeleteBinaryOperatorItem: { - if (!(context.token.rootNode instanceof BinaryOperatorExpr)) break; - - let leftOperand = context.token.rootNode.getLeftOperand(); - let rightOperand = context.token.rootNode.getRightOperand(); - - if (leftOperand === context.token) { - this.replaceCode(context.token.rootNode, rightOperand); - } else { - this.replaceCode(context.token.rootNode, leftOperand); - } - break; - } - - case EditActionType.DeleteFunctionCallExpr: { - this.deleteCode(context.token.rootNode); - break; - } - - case EditActionType.DeleteFunctionCallExprItem: { - let rootNode = context.token.rootNode; + case EditActionType.ReplaceExpressionWithItem: { + const rootNode = context.token.rootNode as Expression; let replacementTkn; - if (!(rootNode instanceof FunctionCallExpr)) break; + for (let i = 0; i < rootNode.tokens.length; i++) { if ( !(rootNode.tokens[i] instanceof TypedEmptyExpr) && - !(rootNode.tokens[i] instanceof NonEditableTkn) + !(rootNode.tokens[i] instanceof NonEditableTkn) && + !(rootNode.tokens[i] instanceof OperatorTkn) ) { replacementTkn = rootNode.tokens[i]; } + this.replaceCode(rootNode, replacementTkn); + break; } - this.replaceCode(rootNode, replacementTkn); - break; } case EditActionType.InsertImportFromDraftMode: { diff --git a/src/editor/consts.ts b/src/editor/consts.ts index 888eebd..c3daae5 100644 --- a/src/editor/consts.ts +++ b/src/editor/consts.ts @@ -150,10 +150,8 @@ export enum EditActionType { DeleteNextChar, DeletePrevChar, DeleteListItem, - DeleteBinaryOperatorItem, - DeleteBinaryOperator, - DeleteFunctionCallExprItem, - DeleteFunctionCallExpr, + DeleteRootNode, + ReplaceExpressionWithItem, DeleteStringLiteral, DeleteToEnd, diff --git a/src/editor/event-router.ts b/src/editor/event-router.ts index 204a5f3..49cc721 100644 --- a/src/editor/event-router.ts +++ b/src/editor/event-router.ts @@ -134,6 +134,21 @@ export class EventRouter { return new EditAction(EditActionType.DeleteListItem, { toRight: true, }); + } else if (this.module.validator.isTknEmpty(context)) { + if (this.module.validator.isAugmentedAssignmentModifierStatement(context)) { + return new EditAction(EditActionType.DeleteStatement); + } + if (context.token.rootNode instanceof ast.Expression) { + if (this.module.validator.canDeleteExpression(context)) { + return new EditAction(EditActionType.DeleteRootNode); + } + return new EditAction(EditActionType.ReplaceExpressionWithItem); + } + if (context.token.rootNode instanceof ast.Statement) { + if (this.module.validator.canDeleteStatement(context)) { + return new EditAction(EditActionType.DeleteStatement); + } + } } break; @@ -185,17 +200,21 @@ export class EventRouter { return new EditAction(EditActionType.DeleteListItem, { toRight: true, }); - } else if (this.module.validator.isBinaryOperatorExprTknEmpty(context)) { - if (this.module.validator.isAllBinaryOperatorExprTknsEmpty(context)) { - return new EditAction(EditActionType.DeleteBinaryOperator); + } else if (this.module.validator.isTknEmpty(context)) { + if (this.module.validator.isAugmentedAssignmentModifierStatement(context)) { + return new EditAction(EditActionType.DeleteStatement); } - - return new EditAction(EditActionType.DeleteBinaryOperatorItem); - } else if (this.module.validator.isFunctionCallExprTknEmpty(context)) { - if (this.module.validator.isAllFunctionCallExprTknsEmpty(context)) { - return new EditAction(EditActionType.DeleteFunctionCallExpr); + if (context.token.rootNode instanceof ast.Expression) { + if (this.module.validator.canDeleteExpression(context)) { + return new EditAction(EditActionType.DeleteRootNode); + } + return new EditAction(EditActionType.ReplaceExpressionWithItem); + } + if (context.token.rootNode instanceof ast.Statement) { + if (this.module.validator.canDeleteStatement(context)) { + return new EditAction(EditActionType.DeleteStatement); + } } - return new EditAction(EditActionType.DeleteFunctionCallExprItem); } else if (this.module.validator.shouldDeleteVarAssignmentOnHole(context)) { return new EditAction(EditActionType.DeleteStatement); } else if (this.module.validator.shouldDeleteHole(context)) { diff --git a/src/editor/validator.ts b/src/editor/validator.ts index 3a4f0b2..b4b256f 100644 --- a/src/editor/validator.ts +++ b/src/editor/validator.ts @@ -12,7 +12,6 @@ import { Expression, FormattedStringCurlyBracketsExpr, FormattedStringExpr, - FunctionCallExpr, IdentifierTkn, IfStatement, ImportStatement, @@ -20,11 +19,12 @@ import { LiteralValExpr, Modifier, NonEditableTkn, + OperatorTkn, Statement, TypedEmptyExpr, ValueOperationExpr, VarAssignmentStmt, - VariableReferenceExpr + VariableReferenceExpr, } from "../syntax-tree/ast"; import { Module } from "../syntax-tree/module"; import { Reference } from "../syntax-tree/scope"; @@ -39,7 +39,7 @@ import { InsertionType, NumberRegex, OperatorCategory, - UnaryOperator + UnaryOperator, } from "./../syntax-tree/consts"; import { EditCodeAction } from "./action-filter"; import { Context } from "./focus"; @@ -382,80 +382,94 @@ export class Validator { context.lineStatement.rootNode instanceof Statement ); } - /** - * logic: checks if token is not null AND instanceof BinaryOperatorExpr AND atEmptyExpressionHole + + /** + * logic: checks if token is not null AND atEmptyExpressionHole */ - isBinaryOperatorExprTknEmpty(providedContext?: Context):boolean{ + isTknEmpty(providedContext?: Context): boolean { const context = providedContext ? providedContext : this.module.focus.getContext(); - if(context.token === null) return false; - if(!(context.token.rootNode instanceof BinaryOperatorExpr)) return false; - if(!(this.atEmptyExpressionHole)) return false; + if (context.token === null) return false; + if (!this.atEmptyExpressionHole) return false; - return true + return true; } /** - * logic: checks if both tokens of BinaryOperatorExpr are empty + * logic: checks if rootNode is instanceof AugmentedAssignmentModifier */ + isAugmentedAssignmentModifierStatement(providedContext?: Context): boolean { + const context = providedContext ? providedContext : this.module.focus.getContext(); + const rootNode = context.token.rootNode; - isAllBinaryOperatorExprTknsEmpty(providedContext? :Context):boolean{ - const context = providedContext? providedContext : this.module.focus.getContext(); - if(context.token.rootNode instanceof BinaryOperatorExpr){ - let leftOperand = context.token.rootNode.getLeftOperand(); - let rightOperand = context.token.rootNode.getRightOperand(); - if(leftOperand instanceof TypedEmptyExpr && rightOperand instanceof TypedEmptyExpr){ - return true; - } + if (rootNode instanceof AugmentedAssignmentModifier) { + return true; } + return false; } - /** - * logic: checks if token is not null AND instanceof BinaryOperatorExpr AND atEmptyExpressionHole + /** + * logic: checks if Statement body is empty and if all tokens of Statement are empty */ - isFunctionCallExprTknEmpty(providedContext?: Context):boolean{ + canDeleteStatement(providedContext?: Context): boolean { const context = providedContext ? providedContext : this.module.focus.getContext(); + const rootNode = context.token.rootNode as Statement; - if(context.token === null) return false; - if(!(context.token.rootNode instanceof FunctionCallExpr)) return false; - if(!(this.atEmptyExpressionHole)) return false; + for (let i = 0; i < rootNode.tokens.length; i++) { + if ( + !(rootNode.tokens[i] instanceof TypedEmptyExpr) && + !(rootNode.tokens[i] instanceof NonEditableTkn) && + !(rootNode.tokens[i] instanceof IdentifierTkn) + ) + return false; + } - return true + if (rootNode.hasBody()) { + for (let i = 0; i < rootNode.body.length; i++) { + if (!(rootNode.body[i] instanceof EmptyLineStmt)) return false; + } + } + + return true; } - /** - * logic: checks if all the expressions in the FunctionCallExpr is empty + * logic: checks if all tokens of Expression are empty */ + canDeleteExpression(providedContext?: Context): boolean { + const context = providedContext ? providedContext : this.module.focus.getContext(); + const rootNode = context.token.rootNode as Expression; - isAllFunctionCallExprTknsEmpty(providedContext? :Context):boolean{ - const context = providedContext? providedContext : this.module.focus.getContext(); - let rootNode = context.token.rootNode; - if(rootNode instanceof FunctionCallExpr){ - for(let i=0; i< rootNode.tokens.length; i++){ - if(!(rootNode.tokens[i] instanceof TypedEmptyExpr) && !(rootNode.tokens[i] instanceof NonEditableTkn)) return false; - } + for (let i = 0; i < rootNode.tokens.length; i++) { + if ( + !(rootNode.tokens[i] instanceof TypedEmptyExpr) && + !(rootNode.tokens[i] instanceof NonEditableTkn) && + !(rootNode.tokens[i] instanceof OperatorTkn) + ) + return false; } + return true; - } - + /** - * + * * logic: checks if at the end of a statement, and not text editable. * AND does not have a body. * AND prev item is not an expression that could be deleted by it self. */ canDeletePrevStatement(providedContext?: Context): boolean { const context = providedContext ? providedContext : this.module.focus.getContext(); + if ( !(context.lineStatement instanceof EmptyLineStmt) && !context.lineStatement?.hasBody() && - this.module.focus.onEndOfLine() && + this.module.focus.onEndOfLine() && !this.module.focus.isTextEditable(providedContext) ) { if (context.expressionToLeft != null) return false; + return true; } From 275caf910e1cbfee718c824b7b943188f4f98ae9 Mon Sep 17 00:00:00 2001 From: Majeed Date: Tue, 24 May 2022 12:15:40 -0400 Subject: [PATCH 06/24] fixed arg pos with createDynamicEditCodeAction --- src/editor/action-filter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/action-filter.ts b/src/editor/action-filter.ts index 86f6927..e46ec3f 100644 --- a/src/editor/action-filter.ts +++ b/src/editor/action-filter.ts @@ -81,8 +81,8 @@ export class ActionFilter { }, null, {}, - new InsertionResult(varRecord[1], "MESSAGE BASED ON INSERTION TYPE", []), //TODO: Need to actually check what the insertion type is and populate the insertion result accordingly null, + new InsertionResult(varRecord[1], "MESSAGE BASED ON INSERTION TYPE", []), //TODO: Need to actually check what the insertion type is and populate the insertion result accordingly [""], varStmt.getIdentifier(), null, From f2cc0db950a20adc9fed4360a78d91d663167fe4 Mon Sep 17 00:00:00 2001 From: Majeed Date: Tue, 24 May 2022 12:16:09 -0400 Subject: [PATCH 07/24] will enable/disable analytics with a switch --- src/logger/analytics.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/logger/analytics.ts b/src/logger/analytics.ts index 957aa14..1b189bd 100644 --- a/src/logger/analytics.ts +++ b/src/logger/analytics.ts @@ -1,6 +1,8 @@ import { sendEventsBatch } from "./requests"; import { getUser } from "./user"; +export const ANALYTICS_ENABLED = false; + export enum LogType { DraftHelpUsed = "draft-help-used", // data: { type: "add-double-quotes"} InsertCode = "insert-code", // data: source: "keyboard" | "autocomplete" | "autocomplete-menu" | "draft-mode" | "defined-vars" @@ -31,7 +33,7 @@ export class Logger { this.interval = interval; this.dispatchEvents = this.dispatchEvents.bind(this); - setInterval(this.dispatchEvents, interval); + if (ANALYTICS_ENABLED) setInterval(this.dispatchEvents, interval); } static Instance() { @@ -41,14 +43,16 @@ export class Logger { } queueEvent(event: LogEvent) { - console.log(event); - this.queue.push(event); + if (ANALYTICS_ENABLED) { + console.log(event); + this.queue.push(event); - if (this.queue.length >= this.maxSize) this.dispatchEvents(); + if (this.queue.length >= this.maxSize) this.dispatchEvents(); + } } dispatchEvents() { - if (this.queue.length === 0) return; + if (this.queue.length === 0 || !ANALYTICS_ENABLED) return; sendEventsBatch(this.queue, getUser(), "nova-editor") .then(() => { From 3f5fecedc09555282386d8f49539a56861ae7606 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 25 May 2022 13:17:55 -0400 Subject: [PATCH 08/24] fixed varassignstatment colors, literal colors, arthmetic colors, comparison colors, varoperationstmt colors, and valueoperationexpr colors --- src/editor/action-executor.ts | 44 ++++++++++++++++++++++++++++++++--- src/editor/consts.ts | 42 ++++++++++++++++----------------- src/editor/toolbox.ts | 2 +- src/syntax-tree/ast.ts | 14 +++++++++-- 4 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index 13e445d..da7e4cb 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -293,6 +293,8 @@ export class ActionExecutor { if (flashGreen) this.flashGreen(action.data?.statement); + this.setTokenColor(action.data?.statement, statement.color); + eventData.code = "var-assignment"; eventData.id = id; @@ -911,6 +913,8 @@ export class ActionExecutor { this.module.focus.updateContext(varAssignStmt.getInitialFocus()); if (flashGreen) this.flashGreen(varAssignStmt); + + this.setTokenColor(varAssignStmt, varAssignStmt.color); } else { if ( context.expressionToLeft instanceof VariableReferenceExpr && @@ -926,6 +930,8 @@ export class ActionExecutor { this.module.focus.updateContext(action.data.modifier.getInitialFocus()); if (flashGreen) this.flashGreen(action.data.modifier); + + this.setTokenColor(action.data.modifier, action.data.modifier.color); } } @@ -1018,6 +1024,7 @@ export class ActionExecutor { const holeDataTypes = exprToLeftRoot.typeOfHoles[context.expressionToLeft.indexInRoot]; const valOprExpr = new ValueOperationExpr( + Docs.AddVarDocs.styles.backgroundColor, context.expressionToLeft, [modifier], context.expressionToLeft.rootNode, @@ -1078,6 +1085,9 @@ export class ActionExecutor { } if (flashGreen) this.flashGreen(action.data.modifier); + + this.setTokenColor(action.data.modifier, action.data.modifier.color); + eventData.code = action.data.modifier.getRenderText(); break; @@ -1104,6 +1114,9 @@ export class ActionExecutor { } if (flashGreen) this.flashGreen(binExpr); + + this.setTokenColor(binExpr, binExpr.color); + eventData.code = action.data.operator; break; @@ -1163,6 +1176,9 @@ export class ActionExecutor { } if (flashGreen) this.flashGreen(newCode); + + this.setTokenColor(newCode, newCode.color); + eventData.code = action.data.expression.getRenderText(); eventData.wrap = true; @@ -1194,6 +1210,9 @@ export class ActionExecutor { this.insertExpression(context, newLiteral); if (flashGreen) this.flashGreen(newLiteral); + + this.setTokenColor(newLiteral, newLiteral.color); + eventData.code = "empty-list"; break; @@ -1249,9 +1268,9 @@ export class ActionExecutor { ) { replacementTkn = rootNode.tokens[i]; } - this.replaceCode(rootNode, replacementTkn); - break; } + this.replaceCode(rootNode, replacementTkn); + break; } case EditActionType.InsertImportFromDraftMode: { @@ -1303,6 +1322,8 @@ export class ActionExecutor { this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); + this.setTokenColor(action.data.codeToReplace.rootNode, action.data.codeToReplace.rootNode.color); + if (root instanceof Expression) root.validateTypes(this.module); eventData.code = action.data.codeToReplace.getRenderText(); @@ -1327,6 +1348,8 @@ export class ActionExecutor { this.insertExpression(this.module.focus.getContext(), action.data.codeToReplace as Expression); this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); + this.setTokenColor(action.data.codeToReplace.rootNode, action.data.codeToReplace.rootNode.color); + if (root instanceof Expression) root.validateTypes(this.module); eventData.code = action.data.codeToReplace.getRenderText(); @@ -1393,6 +1416,8 @@ export class ActionExecutor { if (flashGreen) this.flashGreen(newLiteral); + this.setTokenColor(newLiteral, newLiteral.color); + if (action.data?.source?.type === "keyboard") { eventType = LogType.InsertCode; eventData.source = "keyboard"; @@ -1481,6 +1506,9 @@ export class ActionExecutor { } if (flashGreen) this.flashGreen(action.data.operator); + + this.setTokenColor(action.data.operator, action.data.operator.color); + eventData.code = action.data.operator.getRenderText(); break; @@ -1531,7 +1559,7 @@ export class ActionExecutor { if (this.module.validator.onBeginningOfLine(context)) { const varRef = this.createVarReference(buttonId); - const stmt = new VarOperationStmt(varRef); + const stmt = new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, varRef); this.replaceEmptyStatement(context.lineStatement, stmt); const availableActions = this.module.actionFilter @@ -1580,6 +1608,8 @@ export class ActionExecutor { if (autocompleteData) { this.flashGreen(stmt); + + this.setTokenColor(stmt, stmt.color); } eventData.code = varRef.getRenderText(); @@ -1589,6 +1619,8 @@ export class ActionExecutor { if (autocompleteData) { this.flashGreen(expr); + + this.setTokenColor(expr, expr.color); } eventData.code = expr.getRenderText(); @@ -1706,6 +1738,8 @@ export class ActionExecutor { } } + private setAugmentedAssignmentModifierColor(code: CodeConstruct) {} + private insertEmptyListItem(focusedCode: CodeConstruct, index: number, items: Array) { if (focusedCode instanceof Token || focusedCode instanceof Expression) { const root = focusedCode.rootNode; @@ -2123,6 +2157,10 @@ export class ActionExecutor { root.rebuild(root.getLeftPosition(), 0); + if (replace instanceof Statement) { + this.setTokenColor(replace, replace.color); + } + this.module.editor.executeEdits(replacementRange, replace); if (replace instanceof Token && replace.isEmpty) { diff --git a/src/editor/consts.ts b/src/editor/consts.ts index c3daae5..2655320 100644 --- a/src/editor/consts.ts +++ b/src/editor/consts.ts @@ -1491,7 +1491,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new AugmentedAssignmentModifier( AssignAddDocs.styles.backgroundColor, AugmentedAssignmentOperator.Add @@ -1502,7 +1502,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new AugmentedAssignmentModifier( AssignSubDocs.styles.backgroundColor, AugmentedAssignmentOperator.Subtract @@ -1513,7 +1513,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new AugmentedAssignmentModifier( AssignMultDocs.styles.backgroundColor, AugmentedAssignmentOperator.Multiply @@ -1524,7 +1524,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new AugmentedAssignmentModifier( AssignDivDocs.styles.backgroundColor, AugmentedAssignmentOperator.Divide @@ -1545,7 +1545,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new AugmentedAssignmentModifier( AssignAddDocs.styles.backgroundColor, AugmentedAssignmentOperator.Add @@ -1561,7 +1561,7 @@ export class Actions { ), new VarAction( () => - new ValueOperationExpr(null, [ + new ValueOperationExpr(SplitDocs.styles.backgroundColor, null, [ new MethodCallModifier( SplitDocs.styles.backgroundColor, "split", @@ -1575,7 +1575,7 @@ export class Actions { ), new VarAction( () => - new ValueOperationExpr(null, [ + new ValueOperationExpr(JoinDocs.styles.backgroundColor, null, [ new MethodCallModifier( JoinDocs.styles.backgroundColor, "join", @@ -1600,7 +1600,7 @@ export class Actions { ), new VarAction( () => - new ValueOperationExpr(null, [ + new ValueOperationExpr(ReplaceDocs.styles.backgroundColor, null, [ new MethodCallModifier( ReplaceDocs.styles.backgroundColor, "replace", @@ -1617,7 +1617,7 @@ export class Actions { ), new VarAction( () => - new ValueOperationExpr(null, [ + new ValueOperationExpr(FindDocs.styles.backgroundColor, null, [ new MethodCallModifier( FindDocs.styles.backgroundColor, "find", @@ -1636,7 +1636,7 @@ export class Actions { [ new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new ListAccessModifier(ListIndexDocs.styles.backgroundColor), new AssignmentModifier(AssignDocs.styles.backgroundColor), ]), @@ -1645,7 +1645,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new MethodCallModifier( ListAppendDocs.styles.backgroundColor, "append", @@ -1669,7 +1669,7 @@ export class Actions { ), new VarAction( () => - new ValueOperationExpr(null, [ + new ValueOperationExpr(ListIndexDocs.styles.backgroundColor, null, [ new ListAccessModifier(ListIndexDocs.styles.backgroundColor), ]), "get item from {VAR_ID} at index", @@ -1682,7 +1682,7 @@ export class Actions { [ new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new ListAccessModifier(ListIndexDocs.styles.backgroundColor), new AssignmentModifier(AssignDocs.styles.backgroundColor), ]), @@ -1691,7 +1691,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new MethodCallModifier( ListAppendDocs.styles.backgroundColor, "append", @@ -1715,7 +1715,7 @@ export class Actions { ), new VarAction( () => - new ValueOperationExpr(null, [ + new ValueOperationExpr(ListIndexDocs.styles.backgroundColor, null, [ new ListAccessModifier(ListIndexDocs.styles.backgroundColor), ]), "get item from {VAR_ID} at index", @@ -1728,7 +1728,7 @@ export class Actions { [ new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new ListAccessModifier(ListIndexDocs.styles.backgroundColor), new AssignmentModifier(AssignDocs.styles.backgroundColor), ]), @@ -1737,7 +1737,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new MethodCallModifier( ListAppendDocs.styles.backgroundColor, "append", @@ -1761,7 +1761,7 @@ export class Actions { ), new VarAction( () => - new ValueOperationExpr(null, [ + new ValueOperationExpr(ListIndexDocs.styles.backgroundColor, null, [ new ListAccessModifier(ListIndexDocs.styles.backgroundColor), ]), "get item from {VAR_ID} at index", @@ -1774,7 +1774,7 @@ export class Actions { [ new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new ListAccessModifier(ListIndexDocs.styles.backgroundColor), new AssignmentModifier(AssignDocs.styles.backgroundColor), ]), @@ -1783,7 +1783,7 @@ export class Actions { ), new VarAction( () => - new VarOperationStmt(null, [ + new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, null, [ new MethodCallModifier( ListAppendDocs.styles.backgroundColor, "append", @@ -1807,7 +1807,7 @@ export class Actions { ), new VarAction( () => - new ValueOperationExpr(null, [ + new ValueOperationExpr(ListIndexDocs.styles.backgroundColor, null, [ new ListAccessModifier(ListIndexDocs.styles.backgroundColor), ]), "get item from {VAR_ID} at index", diff --git a/src/editor/toolbox.ts b/src/editor/toolbox.ts index 492d859..9337029 100644 --- a/src/editor/toolbox.ts +++ b/src/editor/toolbox.ts @@ -178,7 +178,7 @@ export class ToolboxController { const toolboxCategories = Actions.instance().toolboxCategories; const hello = Actions.instance().actionsMap; - console.log(hello); + for (const constructGroup of toolboxCategories) { if (constructGroup) { let categoryDiv; diff --git a/src/syntax-tree/ast.ts b/src/syntax-tree/ast.ts index 96ca54f..87e4c7a 100644 --- a/src/syntax-tree/ast.ts +++ b/src/syntax-tree/ast.ts @@ -1735,9 +1735,11 @@ export class VariableReferenceExpr extends Expression { export class ValueOperationExpr extends Expression { isVarSet = false; - constructor(value: Expression, modifiers?: Array, root?: Statement, indexInRoot?: number) { + constructor(color: string, value: Expression, modifiers?: Array, root?: Statement, indexInRoot?: number) { super(value != null ? value.returns : DataType.Void); + this.color = color; + if (value != null) { value.indexInRoot = this.tokens.length; value.rootNode = this; @@ -1793,9 +1795,17 @@ export class ValueOperationExpr extends Expression { export class VarOperationStmt extends Statement { isVarSet = false; - constructor(ref: VariableReferenceExpr, modifiers?: Array, root?: Statement, indexInRoot?: number) { + constructor( + color: string, + ref: VariableReferenceExpr, + modifiers?: Array, + root?: Statement, + indexInRoot?: number + ) { super(); + this.color = color; + if (ref != null) { ref.indexInRoot = this.tokens.length; ref.rootNode = this; From d849347c64dc1def858e6cb50866c8de449d30b6 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 26 May 2022 16:22:41 -0400 Subject: [PATCH 09/24] implemented white holes, literals have no background color, adjusted height of expressions, adjusted height of holes --- src/css/hole.css | 4 +++- src/editor/action-executor.ts | 6 +++--- src/editor/hole.ts | 4 ++-- src/messages/messages.ts | 35 ++++++++++++++++++++++++----------- src/syntax-tree/ast.ts | 2 ++ 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/css/hole.css b/src/css/hole.css index d6ef60d..cc370c8 100644 --- a/src/css/hole.css +++ b/src/css/hole.css @@ -8,6 +8,8 @@ transition-duration: 0.2s; border-radius: 5px; + background-color: white; + z-index: -998; } .editableHole { @@ -29,7 +31,7 @@ .text-editable-expr-hole { border-radius: 15px !important; - border-style: dashed !important; + border-style: dashed !important; } .empty-operator-hole { diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index da7e4cb..873afd2 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -1416,7 +1416,7 @@ export class ActionExecutor { if (flashGreen) this.flashGreen(newLiteral); - this.setTokenColor(newLiteral, newLiteral.color); + // this.setTokenColor(newLiteral, newLiteral.color); if (action.data?.source?.type === "keyboard") { eventType = LogType.InsertCode; @@ -1609,7 +1609,7 @@ export class ActionExecutor { if (autocompleteData) { this.flashGreen(stmt); - this.setTokenColor(stmt, stmt.color); + // this.setTokenColor(stmt, stmt.color); } eventData.code = varRef.getRenderText(); @@ -1620,7 +1620,7 @@ export class ActionExecutor { if (autocompleteData) { this.flashGreen(expr); - this.setTokenColor(expr, expr.color); + // this.setTokenColor(expr, expr.color); } eventData.code = expr.getRenderText(); diff --git a/src/editor/hole.ts b/src/editor/hole.ts index 834cbef..a9079f7 100644 --- a/src/editor/hole.ts +++ b/src/editor/hole.ts @@ -155,11 +155,11 @@ export class Hole { } } - this.element.style.top = `${transform.y + 5}px`; + this.element.style.top = `${transform.y + 5 + 4}px`; this.element.style.left = `${transform.x - leftPadding}px`; this.element.style.width = `${transform.width + rightPadding}px`; - this.element.style.height = `${transform.height - 5 * 2}px`; + this.element.style.height = `${transform.height - 5 * 2 - 10}px`; } remove() { diff --git a/src/messages/messages.ts b/src/messages/messages.ts index 178bb41..b4a129d 100644 --- a/src/messages/messages.ts +++ b/src/messages/messages.ts @@ -2,7 +2,7 @@ import { Selection } from "monaco-editor"; import { Editor } from "../editor/editor"; import { EDITOR_DOM_ID } from "../editor/toolbox"; import { nova } from "../index"; -import { CodeConstruct, Statement, TypedEmptyExpr } from "../syntax-tree/ast"; +import { CodeConstruct, Expression, Statement, TypedEmptyExpr } from "../syntax-tree/ast"; import { Callback, CallbackType } from "../syntax-tree/callback"; /** @@ -127,7 +127,10 @@ export class ConstructHighlight extends CodeHighlight { constructor(editor: Editor, codeToHighlight: CodeConstruct, rgbColour: [number, number, number, number]) { super(editor, codeToHighlight); this.changeHighlightColour(rgbColour); - this.domElement.style.outline = "1px solid black"; + + if (codeToHighlight instanceof Expression) { + this.domElement.style.borderRadius = "20px"; + } } protected createDomElement() { @@ -198,10 +201,20 @@ export class ConstructHighlight extends CodeHighlight { const selection = this.code.getSelection(); const transform = this.editor.computeBoundingBox(selection); - top = (selection.startLineNumber - 1) * this.editor.computeCharHeight(); - left = transform.x; - height = Math.floor(this.editor.computeCharHeight() * 0.95); - width = (selection.endColumn - selection.startColumn) * this.editor.computeCharWidthInvisible(lineNumber); + if (this.code instanceof Expression) { + top = (selection.startLineNumber - 1) * this.editor.computeCharHeight() + 5; + left = transform.x; + height = Math.floor(this.editor.computeCharHeight() * 0.95) - 10; + width = + (selection.endColumn - selection.startColumn) * this.editor.computeCharWidthInvisible(lineNumber); + } else { + top = (selection.startLineNumber - 1) * this.editor.computeCharHeight(); + left = transform.x; + height = Math.floor(this.editor.computeCharHeight() * 0.95); + width = + (selection.endColumn - selection.startColumn) * this.editor.computeCharWidthInvisible(lineNumber) + + 10; + } } if (firstInsertion) { @@ -696,13 +709,13 @@ export class ScopeHighlight { this.headerElement.classList.add("scope-header-highlight"); // this.headerElement.style.backgroundColor = "rgba(75, 200, 255, 0.125)"; this.headerElement.style.backgroundColor = color; - this.headerElement.style.outline = "1px solid black"; + this.headerElement.style.opacity = "0.25"; - this.bodyElement = document.createElement("div"); + https: this.bodyElement = document.createElement("div"); this.bodyElement.classList.add("scope-body-highlight"); // this.bodyElement.style.backgroundColor = "rgba(75, 200, 255, 0.125)"; this.bodyElement.style.backgroundColor = color; - this.bodyElement.style.outline = "1px solid black"; + this.bodyElement.style.opacity = "0.25"; this.updateDimensions(); @@ -749,9 +762,9 @@ export class ScopeHighlight { } this.bodyElement.style.top = `${firstLineInBodyDim.top}px`; - this.bodyElement.style.left = `${firstLineInBodyDim.left}px`; + this.bodyElement.style.left = `${firstLineInBodyDim.left - 10}px`; - this.bodyElement.style.width = `${maxRight - firstLineInBodyDim.left}px`; + this.bodyElement.style.width = `${maxRight - firstLineInBodyDim.left + 10}px`; this.bodyElement.style.height = `${headerDim.height * (maxLineNumber - this.statement.lineNumber)}px`; } diff --git a/src/syntax-tree/ast.ts b/src/syntax-tree/ast.ts index 87e4c7a..ebe3a0f 100644 --- a/src/syntax-tree/ast.ts +++ b/src/syntax-tree/ast.ts @@ -222,6 +222,8 @@ export abstract class Statement implements CodeConstruct { constructor() { for (const type in CallbackType) this.callbacks[type] = new Array(); + this.color = "transparent"; + this.subscribe( CallbackType.delete, new Callback(() => { From b2cb48446b6e314f3739da0ce5a69cd687427d71 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 30 May 2022 10:34:36 -0400 Subject: [PATCH 10/24] increase width of expression ConstructHighlights, fixed curosr highlighting size in holes to match hole size --- src/editor/cursor.ts | 4 ++-- src/messages/messages.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/editor/cursor.ts b/src/editor/cursor.ts index cce1ed0..5613094 100644 --- a/src/editor/cursor.ts +++ b/src/editor/cursor.ts @@ -39,11 +39,11 @@ export class Cursor { const transform = this.editor.computeBoundingBox(selection); - this.element.style.top = `${transform.y + 5}px`; + this.element.style.top = `${transform.y + 5 + 4}px`; this.element.style.left = `${transform.x - leftPadding}px`; this.element.style.width = `${transform.width + rightPadding}px`; - this.element.style.height = `${transform.height - 5 * 2}px`; + this.element.style.height = `${transform.height - 5 * 2 - 10}px`; } setSelection(code: CodeConstruct = null) { diff --git a/src/messages/messages.ts b/src/messages/messages.ts index b4a129d..475a7cf 100644 --- a/src/messages/messages.ts +++ b/src/messages/messages.ts @@ -203,10 +203,11 @@ export class ConstructHighlight extends CodeHighlight { if (this.code instanceof Expression) { top = (selection.startLineNumber - 1) * this.editor.computeCharHeight() + 5; - left = transform.x; + left = transform.x - 5; height = Math.floor(this.editor.computeCharHeight() * 0.95) - 10; width = - (selection.endColumn - selection.startColumn) * this.editor.computeCharWidthInvisible(lineNumber); + (selection.endColumn - selection.startColumn) * this.editor.computeCharWidthInvisible(lineNumber) + + 10; } else { top = (selection.startLineNumber - 1) * this.editor.computeCharHeight(); left = transform.x; From f2375fc6dd0da51a78213a94e6049011e20e5367 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 30 May 2022 14:26:57 -0400 Subject: [PATCH 11/24] changed CallbackType.fail color to less transparent red and reverts back to white instead of fully transparent --- src/editor/hole.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/hole.ts b/src/editor/hole.ts index a9079f7..7b671d9 100644 --- a/src/editor/hole.ts +++ b/src/editor/hole.ts @@ -125,10 +125,10 @@ export class Hole { code.subscribe( CallbackType.fail, new Callback(() => { - hole.element.style.background = `rgba(255, 0, 0, 0.06)`; + hole.element.style.background = `rgba(255, 0, 0, 0.25)`; setTimeout(() => { - hole.element.style.background = `rgba(255, 0, 0, 0)`; + hole.element.style.background = `rgba(255, 255, 255, 1)`; }, 1000); }) ); From 7e2dd777ad1f3dd76db0037c691db57f863f8e5f Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 30 May 2022 14:28:17 -0400 Subject: [PATCH 12/24] changed popup suggestion message color to black so it shows up on white background --- src/css/messages.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/css/messages.css b/src/css/messages.css index c7e8a3a..8d6888a 100644 --- a/src/css/messages.css +++ b/src/css/messages.css @@ -115,7 +115,8 @@ margin: 7px 7px 0 0; font-size: 13px; box-shadow: 0 0 3px 1px #aaa; - + + color:black; background-color: #ffffff; height: 17px; padding: 3px 8px 3px 8px; From 5c088cf9429752084ef1e0193160baead85e97c5 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 30 May 2022 14:28:50 -0400 Subject: [PATCH 13/24] removed background color for literalvalexpr in replaceCode function --- src/editor/action-executor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index 873afd2..e140614 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -2157,7 +2157,7 @@ export class ActionExecutor { root.rebuild(root.getLeftPosition(), 0); - if (replace instanceof Statement) { + if (replace instanceof Statement && !(replace instanceof LiteralValExpr)) { this.setTokenColor(replace, replace.color); } From d4ed64d9c04117a1692dfdf7f6a89a13d6736a61 Mon Sep 17 00:00:00 2001 From: Majeed Date: Tue, 31 May 2022 16:51:50 -0400 Subject: [PATCH 14/24] fixed multiple holes issue --- src/editor/action-executor.ts | 2 ++ src/editor/editor.ts | 13 ++++++------- src/editor/hole.ts | 2 -- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index e140614..997711b 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -1269,7 +1269,9 @@ export class ActionExecutor { replacementTkn = rootNode.tokens[i]; } } + this.replaceCode(rootNode, replacementTkn); + break; } diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 3859455..ffc9135 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -18,7 +18,6 @@ export class Editor { module: Module; cursor: Cursor; monaco: editor.IStandaloneCodeEditor; - holes: Hole[]; mousePosWindow: number[] = [0, 0]; scrollOffsetTop: number = 0; oldCursorLineNumber: number = 1; @@ -310,7 +309,7 @@ export class Editor { }); this.cursor = new Cursor(this); - this.holes = []; + Hole.holes = []; this.module = module; } @@ -322,7 +321,7 @@ export class Editor { } addHoles(code: CodeConstruct) { - for (const hole of this.holes) if (hole.code == code) return; + for (const hole of Hole.holes) if (hole.code == code) return; if ( code instanceof EditableTextTkn || @@ -330,9 +329,9 @@ export class Editor { code instanceof IdentifierTkn || code instanceof EmptyOperatorTkn ) { - this.holes.push(new Hole(this, code)); + Hole.holes.push(new Hole(this, code)); } else if (code instanceof Statement) { - const statement = code; + const statement = code as Statement; statement.tokens.forEach((token) => this.addHoles(token)); } } @@ -463,7 +462,7 @@ export class Editor { reset() { this.monaco.getModel().setValue(""); - this.holes.forEach((hole) => hole.remove()); - this.holes = []; + Hole.holes.forEach((hole) => hole.remove()); + Hole.holes = []; } } diff --git a/src/editor/hole.ts b/src/editor/hole.ts index 7b671d9..5d56062 100644 --- a/src/editor/hole.ts +++ b/src/editor/hole.ts @@ -42,8 +42,6 @@ export class Hole { this.element = element; const hole = this; - Hole.holes.push(hole); - if (code instanceof EmptyOperatorTkn) this.element.classList.add("empty-operator-hole"); else if (code instanceof IdentifierTkn) this.element.classList.add("identifier-hole"); else if (code instanceof EditableTextTkn) { From 8445d7761dc5ae7ba85700f087d86c7a28a07f12 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 1 Jun 2022 11:43:40 -0400 Subject: [PATCH 15/24] closes #591 --- src/messages/messages.ts | 4 ++-- src/syntax-tree/module.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/messages/messages.ts b/src/messages/messages.ts index 475a7cf..49f6a28 100644 --- a/src/messages/messages.ts +++ b/src/messages/messages.ts @@ -190,11 +190,11 @@ export class ConstructHighlight extends CodeHighlight { const text = this.code.getRenderText(); top = transform.y + 5; - left = (this.code.getSelection().startColumn - 1) * this.editor.computeCharWidthInvisible(lineNumber); + left = (this.code.getSelection().startColumn - 1) * this.editor.computeCharWidthInvisible(lineNumber) - 4; width = text.length * this.editor.computeCharWidthInvisible(lineNumber) > 0 - ? text.length * this.editor.computeCharWidthInvisible(lineNumber) + ? text.length * this.editor.computeCharWidthInvisible(lineNumber) + 10 : HIGHLIGHT_DEFAULT_WIDTH; height = transform.height > 0 ? transform.height - 5 * 2 : HIGHLIGHT_DEFAULT_HEIGHT; } else { diff --git a/src/syntax-tree/module.ts b/src/syntax-tree/module.ts index 88a13d2..ee02f81 100644 --- a/src/syntax-tree/module.ts +++ b/src/syntax-tree/module.ts @@ -39,7 +39,7 @@ import { Reference, Scope } from "./scope"; import { TypeChecker } from "./type-checker"; import { VariableController } from "./variable-controller"; -const ERROR_HIGHLIGHT_COLOUR: [number, number, number, number] = [255, 153, 153, 0.5]; +const ERROR_HIGHLIGHT_COLOUR: [number, number, number, number] = [255, 153, 153, 0.8]; /** * The main body of the code which includes an array of statements. From 9234d8879a494d730f68725a1e78e4bc8c2114b8 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 1 Jun 2022 11:50:46 -0400 Subject: [PATCH 16/24] make scopehighlight contain entire constructhighlights of body --- src/messages/messages.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/messages/messages.ts b/src/messages/messages.ts index 49f6a28..fe63a41 100644 --- a/src/messages/messages.ts +++ b/src/messages/messages.ts @@ -708,13 +708,11 @@ export class ScopeHighlight { protected createDomElement(color: string): void { this.headerElement = document.createElement("div"); this.headerElement.classList.add("scope-header-highlight"); - // this.headerElement.style.backgroundColor = "rgba(75, 200, 255, 0.125)"; this.headerElement.style.backgroundColor = color; this.headerElement.style.opacity = "0.25"; - https: this.bodyElement = document.createElement("div"); + this.bodyElement = document.createElement("div"); this.bodyElement.classList.add("scope-body-highlight"); - // this.bodyElement.style.backgroundColor = "rgba(75, 200, 255, 0.125)"; this.bodyElement.style.backgroundColor = color; this.bodyElement.style.opacity = "0.25"; @@ -750,7 +748,7 @@ export class ScopeHighlight { this.headerElement.style.top = `${headerDim.top}px`; this.headerElement.style.left = `${headerDim.left}px`; - this.headerElement.style.width = `${maxRight - headerDim.left}px`; + this.headerElement.style.width = `${maxRight - headerDim.left + 10}px`; this.headerElement.style.height = `${headerDim.height}px`; let firstLineInBody = this.statement.body[0]; @@ -765,7 +763,7 @@ export class ScopeHighlight { this.bodyElement.style.top = `${firstLineInBodyDim.top}px`; this.bodyElement.style.left = `${firstLineInBodyDim.left - 10}px`; - this.bodyElement.style.width = `${maxRight - firstLineInBodyDim.left + 10}px`; + this.bodyElement.style.width = `${maxRight - firstLineInBodyDim.left + 20}px`; this.bodyElement.style.height = `${headerDim.height * (maxLineNumber - this.statement.lineNumber)}px`; } From 336660770aa3bb41627b81a75393fc7ef4286aee Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 1 Jun 2022 12:10:45 -0400 Subject: [PATCH 17/24] Fixes #592 --- src/editor/event-router.ts | 10 ++++++++-- src/editor/validator.ts | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/editor/event-router.ts b/src/editor/event-router.ts index 49cc721..c9006ac 100644 --- a/src/editor/event-router.ts +++ b/src/editor/event-router.ts @@ -135,7 +135,10 @@ export class EventRouter { toRight: true, }); } else if (this.module.validator.isTknEmpty(context)) { - if (this.module.validator.isAugmentedAssignmentModifierStatement(context)) { + if ( + this.module.validator.isAugmentedAssignmentModifierStatement(context) || + this.module.validator.isMethodCallModifierStatement(context) + ) { return new EditAction(EditActionType.DeleteStatement); } if (context.token.rootNode instanceof ast.Expression) { @@ -201,7 +204,10 @@ export class EventRouter { toRight: true, }); } else if (this.module.validator.isTknEmpty(context)) { - if (this.module.validator.isAugmentedAssignmentModifierStatement(context)) { + if ( + this.module.validator.isAugmentedAssignmentModifierStatement(context) || + this.module.validator.isMethodCallModifierStatement(context) + ) { return new EditAction(EditActionType.DeleteStatement); } if (context.token.rootNode instanceof ast.Expression) { diff --git a/src/editor/validator.ts b/src/editor/validator.ts index b4b256f..23e61f4 100644 --- a/src/editor/validator.ts +++ b/src/editor/validator.ts @@ -17,6 +17,7 @@ import { ImportStatement, ListLiteralExpression, LiteralValExpr, + MethodCallModifier, Modifier, NonEditableTkn, OperatorTkn, @@ -409,6 +410,20 @@ export class Validator { return false; } + /** + * logic: checks if rootNode is instanceof MethodCallModifier + */ + isMethodCallModifierStatement(providedContext?: Context): boolean { + const context = providedContext ? providedContext : this.module.focus.getContext(); + const rootNode = context.token.rootNode; + + if (rootNode instanceof MethodCallModifier) { + return true; + } + + return false; + } + /** * logic: checks if Statement body is empty and if all tokens of Statement are empty */ From 89e9ffce1c7662f57bb5c2e5476e55877e2e7995 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 1 Jun 2022 13:15:07 -0400 Subject: [PATCH 18/24] Fixes #593 --- src/editor/action-executor.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index 997711b..5c140b8 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -931,7 +931,7 @@ export class ActionExecutor { if (flashGreen) this.flashGreen(action.data.modifier); - this.setTokenColor(action.data.modifier, action.data.modifier.color); + // this.setTokenColor(action.data.modifier, action.data.modifier.color); } } @@ -1598,7 +1598,11 @@ export class ActionExecutor { }), this.module.focus.getContext() ); - this.flashGreen(modifier.rootNode as Statement); + + const varOpStmt = modifier.rootNode as Statement; + + this.flashGreen(varOpStmt); + this.setTokenColor(varOpStmt, varOpStmt.color); }); buttons.push(button); @@ -1611,7 +1615,7 @@ export class ActionExecutor { if (autocompleteData) { this.flashGreen(stmt); - // this.setTokenColor(stmt, stmt.color); + this.setTokenColor(stmt, stmt.color); } eventData.code = varRef.getRenderText(); @@ -1622,7 +1626,7 @@ export class ActionExecutor { if (autocompleteData) { this.flashGreen(expr); - // this.setTokenColor(expr, expr.color); + this.setTokenColor(expr, expr.color); } eventData.code = expr.getRenderText(); From b39c827f2e88568937b0d242439a536059d8eef8 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 2 Jun 2022 10:24:48 -0400 Subject: [PATCH 19/24] Fixes #595 #596 --- src/docs/choice.json | 2 +- src/docs/import.json | 2 +- src/docs/list-element-assign.json | 2 +- src/docs/list-index.json | 2 +- src/docs/list-item.json | 2 +- src/docs/list-literal.json | 2 +- src/docs/print.json | 2 +- src/docs/randint.json | 2 +- src/docs/range.json | 2 +- src/editor/action-executor.ts | 2 -- 10 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/docs/choice.json b/src/docs/choice.json index dca2002..aa063ed 100644 --- a/src/docs/choice.json +++ b/src/docs/choice.json @@ -13,6 +13,6 @@ ], "search-queries": ["choice", "random choice", "choose randomly from list", "select randomly from array"], "styles":{ - "backgroundColor": "#59C059" + "backgroundColor": "#a4e6a1" } } diff --git a/src/docs/import.json b/src/docs/import.json index 24a5dab..4bf287a 100644 --- a/src/docs/import.json +++ b/src/docs/import.json @@ -18,6 +18,6 @@ ], "search-queries": ["import module", "import random"], "styles":{ - "backgroundColor": "#FF4D6A" + "backgroundColor": "#e897aa" } } diff --git a/src/docs/list-element-assign.json b/src/docs/list-element-assign.json index e426b91..4ca37e0 100644 --- a/src/docs/list-element-assign.json +++ b/src/docs/list-element-assign.json @@ -23,6 +23,6 @@ "set value list index" ], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#7c8ee6" } } diff --git a/src/docs/list-index.json b/src/docs/list-index.json index ac7a46e..32ffa68 100644 --- a/src/docs/list-index.json +++ b/src/docs/list-index.json @@ -18,6 +18,6 @@ ], "search-queries": ["access list item", "access list element", "access list element at index"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#7c8ee6" } } diff --git a/src/docs/list-item.json b/src/docs/list-item.json index 9d33fc1..edcc393 100644 --- a/src/docs/list-item.json +++ b/src/docs/list-item.json @@ -13,6 +13,6 @@ ], "search-queries": ["comma", "list item"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#7c8ee6" } } diff --git a/src/docs/list-literal.json b/src/docs/list-literal.json index 4888558..0faf6ed 100644 --- a/src/docs/list-literal.json +++ b/src/docs/list-literal.json @@ -18,6 +18,6 @@ ], "search-queries": ["empty list", "create empty list", "create list", "array", "create empty array"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#7c8ee6" } } diff --git a/src/docs/print.json b/src/docs/print.json index bcc989f..ae6410a 100644 --- a/src/docs/print.json +++ b/src/docs/print.json @@ -18,6 +18,6 @@ ], "search-queries": ["output", "say", "print", "print output", "console", "write", "see output"], "styles":{ - "backgroundColor": "#9966FF" + "backgroundColor": "#d0b7ed" } } diff --git a/src/docs/randint.json b/src/docs/randint.json index b08fbe5..93c708a 100644 --- a/src/docs/randint.json +++ b/src/docs/randint.json @@ -13,6 +13,6 @@ ], "search-queries": ["random number", "random integer", "randint", "random between"], "styles":{ - "backgroundColor": "#59C059" + "backgroundColor": "#a4e6a1" } } diff --git a/src/docs/range.json b/src/docs/range.json index 7439910..8c39910 100644 --- a/src/docs/range.json +++ b/src/docs/range.json @@ -13,6 +13,6 @@ ], "search-queries": ["range", "sequence", "iterate", "for loop"], "styles":{ - "backgroundColor": "#411b8c" + "backgroundColor": "#a4e6a1" } } diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index 5c140b8..aa38a98 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -1744,8 +1744,6 @@ export class ActionExecutor { } } - private setAugmentedAssignmentModifierColor(code: CodeConstruct) {} - private insertEmptyListItem(focusedCode: CodeConstruct, index: number, items: Array) { if (focusedCode instanceof Token || focusedCode instanceof Expression) { const root = focusedCode.rootNode; From f7f2b2fc0c1665896c63c20ef04fc41726a60b8f Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 2 Jun 2022 13:09:02 -0400 Subject: [PATCH 20/24] changed while loop to have same color as for loop, modified orange color to be lighter --- src/docs/add-var.json | 2 +- src/docs/assign-add.json | 2 +- src/docs/assign-div.json | 2 +- src/docs/assign-mult.json | 2 +- src/docs/assign-sub.json | 2 +- src/docs/assign.json | 2 +- src/docs/break.json | 2 +- src/docs/f-str-item.json | 2 +- src/docs/f-str.json | 2 +- src/docs/find.json | 2 +- src/docs/join.json | 2 +- src/docs/list-append.json | 2 +- src/docs/replace.json | 2 +- src/docs/split.json | 2 +- src/docs/str.json | 2 +- src/docs/to-int.json | 2 +- src/docs/to-str.json | 2 +- src/docs/while.json | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/docs/add-var.json b/src/docs/add-var.json index ffcc615..c011748 100644 --- a/src/docs/add-var.json +++ b/src/docs/add-var.json @@ -27,6 +27,6 @@ ], "search-queries": ["create new variable", "variable", "var"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/assign-add.json b/src/docs/assign-add.json index 2a599d8..2432a33 100644 --- a/src/docs/assign-add.json +++ b/src/docs/assign-add.json @@ -13,6 +13,6 @@ ], "search-queries": ["add to variable"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/assign-div.json b/src/docs/assign-div.json index 169fdea..23837ae 100644 --- a/src/docs/assign-div.json +++ b/src/docs/assign-div.json @@ -13,6 +13,6 @@ ], "search-queries": ["divide variable"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/assign-mult.json b/src/docs/assign-mult.json index 7d7e1e1..a36d1d3 100644 --- a/src/docs/assign-mult.json +++ b/src/docs/assign-mult.json @@ -13,6 +13,6 @@ ], "search-queries": ["multiply variable"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/assign-sub.json b/src/docs/assign-sub.json index b2e4f0f..b554215 100644 --- a/src/docs/assign-sub.json +++ b/src/docs/assign-sub.json @@ -13,6 +13,6 @@ ], "search-queries": ["subtract from variable", "deduct from variable"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/assign.json b/src/docs/assign.json index 58c2ad9..7dd2a47 100644 --- a/src/docs/assign.json +++ b/src/docs/assign.json @@ -13,6 +13,6 @@ ], "search-queries": ["set", "assign", "assign variable", "set variable", "update value variable"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/break.json b/src/docs/break.json index 377ee8d..7c55486 100644 --- a/src/docs/break.json +++ b/src/docs/break.json @@ -42,6 +42,6 @@ ], "search-queries": ["exit", "loop", "break"], "styles":{ - "backgroundColor": "#FFAB19" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/f-str-item.json b/src/docs/f-str-item.json index ab0e305..eb2bd65 100644 --- a/src/docs/f-str-item.json +++ b/src/docs/f-str-item.json @@ -13,6 +13,6 @@ ], "search-queries": ["formatted string item", "formatted text item"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/f-str.json b/src/docs/f-str.json index 2789590..a8c5ff7 100644 --- a/src/docs/f-str.json +++ b/src/docs/f-str.json @@ -13,6 +13,6 @@ ], "search-queries": ["formatted string", "formatted text"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/find.json b/src/docs/find.json index 2d4945d..fcd2bd9 100644 --- a/src/docs/find.json +++ b/src/docs/find.json @@ -13,6 +13,6 @@ ], "search-queries": ["search text", "find text", "find string"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/join.json b/src/docs/join.json index 7e59b47..e26b93e 100644 --- a/src/docs/join.json +++ b/src/docs/join.json @@ -13,6 +13,6 @@ ], "search-queries": ["join string", "join text"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/list-append.json b/src/docs/list-append.json index 2f8ca50..9cd6a6a 100644 --- a/src/docs/list-append.json +++ b/src/docs/list-append.json @@ -25,6 +25,6 @@ "insert end list" ], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/replace.json b/src/docs/replace.json index e81cd69..1336519 100644 --- a/src/docs/replace.json +++ b/src/docs/replace.json @@ -13,6 +13,6 @@ ], "search-queries": ["replace", "replace text", "replace string"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/split.json b/src/docs/split.json index c38f305..2ec8b0a 100644 --- a/src/docs/split.json +++ b/src/docs/split.json @@ -13,6 +13,6 @@ ], "search-queries": ["split", "split text", "split string"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/str.json b/src/docs/str.json index 72cc829..4fe79ee 100644 --- a/src/docs/str.json +++ b/src/docs/str.json @@ -13,6 +13,6 @@ ], "search-queries": ["create text", "create string", "empty string", "empty text"], "styles":{ - "backgroundColor": "#FF8C1A" + "backgroundColor": "#ebba8a" } } diff --git a/src/docs/to-int.json b/src/docs/to-int.json index 7296977..1c5ccf4 100644 --- a/src/docs/to-int.json +++ b/src/docs/to-int.json @@ -13,6 +13,6 @@ ], "search-queries": ["integer cast", "number convert", "convert to number", "convert to integer"], "styles":{ - "backgroundColor": "#a239bf" + "backgroundColor": "#edafdf" } } diff --git a/src/docs/to-str.json b/src/docs/to-str.json index c921ec9..8d0d8df 100644 --- a/src/docs/to-str.json +++ b/src/docs/to-str.json @@ -22,6 +22,6 @@ ], "search-queries": ["string cast", "text convert", "convert to string", "convert to text"], "styles":{ - "backgroundColor": "#a239bf" + "backgroundColor": "#edafdf" } } diff --git a/src/docs/while.json b/src/docs/while.json index af8d896..11dbb29 100644 --- a/src/docs/while.json +++ b/src/docs/while.json @@ -48,6 +48,6 @@ "repeat condition" ], "styles":{ - "backgroundColor": "#FFAB19" + "backgroundColor": "#411b8c" } } From 8c47362d35acbda118e29ffc4ae2a40e30d3dfa7 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 13 Jun 2022 15:39:20 -0400 Subject: [PATCH 21/24] commented out retrieveUser() --- src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3a51a81..8d04965 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ import "./css/index.css"; -import { retrieveUser } from "./logger/user"; import { Module } from "./syntax-tree/module"; // @ts-ignore @@ -25,7 +24,7 @@ self.MonacoEnvironment = { }, }; -retrieveUser(); +// retrieveUser(); const nova = new Module("editor"); const runBtnToOutputWindow = new Map(); runBtnToOutputWindow.set("runCodeBtn", "outputDiv"); From 153712d92c48680880d81f86d7a71961816f4aa2 Mon Sep 17 00:00:00 2001 From: Majeed Date: Tue, 14 Jun 2022 13:29:56 -0400 Subject: [PATCH 22/24] fixed end-line characters from CRLF to LF (Unix standard) --- src/editor/action-executor.ts | 4467 +++++++++++++++++---------------- 1 file changed, 2234 insertions(+), 2233 deletions(-) diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts index aa38a98..0adaf3b 100644 --- a/src/editor/action-executor.ts +++ b/src/editor/action-executor.ts @@ -1,2233 +1,2234 @@ -import { Position, Range } from "monaco-editor"; -import { ErrorMessage } from "../messages/error-msg-generator"; -import { ConstructHighlight, ScopeHighlight } from "../messages/messages"; -import { - AssignmentModifier, - AutocompleteTkn, - BinaryOperatorExpr, - CodeConstruct, - EditableTextTkn, - ElseStatement, - EmptyLineStmt, - EmptyOperatorTkn, - Expression, - FormattedStringCurlyBracketsExpr, - FormattedStringExpr, - IdentifierTkn, - IfStatement, - Importable, - ImportStatement, - ListAccessModifier, - ListLiteralExpression, - LiteralValExpr, - MethodCallModifier, - Modifier, - NonEditableTkn, - OperatorTkn, - Statement, - TemporaryStmt, - Token, - TypedEmptyExpr, - UnaryOperatorExpr, - ValueOperationExpr, - VarAssignmentStmt, - VariableReferenceExpr, - VarOperationStmt, -} from "../syntax-tree/ast"; -import { rebuildBody, replaceInBody } from "../syntax-tree/body"; -import { Callback, CallbackType } from "../syntax-tree/callback"; -import { - addClassToDraftModeResolutionButton, - AutoCompleteType, - BuiltInFunctions, - getOperatorCategory, - IgnoreConversionRecord, - PythonKeywords, - StringRegex, - TAB_SPACES, - Tooltip, - TYPE_MISMATCH_ANY, - TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR, - TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR, -} from "../syntax-tree/consts"; -import { Module } from "../syntax-tree/module"; -import { Reference } from "../syntax-tree/scope"; -import { TypeChecker } from "../syntax-tree/type-checker"; -import { getUserFriendlyType, isImportable } from "../utilities/util"; -import { LogEvent, Logger, LogType } from "./../logger/analytics"; -import { BinaryOperator, DataType, InsertionType } from "./../syntax-tree/consts"; -import { EditCodeAction } from "./action-filter"; -import { Actions, Docs, EditActionType, InsertActionType } from "./consts"; -import { EditAction } from "./data-types"; -import { Context, UpdatableContext } from "./focus"; - -export class ActionExecutor { - module: Module; - - constructor(module: Module) { - this.module = module; - } - - execute(action: EditAction, providedContext?: Context, e?: KeyboardEvent): boolean { - const pressedKey = e?.key; - let context = providedContext ? providedContext : this.module.focus.getContext(); - - let preventDefaultEvent = true; - let flashGreen = false; - - if (action?.data?.autocompleteData) flashGreen = true; - - let { eventType, eventData } = this.getLogEventSource(action?.data?.source); - - switch (action.type) { - case EditActionType.OpenAutocomplete: { - const autocompleteTkn = new AutocompleteTkn( - action.data.firstChar, - action.data.autocompleteType, - action.data.validMatches - ); - - autocompleteTkn.subscribe( - CallbackType.change, - new Callback( - (() => { - if (!this.module.menuController.isMenuOpen()) { - this.openAutocompleteMenu(action.data.validMatches); - } - - this.updateAutocompleteMenu(autocompleteTkn); - - if ( - this.module.menuController.hasNoSuggestions() && - autocompleteTkn.left != autocompleteTkn.getParentStatement().left && - !autocompleteTkn.message - ) { - const message = this.module.messageController.addHoverMessage( - autocompleteTkn, - {}, - `Did you want to create a text message? use double quotes before and after the text, like this: "your desired text"` - ); - - const button = message.createButton("convert to text"); - button.addEventListener("click", () => { - this.module.executer.execute( - new EditAction(EditActionType.ConvertAutocompleteToString, { - token: autocompleteTkn, - source: { type: "draft-mode" }, - }), - this.module.focus.getContext() - ); - }); - } - }).bind(this) - ) - ); - - this.openAutocompleteMenu(action.data.validMatches); - - switch (action.data.autocompleteType) { - case AutoCompleteType.StartOfLine: - this.replaceEmptyStatement(context.lineStatement, new TemporaryStmt(autocompleteTkn)); - - break; - - case AutoCompleteType.AtEmptyOperatorHole: - case AutoCompleteType.AtExpressionHole: - this.insertToken(context, autocompleteTkn); - - break; - - case AutoCompleteType.RightOfExpression: - this.insertToken(context, autocompleteTkn, { toRight: true }); - - break; - case AutoCompleteType.LeftOfExpression: - this.insertToken(context, autocompleteTkn, { toLeft: true }); - - break; - } - - this.module.editor.cursor.setSelection(null); - const match = autocompleteTkn.isTerminatingMatch(); - - if (match) { - this.performMatchAction(match, autocompleteTkn); - } else { - let highlight = new ConstructHighlight(this.module.editor, autocompleteTkn, [230, 235, 255, 0.7]); - - autocompleteTkn.subscribe( - CallbackType.delete, - new Callback(() => { - if (highlight) { - highlight.removeFromDOM(); - highlight = null; - } - }) - ); - } - - break; - } - - case EditActionType.InsertElseStatement: { - const newStatement = new ElseStatement(Docs.ElseDocs.styles.backgroundColor, action.data.hasCondition); - - if (action.data.outside) { - // when the else is being inserted outside - const elseRoot = context.lineStatement.rootNode as Module | Statement; - newStatement.rootNode = elseRoot; - newStatement.indexInRoot = context.lineStatement.indexInRoot; - newStatement.body.push(new EmptyLineStmt(newStatement, 0)); - - replaceInBody(elseRoot, newStatement.indexInRoot, newStatement); - rebuildBody(elseRoot, newStatement.indexInRoot, context.lineStatement.lineNumber); - this.module.editor.executeEdits(this.getBoundaries(context.lineStatement), newStatement); - } else { - // when being inserted inside - const curStmtRoot = context.lineStatement.rootNode as Statement; - const elseRoot = curStmtRoot.rootNode as Module | Statement; - newStatement.rootNode = elseRoot; - newStatement.indexInRoot = curStmtRoot.indexInRoot + 1; - - // indent back and place all of the code below it as its child - const toMoveStatements = curStmtRoot.body.splice( - context.lineStatement.indexInRoot, - curStmtRoot.body.length - context.lineStatement.indexInRoot - ); - - // remove the empty line statement - toMoveStatements.splice(0, 1)[0]; - - if (toMoveStatements.length == 0) newStatement.body.push(new EmptyLineStmt(newStatement, 0)); - const providedLeftPos = new Position( - context.lineStatement.lineNumber, - context.lineStatement.left - TAB_SPACES - ); - newStatement.build(providedLeftPos); - - this.module.editor.executeEdits( - this.getBoundaries(context.lineStatement, { selectIndent: true }), - newStatement - ); - - const topReferences = new Array(); - const bottomReferences = new Array(); - - for (const ref of curStmtRoot.scope.references) { - if (ref.statement.indexInRoot > context.lineStatement.indexInRoot) { - bottomReferences.push(ref); - } else topReferences.push(ref); - } - - if (bottomReferences.length > 0) { - curStmtRoot.scope.references = topReferences; - newStatement.scope.references = bottomReferences; - } - - for (const [i, stmt] of toMoveStatements.entries()) { - stmt.rootNode = newStatement; - stmt.indexInRoot = i; - newStatement.body.push(stmt); - } - - newStatement.init(providedLeftPos); - newStatement.rootNode = elseRoot; - newStatement.indexInRoot = newStatement.indexInRoot; - this.module.addStatementToBody( - elseRoot, - newStatement, - newStatement.indexInRoot, - providedLeftPos.lineNumber - ); - } - - if (flashGreen) this.flashGreen(newStatement); - - let scopeHighlight = new ScopeHighlight(this.module.editor, newStatement, newStatement.color); - eventData.code = "else-statement"; - - break; - } - - case EditActionType.InsertExpression: { - const expression = action.data?.expression as Expression; - - this.insertExpression(context, action.data?.expression); - - if (flashGreen) this.flashGreen(action.data?.expression); - - this.setTokenColor(action.data?.expression, expression.color); - - eventData.code = action.data?.expression?.getRenderText(); - - break; - } - - case EditActionType.InsertStatement: { - const statement = action.data?.statement as Statement; - - this.replaceEmptyStatement(context.lineStatement, statement); - - if (flashGreen) this.flashGreen(action.data?.statement); - - if (statement.hasBody()) { - let scopeHighlight = new ScopeHighlight(this.module.editor, statement, statement.color); - } else { - this.setTokenColor(action.data?.statement, statement.color); - } - - eventData.code = action.data?.statement?.getRenderText(); - - break; - } - - case EditActionType.InsertVarAssignStatement: { - //TODO: Might want to change back to use the case above if no new logic is added - const statement = action.data?.statement; - - const id = action.data?.autocompleteData?.identifier?.trim(); - - if (statement instanceof VarAssignmentStmt && id) statement.setIdentifier(id); - - this.replaceEmptyStatement(context.lineStatement, action.data?.statement as Statement); - - if (flashGreen) this.flashGreen(action.data?.statement); - - this.setTokenColor(action.data?.statement, statement.color); - - eventData.code = "var-assignment"; - eventData.id = id; - - break; - } - - case EditActionType.InsertUnaryOperator: { - if (action.data?.replace) { - this.insertExpression( - context, - new UnaryOperatorExpr( - Docs.NotDocs.styles.backgroundColor, - action.data.operator, - (context.token as TypedEmptyExpr).type[0] - ) - ); - } else if (action.data?.wrap) { - const expr = context.expressionToRight as Expression; - - const initialBoundary = this.getBoundaries(expr); - const root = expr.rootNode as Statement; - - const newCode = new UnaryOperatorExpr( - Docs.NotDocs.styles.backgroundColor, - action.data.operator, - expr.returns, - expr.returns, - expr.rootNode, - expr.indexInRoot - ); - - newCode.setOperand(expr); - root.tokens[newCode.indexInRoot] = newCode; - root.rebuild(root.getLeftPosition(), 0); - - this.module.editor.executeEdits(initialBoundary, newCode); - this.module.focus.updateContext({ - positionToMove: newCode.tokens[1].getLeftPosition(), - }); - } - - eventData.code = action.data.operator; - - break; - } - - case EditActionType.DeleteNextToken: { - if (context.expressionToRight instanceof OperatorTkn) { - this.replaceCode( - context.expressionToRight, - new EmptyOperatorTkn(" ", context.expressionToRight, context.expressionToRight.indexInRoot) - ); - } else if (this.module.validator.atBeginningOfValOperation(context)) { - this.deleteCode(context.expressionToRight.rootNode); - } else if (context.expressionToRight instanceof Modifier) { - this.deleteModifier(context.expressionToRight, { deleting: true }); - } else this.deleteCode(context.expressionToRight); - - break; - } - - case EditActionType.DeletePrevToken: { - if (context.expressionToLeft instanceof OperatorTkn) { - this.replaceCode( - context.expressionToLeft, - new EmptyOperatorTkn(" ", context.expressionToLeft, context.expressionToLeft.indexInRoot) - ); - } else if ( - context.expressionToLeft instanceof VariableReferenceExpr && - context.expressionToLeft.rootNode instanceof VarOperationStmt - ) { - this.deleteCode(context.expressionToLeft.rootNode, { statement: true }); - } else if (context.expressionToLeft instanceof Modifier) this.deleteModifier(context.expressionToLeft); - else this.deleteCode(context.expressionToLeft); - - break; - } - - case EditActionType.DeleteStatement: { - this.deleteCode(context.lineStatement, { statement: true }); - - break; - } - - case EditActionType.DeleteMultiLineStatement: { - if ( - context.lineStatement instanceof IfStatement || - (context.lineStatement instanceof ElseStatement && context.lineStatement.hasCondition) - ) { - const elseStatementsAfterIf = []; - - for ( - let i = context.lineStatement.indexInRoot + 1; - i < context.lineStatement.rootNode.body.length; - i++ - ) { - const line = context.lineStatement.rootNode.body[i]; - - if (line instanceof ElseStatement) elseStatementsAfterIf.push(line); - else break; - } - - for (const elseStmt of elseStatementsAfterIf) { - this.module.messageController.addHoverMessage( - elseStmt, - null, - "add if before the first else, or delete this." - ); - } - } - - while (context.lineStatement.body.length > 0) { - this.module.editor.indentRecursively( - context.lineStatement.body[context.lineStatement.body.length - 1], - { backward: true } - ); - this.module.indentBackStatement(context.lineStatement.body[context.lineStatement.body.length - 1]); - } - - this.deleteCode(context.lineStatement, { statement: true }); - - break; - } - - case EditActionType.DeleteCurLine: { - this.module.deleteLine(context.lineStatement); - let range: Range; - - if (action.data?.pressedBackspace) { - const lineAbove = this.module.focus.getStatementAtLineNumber(context.lineStatement.lineNumber - 1); - this.module.focus.updateContext({ - positionToMove: new Position(lineAbove.lineNumber, lineAbove.right), - }); - range = new Range( - context.lineStatement.lineNumber, - context.lineStatement.left, - lineAbove.lineNumber, - lineAbove.right - ); - } else { - range = new Range( - context.lineStatement.lineNumber, - context.lineStatement.left, - context.lineStatement.lineNumber + 1, - context.lineStatement.left - ); - } - - this.module.editor.executeEdits(range, null, ""); - - break; - } - - case EditActionType.DeleteSelectedModifier: { - this.deleteModifier(context.token.rootNode as Modifier, { deleting: true }); - - break; - } - - case EditActionType.DeletePrevLine: { - const prevLine = this.module.focus.getStatementAtLineNumber(context.lineStatement.lineNumber - 1); - - if (prevLine.left != context.lineStatement.left) { - this.module.editor.indentRecursively(context.lineStatement, { backward: false }); - this.module.indentForwardStatement(context.lineStatement); - } - - const deleteRange = new Range( - prevLine.lineNumber, - prevLine.left, - prevLine.lineNumber + 1, - prevLine.left - ); - this.module.deleteLine(prevLine); - this.module.editor.executeEdits(deleteRange, null, ""); - - break; - } - - case EditActionType.IndentBackwardsIfStmt: { - const root = context.lineStatement.rootNode as Statement | Module; - - const toIndentStatements = new Array(); - - for (let i = context.lineStatement.indexInRoot; i < root.body.length; i++) { - toIndentStatements.push(root.body[i]); - } - - for (const stmt of toIndentStatements.reverse()) { - this.module.editor.indentRecursively(stmt, { backward: true }); - this.module.indentBackStatement(stmt); - } - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.DeleteBackMultiLines: { - for ( - let i = context.lineStatement.rootNode.body.length - 1; - i >= context.lineStatement.indexInRoot; - i-- - ) { - this.module.editor.indentRecursively(context.lineStatement.rootNode.body[i], { backward: true }); - this.module.indentBackStatement(context.lineStatement.rootNode.body[i]); - } - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.IndentBackwards: { - this.module.editor.indentRecursively(context.lineStatement, { backward: true }); - this.module.indentBackStatement(context.lineStatement); - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.IndentForwardsIfStmt: { - const root = context.lineStatement.rootNode as Statement | Module; - - const toIndentStatements = new Array(); - - for (let i = context.lineStatement.indexInRoot; i < root.body.length; i++) { - toIndentStatements.push(root.body[i]); - - if (i + 1 < root.body.length && !(root.body[i + 1] instanceof ElseStatement)) break; - } - - for (const stmt of toIndentStatements) { - this.module.editor.indentRecursively(stmt, { backward: false }); - this.module.indentForwardStatement(stmt); - } - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.IndentForwards: { - this.module.editor.indentRecursively(context.lineStatement, { backward: false }); - this.module.indentForwardStatement(context.lineStatement); - - this.module.focus.fireOnNavChangeCallbacks(); - - break; - } - - case EditActionType.InsertEmptyLine: { - const newEmptyLine = this.module.insertEmptyLine(); - this.module.focus.fireOnNavOffCallbacks(context.lineStatement, newEmptyLine); - - break; - } - - case EditActionType.SelectPrevToken: { - this.module.focus.navigateLeft(); - - break; - } - - case EditActionType.SelectNextToken: { - this.module.focus.navigateRight(); - - break; - } - - case EditActionType.InsertFormattedStringItem: { - const cursorPos = this.module.editor.monaco.getPosition(); - const selectedText = this.module.editor.monaco.getSelection(); - const editableToken = this.module.focus.getTextEditableItem(context); - const token = editableToken.getToken(); - const formattedStringExpr = token.rootNode as FormattedStringExpr; - - const leftText = token.text.substring(0, cursorPos.column - token.left); - const rightText = token.text.substring(cursorPos.column - token.left, token.right); - - const leftToken = token; - leftToken.text = leftText; - const rightToken = new EditableTextTkn("", StringRegex, formattedStringExpr, token.indexInRoot + 1); - rightToken.text = rightText; - - formattedStringExpr.tokens.splice(token.indexInRoot + 1, 0, ...[rightToken]); - - formattedStringExpr.rebuild(formattedStringExpr.getLeftPosition(), 0); - - let rightTokenRange = new Range( - rightToken.getLineNumber(), - rightToken.left, - rightToken.getLineNumber(), - rightToken.right - ); - - this.module.editor.executeEdits(rightTokenRange, rightToken); - - const fStringToken = new FormattedStringCurlyBracketsExpr( - Docs.NotDocs.styles.backgroundColor, - formattedStringExpr, - token.indexInRoot + 1 - ); - - formattedStringExpr.tokens.splice(token.indexInRoot + 1, 0, ...[fStringToken]); - - formattedStringExpr.rebuild(formattedStringExpr.getLeftPosition(), 0); - - let editRange: Range; - - if (selectedText.startColumn != selectedText.endColumn) { - editRange = new Range( - cursorPos.lineNumber, - selectedText.startColumn, - cursorPos.lineNumber, - selectedText.endColumn - ); - } else { - editRange = new Range( - cursorPos.lineNumber, - cursorPos.column, - cursorPos.lineNumber, - cursorPos.column - ); - } - - this.module.editor.executeEdits(editRange, fStringToken); - this.module.focus.updateContext({ tokenToSelect: fStringToken.tokens[1] }); - eventData.code = "f-string-item"; - - break; - } - - case EditActionType.DeleteFStringCurlyBrackets: { - const fStringToRemove = action.data.item as FormattedStringCurlyBracketsExpr; - - const root = fStringToRemove.rootNode; - - const tokenBefore = root.tokens[fStringToRemove.indexInRoot - 1] as EditableTextTkn; - const tokenAfter = root.tokens[fStringToRemove.indexInRoot + 1] as EditableTextTkn; - - const indexToReplace = tokenBefore.indexInRoot; - - const newToken = new EditableTextTkn( - tokenBefore.text + tokenAfter.text, - StringRegex, - root, - fStringToRemove.indexInRoot - 1 - ); - - const focusPos = new Position(tokenBefore.getLineNumber(), tokenBefore.right); - - const replaceRange = new Range( - tokenAfter.getLineNumber(), - tokenAfter.right, - tokenBefore.getLineNumber(), - tokenBefore.left - ); - - this.module.removeItem(fStringToRemove); - this.module.removeItem(tokenAfter); - this.module.removeItem(tokenBefore); - - root.tokens.splice(indexToReplace, 0, newToken); - - root.rebuild(root.getLeftPosition(), 0); - - this.module.editor.executeEdits(replaceRange, newToken); - this.module.focus.updateContext({ positionToMove: focusPos }); - - break; - } - - case EditActionType.InsertChar: { - const cursorPos = this.module.editor.monaco.getPosition(); - const selectedText = this.module.editor.monaco.getSelection(); - const editableToken = this.module.focus.getTextEditableItem(context); - const editableText = editableToken.getEditableText(); - const token = editableToken.getToken(); - let newText = ""; - - if ((pressedKey == "{" || pressedKey == "}") && token.rootNode instanceof FormattedStringExpr) { - this.execute( - new EditAction(EditActionType.InsertFormattedStringItem, { source: { type: "autocomplete" } }) - ); - - break; - } - - if (token instanceof IdentifierTkn && token.isEmptyIdentifier()) { - const curText = ""; - newText = curText + pressedKey; - } else { - const curText = editableText.split(""); - curText.splice( - cursorPos.column - token.left, - Math.abs(selectedText.startColumn - selectedText.endColumn), - pressedKey - ); - - newText = curText.join(""); - } - - this.validateIdentifier(context, newText); - - let editRange: Range; - - if (selectedText.startColumn != selectedText.endColumn) { - editRange = new Range( - cursorPos.lineNumber, - selectedText.startColumn, - cursorPos.lineNumber, - selectedText.endColumn - ); - } else if ( - context.tokenToRight?.isTextEditable && - context.tokenToRight instanceof IdentifierTkn && - context.tokenToRight.isEmpty - ) { - editRange = new Range( - cursorPos.lineNumber, - context.tokenToRight.left, - cursorPos.lineNumber, - context.tokenToRight.right - ); - } else { - editRange = new Range( - cursorPos.lineNumber, - cursorPos.column, - cursorPos.lineNumber, - cursorPos.column - ); - } - - if (editableToken instanceof AutocompleteTkn) { - let match = editableToken.checkMatch(pressedKey); - - if (match) { - this.performMatchAction(match, editableToken); - - break; - } - - match = editableToken.isInsertableTerminatingMatch(pressedKey); - - if (match) { - this.performMatchAction(match, editableToken); - - this.execute(this.module.eventRouter.getKeyAction(e)); - - break; - } - } - - if (editableToken.setEditedText(newText)) this.module.editor.executeEdits(editRange, null, pressedKey); - - break; - } - - case EditActionType.DeleteStringLiteral: { - this.deleteCode(context.tokenToLeft.rootNode); - - break; - } - - case EditActionType.DeletePrevChar: - case EditActionType.DeleteNextChar: { - const cursorPos = this.module.editor.monaco.getPosition(); - const selectedText = this.module.editor.monaco.getSelection(); - const editableToken = this.module.focus.getTextEditableItem(context); - const token = editableToken.getToken(); - - let newText = ""; - - const curText = editableToken.getEditableText().split(""); - const toDeleteItems = - selectedText.startColumn == selectedText.endColumn - ? 1 - : Math.abs(selectedText.startColumn - selectedText.endColumn); - - const toDeletePos = action.type == EditActionType.DeleteNextChar ? 0 : 1; - - curText.splice( - Math.min( - cursorPos.column - token.left - toDeletePos, - selectedText.startColumn - token.left - toDeletePos - ), - toDeleteItems - ); - - newText = curText.join(""); - - this.validateIdentifier(context, newText); - - // check if it needs to turn back into a hole: - if (newText.length == 0) { - let removableExpr: CodeConstruct = null; - - if (context.expression instanceof LiteralValExpr) { - removableExpr = context.expression; - } else if (context.token instanceof AutocompleteTkn) { - removableExpr = context.token; - } else if (context.expressionToLeft instanceof LiteralValExpr) { - removableExpr = context.expressionToLeft; - } else if (context.tokenToLeft instanceof AutocompleteTkn) { - removableExpr = context.tokenToLeft; - } else if (context.expressionToRight instanceof LiteralValExpr) { - removableExpr = context.expressionToRight; - } else if (context.tokenToRight instanceof AutocompleteTkn) { - removableExpr = context.tokenToRight; - } - - if (removableExpr != null) { - if ( - removableExpr instanceof AutocompleteTkn && - removableExpr.rootNode instanceof TemporaryStmt - ) { - this.deleteCode(removableExpr.rootNode, { statement: true }); - } else if ( - removableExpr instanceof AutocompleteTkn && - removableExpr.autocompleteType == AutoCompleteType.AtEmptyOperatorHole - ) { - this.replaceCode( - removableExpr, - new EmptyOperatorTkn(" ", removableExpr.rootNode, removableExpr.indexInRoot) - ); - } else if ( - removableExpr instanceof AutocompleteTkn && - (removableExpr.autocompleteType == AutoCompleteType.RightOfExpression || - removableExpr.autocompleteType == AutoCompleteType.LeftOfExpression) - ) { - this.deleteAutocompleteToken(removableExpr); - } else this.deleteCode(removableExpr); - - break; - } - - let identifier: IdentifierTkn = null; - if (context.tokenToLeft instanceof IdentifierTkn) { - identifier = context.tokenToLeft; - } else if (context.tokenToRight instanceof IdentifierTkn) { - identifier = context.tokenToRight; - } else if (context.token instanceof IdentifierTkn) { - identifier = context.token; - } - - if (identifier != null) { - // reset identifier: - identifier.text = " "; - identifier.isEmpty = true; - - // change editor - this.module.editor.executeEdits( - new Range(cursorPos.lineNumber, identifier.left, cursorPos.lineNumber, identifier.right), - null, - " " - ); - - // rebuild ast - context.lineStatement.build(context.lineStatement.getLeftPosition()); - this.module.focus.updateContext({ tokenToSelect: identifier }); - - break; - } - } - - if (editableToken.setEditedText(newText)) { - let editRange = new Range( - cursorPos.lineNumber, - cursorPos.column, - cursorPos.lineNumber, - cursorPos.column - ); - - if (selectedText.startColumn != selectedText.endColumn) { - editRange = new Range( - cursorPos.lineNumber, - selectedText.startColumn, - cursorPos.lineNumber, - selectedText.endColumn - 1 - ); - } - - this.module.editor.executeEdits(editRange, null, ""); - preventDefaultEvent = false; - } - - break; - } - - case EditActionType.InsertAssignmentModifier: { - if (context.expressionToLeft.rootNode instanceof VarOperationStmt) { - const varOpStmt = context.expressionToLeft.rootNode; - - if ( - action.data.modifier instanceof AssignmentModifier && - context.expressionToLeft instanceof VariableReferenceExpr - ) { - if (context.expressionToLeft.rootNode.draftModeEnabled) { - this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); - } - const initialBoundary = this.getBoundaries(context.expressionToLeft); - - const varAssignStmt = new VarAssignmentStmt( - Docs.AddVarDocs.styles.backgroundColor, - "", - context.expressionToLeft.identifier, - varOpStmt.rootNode, - varOpStmt.indexInRoot - ); - - replaceInBody(varOpStmt.rootNode, varOpStmt.indexInRoot, varAssignStmt); - - this.module.editor.executeEdits(initialBoundary, varAssignStmt); - this.module.focus.updateContext(varAssignStmt.getInitialFocus()); - - if (flashGreen) this.flashGreen(varAssignStmt); - - this.setTokenColor(varAssignStmt, varAssignStmt.color); - } else { - if ( - context.expressionToLeft instanceof VariableReferenceExpr && - context.expressionToLeft.rootNode.draftModeEnabled - ) { - this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); - } - - varOpStmt.appendModifier(action.data.modifier); - varOpStmt.rebuild(varOpStmt.getLeftPosition(), 0); - - this.module.editor.insertAtCurPos([action.data.modifier]); - this.module.focus.updateContext(action.data.modifier.getInitialFocus()); - - if (flashGreen) this.flashGreen(action.data.modifier); - - // this.setTokenColor(action.data.modifier, action.data.modifier.color); - } - } - - eventData.code = action.data.modifier.getRenderText(); - - break; - } - - case EditActionType.InsertModifier: { - const modifier = action.data.modifier as Modifier; - - if (context.expressionToLeft instanceof Modifier) { - if (context.expressionToLeft.rootNode instanceof ValueOperationExpr) { - const valOprExpr = context.expressionToLeft.rootNode; - const valOprExprRoot = valOprExpr.rootNode as Statement; - - let replacementResult = valOprExpr.rootNode.checkInsertionAtHole( - valOprExpr.indexInRoot, - modifier.returns - ); - - const holeTypes = valOprExpr.rootNode.typeOfHoles[valOprExpr.indexInRoot]; - - if (replacementResult.insertionType !== InsertionType.Invalid) { - valOprExpr.appendModifier(modifier); - valOprExprRoot.rebuild(valOprExprRoot.getLeftPosition(), 0); - - this.module.editor.insertAtCurPos([modifier]); - this.module.focus.updateContext(modifier.getInitialFocus()); - - if (replacementResult.insertionType == InsertionType.DraftMode) - this.module.openDraftMode( - valOprExpr, - TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR( - valOprExpr.getKeyword(), - holeTypes, - valOprExpr.returns - ), - [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton( - valOprExpr.getKeyword(), - this.module, - valOprExpr - ); - }), - ] - ); - } - - if (valOprExpr.rootNode instanceof Statement) valOprExpr.rootNode.onInsertInto(valOprExpr); - } - } else if ( - context.expressionToLeft instanceof VariableReferenceExpr && - context.expressionToLeft.rootNode instanceof VarOperationStmt - ) { - if (context.expressionToLeft.rootNode.draftModeEnabled) { - this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); - } - const varOpStmt = context.expressionToLeft.rootNode; - - varOpStmt.appendModifier(modifier); - varOpStmt.rebuild(varOpStmt.getLeftPosition(), 0); - - this.module.editor.insertAtCurPos([modifier]); - this.module.focus.updateContext(modifier.getInitialFocus()); - - if (modifier instanceof MethodCallModifier && modifier.returns !== DataType.Void) { - //TODO: PropertyAccessModifier should also be included here once we have them - this.module.openDraftMode( - varOpStmt, - "This statement has no effect since the value it returns is not stored anywhere.", - [] - ); //TODO: Offer fixes? - } - } else { - const exprToLeftRoot = context.expressionToLeft.rootNode as Statement; - const exprToLeftIndexInRoot = context.expressionToLeft.indexInRoot; - - if (modifier instanceof ListAccessModifier) { - modifier.returns = TypeChecker.getElementTypeFromListType(context.expressionToLeft.returns); - - if (!modifier.returns) modifier.returns = DataType.Any; - } - - const replacementResult = exprToLeftRoot.checkInsertionAtHole( - context.expressionToLeft.indexInRoot, - modifier.returns - ); - const holeDataTypes = exprToLeftRoot.typeOfHoles[context.expressionToLeft.indexInRoot]; - - const valOprExpr = new ValueOperationExpr( - Docs.AddVarDocs.styles.backgroundColor, - context.expressionToLeft, - [modifier], - context.expressionToLeft.rootNode, - context.expressionToLeft.indexInRoot - ); - - if (valOprExpr.rootNode instanceof Statement) valOprExpr.rootNode.onInsertInto(valOprExpr); - - context.expressionToLeft.indexInRoot = 0; - context.expressionToLeft.rootNode = valOprExpr; - - if (replacementResult.insertionType !== InsertionType.Invalid) { - this.module.closeConstructDraftRecord(context.expressionToLeft); - - exprToLeftRoot.tokens[exprToLeftIndexInRoot] = valOprExpr; - exprToLeftRoot.rebuild(exprToLeftRoot.getLeftPosition(), 0); - - this.module.editor.insertAtCurPos([modifier]); - this.module.focus.updateContext(modifier.getInitialFocus()); - - if (replacementResult.insertionType == InsertionType.DraftMode) { - if (valOprExpr.returns === DataType.Any) { - this.module.openDraftMode( - valOprExpr, - TYPE_MISMATCH_ANY(holeDataTypes, valOprExpr.returns), - [ - new IgnoreConversionRecord( - "", - null, - null, - "", - null, - Tooltip.IgnoreWarning - ).getConversionButton("", this.module, valOprExpr), - ] - ); - } else { - this.module.openDraftMode( - valOprExpr, - TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR( - valOprExpr.getKeyword(), - holeDataTypes, - valOprExpr.returns - ), - [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton( - valOprExpr.getKeyword(), - this.module, - valOprExpr - ); - }), - ] - ); - } - } - } - } - - if (flashGreen) this.flashGreen(action.data.modifier); - - this.setTokenColor(action.data.modifier, action.data.modifier.color); - - eventData.code = action.data.modifier.getRenderText(); - - break; - } - - case EditActionType.InsertBinaryOperator: { - let binExpr: BinaryOperatorExpr; - - if (action.data.toRight) { - binExpr = this.replaceWithBinaryOp(action.data.operator, context.expressionToLeft, { - toLeft: true, - }); - } else if (action.data.toLeft) { - binExpr = this.replaceWithBinaryOp(action.data.operator, context.expressionToRight, { - toRight: true, - }); - } else if (action.data.replace) { - binExpr = new BinaryOperatorExpr( - Docs.AddDocs.styles.backgroundColor, - action.data.operator, - (context.token as TypedEmptyExpr).type[0] - ); - this.insertExpression(context, binExpr); - } - - if (flashGreen) this.flashGreen(binExpr); - - this.setTokenColor(binExpr, binExpr.color); - - eventData.code = action.data.operator; - - break; - } - - case EditActionType.WrapExpressionWithItem: { - // both lists and str work on any, so the first step of validation is always OK. - - const initialBoundary = this.getBoundaries(context.expressionToRight); - const expr = context.expressionToRight as Expression; - const indexInRoot = expr.indexInRoot; - const root = expr.rootNode as Statement; - - const newCode = action.data.expression as Expression; - newCode.indexInRoot = expr.indexInRoot; - newCode.rootNode = expr.rootNode; - - const isValidRootInsertion = - newCode.returns == DataType.Any || - root.typeOfHoles[indexInRoot].indexOf(newCode.returns) >= 0 || - root.typeOfHoles[indexInRoot] == DataType.Any; - - let replaceIndex: number = 0; - - for (const [i, token] of newCode.tokens.entries()) { - if (token instanceof TypedEmptyExpr) { - replaceIndex = i; - - break; - } - } - - if (isValidRootInsertion) { - this.module.closeConstructDraftRecord(root.tokens[indexInRoot]); - } - - newCode.tokens[replaceIndex] = context.expressionToRight; - context.expressionToRight.indexInRoot = replaceIndex; - context.expressionToRight.rootNode = newCode; - root.tokens[indexInRoot] = newCode; - root.rebuild(root.getLeftPosition(), 0); - this.module.editor.executeEdits(initialBoundary, newCode); - this.module.focus.updateContext({ - positionToMove: new Position(newCode.lineNumber, newCode.right), - }); - - if (newCode.rootNode instanceof BinaryOperatorExpr) { - newCode.rootNode.onInsertInto(newCode); - newCode.rootNode.validateTypes(this.module); - } else if (newCode.rootNode instanceof Statement) { - newCode.rootNode.onInsertInto(newCode); - } - - if (!isValidRootInsertion) { - this.module.closeConstructDraftRecord(expr); - this.module.openDraftMode(newCode, "DEBUG THIS", []); - } - - if (flashGreen) this.flashGreen(newCode); - - this.setTokenColor(newCode, newCode.color); - - eventData.code = action.data.expression.getRenderText(); - eventData.wrap = true; - - break; - } - - case EditActionType.ConvertAutocompleteToString: { - const autocompleteToken = action.data.token as AutocompleteTkn; - const literalValExpr = new LiteralValExpr( - Docs.StrDocs.styles.backgroundColor, - DataType.String, - autocompleteToken.text, - autocompleteToken.rootNode as Expression | Statement, - autocompleteToken.indexInRoot - ); - - autocompleteToken.draftModeEnabled = false; - this.deleteCode(autocompleteToken); - this.insertExpression(this.module.focus.getContext(), literalValExpr); - - eventData.code = "double-quote"; - eventData.wrap = true; - - break; - } - - case EditActionType.InsertEmptyList: { - const newLiteral = new ListLiteralExpression(Docs.ListLiteralDocs.styles.backgroundColor); - this.insertExpression(context, newLiteral); - - if (flashGreen) this.flashGreen(newLiteral); - - this.setTokenColor(newLiteral, newLiteral.color); - - eventData.code = "empty-list"; - - break; - } - - case EditActionType.InsertEmptyListItem: { - if (action.data.toRight) { - const code = [new NonEditableTkn(", "), new TypedEmptyExpr([DataType.Any])]; - this.insertEmptyListItem(context.tokenToRight, context.tokenToRight.indexInRoot, code); - this.module.editor.insertAtCurPos(code); - this.module.focus.updateContext({ tokenToSelect: code[1] }); - - if (flashGreen) this.flashGreen(code[1]); - } else if (action.data.toLeft) { - const code = [new TypedEmptyExpr([DataType.Any]), new NonEditableTkn(", ")]; - this.insertEmptyListItem(context.tokenToLeft, context.tokenToLeft.indexInRoot + 1, code); - this.module.editor.insertAtCurPos(code); - this.module.focus.updateContext({ tokenToSelect: code[0] }); - - if (flashGreen) this.flashGreen(code[0]); - } - eventData.code = "list-item-comma"; - - break; - } - - case EditActionType.DeleteListItem: { - if (action.data.toRight) { - const items = this.module.removeItems(context.token.rootNode, context.token.indexInRoot, 2); - this.module.editor.executeEdits(this.getCascadedBoundary(items), null, ""); - } else if (action.data.toLeft) { - const items = this.module.removeItems(context.token.rootNode, context.token.indexInRoot - 1, 2); - this.module.editor.executeEdits(this.getCascadedBoundary(items), null, ""); - } - - break; - } - - case EditActionType.DeleteRootNode: { - this.deleteCode(context.token.rootNode); - break; - } - - case EditActionType.ReplaceExpressionWithItem: { - const rootNode = context.token.rootNode as Expression; - let replacementTkn; - - for (let i = 0; i < rootNode.tokens.length; i++) { - if ( - !(rootNode.tokens[i] instanceof TypedEmptyExpr) && - !(rootNode.tokens[i] instanceof NonEditableTkn) && - !(rootNode.tokens[i] instanceof OperatorTkn) - ) { - replacementTkn = rootNode.tokens[i]; - } - } - - this.replaceCode(rootNode, replacementTkn); - - break; - } - - case EditActionType.InsertImportFromDraftMode: { - let currContext = context; - this.module.editor.monaco.setPosition(new Position(1, 1)); - this.module.editor.cursor.setSelection(null); - this.module.insertEmptyLine(); - this.module.editor.monaco.setPosition(new Position(1, 1)); - this.module.editor.cursor.setSelection(null); - currContext = this.module.focus.getContext(); - - const stmt = new ImportStatement( - Docs.ImportDocs.styles.backgroundColor, - action.data?.moduleName, - action.data?.itemName - ); - const insertAction = new EditCodeAction( - "from --- import --- :", - "add-import-btn", - () => stmt, - InsertActionType.InsertStatement, - {}, - null, - [" "], - "import", - null - ); - - insertAction.performAction(this, this.module.eventRouter, currContext, { type: "draft-mode" }); - eventData.code = stmt.getRenderText(); - - break; - } - case EditActionType.InsertMemberCallConversion: - case EditActionType.InsertMemberAccessConversion: { - const root = action.data.codeToReplace; - this.module.focus.updateContext( - new UpdatableContext(null, action.data.codeToReplace.getRightPosition()) - ); - this.execute( - new EditAction(EditActionType.InsertModifier, { - source: action?.data?.source, - modifier: Actions.instance() - .actionsList.find((element) => element.cssId == action.data.conversionConstructId) - .getCodeFunction() as Modifier, - }), - this.module.focus.getContext() - ); - - this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); - - this.setTokenColor(action.data.codeToReplace.rootNode, action.data.codeToReplace.rootNode.color); - - if (root instanceof Expression) root.validateTypes(this.module); - - eventData.code = action.data.codeToReplace.getRenderText(); - - break; - } - case EditActionType.InsertFunctionConversion: - case EditActionType.InsertTypeCast: - case EditActionType.InsertComparisonConversion: { - const root = action.data.codeToReplace; - this.deleteCode(action.data.codeToReplace, { - statement: null, - replaceType: action.data.typeToConvertTo, - }); - this.insertExpression( - this.module.focus.getContext(), - Actions.instance() - .actionsList.find((element) => element.cssId == action.data.conversionConstructId) - .getCodeFunction() as Expression - ); - action.data.codeToReplace.draftModeEnabled = false; - this.insertExpression(this.module.focus.getContext(), action.data.codeToReplace as Expression); - this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); - - this.setTokenColor(action.data.codeToReplace.rootNode, action.data.codeToReplace.rootNode.color); - - if (root instanceof Expression) root.validateTypes(this.module); - eventData.code = action.data.codeToReplace.getRenderText(); - - break; - } - - case EditActionType.SelectClosestTokenAbove: { - this.module.focus.navigateUp(); - - break; - } - - case EditActionType.SelectClosestTokenBelow: { - this.module.focus.navigateDown(); - - break; - } - - case EditActionType.MoveCursorLeft: - preventDefaultEvent = false; - - break; - - case EditActionType.MoveCursorRight: - preventDefaultEvent = false; - - break; - - case EditActionType.SelectLeft: - preventDefaultEvent = false; - break; - - case EditActionType.SelectRight: - preventDefaultEvent = false; - break; - - case EditActionType.SelectToStart: - preventDefaultEvent = false; - break; - - case EditActionType.SelectToEnd: - preventDefaultEvent = false; - break; - - case EditActionType.Copy: - preventDefaultEvent = false; - break; - - case EditActionType.InsertLiteral: { - let color: string; - if (action.data?.literalType == DataType.String) { - color = Docs.StrDocs.styles.backgroundColor; - } else if (action.data?.literalType == DataType.Number) { - color = Docs.NumDocs.styles.backgroundColor; - } else if (action.data?.literalType == DataType.Number) { - color = Docs.NumDocs.styles.backgroundColor; - } else if (action.data?.literalType == DataType.Boolean && action.data?.initialValue == "True") { - color = Docs.TrueDocs.styles.backgroundColor; - } else if (action.data?.literalType == DataType.Boolean && action.data?.initialValue == "False") { - color = Docs.FalseDocs.styles.backgroundColor; - } - const newLiteral = new LiteralValExpr(color, action.data?.literalType, action.data?.initialValue); - this.insertExpression(context, newLiteral); - - if (flashGreen) this.flashGreen(newLiteral); - - // this.setTokenColor(newLiteral, newLiteral.color); - - if (action.data?.source?.type === "keyboard") { - eventType = LogType.InsertCode; - eventData.source = "keyboard"; - eventData.code = `literal-${getUserFriendlyType(newLiteral.returns)}`; - } - - break; - } - - case EditActionType.OpenValidInsertMenu: - this.openAutocompleteMenu( - this.module.actionFilter - .getProcessedInsertionsList() - .filter((item) => item.insertionResult.insertionType != InsertionType.Invalid) - ); - this.styleAutocompleteMenu(context.position); - - break; - - //TODO: Remove later - case EditActionType.OpenValidInsertMenuSingleLevel: - if (!this.module.menuController.isMenuOpen()) { - //TODO: Make this work with ActionFilter - //const suggestions = this.module.getAllValidInsertsList(focusedNode); - //this.module.menuController.buildSingleLevelConstructCategoryMenu(suggestions); - } else this.module.menuController.removeMenus(); - - break; - - case EditActionType.SelectMenuSuggestionAbove: - this.module.menuController.focusOptionAbove(); - - break; - - case EditActionType.SelectMenuSuggestionBelow: - this.module.menuController.focusOptionBelow(); - - break; - - case EditActionType.SelectMenuSuggestion: - this.module.menuController.selectFocusedOption(); - - break; - - case EditActionType.CloseValidInsertMenu: - this.module.menuController.removeMenus(); - - break; - - case EditActionType.OpenSubMenu: - this.module.menuController.openSubMenu(); - - break; - - case EditActionType.CloseSubMenu: - this.module.menuController.closeSubMenu(); - - break; - - case EditActionType.CloseDraftMode: - this.deleteCode(action.data.codeNode); - - break; - - case EditActionType.None: { - preventDefaultEvent = true; - - break; - } - - case EditActionType.InsertOperatorTkn: { - this.replaceCode(context.tokenToLeft, action.data.operator); - - if (context.tokenToLeft.rootNode instanceof BinaryOperatorExpr) { - const root = context.tokenToLeft.rootNode; - root.operator = action.data.operator.operator; - root.operatorCategory = getOperatorCategory(root.operator); - - if (root.getLeftOperand() instanceof TypedEmptyExpr) { - root.updateTypeOfEmptyOperandOnOperatorChange("left"); - } - - if (root.getRightOperand() instanceof TypedEmptyExpr) { - root.updateTypeOfEmptyOperandOnOperatorChange("right"); - } - } - - if (flashGreen) this.flashGreen(action.data.operator); - - this.setTokenColor(action.data.operator, action.data.operator.color); - - eventData.code = action.data.operator.getRenderText(); - - break; - } - - case EditActionType.DeleteUnconvertibleOperandWarning: { - if (action.data.codeToDelete.draftModeEnabled) - this.module.closeConstructDraftRecord(action.data.codeToDelete); - this.deleteCode(action.data.codeToDelete); - - //TODO: Eventually this if statement should go as all constructs will have this method - if ( - action.data.rootExpression instanceof Expression || - action.data.rootExpression instanceof ListAccessModifier - ) - action.data.rootExpression.validateTypes(this.module); - - break; - } - } - - if (eventData && eventType) Logger.Instance().queueEvent(new LogEvent(eventType, eventData)); - - this.module.editor.monaco.focus(); - - return preventDefaultEvent; - } - - createVarReference(buttonId: string): VariableReferenceExpr { - const identifier = document.getElementById(buttonId).innerText; - const dataType = this.module.variableController.getVariableTypeNearLine( - this.module.focus.getFocusedStatement().scope ?? - ( - this.module.focus.getStatementAtLineNumber(this.module.editor.monaco.getPosition().lineNumber) - .rootNode as Statement | Module - ).scope, - this.module.editor.monaco.getPosition().lineNumber, - identifier - ); - - return new VariableReferenceExpr(identifier, dataType, buttonId); - } - - insertVariableReference(buttonId: string, source: {}, providedContext?: Context, autocompleteData?: {}) { - let context = providedContext ? providedContext : this.module.focus.getContext(); - - let { eventType, eventData } = this.getLogEventSource(source); - - if (this.module.validator.onBeginningOfLine(context)) { - const varRef = this.createVarReference(buttonId); - const stmt = new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, varRef); - this.replaceEmptyStatement(context.lineStatement, stmt); - - const availableActions = this.module.actionFilter - .getProcessedInsertionsList() - .filter( - (action) => - action.insertionResult.insertionType !== InsertionType.Invalid && - (action.insertActionType === InsertActionType.InsertAssignmentModifier || - action.insertActionType === InsertActionType.InsertAugmentedAssignmentModifier) - ); - - this.module.openDraftMode( - stmt, - "Variable references should not be used on empty lines. Try converting it to an assignment statement instead!", - (() => { - const buttons = []; - - for (const action of availableActions) { - const button = document.createElement("div"); - addClassToDraftModeResolutionButton(button, stmt); - - const text = `${varRef.identifier}${action.optionName}`.replace(/---/g, ""); - button.innerHTML = text; - - const modifier = action.getCode(); - button.addEventListener("click", () => { - this.module.closeConstructDraftRecord(stmt); - this.module.executer.execute( - new EditAction(EditActionType.InsertAssignmentModifier, { - codeToReplace: stmt, - replacementConstructCssId: action.cssId, - modifier: modifier, - source: { type: "draft-mode" }, - }), - this.module.focus.getContext() - ); - - const varOpStmt = modifier.rootNode as Statement; - - this.flashGreen(varOpStmt); - this.setTokenColor(varOpStmt, varOpStmt.color); - }); - - buttons.push(button); - } - - return buttons; - })() - ); - - if (autocompleteData) { - this.flashGreen(stmt); - - this.setTokenColor(stmt, stmt.color); - } - - eventData.code = varRef.getRenderText(); - } else if (this.module.validator.atEmptyExpressionHole(context)) { - const expr = this.createVarReference(buttonId); - this.insertExpression(context, expr); - - if (autocompleteData) { - this.flashGreen(expr); - - this.setTokenColor(expr, expr.color); - } - - eventData.code = expr.getRenderText(); - } - - if (eventData && eventType) Logger.Instance().queueEvent(new LogEvent(eventType, eventData)); - } - - getLogEventSource(source: any): { eventType: LogType; eventData: any } { - let eventType: LogType; - let eventData: any = null; - - if (source) { - eventData = {}; - - switch (source.type) { - case "toolbox": - eventType = LogType.InsertCode; - eventData.source = "toolbox"; - - break; - - case "autocomplete": - eventType = LogType.InsertCode; - eventData.source = "autocomplete"; - - break; - - case "autocomplete-menu": - eventType = LogType.InsertCode; - eventData = { - source: "autocomplete-menu", - precision: source.precision, - length: source.length, - }; - - break; - - case "defined-variables": - eventType = LogType.InsertCode; - eventData.source = "defined-vars-toolbox"; - - break; - - case "draft-mode": - eventType = LogType.InsertCode; - eventData.source = "draft-mode"; - - break; - } - } - - return { eventType, eventData }; - } - - deleteAutocompleteOnMatch(context: Context): Context { - let token: AutocompleteTkn; - - if (context.token instanceof AutocompleteTkn) token = context.token; - if (context.tokenToLeft instanceof AutocompleteTkn) token = context.tokenToLeft; - if (context.tokenToRight instanceof AutocompleteTkn) token = context.tokenToRight; - - if (token) { - switch (token.autocompleteType) { - case AutoCompleteType.RightOfExpression: - case AutoCompleteType.LeftOfExpression: - this.deleteAutocompleteToken(token); - - break; - - case AutoCompleteType.StartOfLine: - if (token.rootNode instanceof TemporaryStmt) { - this.deleteCode(token.rootNode, { - statement: true, - }); - } else { - this.deleteCode(token); - } - - break; - - case AutoCompleteType.AtExpressionHole: - this.deleteCode(token, {}); - - break; - } - } - - return this.module.focus.getContext(); - } - - private flashGreen(code: CodeConstruct) { - if (code) { - let highlight = new ConstructHighlight(this.module.editor, code, [109, 242, 162, 1]); - - setTimeout(() => { - if (highlight) { - highlight.changeHighlightColour([255, 255, 255, 0]); - - setTimeout(() => { - highlight.removeFromDOM(); - highlight = null; - }, 500); - } - }, 1); - } - } - - private setTokenColor(code: CodeConstruct, color: string) { - const aRgbHex = color.substring(1).match(/.{1,2}/g); - const aRgb = [parseInt(aRgbHex[0], 16), parseInt(aRgbHex[1], 16), parseInt(aRgbHex[2], 16)]; - - if (code) { - let highlight = new ConstructHighlight(this.module.editor, code, [aRgb[0], aRgb[1], aRgb[2], 1]); - } - } - - private insertEmptyListItem(focusedCode: CodeConstruct, index: number, items: Array) { - if (focusedCode instanceof Token || focusedCode instanceof Expression) { - const root = focusedCode.rootNode; - - if (root instanceof Statement && root.tokens.length > 0) { - root.tokens.splice(index, 0, ...items); - - for (let i = 0; i < root.tokens.length; i++) { - root.tokens[i].indexInRoot = i; - root.tokens[i].rootNode = root; - } - - root.rebuild(root.getLeftPosition(), 0); - } - } - } - - private performMatchAction(match: EditCodeAction, token: AutocompleteTkn) { - if ( - match.insertActionType == InsertActionType.InsertNewVariableStmt && - (Object.keys(PythonKeywords).indexOf(token.text.trim()) >= 0 || - Object.keys(BuiltInFunctions).indexOf(token.text.trim()) >= 0) - ) { - // TODO: can insert an interesting warning - return; - } - - let length = 0; - if (match.insertActionType == InsertActionType.InsertNewVariableStmt) length = token.text.length + 1; - else length = match.matchString.length + 1; - - match.performAction( - this, - this.module.eventRouter, - this.module.focus.getContext(), - { type: "autocomplete", precision: "1", length }, - { - identifier: token.text, - } - ); - } - - private insertToken(context: Context, code: Token, { toLeft = false, toRight = false } = {}) { - if (context.token instanceof TypedEmptyExpr || context.token instanceof EmptyOperatorTkn) { - if (context.expression != null) { - const root = context.expression.rootNode as Statement; - root.replace(code, context.expression.indexInRoot); - } else if (context.token != null) { - const root = context.token.rootNode as Statement; - root.replace(code, context.token.indexInRoot); - } - - const range = new Range( - context.position.lineNumber, - context.token.left, - context.position.lineNumber, - context.token.right - ); - - this.module.editor.executeEdits(range, code); - } else if (toRight && context.expressionToLeft != null) { - const root = context.expressionToLeft.rootNode; - code.rootNode = root; - root.tokens.splice(context.expressionToLeft.indexInRoot + 1, 0, code); - root.rebuild(root.getLeftPosition(), 0); - this.module.editor.insertAtCurPos([code]); - } else if (toLeft && context.expressionToRight != null) { - const root = context.expressionToRight.rootNode; - code.rootNode = root; - root.tokens.splice(context.expressionToRight.indexInRoot, 0, code); - root.rebuild(root.getLeftPosition(), 0); - this.module.editor.insertAtCurPos([code]); - } - } - - private insertExpression(context: Context, code: Expression) { - // type checks -- different handling based on type of code construct - // focusedNode.returns != code.returns would work, but we need more context to get the right error message - if (context.token instanceof TypedEmptyExpr) { - const root = context.token.rootNode; - let insertionResult = root.typeValidateInsertionIntoHole(code, context.token); - - if (insertionResult.insertionType != InsertionType.Invalid) { - if (root instanceof Statement) { - root.onInsertInto(code); - } - - if (context.token.message && context.selected) { - //TODO: This should only be closed if the current insertion would fix the current draft mode. Currently we don't know if that is the case. - this.module.messageController.removeMessageFromConstruct(context.token); - } - - // replaces expression with the newly inserted expression - const expr = code as Expression; - - this.module.replaceFocusedExpression(expr); - - const range = new Range( - context.position.lineNumber, - context.token.left, - context.position.lineNumber, - context.token.right - ); - - this.module.editor.executeEdits(range, expr); - - //TODO: This should probably run only if the insert above was successful, we cannot assume that it was - if (!context.token.message) { - const newContext = code.getInitialFocus(); - this.module.focus.updateContext(newContext); - } - } - - if (root instanceof BinaryOperatorExpr) { - root.validateTypes(this.module); - } else if (insertionResult.insertionType == InsertionType.DraftMode) { - this.module.openDraftMode(code, insertionResult.message, [ - ...insertionResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton(code.getKeyword(), this.module, code); - }), - ]); - } else if (isImportable(code)) { - //TODO: This needs to run regardless of what happens above. But for that we need nested draft modes. It should not be a case within the same if block - //The current problem is that a construct can only have a single draft mode on it. This is mostly ok since we often reinsert the construct when fixing a draft mode - //and the reinsertion triggers another draft mode if necessary. But this does not happen for importables because they are not reinserted on a fix so we might lose some - //draft modes this way. - - //A quick fix for now would be to just trigger reinsertion. Otherwise we need a mechanism for having multiple draft modes. I have a commit on a separate branch for that. - //Converting them to a linked list seems to make the most sense. - this.checkImports(code, insertionResult.insertionType); - } - } - } - - private checkImports(insertedCode: Importable, currentInsertionType: InsertionType) { - if (currentInsertionType === InsertionType.Invalid) return; - - const insertionType = insertedCode.validateImportOnInsertion(this.module, currentInsertionType); - if (insertionType === InsertionType.DraftMode && insertedCode instanceof Statement) { - this.module.openImportDraftMode(insertedCode); - } - } - - private openAutocompleteMenu(inserts: EditCodeAction[]) { - if (!this.module.menuController.isMenuOpen()) { - inserts = inserts.filter((insert) => insert.insertionResult.insertionType !== InsertionType.Invalid); - this.module.menuController.buildSingleLevelMenu(inserts); - } else this.module.menuController.removeMenus(); - } - - private replaceEmptyStatement(emptyLine: Statement, statement: Statement) { - const root = emptyLine.rootNode as Statement | Module; - - replaceInBody(root, emptyLine.indexInRoot, statement); - - if (root instanceof Statement) root.notify(CallbackType.replace); - - var range = new Range(emptyLine.lineNumber, statement.left, emptyLine.lineNumber, statement.right); - - if (emptyLine.message) this.module.messageController.removeMessageFromConstruct(emptyLine); - - if (isImportable(statement)) { - this.checkImports(statement, InsertionType.Valid); - } - - this.module.editor.executeEdits(range, statement); - this.module.focus.updateContext(statement.getInitialFocus()); - } - - private replaceWithBinaryOp( - op: BinaryOperator, - expr: Expression, - { toLeft = false, toRight = false } - ): BinaryOperatorExpr { - if (expr instanceof Modifier) expr = expr.rootNode as Expression; - - const initialBoundary = this.getBoundaries(expr); - const root = expr.rootNode as Statement; - const index = expr.indexInRoot; - - const newCode = new BinaryOperatorExpr( - Docs.AddDocs.styles.backgroundColor, - op, - expr.returns, // is not that important, will be replaced in the constructor based on the operator. - root, - expr.indexInRoot - ); - - const curOperand = toLeft ? newCode.getLeftOperand() : newCode.getRightOperand(); - const otherOperand = toLeft ? newCode.getRightOperand() : newCode.getLeftOperand(); - const insertionResult = newCode.typeValidateInsertionIntoHole(expr, curOperand as TypedEmptyExpr); - - /** - * Special cases - * - * if (--- + (--- + ---)|): --> attempting to insert a comparator or binary boolean operation should fail - */ - if (insertionResult.insertionType === InsertionType.Valid) { - const replacementResult = expr.canReplaceWithConstruct(newCode); - - // this can never go into draft mode - if (replacementResult.insertionType !== InsertionType.Invalid) { - if (root.tokens[index].draftModeEnabled) this.module.closeConstructDraftRecord(root.tokens[index]); - - if (toLeft) newCode.replaceLeftOperand(expr); - else newCode.replaceRightOperand(expr); - - expr.indexInRoot = curOperand.indexInRoot; - expr.rootNode = newCode; - - root.tokens[index] = newCode; - //TODO: Call onInsertInto() on this line - root.rebuild(root.getLeftPosition(), 0); - - this.module.editor.executeEdits(initialBoundary, newCode); - this.module.focus.updateContext({ - tokenToSelect: newCode.tokens[otherOperand.indexInRoot], - }); - - if (replacementResult.insertionType !== InsertionType.DraftMode && expr.draftModeEnabled) { - this.module.closeConstructDraftRecord(expr); - } else if (root instanceof BinaryOperatorExpr) { - root.validateTypes(this.module); - } else if (replacementResult.insertionType === InsertionType.DraftMode) { - this.module.openDraftMode(newCode, replacementResult.message, [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton(newCode.getRenderText(), this.module, newCode); - }), - ]); - } - - if (newCode.rootNode instanceof Statement) newCode.rootNode.onInsertInto(newCode); - - return newCode; - } - } - } - - private getCascadedBoundary(codes: Array): Range { - if (codes.length > 1) { - const lineNumber = codes[0].getLineNumber(); - - return new Range(lineNumber, codes[0].left, lineNumber, codes[codes.length - 1].right); - } else return this.getBoundaries(codes[0]); - } - - private getBoundaries(code: CodeConstruct, { selectIndent = false } = {}): Range { - const lineNumber = code.getLineNumber(); - - if (code instanceof Statement && code.hasBody()) { - const stmtStack = new Array(); - stmtStack.unshift(...code.body); - let endLineNumber = 0; - let endColumn = 0; - - while (stmtStack.length > 0) { - const curStmt = stmtStack.pop(); - - if (curStmt instanceof Statement && curStmt.hasBody()) stmtStack.unshift(...curStmt.body); - - if (endLineNumber < curStmt.lineNumber) { - endLineNumber = curStmt.lineNumber; - endColumn = curStmt.right; - } - } - - return new Range(lineNumber, code.left, endLineNumber, endColumn); - } else if (code instanceof Statement || code instanceof Token) { - if (selectIndent) { - return new Range(lineNumber, code.left - TAB_SPACES, lineNumber, code.right); - } else return new Range(lineNumber, code.left, lineNumber, code.right); - } - } - - private deleteModifier(mod: Modifier, { deleting = false } = {}) { - // TODO: this will be a prototype version of the code. needs to be cleaned and iterated on -> - // e.g. merge the operations for VarOperationStmt and ValueOperationExpr - - // TODO: if deleting, should not move cursor - const removeRange = this.getBoundaries(mod); - const rootOfExprToLeft = mod.rootNode; - - rootOfExprToLeft.tokens.splice(mod.indexInRoot, 1); - this.module.recursiveNotify(mod, CallbackType.delete); - - this.module.closeConstructDraftRecord(rootOfExprToLeft); - - let built = false; - let positionToMove: Position; - - if (rootOfExprToLeft.tokens.length == 1) { - // only a val or var-ref is remaining: - if (rootOfExprToLeft instanceof ValueOperationExpr) { - rootOfExprToLeft.updateReturnType(); - - let replacementResult = rootOfExprToLeft.rootNode.checkInsertionAtHole( - rootOfExprToLeft.indexInRoot, - rootOfExprToLeft.returns - ); - - if (replacementResult.insertionType == InsertionType.DraftMode) { - const ref = rootOfExprToLeft.getVarRef(); - if (ref instanceof VariableReferenceExpr) { - const line = this.module.focus.getContext().lineStatement; - - const varType = this.module.variableController.getVariableTypeNearLine( - line.rootNode instanceof Module ? this.module.scope : line.scope, - line.lineNumber, - ref.identifier, - false - ); - - let expectedTypes = rootOfExprToLeft.rootNode.typeOfHoles[rootOfExprToLeft.indexInRoot]; - const currentAllowedTypes = rootOfExprToLeft.rootNode.getCurrentAllowedTypesOfHole( - rootOfExprToLeft.indexInRoot, - false - ); - - if (currentAllowedTypes.length > 0) { - expectedTypes = currentAllowedTypes; - } - - this.module.openDraftMode( - rootOfExprToLeft, - TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR(ref.identifier, varType, expectedTypes), - [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton( - ref.identifier, - this.module, - rootOfExprToLeft - ); - }), - ] - ); - } else { - let expectedTypes = rootOfExprToLeft.rootNode.typeOfHoles[rootOfExprToLeft.indexInRoot]; - - const currentAllowedTypes = rootOfExprToLeft.rootNode.getCurrentAllowedTypesOfHole( - rootOfExprToLeft.indexInRoot, - false - ); - - if (currentAllowedTypes.length > 0) { - expectedTypes = currentAllowedTypes; - } - - this.module.openDraftMode( - ref, - TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR( - ref.getKeyword(), - ref.returns, - expectedTypes - ), - [ - ...replacementResult.conversionRecords.map((conversionRecord) => { - return conversionRecord.getConversionButton( - ref.getKeyword(), - this.module, - rootOfExprToLeft - ); - }), - ] - ); - } - } - const value = rootOfExprToLeft.tokens[0]; - rootOfExprToLeft.rootNode.tokens[rootOfExprToLeft.indexInRoot] = value; - value.rootNode = rootOfExprToLeft.rootNode; - value.indexInRoot = rootOfExprToLeft.indexInRoot; - - rootOfExprToLeft.rootNode.rebuild(rootOfExprToLeft.rootNode.getLeftPosition(), 0); - positionToMove = new Position(value.getLineNumber(), value.right); - built = true; - } - } - - if (!built) { - rootOfExprToLeft.rebuild(rootOfExprToLeft.getLeftPosition(), 0); - positionToMove = new Position(rootOfExprToLeft.getLineNumber(), rootOfExprToLeft.right); - } - - this.module.editor.executeEdits(removeRange, null, ""); - if (!deleting) { - this.module.focus.updateContext({ - positionToMove, - }); - } - } - - private deleteAutocompleteToken(token: Token) { - const range = this.getBoundaries(token); - const root = token.rootNode as Statement; - root.tokens.splice(token.indexInRoot, 1); - - root.rebuild(root.getLeftPosition(), 0); - token.notify(CallbackType.delete); - - this.module.editor.executeEdits(range, null, ""); - } - - private replaceCode(code: CodeConstruct, replace: CodeConstruct) { - const replacementRange = this.getBoundaries(code); - const root = code.rootNode; - - if (root instanceof Statement) { - root.tokens.splice(code.indexInRoot, 1, replace); - - this.module.recursiveNotify(code, CallbackType.delete); - - for (let i = 0; i < root.tokens.length; i++) { - root.tokens[i].indexInRoot = i; - root.tokens[i].rootNode = root; - } - - root.rebuild(root.getLeftPosition(), 0); - - if (replace instanceof Statement && !(replace instanceof LiteralValExpr)) { - this.setTokenColor(replace, replace.color); - } - - this.module.editor.executeEdits(replacementRange, replace); - - if (replace instanceof Token && replace.isEmpty) { - this.module.focus.updateContext({ tokenToSelect: replace }); - } else this.module.focus.updateContext({ positionToMove: replace.getRightPosition() }); - } - } - - private deleteCode(code: CodeConstruct, { statement = false, replaceType = null } = {}) { - const root = code.rootNode; - const replacementRange = this.getBoundaries(code); - let replacement: CodeConstruct; - - if (statement) replacement = this.module.removeStatement(code as Statement); - else replacement = this.module.replaceItemWTypedEmptyExpr(code, replaceType); - - this.module.editor.executeEdits(replacementRange, replacement); - this.module.focus.updateContext({ tokenToSelect: replacement }); - - if (root instanceof Expression) root.validateTypes(this.module); - } - - private validateIdentifier(context: Context, identifierText: string) { - let focusedNode = null; - - if (context.token && context.selected && context.token instanceof IdentifierTkn) { - focusedNode = context.token; - } else if (context.tokenToLeft && context.tokenToLeft instanceof IdentifierTkn) { - focusedNode = context.tokenToLeft; - } else if (context.tokenToRight && context.tokenToRight instanceof IdentifierTkn) { - focusedNode = context.tokenToRight; - } - - if ( - focusedNode instanceof IdentifierTkn || - context.tokenToLeft instanceof IdentifierTkn || - context.tokenToRight instanceof IdentifierTkn - ) { - if (Object.keys(PythonKeywords).indexOf(identifierText) > -1) { - this.module.messageController.addPopUpMessage( - focusedNode, - { identifier: identifierText }, - ErrorMessage.identifierIsKeyword - ); - } else if (Object.keys(BuiltInFunctions).indexOf(identifierText) > -1) { - this.module.messageController.addPopUpMessage( - focusedNode, - { identifier: identifierText }, - ErrorMessage.identifierIsBuiltInFunc - ); - } - } - } - - private updateAutocompleteMenu(autocompleteTkn: AutocompleteTkn) { - this.module.menuController.updateMenuOptions(autocompleteTkn.getEditableText()); - this.module.menuController.updatePosition( - this.module.menuController.getNewMenuPositionFromCode(autocompleteTkn) - ); - } - - private styleAutocompleteMenu(pos: Position) { - this.module.menuController.styleMenuOptions(); - this.module.menuController.updatePosition(this.module.menuController.getNewMenuPositionFromPosition(pos)); - } -} +import { Position, Range } from "monaco-editor"; + +import { ErrorMessage } from "../messages/error-msg-generator"; +import { ConstructHighlight, ScopeHighlight } from "../messages/messages"; +import { + AssignmentModifier, + AutocompleteTkn, + BinaryOperatorExpr, + CodeConstruct, + EditableTextTkn, + ElseStatement, + EmptyLineStmt, + EmptyOperatorTkn, + Expression, + FormattedStringCurlyBracketsExpr, + FormattedStringExpr, + IdentifierTkn, + IfStatement, + Importable, + ImportStatement, + ListAccessModifier, + ListLiteralExpression, + LiteralValExpr, + MethodCallModifier, + Modifier, + NonEditableTkn, + OperatorTkn, + Statement, + TemporaryStmt, + Token, + TypedEmptyExpr, + UnaryOperatorExpr, + ValueOperationExpr, + VarAssignmentStmt, + VariableReferenceExpr, + VarOperationStmt, +} from "../syntax-tree/ast"; +import { rebuildBody, replaceInBody } from "../syntax-tree/body"; +import { Callback, CallbackType } from "../syntax-tree/callback"; +import { + addClassToDraftModeResolutionButton, + AutoCompleteType, + BuiltInFunctions, + getOperatorCategory, + IgnoreConversionRecord, + PythonKeywords, + StringRegex, + TAB_SPACES, + Tooltip, + TYPE_MISMATCH_ANY, + TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR, + TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR, +} from "../syntax-tree/consts"; +import { Module } from "../syntax-tree/module"; +import { Reference } from "../syntax-tree/scope"; +import { TypeChecker } from "../syntax-tree/type-checker"; +import { getUserFriendlyType, isImportable } from "../utilities/util"; +import { LogEvent, Logger, LogType } from "./../logger/analytics"; +import { BinaryOperator, DataType, InsertionType } from "./../syntax-tree/consts"; +import { EditCodeAction } from "./action-filter"; +import { Actions, Docs, EditActionType, InsertActionType } from "./consts"; +import { EditAction } from "./data-types"; +import { Context, UpdatableContext } from "./focus"; + +export class ActionExecutor { + module: Module; + + constructor(module: Module) { + this.module = module; + } + + execute(action: EditAction, providedContext?: Context, e?: KeyboardEvent): boolean { + const pressedKey = e?.key; + let context = providedContext ? providedContext : this.module.focus.getContext(); + + let preventDefaultEvent = true; + let flashGreen = false; + + if (action?.data?.autocompleteData) flashGreen = true; + + let { eventType, eventData } = this.getLogEventSource(action?.data?.source); + + switch (action.type) { + case EditActionType.OpenAutocomplete: { + const autocompleteTkn = new AutocompleteTkn( + action.data.firstChar, + action.data.autocompleteType, + action.data.validMatches + ); + + autocompleteTkn.subscribe( + CallbackType.change, + new Callback( + (() => { + if (!this.module.menuController.isMenuOpen()) { + this.openAutocompleteMenu(action.data.validMatches); + } + + this.updateAutocompleteMenu(autocompleteTkn); + + if ( + this.module.menuController.hasNoSuggestions() && + autocompleteTkn.left != autocompleteTkn.getParentStatement().left && + !autocompleteTkn.message + ) { + const message = this.module.messageController.addHoverMessage( + autocompleteTkn, + {}, + `Did you want to create a text message? use double quotes before and after the text, like this: "your desired text"` + ); + + const button = message.createButton("convert to text"); + button.addEventListener("click", () => { + this.module.executer.execute( + new EditAction(EditActionType.ConvertAutocompleteToString, { + token: autocompleteTkn, + source: { type: "draft-mode" }, + }), + this.module.focus.getContext() + ); + }); + } + }).bind(this) + ) + ); + + this.openAutocompleteMenu(action.data.validMatches); + + switch (action.data.autocompleteType) { + case AutoCompleteType.StartOfLine: + this.replaceEmptyStatement(context.lineStatement, new TemporaryStmt(autocompleteTkn)); + + break; + + case AutoCompleteType.AtEmptyOperatorHole: + case AutoCompleteType.AtExpressionHole: + this.insertToken(context, autocompleteTkn); + + break; + + case AutoCompleteType.RightOfExpression: + this.insertToken(context, autocompleteTkn, { toRight: true }); + + break; + case AutoCompleteType.LeftOfExpression: + this.insertToken(context, autocompleteTkn, { toLeft: true }); + + break; + } + + this.module.editor.cursor.setSelection(null); + const match = autocompleteTkn.isTerminatingMatch(); + + if (match) { + this.performMatchAction(match, autocompleteTkn); + } else { + let highlight = new ConstructHighlight(this.module.editor, autocompleteTkn, [230, 235, 255, 0.7]); + + autocompleteTkn.subscribe( + CallbackType.delete, + new Callback(() => { + if (highlight) { + highlight.removeFromDOM(); + highlight = null; + } + }) + ); + } + + break; + } + + case EditActionType.InsertElseStatement: { + const newStatement = new ElseStatement(Docs.ElseDocs.styles.backgroundColor, action.data.hasCondition); + + if (action.data.outside) { + // when the else is being inserted outside + const elseRoot = context.lineStatement.rootNode as Module | Statement; + newStatement.rootNode = elseRoot; + newStatement.indexInRoot = context.lineStatement.indexInRoot; + newStatement.body.push(new EmptyLineStmt(newStatement, 0)); + + replaceInBody(elseRoot, newStatement.indexInRoot, newStatement); + rebuildBody(elseRoot, newStatement.indexInRoot, context.lineStatement.lineNumber); + this.module.editor.executeEdits(this.getBoundaries(context.lineStatement), newStatement); + } else { + // when being inserted inside + const curStmtRoot = context.lineStatement.rootNode as Statement; + const elseRoot = curStmtRoot.rootNode as Module | Statement; + newStatement.rootNode = elseRoot; + newStatement.indexInRoot = curStmtRoot.indexInRoot + 1; + + // indent back and place all of the code below it as its child + const toMoveStatements = curStmtRoot.body.splice( + context.lineStatement.indexInRoot, + curStmtRoot.body.length - context.lineStatement.indexInRoot + ); + + // remove the empty line statement + toMoveStatements.splice(0, 1)[0]; + + if (toMoveStatements.length == 0) newStatement.body.push(new EmptyLineStmt(newStatement, 0)); + const providedLeftPos = new Position( + context.lineStatement.lineNumber, + context.lineStatement.left - TAB_SPACES + ); + newStatement.build(providedLeftPos); + + this.module.editor.executeEdits( + this.getBoundaries(context.lineStatement, { selectIndent: true }), + newStatement + ); + + const topReferences = new Array(); + const bottomReferences = new Array(); + + for (const ref of curStmtRoot.scope.references) { + if (ref.statement.indexInRoot > context.lineStatement.indexInRoot) { + bottomReferences.push(ref); + } else topReferences.push(ref); + } + + if (bottomReferences.length > 0) { + curStmtRoot.scope.references = topReferences; + newStatement.scope.references = bottomReferences; + } + + for (const [i, stmt] of toMoveStatements.entries()) { + stmt.rootNode = newStatement; + stmt.indexInRoot = i; + newStatement.body.push(stmt); + } + + newStatement.init(providedLeftPos); + newStatement.rootNode = elseRoot; + newStatement.indexInRoot = newStatement.indexInRoot; + this.module.addStatementToBody( + elseRoot, + newStatement, + newStatement.indexInRoot, + providedLeftPos.lineNumber + ); + } + + if (flashGreen) this.flashGreen(newStatement); + + let scopeHighlight = new ScopeHighlight(this.module.editor, newStatement, newStatement.color); + eventData.code = "else-statement"; + + break; + } + + case EditActionType.InsertExpression: { + const expression = action.data?.expression as Expression; + + this.insertExpression(context, action.data?.expression); + + if (flashGreen) this.flashGreen(action.data?.expression); + + this.setTokenColor(action.data?.expression, expression.color); + + eventData.code = action.data?.expression?.getRenderText(); + + break; + } + + case EditActionType.InsertStatement: { + const statement = action.data?.statement as Statement; + + this.replaceEmptyStatement(context.lineStatement, statement); + + if (flashGreen) this.flashGreen(action.data?.statement); + + if (statement.hasBody()) { + let scopeHighlight = new ScopeHighlight(this.module.editor, statement, statement.color); + } else { + this.setTokenColor(action.data?.statement, statement.color); + } + + eventData.code = action.data?.statement?.getRenderText(); + + break; + } + + case EditActionType.InsertVarAssignStatement: { + //TODO: Might want to change back to use the case above if no new logic is added + const statement = action.data?.statement; + + const id = action.data?.autocompleteData?.identifier?.trim(); + + if (statement instanceof VarAssignmentStmt && id) statement.setIdentifier(id); + + this.replaceEmptyStatement(context.lineStatement, action.data?.statement as Statement); + + if (flashGreen) this.flashGreen(action.data?.statement); + + this.setTokenColor(action.data?.statement, statement.color); + + eventData.code = "var-assignment"; + eventData.id = id; + + break; + } + + case EditActionType.InsertUnaryOperator: { + if (action.data?.replace) { + this.insertExpression( + context, + new UnaryOperatorExpr( + Docs.NotDocs.styles.backgroundColor, + action.data.operator, + (context.token as TypedEmptyExpr).type[0] + ) + ); + } else if (action.data?.wrap) { + const expr = context.expressionToRight as Expression; + + const initialBoundary = this.getBoundaries(expr); + const root = expr.rootNode as Statement; + + const newCode = new UnaryOperatorExpr( + Docs.NotDocs.styles.backgroundColor, + action.data.operator, + expr.returns, + expr.returns, + expr.rootNode, + expr.indexInRoot + ); + + newCode.setOperand(expr); + root.tokens[newCode.indexInRoot] = newCode; + root.rebuild(root.getLeftPosition(), 0); + + this.module.editor.executeEdits(initialBoundary, newCode); + this.module.focus.updateContext({ + positionToMove: newCode.tokens[1].getLeftPosition(), + }); + } + + eventData.code = action.data.operator; + + break; + } + + case EditActionType.DeleteNextToken: { + if (context.expressionToRight instanceof OperatorTkn) { + this.replaceCode( + context.expressionToRight, + new EmptyOperatorTkn(" ", context.expressionToRight, context.expressionToRight.indexInRoot) + ); + } else if (this.module.validator.atBeginningOfValOperation(context)) { + this.deleteCode(context.expressionToRight.rootNode); + } else if (context.expressionToRight instanceof Modifier) { + this.deleteModifier(context.expressionToRight, { deleting: true }); + } else this.deleteCode(context.expressionToRight); + + break; + } + + case EditActionType.DeletePrevToken: { + if (context.expressionToLeft instanceof OperatorTkn) { + this.replaceCode( + context.expressionToLeft, + new EmptyOperatorTkn(" ", context.expressionToLeft, context.expressionToLeft.indexInRoot) + ); + } else if ( + context.expressionToLeft instanceof VariableReferenceExpr && + context.expressionToLeft.rootNode instanceof VarOperationStmt + ) { + this.deleteCode(context.expressionToLeft.rootNode, { statement: true }); + } else if (context.expressionToLeft instanceof Modifier) this.deleteModifier(context.expressionToLeft); + else this.deleteCode(context.expressionToLeft); + + break; + } + + case EditActionType.DeleteStatement: { + this.deleteCode(context.lineStatement, { statement: true }); + + break; + } + + case EditActionType.DeleteMultiLineStatement: { + if ( + context.lineStatement instanceof IfStatement || + (context.lineStatement instanceof ElseStatement && context.lineStatement.hasCondition) + ) { + const elseStatementsAfterIf = []; + + for ( + let i = context.lineStatement.indexInRoot + 1; + i < context.lineStatement.rootNode.body.length; + i++ + ) { + const line = context.lineStatement.rootNode.body[i]; + + if (line instanceof ElseStatement) elseStatementsAfterIf.push(line); + else break; + } + + for (const elseStmt of elseStatementsAfterIf) { + this.module.messageController.addHoverMessage( + elseStmt, + null, + "add if before the first else, or delete this." + ); + } + } + + while (context.lineStatement.body.length > 0) { + this.module.editor.indentRecursively( + context.lineStatement.body[context.lineStatement.body.length - 1], + { backward: true } + ); + this.module.indentBackStatement(context.lineStatement.body[context.lineStatement.body.length - 1]); + } + + this.deleteCode(context.lineStatement, { statement: true }); + + break; + } + + case EditActionType.DeleteCurLine: { + this.module.deleteLine(context.lineStatement); + let range: Range; + + if (action.data?.pressedBackspace) { + const lineAbove = this.module.focus.getStatementAtLineNumber(context.lineStatement.lineNumber - 1); + this.module.focus.updateContext({ + positionToMove: new Position(lineAbove.lineNumber, lineAbove.right), + }); + range = new Range( + context.lineStatement.lineNumber, + context.lineStatement.left, + lineAbove.lineNumber, + lineAbove.right + ); + } else { + range = new Range( + context.lineStatement.lineNumber, + context.lineStatement.left, + context.lineStatement.lineNumber + 1, + context.lineStatement.left + ); + } + + this.module.editor.executeEdits(range, null, ""); + + break; + } + + case EditActionType.DeleteSelectedModifier: { + this.deleteModifier(context.token.rootNode as Modifier, { deleting: true }); + + break; + } + + case EditActionType.DeletePrevLine: { + const prevLine = this.module.focus.getStatementAtLineNumber(context.lineStatement.lineNumber - 1); + + if (prevLine.left != context.lineStatement.left) { + this.module.editor.indentRecursively(context.lineStatement, { backward: false }); + this.module.indentForwardStatement(context.lineStatement); + } + + const deleteRange = new Range( + prevLine.lineNumber, + prevLine.left, + prevLine.lineNumber + 1, + prevLine.left + ); + this.module.deleteLine(prevLine); + this.module.editor.executeEdits(deleteRange, null, ""); + + break; + } + + case EditActionType.IndentBackwardsIfStmt: { + const root = context.lineStatement.rootNode as Statement | Module; + + const toIndentStatements = new Array(); + + for (let i = context.lineStatement.indexInRoot; i < root.body.length; i++) { + toIndentStatements.push(root.body[i]); + } + + for (const stmt of toIndentStatements.reverse()) { + this.module.editor.indentRecursively(stmt, { backward: true }); + this.module.indentBackStatement(stmt); + } + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.DeleteBackMultiLines: { + for ( + let i = context.lineStatement.rootNode.body.length - 1; + i >= context.lineStatement.indexInRoot; + i-- + ) { + this.module.editor.indentRecursively(context.lineStatement.rootNode.body[i], { backward: true }); + this.module.indentBackStatement(context.lineStatement.rootNode.body[i]); + } + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.IndentBackwards: { + this.module.editor.indentRecursively(context.lineStatement, { backward: true }); + this.module.indentBackStatement(context.lineStatement); + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.IndentForwardsIfStmt: { + const root = context.lineStatement.rootNode as Statement | Module; + + const toIndentStatements = new Array(); + + for (let i = context.lineStatement.indexInRoot; i < root.body.length; i++) { + toIndentStatements.push(root.body[i]); + + if (i + 1 < root.body.length && !(root.body[i + 1] instanceof ElseStatement)) break; + } + + for (const stmt of toIndentStatements) { + this.module.editor.indentRecursively(stmt, { backward: false }); + this.module.indentForwardStatement(stmt); + } + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.IndentForwards: { + this.module.editor.indentRecursively(context.lineStatement, { backward: false }); + this.module.indentForwardStatement(context.lineStatement); + + this.module.focus.fireOnNavChangeCallbacks(); + + break; + } + + case EditActionType.InsertEmptyLine: { + const newEmptyLine = this.module.insertEmptyLine(); + this.module.focus.fireOnNavOffCallbacks(context.lineStatement, newEmptyLine); + + break; + } + + case EditActionType.SelectPrevToken: { + this.module.focus.navigateLeft(); + + break; + } + + case EditActionType.SelectNextToken: { + this.module.focus.navigateRight(); + + break; + } + + case EditActionType.InsertFormattedStringItem: { + const cursorPos = this.module.editor.monaco.getPosition(); + const selectedText = this.module.editor.monaco.getSelection(); + const editableToken = this.module.focus.getTextEditableItem(context); + const token = editableToken.getToken(); + const formattedStringExpr = token.rootNode as FormattedStringExpr; + + const leftText = token.text.substring(0, cursorPos.column - token.left); + const rightText = token.text.substring(cursorPos.column - token.left, token.right); + + const leftToken = token; + leftToken.text = leftText; + const rightToken = new EditableTextTkn("", StringRegex, formattedStringExpr, token.indexInRoot + 1); + rightToken.text = rightText; + + formattedStringExpr.tokens.splice(token.indexInRoot + 1, 0, ...[rightToken]); + + formattedStringExpr.rebuild(formattedStringExpr.getLeftPosition(), 0); + + let rightTokenRange = new Range( + rightToken.getLineNumber(), + rightToken.left, + rightToken.getLineNumber(), + rightToken.right + ); + + this.module.editor.executeEdits(rightTokenRange, rightToken); + + const fStringToken = new FormattedStringCurlyBracketsExpr( + Docs.NotDocs.styles.backgroundColor, + formattedStringExpr, + token.indexInRoot + 1 + ); + + formattedStringExpr.tokens.splice(token.indexInRoot + 1, 0, ...[fStringToken]); + + formattedStringExpr.rebuild(formattedStringExpr.getLeftPosition(), 0); + + let editRange: Range; + + if (selectedText.startColumn != selectedText.endColumn) { + editRange = new Range( + cursorPos.lineNumber, + selectedText.startColumn, + cursorPos.lineNumber, + selectedText.endColumn + ); + } else { + editRange = new Range( + cursorPos.lineNumber, + cursorPos.column, + cursorPos.lineNumber, + cursorPos.column + ); + } + + this.module.editor.executeEdits(editRange, fStringToken); + this.module.focus.updateContext({ tokenToSelect: fStringToken.tokens[1] }); + eventData.code = "f-string-item"; + + break; + } + + case EditActionType.DeleteFStringCurlyBrackets: { + const fStringToRemove = action.data.item as FormattedStringCurlyBracketsExpr; + + const root = fStringToRemove.rootNode; + + const tokenBefore = root.tokens[fStringToRemove.indexInRoot - 1] as EditableTextTkn; + const tokenAfter = root.tokens[fStringToRemove.indexInRoot + 1] as EditableTextTkn; + + const indexToReplace = tokenBefore.indexInRoot; + + const newToken = new EditableTextTkn( + tokenBefore.text + tokenAfter.text, + StringRegex, + root, + fStringToRemove.indexInRoot - 1 + ); + + const focusPos = new Position(tokenBefore.getLineNumber(), tokenBefore.right); + + const replaceRange = new Range( + tokenAfter.getLineNumber(), + tokenAfter.right, + tokenBefore.getLineNumber(), + tokenBefore.left + ); + + this.module.removeItem(fStringToRemove); + this.module.removeItem(tokenAfter); + this.module.removeItem(tokenBefore); + + root.tokens.splice(indexToReplace, 0, newToken); + + root.rebuild(root.getLeftPosition(), 0); + + this.module.editor.executeEdits(replaceRange, newToken); + this.module.focus.updateContext({ positionToMove: focusPos }); + + break; + } + + case EditActionType.InsertChar: { + const cursorPos = this.module.editor.monaco.getPosition(); + const selectedText = this.module.editor.monaco.getSelection(); + const editableToken = this.module.focus.getTextEditableItem(context); + const editableText = editableToken.getEditableText(); + const token = editableToken.getToken(); + let newText = ""; + + if ((pressedKey == "{" || pressedKey == "}") && token.rootNode instanceof FormattedStringExpr) { + this.execute( + new EditAction(EditActionType.InsertFormattedStringItem, { source: { type: "autocomplete" } }) + ); + + break; + } + + if (token instanceof IdentifierTkn && token.isEmptyIdentifier()) { + const curText = ""; + newText = curText + pressedKey; + } else { + const curText = editableText.split(""); + curText.splice( + cursorPos.column - token.left, + Math.abs(selectedText.startColumn - selectedText.endColumn), + pressedKey + ); + + newText = curText.join(""); + } + + this.validateIdentifier(context, newText); + + let editRange: Range; + + if (selectedText.startColumn != selectedText.endColumn) { + editRange = new Range( + cursorPos.lineNumber, + selectedText.startColumn, + cursorPos.lineNumber, + selectedText.endColumn + ); + } else if ( + context.tokenToRight?.isTextEditable && + context.tokenToRight instanceof IdentifierTkn && + context.tokenToRight.isEmpty + ) { + editRange = new Range( + cursorPos.lineNumber, + context.tokenToRight.left, + cursorPos.lineNumber, + context.tokenToRight.right + ); + } else { + editRange = new Range( + cursorPos.lineNumber, + cursorPos.column, + cursorPos.lineNumber, + cursorPos.column + ); + } + + if (editableToken instanceof AutocompleteTkn) { + let match = editableToken.checkMatch(pressedKey); + + if (match) { + this.performMatchAction(match, editableToken); + + break; + } + + match = editableToken.isInsertableTerminatingMatch(pressedKey); + + if (match) { + this.performMatchAction(match, editableToken); + + this.execute(this.module.eventRouter.getKeyAction(e)); + + break; + } + } + + if (editableToken.setEditedText(newText)) this.module.editor.executeEdits(editRange, null, pressedKey); + + break; + } + + case EditActionType.DeleteStringLiteral: { + this.deleteCode(context.tokenToLeft.rootNode); + + break; + } + + case EditActionType.DeletePrevChar: + case EditActionType.DeleteNextChar: { + const cursorPos = this.module.editor.monaco.getPosition(); + const selectedText = this.module.editor.monaco.getSelection(); + const editableToken = this.module.focus.getTextEditableItem(context); + const token = editableToken.getToken(); + + let newText = ""; + + const curText = editableToken.getEditableText().split(""); + const toDeleteItems = + selectedText.startColumn == selectedText.endColumn + ? 1 + : Math.abs(selectedText.startColumn - selectedText.endColumn); + + const toDeletePos = action.type == EditActionType.DeleteNextChar ? 0 : 1; + + curText.splice( + Math.min( + cursorPos.column - token.left - toDeletePos, + selectedText.startColumn - token.left - toDeletePos + ), + toDeleteItems + ); + + newText = curText.join(""); + + this.validateIdentifier(context, newText); + + // check if it needs to turn back into a hole: + if (newText.length == 0) { + let removableExpr: CodeConstruct = null; + + if (context.expression instanceof LiteralValExpr) { + removableExpr = context.expression; + } else if (context.token instanceof AutocompleteTkn) { + removableExpr = context.token; + } else if (context.expressionToLeft instanceof LiteralValExpr) { + removableExpr = context.expressionToLeft; + } else if (context.tokenToLeft instanceof AutocompleteTkn) { + removableExpr = context.tokenToLeft; + } else if (context.expressionToRight instanceof LiteralValExpr) { + removableExpr = context.expressionToRight; + } else if (context.tokenToRight instanceof AutocompleteTkn) { + removableExpr = context.tokenToRight; + } + + if (removableExpr != null) { + if ( + removableExpr instanceof AutocompleteTkn && + removableExpr.rootNode instanceof TemporaryStmt + ) { + this.deleteCode(removableExpr.rootNode, { statement: true }); + } else if ( + removableExpr instanceof AutocompleteTkn && + removableExpr.autocompleteType == AutoCompleteType.AtEmptyOperatorHole + ) { + this.replaceCode( + removableExpr, + new EmptyOperatorTkn(" ", removableExpr.rootNode, removableExpr.indexInRoot) + ); + } else if ( + removableExpr instanceof AutocompleteTkn && + (removableExpr.autocompleteType == AutoCompleteType.RightOfExpression || + removableExpr.autocompleteType == AutoCompleteType.LeftOfExpression) + ) { + this.deleteAutocompleteToken(removableExpr); + } else this.deleteCode(removableExpr); + + break; + } + + let identifier: IdentifierTkn = null; + if (context.tokenToLeft instanceof IdentifierTkn) { + identifier = context.tokenToLeft; + } else if (context.tokenToRight instanceof IdentifierTkn) { + identifier = context.tokenToRight; + } else if (context.token instanceof IdentifierTkn) { + identifier = context.token; + } + + if (identifier != null) { + // reset identifier: + identifier.text = " "; + identifier.isEmpty = true; + + // change editor + this.module.editor.executeEdits( + new Range(cursorPos.lineNumber, identifier.left, cursorPos.lineNumber, identifier.right), + null, + " " + ); + + // rebuild ast + context.lineStatement.build(context.lineStatement.getLeftPosition()); + this.module.focus.updateContext({ tokenToSelect: identifier }); + + break; + } + } + + if (editableToken.setEditedText(newText)) { + let editRange = new Range( + cursorPos.lineNumber, + cursorPos.column, + cursorPos.lineNumber, + cursorPos.column + ); + + if (selectedText.startColumn != selectedText.endColumn) { + editRange = new Range( + cursorPos.lineNumber, + selectedText.startColumn, + cursorPos.lineNumber, + selectedText.endColumn - 1 + ); + } + + this.module.editor.executeEdits(editRange, null, ""); + preventDefaultEvent = false; + } + + break; + } + + case EditActionType.InsertAssignmentModifier: { + if (context.expressionToLeft.rootNode instanceof VarOperationStmt) { + const varOpStmt = context.expressionToLeft.rootNode; + + if ( + action.data.modifier instanceof AssignmentModifier && + context.expressionToLeft instanceof VariableReferenceExpr + ) { + if (context.expressionToLeft.rootNode.draftModeEnabled) { + this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); + } + const initialBoundary = this.getBoundaries(context.expressionToLeft); + + const varAssignStmt = new VarAssignmentStmt( + Docs.AddVarDocs.styles.backgroundColor, + "", + context.expressionToLeft.identifier, + varOpStmt.rootNode, + varOpStmt.indexInRoot + ); + + replaceInBody(varOpStmt.rootNode, varOpStmt.indexInRoot, varAssignStmt); + + this.module.editor.executeEdits(initialBoundary, varAssignStmt); + this.module.focus.updateContext(varAssignStmt.getInitialFocus()); + + if (flashGreen) this.flashGreen(varAssignStmt); + + this.setTokenColor(varAssignStmt, varAssignStmt.color); + } else { + if ( + context.expressionToLeft instanceof VariableReferenceExpr && + context.expressionToLeft.rootNode.draftModeEnabled + ) { + this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); + } + + varOpStmt.appendModifier(action.data.modifier); + varOpStmt.rebuild(varOpStmt.getLeftPosition(), 0); + + this.module.editor.insertAtCurPos([action.data.modifier]); + this.module.focus.updateContext(action.data.modifier.getInitialFocus()); + + if (flashGreen) this.flashGreen(action.data.modifier); + + // this.setTokenColor(action.data.modifier, action.data.modifier.color); + } + } + + eventData.code = action.data.modifier.getRenderText(); + + break; + } + + case EditActionType.InsertModifier: { + const modifier = action.data.modifier as Modifier; + + if (context.expressionToLeft instanceof Modifier) { + if (context.expressionToLeft.rootNode instanceof ValueOperationExpr) { + const valOprExpr = context.expressionToLeft.rootNode; + const valOprExprRoot = valOprExpr.rootNode as Statement; + + let replacementResult = valOprExpr.rootNode.checkInsertionAtHole( + valOprExpr.indexInRoot, + modifier.returns + ); + + const holeTypes = valOprExpr.rootNode.typeOfHoles[valOprExpr.indexInRoot]; + + if (replacementResult.insertionType !== InsertionType.Invalid) { + valOprExpr.appendModifier(modifier); + valOprExprRoot.rebuild(valOprExprRoot.getLeftPosition(), 0); + + this.module.editor.insertAtCurPos([modifier]); + this.module.focus.updateContext(modifier.getInitialFocus()); + + if (replacementResult.insertionType == InsertionType.DraftMode) + this.module.openDraftMode( + valOprExpr, + TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR( + valOprExpr.getKeyword(), + holeTypes, + valOprExpr.returns + ), + [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton( + valOprExpr.getKeyword(), + this.module, + valOprExpr + ); + }), + ] + ); + } + + if (valOprExpr.rootNode instanceof Statement) valOprExpr.rootNode.onInsertInto(valOprExpr); + } + } else if ( + context.expressionToLeft instanceof VariableReferenceExpr && + context.expressionToLeft.rootNode instanceof VarOperationStmt + ) { + if (context.expressionToLeft.rootNode.draftModeEnabled) { + this.module.closeConstructDraftRecord(context.expressionToLeft.rootNode); + } + const varOpStmt = context.expressionToLeft.rootNode; + + varOpStmt.appendModifier(modifier); + varOpStmt.rebuild(varOpStmt.getLeftPosition(), 0); + + this.module.editor.insertAtCurPos([modifier]); + this.module.focus.updateContext(modifier.getInitialFocus()); + + if (modifier instanceof MethodCallModifier && modifier.returns !== DataType.Void) { + //TODO: PropertyAccessModifier should also be included here once we have them + this.module.openDraftMode( + varOpStmt, + "This statement has no effect since the value it returns is not stored anywhere.", + [] + ); //TODO: Offer fixes? + } + } else { + const exprToLeftRoot = context.expressionToLeft.rootNode as Statement; + const exprToLeftIndexInRoot = context.expressionToLeft.indexInRoot; + + if (modifier instanceof ListAccessModifier) { + modifier.returns = TypeChecker.getElementTypeFromListType(context.expressionToLeft.returns); + + if (!modifier.returns) modifier.returns = DataType.Any; + } + + const replacementResult = exprToLeftRoot.checkInsertionAtHole( + context.expressionToLeft.indexInRoot, + modifier.returns + ); + const holeDataTypes = exprToLeftRoot.typeOfHoles[context.expressionToLeft.indexInRoot]; + + const valOprExpr = new ValueOperationExpr( + Docs.AddVarDocs.styles.backgroundColor, + context.expressionToLeft, + [modifier], + context.expressionToLeft.rootNode, + context.expressionToLeft.indexInRoot + ); + + if (valOprExpr.rootNode instanceof Statement) valOprExpr.rootNode.onInsertInto(valOprExpr); + + context.expressionToLeft.indexInRoot = 0; + context.expressionToLeft.rootNode = valOprExpr; + + if (replacementResult.insertionType !== InsertionType.Invalid) { + this.module.closeConstructDraftRecord(context.expressionToLeft); + + exprToLeftRoot.tokens[exprToLeftIndexInRoot] = valOprExpr; + exprToLeftRoot.rebuild(exprToLeftRoot.getLeftPosition(), 0); + + this.module.editor.insertAtCurPos([modifier]); + this.module.focus.updateContext(modifier.getInitialFocus()); + + if (replacementResult.insertionType == InsertionType.DraftMode) { + if (valOprExpr.returns === DataType.Any) { + this.module.openDraftMode( + valOprExpr, + TYPE_MISMATCH_ANY(holeDataTypes, valOprExpr.returns), + [ + new IgnoreConversionRecord( + "", + null, + null, + "", + null, + Tooltip.IgnoreWarning + ).getConversionButton("", this.module, valOprExpr), + ] + ); + } else { + this.module.openDraftMode( + valOprExpr, + TYPE_MISMATCH_ON_FUNC_ARG_DRAFT_MODE_STR( + valOprExpr.getKeyword(), + holeDataTypes, + valOprExpr.returns + ), + [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton( + valOprExpr.getKeyword(), + this.module, + valOprExpr + ); + }), + ] + ); + } + } + } + } + + if (flashGreen) this.flashGreen(action.data.modifier); + + this.setTokenColor(action.data.modifier, action.data.modifier.color); + + eventData.code = action.data.modifier.getRenderText(); + + break; + } + + case EditActionType.InsertBinaryOperator: { + let binExpr: BinaryOperatorExpr; + + if (action.data.toRight) { + binExpr = this.replaceWithBinaryOp(action.data.operator, context.expressionToLeft, { + toLeft: true, + }); + } else if (action.data.toLeft) { + binExpr = this.replaceWithBinaryOp(action.data.operator, context.expressionToRight, { + toRight: true, + }); + } else if (action.data.replace) { + binExpr = new BinaryOperatorExpr( + Docs.AddDocs.styles.backgroundColor, + action.data.operator, + (context.token as TypedEmptyExpr).type[0] + ); + this.insertExpression(context, binExpr); + } + + if (flashGreen) this.flashGreen(binExpr); + + this.setTokenColor(binExpr, binExpr.color); + + eventData.code = action.data.operator; + + break; + } + + case EditActionType.WrapExpressionWithItem: { + // both lists and str work on any, so the first step of validation is always OK. + + const initialBoundary = this.getBoundaries(context.expressionToRight); + const expr = context.expressionToRight as Expression; + const indexInRoot = expr.indexInRoot; + const root = expr.rootNode as Statement; + + const newCode = action.data.expression as Expression; + newCode.indexInRoot = expr.indexInRoot; + newCode.rootNode = expr.rootNode; + + const isValidRootInsertion = + newCode.returns == DataType.Any || + root.typeOfHoles[indexInRoot].indexOf(newCode.returns) >= 0 || + root.typeOfHoles[indexInRoot] == DataType.Any; + + let replaceIndex: number = 0; + + for (const [i, token] of newCode.tokens.entries()) { + if (token instanceof TypedEmptyExpr) { + replaceIndex = i; + + break; + } + } + + if (isValidRootInsertion) { + this.module.closeConstructDraftRecord(root.tokens[indexInRoot]); + } + + newCode.tokens[replaceIndex] = context.expressionToRight; + context.expressionToRight.indexInRoot = replaceIndex; + context.expressionToRight.rootNode = newCode; + root.tokens[indexInRoot] = newCode; + root.rebuild(root.getLeftPosition(), 0); + this.module.editor.executeEdits(initialBoundary, newCode); + this.module.focus.updateContext({ + positionToMove: new Position(newCode.lineNumber, newCode.right), + }); + + if (newCode.rootNode instanceof BinaryOperatorExpr) { + newCode.rootNode.onInsertInto(newCode); + newCode.rootNode.validateTypes(this.module); + } else if (newCode.rootNode instanceof Statement) { + newCode.rootNode.onInsertInto(newCode); + } + + if (!isValidRootInsertion) { + this.module.closeConstructDraftRecord(expr); + this.module.openDraftMode(newCode, "DEBUG THIS", []); + } + + if (flashGreen) this.flashGreen(newCode); + + this.setTokenColor(newCode, newCode.color); + + eventData.code = action.data.expression.getRenderText(); + eventData.wrap = true; + + break; + } + + case EditActionType.ConvertAutocompleteToString: { + const autocompleteToken = action.data.token as AutocompleteTkn; + const literalValExpr = new LiteralValExpr( + Docs.StrDocs.styles.backgroundColor, + DataType.String, + autocompleteToken.text, + autocompleteToken.rootNode as Expression | Statement, + autocompleteToken.indexInRoot + ); + + autocompleteToken.draftModeEnabled = false; + this.deleteCode(autocompleteToken); + this.insertExpression(this.module.focus.getContext(), literalValExpr); + + eventData.code = "double-quote"; + eventData.wrap = true; + + break; + } + + case EditActionType.InsertEmptyList: { + const newLiteral = new ListLiteralExpression(Docs.ListLiteralDocs.styles.backgroundColor); + this.insertExpression(context, newLiteral); + + if (flashGreen) this.flashGreen(newLiteral); + + this.setTokenColor(newLiteral, newLiteral.color); + + eventData.code = "empty-list"; + + break; + } + + case EditActionType.InsertEmptyListItem: { + if (action.data.toRight) { + const code = [new NonEditableTkn(", "), new TypedEmptyExpr([DataType.Any])]; + this.insertEmptyListItem(context.tokenToRight, context.tokenToRight.indexInRoot, code); + this.module.editor.insertAtCurPos(code); + this.module.focus.updateContext({ tokenToSelect: code[1] }); + + if (flashGreen) this.flashGreen(code[1]); + } else if (action.data.toLeft) { + const code = [new TypedEmptyExpr([DataType.Any]), new NonEditableTkn(", ")]; + this.insertEmptyListItem(context.tokenToLeft, context.tokenToLeft.indexInRoot + 1, code); + this.module.editor.insertAtCurPos(code); + this.module.focus.updateContext({ tokenToSelect: code[0] }); + + if (flashGreen) this.flashGreen(code[0]); + } + eventData.code = "list-item-comma"; + + break; + } + + case EditActionType.DeleteListItem: { + if (action.data.toRight) { + const items = this.module.removeItems(context.token.rootNode, context.token.indexInRoot, 2); + this.module.editor.executeEdits(this.getCascadedBoundary(items), null, ""); + } else if (action.data.toLeft) { + const items = this.module.removeItems(context.token.rootNode, context.token.indexInRoot - 1, 2); + this.module.editor.executeEdits(this.getCascadedBoundary(items), null, ""); + } + + break; + } + + case EditActionType.DeleteRootNode: { + this.deleteCode(context.token.rootNode); + break; + } + + case EditActionType.ReplaceExpressionWithItem: { + const rootNode = context.token.rootNode as Expression; + let replacementTkn; + + for (let i = 0; i < rootNode.tokens.length; i++) { + if ( + !(rootNode.tokens[i] instanceof TypedEmptyExpr) && + !(rootNode.tokens[i] instanceof NonEditableTkn) && + !(rootNode.tokens[i] instanceof OperatorTkn) + ) { + replacementTkn = rootNode.tokens[i]; + } + } + + this.replaceCode(rootNode, replacementTkn); + + break; + } + + case EditActionType.InsertImportFromDraftMode: { + let currContext = context; + this.module.editor.monaco.setPosition(new Position(1, 1)); + this.module.editor.cursor.setSelection(null); + this.module.insertEmptyLine(); + this.module.editor.monaco.setPosition(new Position(1, 1)); + this.module.editor.cursor.setSelection(null); + currContext = this.module.focus.getContext(); + + const stmt = new ImportStatement( + Docs.ImportDocs.styles.backgroundColor, + action.data?.moduleName, + action.data?.itemName + ); + const insertAction = new EditCodeAction( + "from --- import --- :", + "add-import-btn", + () => stmt, + InsertActionType.InsertStatement, + {}, + null, + [" "], + "import", + null + ); + + insertAction.performAction(this, this.module.eventRouter, currContext, { type: "draft-mode" }); + eventData.code = stmt.getRenderText(); + + break; + } + case EditActionType.InsertMemberCallConversion: + case EditActionType.InsertMemberAccessConversion: { + const root = action.data.codeToReplace; + this.module.focus.updateContext( + new UpdatableContext(null, action.data.codeToReplace.getRightPosition()) + ); + this.execute( + new EditAction(EditActionType.InsertModifier, { + source: action?.data?.source, + modifier: Actions.instance() + .actionsList.find((element) => element.cssId == action.data.conversionConstructId) + .getCodeFunction() as Modifier, + }), + this.module.focus.getContext() + ); + + this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); + + this.setTokenColor(action.data.codeToReplace.rootNode, action.data.codeToReplace.rootNode.color); + + if (root instanceof Expression) root.validateTypes(this.module); + + eventData.code = action.data.codeToReplace.getRenderText(); + + break; + } + case EditActionType.InsertFunctionConversion: + case EditActionType.InsertTypeCast: + case EditActionType.InsertComparisonConversion: { + const root = action.data.codeToReplace; + this.deleteCode(action.data.codeToReplace, { + statement: null, + replaceType: action.data.typeToConvertTo, + }); + this.insertExpression( + this.module.focus.getContext(), + Actions.instance() + .actionsList.find((element) => element.cssId == action.data.conversionConstructId) + .getCodeFunction() as Expression + ); + action.data.codeToReplace.draftModeEnabled = false; + this.insertExpression(this.module.focus.getContext(), action.data.codeToReplace as Expression); + this.flashGreen(action.data.codeToReplace.rootNode as CodeConstruct); + + this.setTokenColor(action.data.codeToReplace.rootNode, action.data.codeToReplace.rootNode.color); + + if (root instanceof Expression) root.validateTypes(this.module); + eventData.code = action.data.codeToReplace.getRenderText(); + + break; + } + + case EditActionType.SelectClosestTokenAbove: { + this.module.focus.navigateUp(); + + break; + } + + case EditActionType.SelectClosestTokenBelow: { + this.module.focus.navigateDown(); + + break; + } + + case EditActionType.MoveCursorLeft: + preventDefaultEvent = false; + + break; + + case EditActionType.MoveCursorRight: + preventDefaultEvent = false; + + break; + + case EditActionType.SelectLeft: + preventDefaultEvent = false; + break; + + case EditActionType.SelectRight: + preventDefaultEvent = false; + break; + + case EditActionType.SelectToStart: + preventDefaultEvent = false; + break; + + case EditActionType.SelectToEnd: + preventDefaultEvent = false; + break; + + case EditActionType.Copy: + preventDefaultEvent = false; + break; + + case EditActionType.InsertLiteral: { + let color: string; + if (action.data?.literalType == DataType.String) { + color = Docs.StrDocs.styles.backgroundColor; + } else if (action.data?.literalType == DataType.Number) { + color = Docs.NumDocs.styles.backgroundColor; + } else if (action.data?.literalType == DataType.Number) { + color = Docs.NumDocs.styles.backgroundColor; + } else if (action.data?.literalType == DataType.Boolean && action.data?.initialValue == "True") { + color = Docs.TrueDocs.styles.backgroundColor; + } else if (action.data?.literalType == DataType.Boolean && action.data?.initialValue == "False") { + color = Docs.FalseDocs.styles.backgroundColor; + } + const newLiteral = new LiteralValExpr(color, action.data?.literalType, action.data?.initialValue); + this.insertExpression(context, newLiteral); + + if (flashGreen) this.flashGreen(newLiteral); + + // this.setTokenColor(newLiteral, newLiteral.color); + + if (action.data?.source?.type === "keyboard") { + eventType = LogType.InsertCode; + eventData.source = "keyboard"; + eventData.code = `literal-${getUserFriendlyType(newLiteral.returns)}`; + } + + break; + } + + case EditActionType.OpenValidInsertMenu: + this.openAutocompleteMenu( + this.module.actionFilter + .getProcessedInsertionsList() + .filter((item) => item.insertionResult.insertionType != InsertionType.Invalid) + ); + this.styleAutocompleteMenu(context.position); + + break; + + //TODO: Remove later + case EditActionType.OpenValidInsertMenuSingleLevel: + if (!this.module.menuController.isMenuOpen()) { + //TODO: Make this work with ActionFilter + //const suggestions = this.module.getAllValidInsertsList(focusedNode); + //this.module.menuController.buildSingleLevelConstructCategoryMenu(suggestions); + } else this.module.menuController.removeMenus(); + + break; + + case EditActionType.SelectMenuSuggestionAbove: + this.module.menuController.focusOptionAbove(); + + break; + + case EditActionType.SelectMenuSuggestionBelow: + this.module.menuController.focusOptionBelow(); + + break; + + case EditActionType.SelectMenuSuggestion: + this.module.menuController.selectFocusedOption(); + + break; + + case EditActionType.CloseValidInsertMenu: + this.module.menuController.removeMenus(); + + break; + + case EditActionType.OpenSubMenu: + this.module.menuController.openSubMenu(); + + break; + + case EditActionType.CloseSubMenu: + this.module.menuController.closeSubMenu(); + + break; + + case EditActionType.CloseDraftMode: + this.deleteCode(action.data.codeNode); + + break; + + case EditActionType.None: { + preventDefaultEvent = true; + + break; + } + + case EditActionType.InsertOperatorTkn: { + this.replaceCode(context.tokenToLeft, action.data.operator); + + if (context.tokenToLeft.rootNode instanceof BinaryOperatorExpr) { + const root = context.tokenToLeft.rootNode; + root.operator = action.data.operator.operator; + root.operatorCategory = getOperatorCategory(root.operator); + + if (root.getLeftOperand() instanceof TypedEmptyExpr) { + root.updateTypeOfEmptyOperandOnOperatorChange("left"); + } + + if (root.getRightOperand() instanceof TypedEmptyExpr) { + root.updateTypeOfEmptyOperandOnOperatorChange("right"); + } + } + + if (flashGreen) this.flashGreen(action.data.operator); + + this.setTokenColor(action.data.operator, action.data.operator.color); + + eventData.code = action.data.operator.getRenderText(); + + break; + } + + case EditActionType.DeleteUnconvertibleOperandWarning: { + if (action.data.codeToDelete.draftModeEnabled) + this.module.closeConstructDraftRecord(action.data.codeToDelete); + this.deleteCode(action.data.codeToDelete); + + //TODO: Eventually this if statement should go as all constructs will have this method + if ( + action.data.rootExpression instanceof Expression || + action.data.rootExpression instanceof ListAccessModifier + ) + action.data.rootExpression.validateTypes(this.module); + + break; + } + } + + if (eventData && eventType) Logger.Instance().queueEvent(new LogEvent(eventType, eventData)); + + this.module.editor.monaco.focus(); + + return preventDefaultEvent; + } + + createVarReference(buttonId: string): VariableReferenceExpr { + const identifier = document.getElementById(buttonId).innerText; + const dataType = this.module.variableController.getVariableTypeNearLine( + this.module.focus.getFocusedStatement().scope ?? + ( + this.module.focus.getStatementAtLineNumber(this.module.editor.monaco.getPosition().lineNumber) + .rootNode as Statement | Module + ).scope, + this.module.editor.monaco.getPosition().lineNumber, + identifier + ); + + return new VariableReferenceExpr(identifier, dataType, buttonId); + } + + insertVariableReference(buttonId: string, source: {}, providedContext?: Context, autocompleteData?: {}) { + let context = providedContext ? providedContext : this.module.focus.getContext(); + + let { eventType, eventData } = this.getLogEventSource(source); + + if (this.module.validator.onBeginningOfLine(context)) { + const varRef = this.createVarReference(buttonId); + const stmt = new VarOperationStmt(Docs.AddVarDocs.styles.backgroundColor, varRef); + this.replaceEmptyStatement(context.lineStatement, stmt); + + const availableActions = this.module.actionFilter + .getProcessedInsertionsList() + .filter( + (action) => + action.insertionResult.insertionType !== InsertionType.Invalid && + (action.insertActionType === InsertActionType.InsertAssignmentModifier || + action.insertActionType === InsertActionType.InsertAugmentedAssignmentModifier) + ); + + this.module.openDraftMode( + stmt, + "Variable references should not be used on empty lines. Try converting it to an assignment statement instead!", + (() => { + const buttons = []; + + for (const action of availableActions) { + const button = document.createElement("div"); + addClassToDraftModeResolutionButton(button, stmt); + + const text = `${varRef.identifier}${action.optionName}`.replace(/---/g, ""); + button.innerHTML = text; + + const modifier = action.getCode(); + button.addEventListener("click", () => { + this.module.closeConstructDraftRecord(stmt); + this.module.executer.execute( + new EditAction(EditActionType.InsertAssignmentModifier, { + codeToReplace: stmt, + replacementConstructCssId: action.cssId, + modifier: modifier, + source: { type: "draft-mode" }, + }), + this.module.focus.getContext() + ); + + const varOpStmt = modifier.rootNode as Statement; + + this.flashGreen(varOpStmt); + this.setTokenColor(varOpStmt, varOpStmt.color); + }); + + buttons.push(button); + } + + return buttons; + })() + ); + + if (autocompleteData) { + this.flashGreen(stmt); + + this.setTokenColor(stmt, stmt.color); + } + + eventData.code = varRef.getRenderText(); + } else if (this.module.validator.atEmptyExpressionHole(context)) { + const expr = this.createVarReference(buttonId); + this.insertExpression(context, expr); + + if (autocompleteData) { + this.flashGreen(expr); + + this.setTokenColor(expr, expr.color); + } + + eventData.code = expr.getRenderText(); + } + + if (eventData && eventType) Logger.Instance().queueEvent(new LogEvent(eventType, eventData)); + } + + getLogEventSource(source: any): { eventType: LogType; eventData: any } { + let eventType: LogType; + let eventData: any = null; + + if (source) { + eventData = {}; + + switch (source.type) { + case "toolbox": + eventType = LogType.InsertCode; + eventData.source = "toolbox"; + + break; + + case "autocomplete": + eventType = LogType.InsertCode; + eventData.source = "autocomplete"; + + break; + + case "autocomplete-menu": + eventType = LogType.InsertCode; + eventData = { + source: "autocomplete-menu", + precision: source.precision, + length: source.length, + }; + + break; + + case "defined-variables": + eventType = LogType.InsertCode; + eventData.source = "defined-vars-toolbox"; + + break; + + case "draft-mode": + eventType = LogType.InsertCode; + eventData.source = "draft-mode"; + + break; + } + } + + return { eventType, eventData }; + } + + deleteAutocompleteOnMatch(context: Context): Context { + let token: AutocompleteTkn; + + if (context.token instanceof AutocompleteTkn) token = context.token; + if (context.tokenToLeft instanceof AutocompleteTkn) token = context.tokenToLeft; + if (context.tokenToRight instanceof AutocompleteTkn) token = context.tokenToRight; + + if (token) { + switch (token.autocompleteType) { + case AutoCompleteType.RightOfExpression: + case AutoCompleteType.LeftOfExpression: + this.deleteAutocompleteToken(token); + + break; + + case AutoCompleteType.StartOfLine: + if (token.rootNode instanceof TemporaryStmt) { + this.deleteCode(token.rootNode, { + statement: true, + }); + } else { + this.deleteCode(token); + } + + break; + + case AutoCompleteType.AtExpressionHole: + this.deleteCode(token, {}); + + break; + } + } + + return this.module.focus.getContext(); + } + + private flashGreen(code: CodeConstruct) { + if (code) { + let highlight = new ConstructHighlight(this.module.editor, code, [109, 242, 162, 1]); + + setTimeout(() => { + if (highlight) { + highlight.changeHighlightColour([255, 255, 255, 0]); + + setTimeout(() => { + highlight.removeFromDOM(); + highlight = null; + }, 500); + } + }, 1); + } + } + + private setTokenColor(code: CodeConstruct, color: string) { + const aRgbHex = color.substring(1).match(/.{1,2}/g); + const aRgb = [parseInt(aRgbHex[0], 16), parseInt(aRgbHex[1], 16), parseInt(aRgbHex[2], 16)]; + + if (code) { + let highlight = new ConstructHighlight(this.module.editor, code, [aRgb[0], aRgb[1], aRgb[2], 1]); + } + } + + private insertEmptyListItem(focusedCode: CodeConstruct, index: number, items: Array) { + if (focusedCode instanceof Token || focusedCode instanceof Expression) { + const root = focusedCode.rootNode; + + if (root instanceof Statement && root.tokens.length > 0) { + root.tokens.splice(index, 0, ...items); + + for (let i = 0; i < root.tokens.length; i++) { + root.tokens[i].indexInRoot = i; + root.tokens[i].rootNode = root; + } + + root.rebuild(root.getLeftPosition(), 0); + } + } + } + + private performMatchAction(match: EditCodeAction, token: AutocompleteTkn) { + if ( + match.insertActionType == InsertActionType.InsertNewVariableStmt && + (Object.keys(PythonKeywords).indexOf(token.text.trim()) >= 0 || + Object.keys(BuiltInFunctions).indexOf(token.text.trim()) >= 0) + ) { + // TODO: can insert an interesting warning + return; + } + + let length = 0; + if (match.insertActionType == InsertActionType.InsertNewVariableStmt) length = token.text.length + 1; + else length = match.matchString.length + 1; + + match.performAction( + this, + this.module.eventRouter, + this.module.focus.getContext(), + { type: "autocomplete", precision: "1", length }, + { + identifier: token.text, + } + ); + } + + private insertToken(context: Context, code: Token, { toLeft = false, toRight = false } = {}) { + if (context.token instanceof TypedEmptyExpr || context.token instanceof EmptyOperatorTkn) { + if (context.expression != null) { + const root = context.expression.rootNode as Statement; + root.replace(code, context.expression.indexInRoot); + } else if (context.token != null) { + const root = context.token.rootNode as Statement; + root.replace(code, context.token.indexInRoot); + } + + const range = new Range( + context.position.lineNumber, + context.token.left, + context.position.lineNumber, + context.token.right + ); + + this.module.editor.executeEdits(range, code); + } else if (toRight && context.expressionToLeft != null) { + const root = context.expressionToLeft.rootNode; + code.rootNode = root; + root.tokens.splice(context.expressionToLeft.indexInRoot + 1, 0, code); + root.rebuild(root.getLeftPosition(), 0); + this.module.editor.insertAtCurPos([code]); + } else if (toLeft && context.expressionToRight != null) { + const root = context.expressionToRight.rootNode; + code.rootNode = root; + root.tokens.splice(context.expressionToRight.indexInRoot, 0, code); + root.rebuild(root.getLeftPosition(), 0); + this.module.editor.insertAtCurPos([code]); + } + } + + private insertExpression(context: Context, code: Expression) { + // type checks -- different handling based on type of code construct + // focusedNode.returns != code.returns would work, but we need more context to get the right error message + if (context.token instanceof TypedEmptyExpr) { + const root = context.token.rootNode; + let insertionResult = root.typeValidateInsertionIntoHole(code, context.token); + + if (insertionResult.insertionType != InsertionType.Invalid) { + if (root instanceof Statement) { + root.onInsertInto(code); + } + + if (context.token.message && context.selected) { + //TODO: This should only be closed if the current insertion would fix the current draft mode. Currently we don't know if that is the case. + this.module.messageController.removeMessageFromConstruct(context.token); + } + + // replaces expression with the newly inserted expression + const expr = code as Expression; + + this.module.replaceFocusedExpression(expr); + + const range = new Range( + context.position.lineNumber, + context.token.left, + context.position.lineNumber, + context.token.right + ); + + this.module.editor.executeEdits(range, expr); + + //TODO: This should probably run only if the insert above was successful, we cannot assume that it was + if (!context.token.message) { + const newContext = code.getInitialFocus(); + this.module.focus.updateContext(newContext); + } + } + + if (root instanceof BinaryOperatorExpr) { + root.validateTypes(this.module); + } else if (insertionResult.insertionType == InsertionType.DraftMode) { + this.module.openDraftMode(code, insertionResult.message, [ + ...insertionResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton(code.getKeyword(), this.module, code); + }), + ]); + } else if (isImportable(code)) { + //TODO: This needs to run regardless of what happens above. But for that we need nested draft modes. It should not be a case within the same if block + //The current problem is that a construct can only have a single draft mode on it. This is mostly ok since we often reinsert the construct when fixing a draft mode + //and the reinsertion triggers another draft mode if necessary. But this does not happen for importables because they are not reinserted on a fix so we might lose some + //draft modes this way. + + //A quick fix for now would be to just trigger reinsertion. Otherwise we need a mechanism for having multiple draft modes. I have a commit on a separate branch for that. + //Converting them to a linked list seems to make the most sense. + this.checkImports(code, insertionResult.insertionType); + } + } + } + + private checkImports(insertedCode: Importable, currentInsertionType: InsertionType) { + if (currentInsertionType === InsertionType.Invalid) return; + + const insertionType = insertedCode.validateImportOnInsertion(this.module, currentInsertionType); + if (insertionType === InsertionType.DraftMode && insertedCode instanceof Statement) { + this.module.openImportDraftMode(insertedCode); + } + } + + private openAutocompleteMenu(inserts: EditCodeAction[]) { + if (!this.module.menuController.isMenuOpen()) { + inserts = inserts.filter((insert) => insert.insertionResult.insertionType !== InsertionType.Invalid); + this.module.menuController.buildSingleLevelMenu(inserts); + } else this.module.menuController.removeMenus(); + } + + private replaceEmptyStatement(emptyLine: Statement, statement: Statement) { + const root = emptyLine.rootNode as Statement | Module; + + replaceInBody(root, emptyLine.indexInRoot, statement); + + if (root instanceof Statement) root.notify(CallbackType.replace); + + var range = new Range(emptyLine.lineNumber, statement.left, emptyLine.lineNumber, statement.right); + + if (emptyLine.message) this.module.messageController.removeMessageFromConstruct(emptyLine); + + if (isImportable(statement)) { + this.checkImports(statement, InsertionType.Valid); + } + + this.module.editor.executeEdits(range, statement); + this.module.focus.updateContext(statement.getInitialFocus()); + } + + private replaceWithBinaryOp( + op: BinaryOperator, + expr: Expression, + { toLeft = false, toRight = false } + ): BinaryOperatorExpr { + if (expr instanceof Modifier) expr = expr.rootNode as Expression; + + const initialBoundary = this.getBoundaries(expr); + const root = expr.rootNode as Statement; + const index = expr.indexInRoot; + + const newCode = new BinaryOperatorExpr( + Docs.AddDocs.styles.backgroundColor, + op, + expr.returns, // is not that important, will be replaced in the constructor based on the operator. + root, + expr.indexInRoot + ); + + const curOperand = toLeft ? newCode.getLeftOperand() : newCode.getRightOperand(); + const otherOperand = toLeft ? newCode.getRightOperand() : newCode.getLeftOperand(); + const insertionResult = newCode.typeValidateInsertionIntoHole(expr, curOperand as TypedEmptyExpr); + + /** + * Special cases + * + * if (--- + (--- + ---)|): --> attempting to insert a comparator or binary boolean operation should fail + */ + if (insertionResult.insertionType === InsertionType.Valid) { + const replacementResult = expr.canReplaceWithConstruct(newCode); + + // this can never go into draft mode + if (replacementResult.insertionType !== InsertionType.Invalid) { + if (root.tokens[index].draftModeEnabled) this.module.closeConstructDraftRecord(root.tokens[index]); + + if (toLeft) newCode.replaceLeftOperand(expr); + else newCode.replaceRightOperand(expr); + + expr.indexInRoot = curOperand.indexInRoot; + expr.rootNode = newCode; + + root.tokens[index] = newCode; + //TODO: Call onInsertInto() on this line + root.rebuild(root.getLeftPosition(), 0); + + this.module.editor.executeEdits(initialBoundary, newCode); + this.module.focus.updateContext({ + tokenToSelect: newCode.tokens[otherOperand.indexInRoot], + }); + + if (replacementResult.insertionType !== InsertionType.DraftMode && expr.draftModeEnabled) { + this.module.closeConstructDraftRecord(expr); + } else if (root instanceof BinaryOperatorExpr) { + root.validateTypes(this.module); + } else if (replacementResult.insertionType === InsertionType.DraftMode) { + this.module.openDraftMode(newCode, replacementResult.message, [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton(newCode.getRenderText(), this.module, newCode); + }), + ]); + } + + if (newCode.rootNode instanceof Statement) newCode.rootNode.onInsertInto(newCode); + + return newCode; + } + } + } + + private getCascadedBoundary(codes: Array): Range { + if (codes.length > 1) { + const lineNumber = codes[0].getLineNumber(); + + return new Range(lineNumber, codes[0].left, lineNumber, codes[codes.length - 1].right); + } else return this.getBoundaries(codes[0]); + } + + private getBoundaries(code: CodeConstruct, { selectIndent = false } = {}): Range { + const lineNumber = code.getLineNumber(); + + if (code instanceof Statement && code.hasBody()) { + const stmtStack = new Array(); + stmtStack.unshift(...code.body); + let endLineNumber = 0; + let endColumn = 0; + + while (stmtStack.length > 0) { + const curStmt = stmtStack.pop(); + + if (curStmt instanceof Statement && curStmt.hasBody()) stmtStack.unshift(...curStmt.body); + + if (endLineNumber < curStmt.lineNumber) { + endLineNumber = curStmt.lineNumber; + endColumn = curStmt.right; + } + } + + return new Range(lineNumber, code.left, endLineNumber, endColumn); + } else if (code instanceof Statement || code instanceof Token) { + if (selectIndent) { + return new Range(lineNumber, code.left - TAB_SPACES, lineNumber, code.right); + } else return new Range(lineNumber, code.left, lineNumber, code.right); + } + } + + private deleteModifier(mod: Modifier, { deleting = false } = {}) { + // TODO: this will be a prototype version of the code. needs to be cleaned and iterated on -> + // e.g. merge the operations for VarOperationStmt and ValueOperationExpr + + // TODO: if deleting, should not move cursor + const removeRange = this.getBoundaries(mod); + const rootOfExprToLeft = mod.rootNode; + + rootOfExprToLeft.tokens.splice(mod.indexInRoot, 1); + this.module.recursiveNotify(mod, CallbackType.delete); + + this.module.closeConstructDraftRecord(rootOfExprToLeft); + + let built = false; + let positionToMove: Position; + + if (rootOfExprToLeft.tokens.length == 1) { + // only a val or var-ref is remaining: + if (rootOfExprToLeft instanceof ValueOperationExpr) { + rootOfExprToLeft.updateReturnType(); + + let replacementResult = rootOfExprToLeft.rootNode.checkInsertionAtHole( + rootOfExprToLeft.indexInRoot, + rootOfExprToLeft.returns + ); + + if (replacementResult.insertionType == InsertionType.DraftMode) { + const ref = rootOfExprToLeft.getVarRef(); + if (ref instanceof VariableReferenceExpr) { + const line = this.module.focus.getContext().lineStatement; + + const varType = this.module.variableController.getVariableTypeNearLine( + line.rootNode instanceof Module ? this.module.scope : line.scope, + line.lineNumber, + ref.identifier, + false + ); + + let expectedTypes = rootOfExprToLeft.rootNode.typeOfHoles[rootOfExprToLeft.indexInRoot]; + const currentAllowedTypes = rootOfExprToLeft.rootNode.getCurrentAllowedTypesOfHole( + rootOfExprToLeft.indexInRoot, + false + ); + + if (currentAllowedTypes.length > 0) { + expectedTypes = currentAllowedTypes; + } + + this.module.openDraftMode( + rootOfExprToLeft, + TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR(ref.identifier, varType, expectedTypes), + [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton( + ref.identifier, + this.module, + rootOfExprToLeft + ); + }), + ] + ); + } else { + let expectedTypes = rootOfExprToLeft.rootNode.typeOfHoles[rootOfExprToLeft.indexInRoot]; + + const currentAllowedTypes = rootOfExprToLeft.rootNode.getCurrentAllowedTypesOfHole( + rootOfExprToLeft.indexInRoot, + false + ); + + if (currentAllowedTypes.length > 0) { + expectedTypes = currentAllowedTypes; + } + + this.module.openDraftMode( + ref, + TYPE_MISMATCH_ON_MODIFIER_DELETION_DRAFT_MODE_STR( + ref.getKeyword(), + ref.returns, + expectedTypes + ), + [ + ...replacementResult.conversionRecords.map((conversionRecord) => { + return conversionRecord.getConversionButton( + ref.getKeyword(), + this.module, + rootOfExprToLeft + ); + }), + ] + ); + } + } + const value = rootOfExprToLeft.tokens[0]; + rootOfExprToLeft.rootNode.tokens[rootOfExprToLeft.indexInRoot] = value; + value.rootNode = rootOfExprToLeft.rootNode; + value.indexInRoot = rootOfExprToLeft.indexInRoot; + + rootOfExprToLeft.rootNode.rebuild(rootOfExprToLeft.rootNode.getLeftPosition(), 0); + positionToMove = new Position(value.getLineNumber(), value.right); + built = true; + } + } + + if (!built) { + rootOfExprToLeft.rebuild(rootOfExprToLeft.getLeftPosition(), 0); + positionToMove = new Position(rootOfExprToLeft.getLineNumber(), rootOfExprToLeft.right); + } + + this.module.editor.executeEdits(removeRange, null, ""); + if (!deleting) { + this.module.focus.updateContext({ + positionToMove, + }); + } + } + + private deleteAutocompleteToken(token: Token) { + const range = this.getBoundaries(token); + const root = token.rootNode as Statement; + root.tokens.splice(token.indexInRoot, 1); + + root.rebuild(root.getLeftPosition(), 0); + token.notify(CallbackType.delete); + + this.module.editor.executeEdits(range, null, ""); + } + + private replaceCode(code: CodeConstruct, replace: CodeConstruct) { + const replacementRange = this.getBoundaries(code); + const root = code.rootNode; + + if (root instanceof Statement) { + root.tokens.splice(code.indexInRoot, 1, replace); + + this.module.recursiveNotify(code, CallbackType.delete); + + for (let i = 0; i < root.tokens.length; i++) { + root.tokens[i].indexInRoot = i; + root.tokens[i].rootNode = root; + } + + root.rebuild(root.getLeftPosition(), 0); + + if (replace instanceof Statement && !(replace instanceof LiteralValExpr)) { + this.setTokenColor(replace, replace.color); + } + + this.module.editor.executeEdits(replacementRange, replace); + + if (replace instanceof Token && replace.isEmpty) { + this.module.focus.updateContext({ tokenToSelect: replace }); + } else this.module.focus.updateContext({ positionToMove: replace.getRightPosition() }); + } + } + + private deleteCode(code: CodeConstruct, { statement = false, replaceType = null } = {}) { + const root = code.rootNode; + const replacementRange = this.getBoundaries(code); + let replacement: CodeConstruct; + + if (statement) replacement = this.module.removeStatement(code as Statement); + else replacement = this.module.replaceItemWTypedEmptyExpr(code, replaceType); + + this.module.editor.executeEdits(replacementRange, replacement); + this.module.focus.updateContext({ tokenToSelect: replacement }); + + if (root instanceof Expression) root.validateTypes(this.module); + } + + private validateIdentifier(context: Context, identifierText: string) { + let focusedNode = null; + + if (context.token && context.selected && context.token instanceof IdentifierTkn) { + focusedNode = context.token; + } else if (context.tokenToLeft && context.tokenToLeft instanceof IdentifierTkn) { + focusedNode = context.tokenToLeft; + } else if (context.tokenToRight && context.tokenToRight instanceof IdentifierTkn) { + focusedNode = context.tokenToRight; + } + + if ( + focusedNode instanceof IdentifierTkn || + context.tokenToLeft instanceof IdentifierTkn || + context.tokenToRight instanceof IdentifierTkn + ) { + if (Object.keys(PythonKeywords).indexOf(identifierText) > -1) { + this.module.messageController.addPopUpMessage( + focusedNode, + { identifier: identifierText }, + ErrorMessage.identifierIsKeyword + ); + } else if (Object.keys(BuiltInFunctions).indexOf(identifierText) > -1) { + this.module.messageController.addPopUpMessage( + focusedNode, + { identifier: identifierText }, + ErrorMessage.identifierIsBuiltInFunc + ); + } + } + } + + private updateAutocompleteMenu(autocompleteTkn: AutocompleteTkn) { + this.module.menuController.updateMenuOptions(autocompleteTkn.getEditableText()); + this.module.menuController.updatePosition( + this.module.menuController.getNewMenuPositionFromCode(autocompleteTkn) + ); + } + + private styleAutocompleteMenu(pos: Position) { + this.module.menuController.styleMenuOptions(); + this.module.menuController.updatePosition(this.module.menuController.getNewMenuPositionFromPosition(pos)); + } +} From 3137a9fc58ada0c89403ff9d1ec7d074a1c29a42 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Jun 2022 16:19:08 -0400 Subject: [PATCH 23/24] fixed MethodCallModifier hole deletion --- src/editor/event-router.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/editor/event-router.ts b/src/editor/event-router.ts index b7c41ea..3a85b41 100644 --- a/src/editor/event-router.ts +++ b/src/editor/event-router.ts @@ -1,4 +1,4 @@ -import { editor, IKeyboardEvent, IScrollEvent, Position } from "monaco-editor"; +import { editor, Position } from "monaco-editor"; import * as ast from "../syntax-tree/ast"; import { Module } from "../syntax-tree/module"; @@ -136,10 +136,10 @@ export class EventRouter { toRight: true, }); } else if (this.module.validator.isTknEmpty(context)) { - if ( - this.module.validator.isAugmentedAssignmentModifierStatement(context) || - this.module.validator.isMethodCallModifierStatement(context) - ) { + if (this.module.validator.isMethodCallModifierStatement(context)) { + return new EditAction(EditActionType.DeleteSelectedModifier); + } + if (this.module.validator.isAugmentedAssignmentModifierStatement(context)) { return new EditAction(EditActionType.DeleteStatement); } if (context.token.rootNode instanceof ast.Expression) { @@ -206,10 +206,10 @@ export class EventRouter { toRight: true, }); } else if (this.module.validator.isTknEmpty(context)) { - if ( - this.module.validator.isAugmentedAssignmentModifierStatement(context) || - this.module.validator.isMethodCallModifierStatement(context) - ) { + if (this.module.validator.isMethodCallModifierStatement(context)) { + return new EditAction(EditActionType.DeleteSelectedModifier); + } + if (this.module.validator.isAugmentedAssignmentModifierStatement(context)) { return new EditAction(EditActionType.DeleteStatement); } if (context.token.rootNode instanceof ast.Expression) { From 3540a8d692395b6cb207d64e57386ae76d6dec9f Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Jun 2022 16:23:51 -0400 Subject: [PATCH 24/24] reimported IKeyboardEvent and IScrollEvent --- src/editor/event-router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/event-router.ts b/src/editor/event-router.ts index 3a85b41..43531ee 100644 --- a/src/editor/event-router.ts +++ b/src/editor/event-router.ts @@ -1,4 +1,4 @@ -import { editor, Position } from "monaco-editor"; +import { editor, IKeyboardEvent, IScrollEvent, Position } from "monaco-editor"; import * as ast from "../syntax-tree/ast"; import { Module } from "../syntax-tree/module";