diff --git a/Projects/Foundations/UI/Spaces/CodeEditor-Space/CodeEditorSpace.js b/Projects/Foundations/UI/Spaces/CodeEditor-Space/CodeEditorSpace.js index 97741c9d1..c62804b2d 100644 --- a/Projects/Foundations/UI/Spaces/CodeEditor-Space/CodeEditorSpace.js +++ b/Projects/Foundations/UI/Spaces/CodeEditor-Space/CodeEditorSpace.js @@ -6,7 +6,6 @@ function newFoundationsCodeEditorSpace() { container: undefined, editorPage: undefined, openSpaceArea: openSpaceArea, - // navigateTo: navigateTo, physics: physics, draw: draw, getContainer: getContainer, diff --git a/Projects/Foundations/UI/Spaces/CodeEditor-Space/EditorPage.js b/Projects/Foundations/UI/Spaces/CodeEditor-Space/EditorPage.js index 3ef5a294a..031cd2268 100644 --- a/Projects/Foundations/UI/Spaces/CodeEditor-Space/EditorPage.js +++ b/Projects/Foundations/UI/Spaces/CodeEditor-Space/EditorPage.js @@ -66,7 +66,7 @@ function newFoundationsCodeEditorEditorPage() { monacoEditor = undefined } - function render(originatingNode, rootNodes) { + function render(originatingNode) { thisObject.originatingNode = originatingNode intellisenseModels = [] @@ -93,6 +93,9 @@ function newFoundationsCodeEditorEditorPage() { monaco.editor.createModel(codeString, "javascript") monaco.editor.createModel(recordFormulaString, "javascript") + showAutocompletionFor(tradingEngineObj) + + monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true) monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ @@ -109,70 +112,145 @@ function newFoundationsCodeEditorEditorPage() { monacoEditor.getModel().onDidChangeContent(function () { originatingNode.payload.node.code = monacoEditor.getValue() }) + + + + function showAutocompletionFor(obj) { + // Disable default autocompletion for javascript + monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ noLib: true }); + + // Helper function to return the monaco completion item type of a thing + function getType(thing, isMember) { + isMember = (isMember === undefined) ? (typeof isMember == "boolean") ? isMember : false : false; // Give isMember a default value of false + + switch ((typeof thing).toLowerCase()) { + case "object": + return monaco.languages.CompletionItemKind.Class; + + case "function": + return (isMember) ? monaco.languages.CompletionItemKind.Method : monaco.languages.CompletionItemKind.Function; + + default: + return (isMember) ? monaco.languages.CompletionItemKind.Property : monaco.languages.CompletionItemKind.letiable; + } + } + + // Register object that will return autocomplete items + monaco.languages.registerCompletionItemProvider('javascript', { + // Run this function when the period or open parenthesis is typed (and anything after a space) + triggerCharacters: ['.', '('], + + // Function to generate autocompletion results + provideCompletionItems: function(model, position, token) { + // Split everything the user has typed on the current line up at each space, and only look at the last word + let last_chars = model.getValueInRange({startLineNumber: position.lineNumber, startColumn: 0, endLineNumber: position.lineNumber, endColumn: position.column}); + let words = last_chars.replace("\t", "").split(" "); + let active_typing = words[words.length - 1]; // What the user is currently typing (everything after the last space) + if (active_typing.includes('(')) { + let split = active_typing.split('('); + active_typing = split[split.length-1] + } + + // If the last character typed is a period then we need to look at member objects of the obj object + let is_member = active_typing.charAt(active_typing.length - 1) === "."; + + // Array of autocompletion results + let result = []; + + // Used for generic handling between member and non-member objects + let last_token = obj; + let prefix = ''; + + if (is_member) { + // Is a member, get a list of all members, and the prefix + let parents = active_typing.substring(0, active_typing.length - 1).split("."); + last_token = obj[parents[0]]; + prefix = parents[0]; + + // Loop through all the parents the current one will have (to generate prefix) + for (let i = 1; i < parents.length; i++) { + if (last_token.hasOwnProperty(parents[i])) { + prefix += '.' + parents[i]; + last_token = last_token[parents[i]]; + } else { + // Not valid + return result; + } + } + + prefix += '.'; + } + + // Get all the child properties of the last token + for (let prop in last_token) { + // Do not show properites that begin with "__" + if (last_token.hasOwnProperty(prop) && !prop.startsWith("__")) { + // Get the detail type (try-catch) incase object does not have prototype + let details = ''; + try { + details = last_token[prop].__proto__.constructor.name; + } catch (e) { + details = typeof last_token[prop]; + } + + // Create completion object + let to_push = { + label: prefix + prop, + kind: getType(last_token[prop], is_member), + detail: details, + insertText: prop + }; + + // Change insertText and documentation for functions + if (to_push.detail.toLowerCase() === 'function') { + to_push.insertText += "("; + to_push.documentation = (last_token[prop].toString()).split("{")[0]; // Show function prototype in the documentation popup + } + + // Add to final results + result.push(to_push); + } + } + + return { + suggestions: result + }; + } + }); + } + }); } /** * The autocomplete models will be based on: - * 1. all data mines loaded into workspace - * 2. trading engine loaded into the workspace + * 1. all data mines loaded into workspace - by creating a model in monaco (basically is like opening a file containing all the variables in vs-code, thus it will make them available in other files as well) + * 2. trading engine loaded into the workspace - we create an obj. representation using getProtocolNode and we are using monaco registerCompletionItemProvider * - * TODO: It is compulsory that at one moment in time we should add proper definitions for our variables (to mimic some framework), but this is a different task and can be improved over time - * TODO: It is recommended that for extending it to read the monaco editor docs: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.completionitemprovider.html and https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example - * TODO: Right now the intellisense model is rather primitive being built on existing code available in the workspace without having any definitions, but strong enough to provide accurate autocompletion */ function buildIntelliSenseModels() { UI.projects.foundations.spaces.designSpace.workspace.workspaceNode.rootNodes.forEach(node => { if (node.type === 'Data Mine') { - //Getting all the Javascript code into one single variable + //Getting all the Javascript code into one single letiable UI.projects.visualScripting.utilities.branches.nodeBranchToArray(node, 'Procedure Javascript Code').forEach(node => { codeString += '\n' codeString += node.code }) - //Getting all the Formula code into one single variable + //Getting all the Formula code into one single letiable UI.projects.visualScripting.utilities.branches.nodeBranchToArray(node, 'Record Formula').forEach(node => { recordFormulaString += '\n' recordFormulaString += node.code }) } else if (node.type === 'Trading Engine') { // Create an object representation for - tradingEngineObj = UI.projects.visualScripting.functionLibraries.protocolNode.getProtocolNode(node, false, true, true, false, false, undefined) + tradingEngineObj = {tradingEngine : UI.projects.visualScripting.functionLibraries.protocolNode.getProtocolNode(node, false, false, false, false, false, undefined)} } }) - console.log(codeString) - console.log(recordFormulaString) - - function extractCodeValues(node) { - - } - - function stepBackUntilNodeType(originatingNode, nodeType) { - if (originatingNode === undefined) { - return - } - if (nodeType === undefined) { - return - } - if (originatingNode.payload === undefined) { - return - } - if (originatingNode.payload.parentNode === undefined) { - return - } - - - let parent = originatingNode.payload.parentNode; - if (parent.type === nodeType) { - console.log(parent) - return parent - } else { - stepBackUntilNodeType(parent, nodeType) - } - } } + function createCodePath() { let parentNodeStructure = [] @@ -202,5 +280,4 @@ function newFoundationsCodeEditorEditorPage() { } } } - } \ No newline at end of file diff --git a/Projects/Foundations/UI/Spaces/Floating-Space/CodeEditor.js b/Projects/Foundations/UI/Spaces/Floating-Space/CodeEditor.js index 5724ef4f7..638859952 100644 --- a/Projects/Foundations/UI/Spaces/Floating-Space/CodeEditor.js +++ b/Projects/Foundations/UI/Spaces/Floating-Space/CodeEditor.js @@ -1,6 +1,7 @@ /** * * The only reason we are keeping this class is to keep consistency across the uiObject implementation + * It will also provide a good place to enrich the action received, separating the logic of the action from the editor space * Entire functionality is provided by CodeEditor Space */ function newCodeEditor() { diff --git a/Projects/Foundations/UI/Spaces/Floating-Space/FormulaEditor.js b/Projects/Foundations/UI/Spaces/Floating-Space/FormulaEditor.js index 580788436..65b62c35c 100644 --- a/Projects/Foundations/UI/Spaces/Floating-Space/FormulaEditor.js +++ b/Projects/Foundations/UI/Spaces/Floating-Space/FormulaEditor.js @@ -1,200 +1,30 @@ - +/** + * + * The only reason we are keeping this class is to keep consistency across the uiObject implementation when it comes to action handlers + * It will also provide a good place to enrich the action received, separating the logic of the action from the editor space + * Entire functionality is provided by CodeEditor Space + */ function newFormulaEditor() { const MODULE_NAME = 'Formula Editor' let thisObject = { - isVisibleFunction: undefined, - visible: false, - imagePathOK: undefined, - imagePathNOT_OK: undefined, - rawRadius: undefined, - targetRadius: undefined, - currentRadius: undefined, - container: undefined, - payload: undefined, - deactivate: deactivate, activate: activate, - physics: physics, - drawBackground: drawBackground, - drawForeground: drawForeground, - getContainer: getContainer, - finalize: finalize, initialize: initialize } - thisObject.container = newContainer() - thisObject.container.initialize(MODULE_NAME) - thisObject.container.isClickeable = false - thisObject.container.isDraggeable = false - thisObject.container.detectMouseOver = false - thisObject.container.frame.radius = 0 - thisObject.container.frame.position.x = 0 - thisObject.container.frame.position.y = 0 - - let isMouseOver = false - let SIZE_FACTOR = 1.0 - return thisObject + function finalize() { - thisObject.container.finalize() - thisObject.container = undefined - thisObject.payload = undefined - thisObject.isVisibleFunction = undefined + } function initialize() { } - function deactivate() { - if (thisObject.visible === true) { - thisObject.visible = false - let textArea = document.getElementById('textArea') - textArea.style.display = 'none' - thisObject.payload.node.code = textArea.value - } - EDITOR_ON_FOCUS = false - } function activate(action) { - thisObject.visible = true - thisObject.payload = action.node.payload - thisObject.rawRadius = 8 - thisObject.targetRadius = thisObject.container.frame.radius - thisObject.currentRadius = 0 - thisObject.payload.uiObject.resetErrorMessage() - thisObject.payload.uiObject.uiObjectTitle.exitEditMode() - - let textArea = document.getElementById('textArea') - textArea.value = thisObject.payload.node.code - textArea.style = 'resize: none;' + - ' border: none;' + - ' outline: none;' + - 'box-shadow: none;' + - 'overflow:hidden;' + - 'font-family: ' + UI_FONT.PRIMARY + ';' + - 'font-size: 14px;' + - 'background-color: rgb(' + UI_COLOR.MANGANESE_PURPLE + ');' + - 'color:rgb(255, 255, 255);' + - 'width: ' + thisObject.container.frame.width + 'px;' + - 'height: ' + thisObject.container.frame.height + 'px' - textArea.style.display = 'block' - textArea.focus() - EDITOR_ON_FOCUS = true - } - - function getContainer(point) { - let container - if (thisObject.visible === true) { - if (point.x === VERY_LARGE_NUMBER) { - /* The the mouse leaves the canvas, and event of mouse over with a ridiculous coordinate is triggered so that - anyone can react. In our case, the code editor has a text area that is not part of the canvas, so the event is - triggered. We compensate recognizing this coordinate and returning our container. */ - return thisObject.container - } - - if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { - return thisObject.container - } else { - return undefined - } - } - } - - function physics() { - if (Math.abs(thisObject.currentRadius - thisObject.targetRadius) >= 0.5) { - if (thisObject.currentRadius < thisObject.targetRadius) { - thisObject.currentRadius = thisObject.currentRadius + 0.5 - } else { - thisObject.currentRadius = thisObject.currentRadius - 0.5 - } - } - - thisObject.container.frame.position.x = 0 - thisObject.container.frame.position.y = 0 - - thisObject.container.frame.width = thisObject.container.frame.radius * 1 * SIZE_FACTOR - thisObject.container.frame.height = thisObject.container.frame.radius * 1 * SIZE_FACTOR - - let textAreaPosition = { - x: 0 - thisObject.container.frame.width / 2, - y: 0 - thisObject.container.frame.height * 3 / 7 + CURRENT_TOP_MARGIN - } - - textAreaPosition = thisObject.container.frame.frameThisPoint(textAreaPosition) - if (thisObject.visible === true) { - let checkPosition = { - x: textAreaPosition.x, - y: textAreaPosition.y - CURRENT_TOP_MARGIN - } - if (thisObject.isVisibleFunction(checkPosition) === false) { - deactivate() - } - } - if (thisObject.visible === true) { - let textAreaDiv = document.getElementById('textAreaDiv') - textAreaDiv.style = 'position:fixed; top:' + textAreaPosition.y + 'px; left:' + textAreaPosition.x + 'px; z-index:1; ' - } - } - - function drawBackground() { - if (thisObject.visible === true) { - let position = { - x: 0, - y: 0 - } - - position = thisObject.container.frame.frameThisPoint(position) - - let radius = thisObject.container.frame.radius * SIZE_FACTOR - - if (radius > 0.5) { - browserCanvasContext.beginPath() - browserCanvasContext.arc(position.x, position.y, radius + 3, 0, Math.PI * 2, true) - browserCanvasContext.closePath() - browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + 1 + ')' - browserCanvasContext.fill() - - browserCanvasContext.beginPath() - browserCanvasContext.arc(position.x, position.y, radius , 0, Math.PI * 2, true) - browserCanvasContext.closePath() - browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.MANGANESE_PURPLE + ', ' + 1 + ')' - browserCanvasContext.fill() - } - } - } - function drawForeground() { - let iconPosition = { - x: 0, - y: thisObject.currentRadius * 1.5 - } - - iconPosition = thisObject.container.frame.frameThisPoint(iconPosition) - - /* Menu Item */ - - if (thisObject.canDrawIcon === true && thisObject.currentRadius > 1) { - browserCanvasContext.drawImage(thisObject.icon, iconPosition.x - thisObject.currentRadius, iconPosition.y - thisObject.currentRadius, thisObject.currentRadius * 2, thisObject.currentRadius * 2) - - /* Menu Label */ - - let label = 'Code looks good!' - let labelPoint - let fontSize = 10 - - browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY - - if (thisObject.currentRadius >= thisObject.targetRadius) { - labelPoint = { - x: iconPosition.x + thisObject.currentRadius + 10, - y: iconPosition.y + fontSize * FONT_ASPECT_RATIO - } - - browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY - browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' - browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) - } - } + UI.projects.foundations.spaces.codeEditorSpace.openSpaceArea(action.node) } } diff --git a/Projects/Foundations/UI/Spaces/Floating-Space/UiObject.js b/Projects/Foundations/UI/Spaces/Floating-Space/UiObject.js index a6c30fed1..cd3f889dc 100644 --- a/Projects/Foundations/UI/Spaces/Floating-Space/UiObject.js +++ b/Projects/Foundations/UI/Spaces/Floating-Space/UiObject.js @@ -16,7 +16,6 @@ function newUiObject() { payload: undefined, configEditor: undefined, conditionEditor: undefined, - formulaEditor: undefined, listSelector: undefined, uiObjectTitle: undefined, icon: undefined, @@ -202,10 +201,6 @@ function newUiObject() { thisObject.conditionEditor = undefined } - if (thisObject.formulaEditor !== undefined) { - thisObject.formulaEditor.finalize() - thisObject.formulaEditor = undefined - } if (thisObject.listSelector !== undefined) { thisObject.listSelector.finalize() @@ -303,11 +298,6 @@ function newUiObject() { if (container !== undefined) { return container } } - if (thisObject.formulaEditor !== undefined) { - container = thisObject.formulaEditor.getContainer(point) - if (container !== undefined) { return container } - } - if (thisObject.listSelector !== undefined) { container = thisObject.listSelector.getContainer(point) if (container !== undefined) { return container } @@ -326,11 +316,6 @@ function newUiObject() { getitle = false } } - if (thisObject.formulaEditor !== undefined) { - if (thisObject.formulaEditor.visible === true) { - getitle = false - } - } if (thisObject.listSelector !== undefined) { if (thisObject.listSelector.visible === true) { getitle = false @@ -380,9 +365,6 @@ function newUiObject() { thisObject.conditionEditor.physics() } - if (thisObject.formulaEditor !== undefined) { - thisObject.formulaEditor.physics() - } if (thisObject.listSelector !== undefined) { thisObject.listSelector.physics() @@ -1354,9 +1336,7 @@ function newUiObject() { if (thisObject.conditionEditor !== undefined) { thisObject.conditionEditor.deactivate() } - if (thisObject.formulaEditor !== undefined) { - thisObject.formulaEditor.deactivate() - } + if (thisObject.listSelector !== undefined) { thisObject.listSelector.deactivate() } @@ -1383,9 +1363,6 @@ function newUiObject() { if (thisObject.conditionEditor !== undefined) { thisObject.conditionEditor.deactivate() } - if (thisObject.formulaEditor !== undefined) { - thisObject.formulaEditor.deactivate() - } if (thisObject.listSelector !== undefined) { thisObject.listSelector.deactivate() } @@ -1461,18 +1438,12 @@ function newUiObject() { drawChainLine() - if (thisObject.configEditor !== undefined) { - thisObject.configEditor.drawBackground() - thisObject.configEditor.drawForeground() - } + if (thisObject.conditionEditor !== undefined) { thisObject.conditionEditor.drawBackground() thisObject.conditionEditor.drawForeground() } - if (thisObject.formulaEditor !== undefined) { - thisObject.formulaEditor.drawBackground() - thisObject.formulaEditor.drawForeground() - } + if (thisObject.listSelector !== undefined) { thisObject.listSelector.drawBackground() thisObject.listSelector.drawForeground() @@ -1494,12 +1465,7 @@ function newUiObject() { drawTitle = false } } - if (thisObject.formulaEditor !== undefined) { - if (thisObject.formulaEditor.visible === true) { - drawMenu = false - drawTitle = false - } - } + if (thisObject.listSelector !== undefined) { if (thisObject.listSelector.visible === true) { drawMenu = false @@ -1678,9 +1644,6 @@ function newUiObject() { if (thisObject.conditionEditor.visible === true) { return true } } - if (thisObject.formulaEditor !== undefined) { - if (thisObject.formulaEditor.visible === true) { return true } - } if (thisObject.listSelector !== undefined) { if (thisObject.listSelector.visible === true) { return true } } diff --git a/Projects/Foundations/UI/Spaces/Floating-Space/UiObjectConstructor.js b/Projects/Foundations/UI/Spaces/Floating-Space/UiObjectConstructor.js index 776264341..2864cc672 100644 --- a/Projects/Foundations/UI/Spaces/Floating-Space/UiObjectConstructor.js +++ b/Projects/Foundations/UI/Spaces/Floating-Space/UiObjectConstructor.js @@ -484,8 +484,6 @@ function newUiObjectConstructor() { } if (schemaDocument.editors.formula === true) { uiObject.formulaEditor = newFormulaEditor() - uiObject.formulaEditor.isVisibleFunction = uiObject.isVisibleFunction - uiObject.formulaEditor.container.connectToParent(uiObject.container, false, false, true, true, false, false, false, false) uiObject.formulaEditor.initialize() } if (schemaDocument.editors.list === true) {