diff --git a/src/css/console.css b/src/css/console.css
index 674ff42..e459495 100644
--- a/src/css/console.css
+++ b/src/css/console.css
@@ -59,6 +59,12 @@
border: solid #4f4f4f 0.5px;
}
+.settings-btn{
+ box-shadow: none;
+ background-color: #69a;
+ color: #fff !important;
+}
+
.console-button {
color: #000;
cursor: pointer;
diff --git a/src/css/index.css b/src/css/index.css
index d773f75..7be2eea 100644
--- a/src/css/index.css
+++ b/src/css/index.css
@@ -12,6 +12,7 @@
@import url("notification.css");
@import url("tooltip.css");
@import url("accordion.css");
+@import url("settings.css");
@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,600&display=swap");
@@ -33,7 +34,7 @@ body {
}
.button {
- color: white;
+ color: #0d0c22;
cursor: pointer;
border-radius: 4px;
display: flex;
diff --git a/src/css/settings.css b/src/css/settings.css
new file mode 100644
index 0000000..d4d62ea
--- /dev/null
+++ b/src/css/settings.css
@@ -0,0 +1,111 @@
+.settingsModal {
+ display: block;
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgb(102, 153, 170);
+ background-color: rgba(102, 153, 170, 0.4);
+}
+
+.settingsContainer{
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ position: fixed;
+ left: 50%;
+ top: 25%;
+ transform: translate(-50%, -50%);
+ width: 300px;
+ height: 300px;
+ border: 1px solid black;
+ background-color: #fff;
+ padding: 10px;
+ font-family: Consolas, "Courier New", monospace;
+ border-radius: 10px;
+}
+
+.settingsHeader{
+ text-align: center;
+ font-size: 1.5rem;
+ font-weight: 500;
+ border-bottom: 1px solid black;
+}
+
+.settingsFooter{
+ margin-top: auto;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.exitBtn{
+ padding: 8px 10px;
+ color: #fff;
+ background-color: rgb(225, 100, 116);
+ border-radius: 4px;
+ cursor: pointer;
+ box-shadow: rgb(0 0 0 / 20%) 0px 3px 1px -2px, rgb(0 0 0 / 14%) 0px 2px 2px 0px, rgb(0 0 0 / 12%) 0px 1px 5px 0px;
+}
+
+.setting{
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.toggleBtn{
+ position:relative;
+ display: inline-block;
+ width: 60px;
+ height: 34px;
+}
+
+.toggleBtn input{
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.toggleBtnSlider{
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ -webkit-transition: .4s;
+ transition: .4s;
+ border-radius: 34px;
+}
+
+.toggleBtnSlider:before {
+ position: absolute;
+ content: "";
+ height: 26px;
+ width: 26px;
+ left: 4px;
+ bottom: 4px;
+ background-color: white;
+ -webkit-transition: .4s;
+ transition: .4s;
+ border-radius: 50%;
+}
+
+input:checked + .toggleBtnSlider {
+ background-color: #69a;
+}
+
+input:focus + .toggleBtnSlider {
+ box-shadow: 0 0 1px #69a;
+}
+
+input:checked + .toggleBtnSlider:before {
+ -webkit-transform: translateX(26px);
+ -ms-transform: translateX(26px);
+ transform: translateX(26px);
+}
\ No newline at end of file
diff --git a/src/css/suggestion-menu.css b/src/css/suggestion-menu.css
index 311d20e..40fcf1c 100644
--- a/src/css/suggestion-menu.css
+++ b/src/css/suggestion-menu.css
@@ -120,3 +120,40 @@
.categoryOption {
padding-left: 10px;
}
+
+.spotlightModal{
+ display: block;
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgb(0,0,0);
+ background-color: rgba(0,0,0,0.4);
+}
+
+.spotlight{
+ font-family: Consolas, "Courier New", monospace;
+ padding: 7px;
+ box-shadow: 0px 0px 6px 1px #ccc inset;
+ border: 1px solid #ccc;
+
+ transition: box-shadow 0.15s ease-in-out;
+ -webkit-transition: box-shadow 0.15s ease-in-out;
+ -moz-transition: box-shadow 0.15s ease-in-out;
+ -o-transition: box-shadow 0.15s ease-in-out;
+ -ms-transition: box-shadow 0.15s ease-in-out;
+}
+
+.spotlight:focus{
+ box-shadow: 0px 0px 6px 1px #aaa inset;
+ font-weight: bold;
+}
+
+.spotlight:focus-visible {
+ border: 1px solid #bbb;
+ outline: none;
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/src/editor/action-executor.ts b/src/editor/action-executor.ts
index e52fc2d..a205856 100644
--- a/src/editor/action-executor.ts
+++ b/src/editor/action-executor.ts
@@ -54,6 +54,7 @@ import {
import { Module } from "../syntax-tree/module";
import { Reference } from "../syntax-tree/scope";
import { TypeChecker } from "../syntax-tree/type-checker";
+import { SettingsController } from "../utilities/settings";
import { getUserFriendlyType, isImportable } from "../utilities/util";
import { LogEvent, Logger, LogType } from "./../logger/analytics";
import { BinaryOperator, DataType, InsertionType } from "./../syntax-tree/consts";
@@ -272,7 +273,15 @@ export class ActionExecutor {
if (flashGreen) this.flashGreen(action.data?.statement);
if (statement.hasBody()) {
- let scopeHighlight = new ScopeHighlight(this.module.editor, statement, statement.color);
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ let scopeHighlight = new ScopeHighlight(this.module.editor, statement, statement.color);
+ } else {
+ let scopeHighlight = new ScopeHighlight(
+ this.module.editor,
+ statement,
+ "rgba(75, 200, 255, 0.125)"
+ );
+ }
} else {
this.setTokenColor(action.data?.statement, statement.color);
}
@@ -1432,9 +1441,10 @@ export class ActionExecutor {
this.openAutocompleteMenu(
this.module.actionFilter
.getProcessedInsertionsList()
- .filter((item) => item.insertionResult.insertionType != InsertionType.Invalid)
+ .filter((item) => item.insertionResult.insertionType != InsertionType.Invalid),
+ true
);
- this.styleAutocompleteMenu(context.position);
+ this.styleAutocompleteMenu(context.position, true);
break;
@@ -1735,6 +1745,9 @@ export class ActionExecutor {
}
private setTokenColor(code: CodeConstruct, color: string) {
+ if (!SettingsController.getInstance().config.enabledColoredBlocks) {
+ return;
+ }
const aRgbHex = color.substring(1).match(/.{1,2}/g);
const aRgb = [parseInt(aRgbHex[0], 16), parseInt(aRgbHex[1], 16), parseInt(aRgbHex[2], 16)];
@@ -1886,10 +1899,10 @@ export class ActionExecutor {
}
}
- private openAutocompleteMenu(inserts: EditCodeAction[]) {
+ private openAutocompleteMenu(inserts: EditCodeAction[], isSpotlightSearch = false) {
if (!this.module.menuController.isMenuOpen()) {
inserts = inserts.filter((insert) => insert.insertionResult.insertionType !== InsertionType.Invalid);
- this.module.menuController.buildSingleLevelMenu(inserts);
+ this.module.menuController.buildSingleLevelMenu(inserts, { left: 0, top: 0 }, isSpotlightSearch);
} else this.module.menuController.removeMenus();
}
@@ -2225,8 +2238,11 @@ export class ActionExecutor {
);
}
- private styleAutocompleteMenu(pos: Position) {
+ private styleAutocompleteMenu(pos: Position, isSpotlightSearch: boolean = false) {
this.module.menuController.styleMenuOptions();
- this.module.menuController.updatePosition(this.module.menuController.getNewMenuPositionFromPosition(pos));
+ this.module.menuController.updatePosition(
+ this.module.menuController.getNewMenuPositionFromPosition(pos),
+ isSpotlightSearch
+ );
}
}
diff --git a/src/editor/consts.ts b/src/editor/consts.ts
index 2655320..b5fd349 100644
--- a/src/editor/consts.ts
+++ b/src/editor/consts.ts
@@ -287,6 +287,12 @@ export enum InsertActionType {
InsertOperatorTkn,
}
+export enum settingsConfigCategories {
+ enabledColoredBlocks = "enabledColoredBlocks",
+ enabledSpotlightSearch = "enabledSpotlightSearch",
+ enabledTyping = "enabledTyping",
+}
+
export const Docs: any = {
AddVarDocs,
AddDocs,
diff --git a/src/editor/cursor.ts b/src/editor/cursor.ts
index 5613094..818246e 100644
--- a/src/editor/cursor.ts
+++ b/src/editor/cursor.ts
@@ -1,4 +1,5 @@
import { CodeConstruct, EmptyLineStmt, TypedEmptyExpr } from "../syntax-tree/ast";
+import { SettingsController } from "../utilities/settings";
import { Editor } from "./editor";
export class Cursor {
@@ -17,7 +18,11 @@ export class Cursor {
const cursor = this;
function loop() {
- cursor.setTransform(cursor.code);
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ cursor.setTransformColor(cursor.code);
+ } else {
+ cursor.setTransform(cursor.code);
+ }
requestAnimationFrame(loop);
}
@@ -39,6 +44,27 @@ export class Cursor {
const transform = this.editor.computeBoundingBox(selection);
+ this.element.style.top = `${transform.y + 5}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`;
+ }
+
+ setTransformColor(code: CodeConstruct) {
+ let leftPadding = 0;
+ let rightPadding = 0;
+
+ const selection = code != null ? code.getSelection() : this.editor.monaco.getSelection();
+
+ if (code instanceof TypedEmptyExpr) this.element.style.borderRadius = "15px";
+ else this.element.style.borderRadius = "0";
+
+ this.element.style.visibility = "visible";
+ if (!code || code instanceof EmptyLineStmt) this.element.style.visibility = "hidden";
+
+ const transform = this.editor.computeBoundingBox(selection);
+
this.element.style.top = `${transform.y + 5 + 4}px`;
this.element.style.left = `${transform.x - leftPadding}px`;
diff --git a/src/editor/event-router.ts b/src/editor/event-router.ts
index 43531ee..cf147fd 100644
--- a/src/editor/event-router.ts
+++ b/src/editor/event-router.ts
@@ -2,6 +2,7 @@ import { editor, IKeyboardEvent, IScrollEvent, Position } from "monaco-editor";
import * as ast from "../syntax-tree/ast";
import { Module } from "../syntax-tree/module";
+import { SettingsController } from "../utilities/settings";
import { AutoCompleteType, DataType, IdentifierRegex, InsertionType } from "./../syntax-tree/consts";
import { EditCodeAction } from "./action-filter";
import { Actions, Docs, EditActionType, InsertActionType, KeyPress } from "./consts";
@@ -271,7 +272,12 @@ export class EventRouter {
case KeyPress.Space: {
if (inTextEditMode) return new EditAction(EditActionType.InsertChar);
- if (!inTextEditMode && e.ctrlKey && e.key.length == 1) {
+ if (
+ !inTextEditMode &&
+ e.ctrlKey &&
+ e.key.length == 1 &&
+ SettingsController.getInstance().config.enabledSpotlightSearch
+ ) {
return new EditAction(EditActionType.OpenValidInsertMenu);
}
@@ -279,7 +285,7 @@ export class EventRouter {
}
default: {
- if (e.key.length == 1) {
+ if (e.key.length == 1 && SettingsController.getInstance().config.enabledTyping) {
if (inTextEditMode) {
switch (e.key) {
case KeyPress.C:
diff --git a/src/editor/hole.ts b/src/editor/hole.ts
index 5d56062..d4975a2 100644
--- a/src/editor/hole.ts
+++ b/src/editor/hole.ts
@@ -12,6 +12,7 @@ import { Callback, CallbackType } from "../syntax-tree/callback";
import { InsertionType } from "../syntax-tree/consts";
import { Module } from "../syntax-tree/module";
import { Reference } from "../syntax-tree/scope";
+import { SettingsController } from "../utilities/settings";
import { Editor } from "./editor";
import { Context } from "./focus";
import { Validator } from "./validator";
@@ -107,7 +108,11 @@ export class Hole {
code.subscribe(
CallbackType.delete,
new Callback(() => {
- hole.setTransform(null);
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ hole.setTransformColor(null);
+ } else {
+ hole.setTransform(null);
+ }
hole.remove();
})
);
@@ -115,7 +120,11 @@ export class Hole {
code.subscribe(
CallbackType.replace,
new Callback(() => {
- hole.setTransform(null);
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ hole.setTransformColor(null);
+ } else {
+ hole.setTransform(null);
+ }
hole.remove();
})
);
@@ -123,16 +132,28 @@ export class Hole {
code.subscribe(
CallbackType.fail,
new Callback(() => {
- hole.element.style.background = `rgba(255, 0, 0, 0.25)`;
-
- setTimeout(() => {
- hole.element.style.background = `rgba(255, 255, 255, 1)`;
- }, 1000);
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ hole.element.style.background = `rgba(255, 0, 0, 0.25)`;
+
+ setTimeout(() => {
+ hole.element.style.background = `rgba(255, 255, 255, 1)`;
+ }, 1000);
+ } else {
+ hole.element.style.background = `rgba(255, 0, 0, 0.06)`;
+
+ setTimeout(() => {
+ hole.element.style.background = `rgba(255, 255, 255, 0)`;
+ }, 1000);
+ }
})
);
function loop() {
- hole.setTransform(code);
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ hole.setTransformColor(code);
+ } else {
+ hole.setTransform(code);
+ }
requestAnimationFrame(loop);
}
@@ -153,6 +174,27 @@ export class Hole {
}
}
+ this.element.style.top = `${transform.y + 5}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`;
+ }
+
+ setTransformColor(code: CodeConstruct) {
+ let leftPadding = 0;
+ let rightPadding = 0;
+ let transform = { x: 0, y: 0, width: 0, height: 0 };
+
+ if (code) {
+ transform = this.editor.computeBoundingBox(code.getSelection());
+
+ if (transform.width == 0) {
+ transform.x -= 7;
+ transform.width = 14;
+ }
+ }
+
this.element.style.top = `${transform.y + 5 + 4}px`;
this.element.style.left = `${transform.x - leftPadding}px`;
diff --git a/src/editor/toolbox.ts b/src/editor/toolbox.ts
index 9337029..02a3a89 100644
--- a/src/editor/toolbox.ts
+++ b/src/editor/toolbox.ts
@@ -6,6 +6,7 @@ import { addTextToConsole, clearConsole, CONSOLE_ERR_TXT_CLASS } from "../pyodid
import { CodeConstruct, Expression, Modifier, Statement, VariableReferenceExpr } from "../syntax-tree/ast";
import { DataType, InsertionType, Tooltip } from "../syntax-tree/consts";
import { Module } from "../syntax-tree/module";
+import { SettingsController } from "../utilities/settings";
import { getUserFriendlyType } from "../utilities/util";
import { LogEvent, Logger, LogType } from "./../logger/analytics";
import { Accordion, TooltipType } from "./accordion";
@@ -177,7 +178,6 @@ export class ToolboxController {
const staticDummySpace = document.getElementById("static-toolbox-dummy-space");
const toolboxCategories = Actions.instance().toolboxCategories;
- const hello = Actions.instance().actionsMap;
for (const constructGroup of toolboxCategories) {
if (constructGroup) {
@@ -218,6 +218,26 @@ export class ToolboxController {
toolboxDiv.clientHeight - toolboxDiv.children[toolboxDiv.children.length - 2].clientHeight - 20
}px`;
}
+
+ toggleToolboxColors() {
+ const toolboxCategories = Actions.instance().toolboxCategories;
+ const isColored = SettingsController.getInstance().config.enabledColoredBlocks;
+
+ for (const constructGroup of toolboxCategories) {
+ if (constructGroup) {
+ for (const item of constructGroup.items) {
+ const button = document.getElementById(item.cssId);
+ if (isColored) {
+ button.style.backgroundColor = item.documentation.styles.backgroundColor;
+ button.style.color = "#fff";
+ } else {
+ button.style.backgroundColor = "#fff";
+ button.style.color = "#0d0c22";
+ }
+ }
+ }
+ }
+ }
}
export class ToolboxButton {
@@ -228,9 +248,16 @@ export class ToolboxButton {
this.container.classList.add("var-button-container");
const button = document.createElement("div");
- button.style.backgroundColor = btnColor;
button.classList.add("button");
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ button.style.backgroundColor = btnColor;
+ button.style.color = "#fff";
+ } else {
+ button.style.backgroundColor = "#fff";
+ button.style.color = "#0d0c22";
+ }
+
if (!(code instanceof Expression) && !(code instanceof Modifier)) {
button.classList.add("statement-button");
} else if (code instanceof Modifier) {
diff --git a/src/index.html b/src/index.html
index d0c3780..2105d1c 100644
--- a/src/index.html
+++ b/src/index.html
@@ -32,6 +32,7 @@
Defined Variables
diff --git a/src/messages/messages.ts b/src/messages/messages.ts
index fe63a41..c6fc0f9 100644
--- a/src/messages/messages.ts
+++ b/src/messages/messages.ts
@@ -4,6 +4,7 @@ import { EDITOR_DOM_ID } from "../editor/toolbox";
import { nova } from "../index";
import { CodeConstruct, Expression, Statement, TypedEmptyExpr } from "../syntax-tree/ast";
import { Callback, CallbackType } from "../syntax-tree/callback";
+import { SettingsController } from "../utilities/settings";
/**
* Class name of the DOM element to which messages are appended to.
@@ -137,7 +138,11 @@ export class ConstructHighlight extends CodeHighlight {
super.createDomElement();
this.domElement.classList.add("highlight");
- this.updateDimensions(true);
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ this.updateDimensionsColor(true);
+ } else {
+ this.updateDimensions(true);
+ }
document.querySelector(editorDomElementClass).appendChild(this.domElement);
}
@@ -184,6 +189,57 @@ export class ConstructHighlight extends CodeHighlight {
let width = 0;
let height = 0;
+ //no idea why these need separate handling... This was the easiest fix.
+ if (this.code instanceof TypedEmptyExpr) {
+ const transform = this.editor.computeBoundingBox(this.code.getSelection());
+ const text = this.code.getRenderText();
+
+ top = transform.y + 5;
+ left = (this.code.getSelection().startColumn - 1) * this.editor.computeCharWidthInvisible(lineNumber);
+
+ width =
+ text.length * this.editor.computeCharWidthInvisible(lineNumber) > 0
+ ? text.length * this.editor.computeCharWidthInvisible(lineNumber)
+ : HIGHLIGHT_DEFAULT_WIDTH;
+ height = transform.height > 0 ? transform.height - 5 * 2 : HIGHLIGHT_DEFAULT_HEIGHT;
+ } else {
+ const selection = this.code.getSelection();
+ const transform = this.editor.computeBoundingBox(selection);
+
+ if (this.code instanceof Expression) {
+ 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;
+ } 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);
+ }
+ }
+
+ if (firstInsertion) {
+ this.domElement.style.top = `${top}px`;
+ this.domElement.style.left = `${left}px`;
+ }
+
+ this.domElement.style.width = `${width}px`;
+ this.domElement.style.height = `${height}px`;
+ }
+
+ protected updateDimensionsColor(firstInsertion: boolean = false) {
+ //instanceof Token does not have lineNumber
+ let lineNumber = this.code.getLineNumber();
+
+ let top = 0;
+ let left = 0;
+ let width = 0;
+ let height = 0;
+
//no idea why these need separate handling... This was the easiest fix.
if (this.code instanceof TypedEmptyExpr) {
const transform = this.editor.computeBoundingBox(this.code.getSelection());
@@ -665,7 +721,11 @@ export class ScopeHighlight {
const onChange = new Callback(
(() => {
- this.updateDimensions();
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ this.updateDimensionsColor();
+ } else {
+ this.updateDimensions();
+ }
}).bind(this)
);
@@ -709,14 +769,19 @@ export class ScopeHighlight {
this.headerElement = document.createElement("div");
this.headerElement.classList.add("scope-header-highlight");
this.headerElement.style.backgroundColor = color;
- this.headerElement.style.opacity = "0.25";
this.bodyElement = document.createElement("div");
this.bodyElement.classList.add("scope-body-highlight");
this.bodyElement.style.backgroundColor = color;
- this.bodyElement.style.opacity = "0.25";
- this.updateDimensions();
+ //TODO: change all colors to rgba - temporary fix
+ if (SettingsController.getInstance().config.enabledColoredBlocks) {
+ this.headerElement.style.opacity = "0.25";
+ this.bodyElement.style.opacity = "0.25";
+ this.updateDimensionsColor();
+ } else {
+ this.updateDimensions();
+ }
document.querySelector(editorDomElementClass).appendChild(this.headerElement);
document.querySelector(editorDomElementClass).appendChild(this.bodyElement);
@@ -748,6 +813,47 @@ 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.height = `${headerDim.height}px`;
+
+ let firstLineInBody = this.statement.body[0];
+ let firstLineInBodyDim: LineDimension;
+
+ if (firstLineInBody) {
+ firstLineInBodyDim = LineDimension.compute(firstLineInBody, this.editor);
+ } else {
+ firstLineInBodyDim = LineDimension.compute(this.statement, this.editor);
+ }
+
+ this.bodyElement.style.top = `${firstLineInBodyDim.top}px`;
+ this.bodyElement.style.left = `${firstLineInBodyDim.left}px`;
+
+ this.bodyElement.style.width = `${maxRight - firstLineInBodyDim.left}px`;
+ this.bodyElement.style.height = `${headerDim.height * (maxLineNumber - this.statement.lineNumber)}px`;
+ }
+
+ protected updateDimensionsColor(): void {
+ const headerDim = LineDimension.compute(this.statement, this.editor);
+
+ let maxRight = headerDim.right;
+ let maxLineNumber = 0;
+
+ const stack = Array();
+ stack.unshift(...this.statement.body);
+
+ while (stack.length > 0) {
+ const line = stack.pop();
+
+ const lineDim = LineDimension.compute(line, this.editor);
+ if (lineDim.right > maxRight) maxRight = lineDim.right;
+ if (line.lineNumber > maxLineNumber) maxLineNumber = line.lineNumber;
+
+ if (line.hasBody()) stack.unshift(...line.body);
+ }
+
+ this.headerElement.style.top = `${headerDim.top}px`;
+ this.headerElement.style.left = `${headerDim.left}px`;
+
this.headerElement.style.width = `${maxRight - headerDim.left + 10}px`;
this.headerElement.style.height = `${headerDim.height}px`;
diff --git a/src/suggestions/suggestions-controller.ts b/src/suggestions/suggestions-controller.ts
index f7305a2..43f1fad 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, Docs, InsertActionType } from "../editor/consts";
+import { Actions, Docs, EditActionType, InsertActionType } from "../editor/consts";
import { Editor } from "../editor/editor";
import { EDITOR_DOM_ID } from "../editor/toolbox";
import { Validator } from "../editor/validator";
@@ -32,18 +32,52 @@ class Menu {
static menuCount = 0;
static idPrefix = "suggestion-menu-";
htmlElement: HTMLDivElement;
+ searchBar: HTMLInputElement;
+ modal: HTMLDivElement;
private optionsInViewPort;
- constructor(options: Map) {
- this.htmlElement = document.createElement("div");
- this.htmlElement.classList.add(MenuController.menuElementClass);
- this.htmlElement.id = `${Menu.idPrefix}${Menu.menuCount}`;
- document.getElementById(EDITOR_DOM_ID).appendChild(this.htmlElement);
+ constructor(options: EditCodeAction[], optionsMap: Map, isSpotlightSearch: boolean = false) {
+ if (isSpotlightSearch) {
+ this.modal = document.createElement("div");
+ this.modal.classList.add(MenuController.modalClass);
+ document.getElementById(EDITOR_DOM_ID).appendChild(this.modal);
+
+ this.htmlElement = document.createElement("div");
+ this.htmlElement.classList.add(MenuController.menuElementClass);
+ this.htmlElement.id = `${Menu.idPrefix}${Menu.menuCount}`;
+ this.modal.appendChild(this.htmlElement);
+
+ this.searchBar = document.createElement("input");
+ this.searchBar.classList.add(MenuController.spotlightElementClass);
+ this.searchBar.placeholder = "Search for Blocks";
+ this.htmlElement.appendChild(this.searchBar);
+
+ const menuController = MenuController.getInstance();
+
+ window.onclick = (e: MouseEvent) => {
+ if (e.target == this.modal) {
+ menuController.removeMenus();
+ }
+ };
+
+ this.searchBar.addEventListener("keydown", (e: KeyboardEvent) => {
+ menuController.spotlightSearchOnKeyDown(e, options);
+ });
+
+ this.searchBar.addEventListener("input", (e: Event) => {
+ menuController.spotlightSearchOnChange(e, options);
+ });
+ } else {
+ this.htmlElement = document.createElement("div");
+ this.htmlElement.classList.add(MenuController.menuElementClass);
+ this.htmlElement.id = `${Menu.idPrefix}${Menu.menuCount}`;
+ document.getElementById(EDITOR_DOM_ID).appendChild(this.htmlElement);
+ }
Menu.menuCount++;
- for (const [key, value] of options) {
+ for (const [key, value] of optionsMap) {
const option = new MenuOption(key, false, null, this, null, value);
option.attachToParentMenu(this);
@@ -225,7 +259,13 @@ class Menu {
}
removeFromDOM() {
- document.getElementById(EDITOR_DOM_ID).removeChild(this.htmlElement);
+ let node = this.htmlElement;
+ let parentNode = node.parentNode;
+ parentNode.removeChild(this.htmlElement);
+
+ if (parentNode == this.modal) {
+ document.getElementById(EDITOR_DOM_ID).removeChild(this.modal);
+ }
}
getOptionByText(optionText: string) {
@@ -417,6 +457,8 @@ export class MenuController {
static optionElementClass: string = "suggestionOptionParent";
static draftModeOptionElementClass: string = "draftModeOptionElementClass";
static menuElementClass: string = "suggestionMenuParent";
+ static modalClass: string = "spotlightModal";
+ static spotlightElementClass: string = "spotlight";
static optionTextElementClass: string = "suggestionOptionText";
static selectedOptionElementClass: string = "selectedSuggestionOptionParent";
@@ -454,7 +496,11 @@ export class MenuController {
*
* @param pos Starting top-left corner of this menu in the editor.
*/
- buildSingleLevelMenu(suggestions: EditCodeAction[], pos: any = { left: 0, top: 0 }) {
+ buildSingleLevelMenu(
+ suggestions: EditCodeAction[],
+ pos: any = { left: 0, top: 0 },
+ isSpotlightSearch: boolean = false
+ ) {
if (this.menus.length > 0) this.removeMenus();
else if (suggestions.length >= 0) {
//TODO: Very hacky way of fixing #569
@@ -464,7 +510,7 @@ export class MenuController {
suggestions.push(Actions.instance().actionsList[0]); //this does not have to be this specific aciton, just need one to create the option so that the menu is created and then we immediately delete the option
}
- const menu = this.module.menuController.buildMenu(suggestions, pos);
+ const menu = this.module.menuController.buildMenu(suggestions, pos, isSpotlightSearch);
//TODO: Continuation of very hacky way of fixing #569
if (suggestions.length === 0) {
@@ -507,7 +553,11 @@ export class MenuController {
*
* @returns the constructed menu. Null if no options was empty.
*/
- private buildMenu(options: EditCodeAction[], pos: any = { left: 0, top: 0 }): Menu {
+ private buildMenu(
+ options: EditCodeAction[],
+ pos: any = { left: 0, top: 0 },
+ isSpotlightSearch: boolean = false
+ ): Menu {
if (options.length > 0) {
const menuOptions = new Map();
@@ -523,7 +573,7 @@ export class MenuController {
});
}
- const menu = new Menu(menuOptions);
+ const menu = new Menu(options, menuOptions, isSpotlightSearch);
//TODO: These are the same values as the ones used for mouse offset by the messages so maybe make them shared in some util file
menu.htmlElement.style.left = `${pos.left + document.getElementById(EDITOR_DOM_ID).offsetLeft}px`;
@@ -547,6 +597,51 @@ export class MenuController {
return null;
}
+ spotlightSearchOnKeyDown(e: KeyboardEvent, options: EditCodeAction[]) {
+ const context = this.module.focus.getContext();
+ const action = this.module.eventRouter.getKeyAction(e, context);
+
+ if (
+ action.type == EditActionType.SelectMenuSuggestion ||
+ action.type == EditActionType.SelectMenuSuggestionAbove ||
+ action.type == EditActionType.SelectMenuSuggestionBelow
+ ) {
+ const preventDefaultEvent = this.module.executer.execute(action, context, e);
+
+ if (preventDefaultEvent) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+ }
+
+ spotlightSearchOnChange(e: Event, options: EditCodeAction[]) {
+ const context = this.module.focus.getContext();
+ const target = e.target as HTMLInputElement;
+ let prevText = target.value.slice(0, -1);
+ let curText = target.value;
+ let lastkey = target.value.slice(-1);
+
+ //check match
+ for (const match of options) {
+ if (match.terminatingChars.indexOf(lastkey) >= 0) {
+ if (match.trimSpacesBeforeTermChar) prevText = prevText.trim();
+
+ if (prevText == match.matchString || (match.matchRegex != null && match.matchRegex.test(prevText)))
+ match.performAction(this.module.executer, this.module.eventRouter, context, {
+ type: "autocomplete-menu",
+ precision: this.calculateAutocompleteMatchPrecision(prevText, match.matchString),
+ length:
+ match.insertActionType === InsertActionType.InsertNewVariableStmt
+ ? prevText.length + 1
+ : match.matchString.length + 1,
+ });
+ }
+ }
+
+ this.updateMenuOptions(curText);
+ }
+
removeMenus() {
this.menus.forEach((menu) => {
menu.close();
@@ -942,12 +1037,20 @@ export class MenuController {
}
}
- updatePosition(pos: { left: number; top: number }) {
+ updatePosition(pos: { left: number; top: number }, isSpotlightSearch: boolean = false) {
const element = this.menus[this.focusedMenuIndex]?.htmlElement;
if (element) {
- element.style.left = `${pos.left}px`;
- element.style.top = `${pos.top}px`;
+ if (isSpotlightSearch) {
+ //centers the menu on the page
+ element.style.left = "50%";
+ element.style.top = "50%";
+ element.style.marginLeft = -element.offsetWidth / 2 + "px";
+ element.style.marginTop = -element.offsetHeight / 2 + "px";
+ } else {
+ element.style.left = `${pos.left}px`;
+ element.style.top = `${pos.top}px`;
+ }
}
}
diff --git a/src/syntax-tree/module.ts b/src/syntax-tree/module.ts
index ee02f81..1d0c30a 100644
--- a/src/syntax-tree/module.ts
+++ b/src/syntax-tree/module.ts
@@ -1,4 +1,5 @@
import { Position, Range } from "monaco-editor";
+
import { ActionExecutor } from "../editor/action-executor";
import { ActionFilter } from "../editor/action-filter";
import { CodeStatus, EditActionType } from "../editor/consts";
@@ -15,6 +16,7 @@ import { MessageController } from "../messages/message-controller";
import { ConstructHighlight } from "../messages/messages";
import { NotificationManager } from "../messages/notifications";
import { MenuController } from "../suggestions/suggestions-controller";
+import { SettingsController } from "../utilities/settings";
import { Util } from "../utilities/util";
import {
AutocompleteTkn,
@@ -58,6 +60,7 @@ export class Module {
typeSystem: TypeChecker;
notificationManager: NotificationManager;
toolboxController: ToolboxController;
+ settingsController: SettingsController;
scope: Scope;
draftExpressions: DraftRecord[];
@@ -146,6 +149,9 @@ export class Module {
this.menuController = MenuController.getInstance();
this.menuController.setInstance(this, this.editor);
+ this.settingsController = SettingsController.getInstance();
+ this.settingsController.setInstance(this);
+
Util.getInstance(this);
}
diff --git a/src/utilities/settings.ts b/src/utilities/settings.ts
new file mode 100644
index 0000000..d90ebc5
--- /dev/null
+++ b/src/utilities/settings.ts
@@ -0,0 +1,123 @@
+import { settingsConfigCategories } from "../editor/consts";
+import { Module } from "../syntax-tree/module";
+
+// Singleton controlling settings for the program
+export class SettingsController {
+ private static instance: SettingsController;
+
+ private modal: HTMLDivElement;
+ private settingsContainer: HTMLDivElement;
+ private settingsHeader: HTMLDivElement;
+ private settingsFooter: HTMLDivElement;
+ private exitBtn: HTMLDivElement;
+
+ public config: any;
+
+ module: Module;
+
+ constructor() {
+ this.config = {
+ [settingsConfigCategories.enabledColoredBlocks]: false,
+ [settingsConfigCategories.enabledSpotlightSearch]: false,
+ [settingsConfigCategories.enabledTyping]: false,
+ };
+
+ this.addEventListeners();
+ }
+
+ static getInstance() {
+ if (!SettingsController.instance) SettingsController.instance = new SettingsController();
+
+ return SettingsController.instance;
+ }
+
+ setInstance(module: Module) {
+ this.module = module;
+ }
+
+ private addEventListeners() {
+ const settingsBtn = document.getElementById("settingsBtn");
+
+ settingsBtn.addEventListener("click", () => {
+ this.renderSettings();
+ });
+ }
+
+ private renderSettings() {
+ this.modal = document.createElement("div");
+ this.modal.classList.add("settingsModal");
+ this.modal.id = "settingsModal";
+ document.getElementById("editor-container").appendChild(this.modal);
+
+ this.settingsContainer = document.createElement("div");
+ this.settingsContainer.classList.add("settingsContainer");
+ this.settingsContainer.id = "settingsContainer";
+ document.getElementById("settingsModal").appendChild(this.settingsContainer);
+
+ this.settingsHeader = document.createElement("div");
+ this.settingsHeader.classList.add("settingsHeader");
+ this.settingsHeader.id = "settingsHeader";
+ this.settingsHeader.innerHTML = "Settings";
+ this.settingsContainer.appendChild(this.settingsHeader);
+
+ Object.keys(this.config).map((key) => {
+ const setting = document.createElement("div");
+ setting.classList.add("setting");
+ this.settingsContainer.appendChild(setting);
+
+ const settingText = document.createElement("div");
+ settingText.classList.add("settingText");
+ settingText.innerHTML = key;
+ setting.appendChild(settingText);
+
+ const toggleBtn = document.createElement("label");
+ toggleBtn.classList.add("toggleBtn");
+ setting.appendChild(toggleBtn);
+
+ const toggleBtnCheckbox = document.createElement("input");
+ toggleBtnCheckbox.type = "checkbox";
+ toggleBtnCheckbox.checked = this.config[key];
+ toggleBtnCheckbox.id = key;
+ toggleBtn.appendChild(toggleBtnCheckbox);
+
+ const toggleBtnSlider = document.createElement("span");
+ toggleBtnSlider.classList.add("toggleBtnSlider");
+ toggleBtn.appendChild(toggleBtnSlider);
+
+ toggleBtnCheckbox.addEventListener("change", () => {
+ this.config[key] = toggleBtnCheckbox.checked;
+
+ //TODO: update the blocks to be colorless
+ //re-render the toolbox
+ if (toggleBtnCheckbox.id == settingsConfigCategories.enabledColoredBlocks) {
+ this.module.toolboxController.toggleToolboxColors();
+ }
+ });
+ });
+
+ this.settingsFooter = document.createElement("div");
+ this.settingsFooter.classList.add("settingsFooter");
+ this.settingsFooter.id = "settingsFooter";
+ this.settingsContainer.appendChild(this.settingsFooter);
+
+ this.exitBtn = document.createElement("div");
+ this.exitBtn.classList.add("exitBtn");
+ this.exitBtn.id = "exitBtn";
+ this.exitBtn.innerHTML = "Close";
+ this.settingsFooter.appendChild(this.exitBtn);
+
+ this.exitBtn.addEventListener("click", () => {
+ this.closeSettings();
+ });
+
+ window.onclick = (e: MouseEvent) => {
+ if (e.target === this.modal) {
+ this.closeSettings();
+ }
+ };
+ }
+
+ private closeSettings() {
+ this.modal.remove();
+ }
+}