1069 lines
37 KiB
JavaScript
1069 lines
37 KiB
JavaScript
import {startEditor, getWrapper, RegisteredFileSystemProvider, registerFileSystemOverlay,
|
|
RegisteredMemoryFile, useWorkerFactory, api2, Uri, CommandsQuickAccessProvider} from "./monacoPack.js";
|
|
import { createUserConfig } from "./monacoConfig.js";
|
|
import { format as SQLformatter } from "../monaco/resources/sql_formatter.js";
|
|
|
|
var currentUserConfig, currentSetup;
|
|
|
|
export const configureMonacoWorkers = () => {
|
|
return new Promise((resolve, reject) => {
|
|
try{
|
|
useWorkerFactory({
|
|
basePath: "./worker",
|
|
});
|
|
resolve();
|
|
} catch(e) {
|
|
console.error(e);
|
|
reject(e);
|
|
}
|
|
});
|
|
};
|
|
|
|
// UTILITY FUNCTIONS
|
|
/**
|
|
* Function that allows to format the expression builder code
|
|
* @param {String} text: the expression builder text to format
|
|
* @returns the text formatted
|
|
*/
|
|
function expBuilderFormatter(text){
|
|
// remove previous indentation
|
|
text = text.replace(/\n|\t|\r/g,"");
|
|
let currIndentLvl = 0;
|
|
let inString = false;
|
|
let inFunction = false;
|
|
|
|
function getFunctionSize(text){
|
|
let lvl = 0;
|
|
let s = 0;
|
|
for(let i=0; i<text.length; i++){
|
|
if(text[i] == '('){
|
|
if(lvl > 0){
|
|
s++;
|
|
}
|
|
lvl++;
|
|
} else if(text[i] == ')'){
|
|
lvl--;
|
|
if(lvl == 0){
|
|
return s;
|
|
} else {
|
|
s++;
|
|
}
|
|
} else {
|
|
s++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(let i=0; i<text.length; i++){
|
|
if(text[i] == "(" && text[i+1] != ")" && !inString && inFunction){
|
|
let functionSize = getFunctionSize(text.slice(i));
|
|
if(functionSize > 80){
|
|
currIndentLvl++;
|
|
text = text.slice(0, i+1) + "\n" + "\t".repeat(currIndentLvl) + text.slice(i+1, text.length);
|
|
inFunction = false;
|
|
i = i + 1 + currIndentLvl;
|
|
} else {
|
|
i = i + functionSize + 1;
|
|
}
|
|
} else if(text[i] == ")" && text[i-1] != "(" && !inString && text[i+1] == "]"){
|
|
currIndentLvl--;
|
|
text = text.slice(0, i) + "\n" + "\t".repeat(currIndentLvl) + text.slice(i, text.length);
|
|
i = i + 1 + currIndentLvl;
|
|
} else if(text[i] == "," && !inString){
|
|
text = text.slice(0, i+1) + "\n" + "\t".repeat(currIndentLvl) + text.slice(i+1, text.length);
|
|
i = i + 1 + currIndentLvl;
|
|
} else if(text[i] == "'"){
|
|
inString = !inString;
|
|
} else if(text[i] == "["){
|
|
inFunction = true;
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
|
|
/**
|
|
* Function to add/remove bookmarks on the specified line
|
|
* @param {int} lineNum: line where to toggle the bookmark
|
|
*/
|
|
function toggleBookmark(lineNum) {
|
|
let editor = getEditor();
|
|
let monaco = getMonaco();
|
|
let lineBookmarks = [];
|
|
|
|
// check the decorators on the selected line
|
|
editor.getLineDecorations(lineNum).forEach(decoration => {
|
|
if (decoration.options.className === 'Bookmark') {
|
|
lineBookmarks.push(decoration.id);
|
|
}
|
|
});
|
|
|
|
// add the bookmark if no decorators are present, remove the decorator otherwise
|
|
if (lineBookmarks.length == 0) {
|
|
editor.deltaDecorations(
|
|
[],
|
|
[
|
|
{
|
|
range: new monaco.Range(lineNum, 1, lineNum, Infinity),
|
|
options: {
|
|
isWholeLine: true,
|
|
className:'Bookmark',
|
|
glyphMarginClassName:'BookmarkGlyphMarginClass',
|
|
overviewRuler:{
|
|
color: 'rgba(21,126,251,0.8)',
|
|
},
|
|
stickiness: 2
|
|
}
|
|
}
|
|
]
|
|
);
|
|
if(!editor.bookmarks.includes(lineNum)){
|
|
editor.bookmarks.push(lineNum);
|
|
}
|
|
}
|
|
else {
|
|
editor.deltaDecorations(lineBookmarks, []);
|
|
editor.bookmarks.splice(editor.bookmarks.indexOf(lineNum), 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to move the focus on the next bookmark
|
|
* @param {int} currentLine: the current position of the focus
|
|
*/
|
|
function toNextBookmark(currentLine){
|
|
let editor = getEditor();
|
|
let bookmarks = [];
|
|
for(let i=0;i<editor.getModel().getAllMarginDecorations().length;i++){
|
|
bookmarks.push(editor.getModel().getAllMarginDecorations()[i].range.startLineNumber);
|
|
}
|
|
|
|
// find next bookmark
|
|
let next = Infinity;
|
|
for(let i = 0; i < bookmarks.length; i++){
|
|
if(((bookmarks[i] - currentLine) < next) && (bookmarks[i] > currentLine)){
|
|
next = bookmarks[i];
|
|
break;
|
|
}
|
|
}
|
|
// move to next bookmark
|
|
if((next == Infinity) && (bookmarks.length > 0)){
|
|
editor.setPosition({ lineNumber: bookmarks[0], column: 1 });
|
|
editor.revealLineInCenter(bookmarks[0]);
|
|
}
|
|
else{
|
|
editor.setPosition({ lineNumber: next, column: 1 });
|
|
editor.revealLineInCenter(next);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to move the focus on the previous bookmark
|
|
* @param {*} currentLine: the current position of the focus
|
|
*/
|
|
function toPreviousBookmark(currentLine){
|
|
let editor = getEditor();
|
|
let bookmarks = [];
|
|
for(let i=0;i<editor.getModel().getAllMarginDecorations().length;i++){
|
|
bookmarks.push(editor.getModel().getAllMarginDecorations()[i].range.startLineNumber);
|
|
}
|
|
|
|
// find previous bookmark
|
|
let next = Infinity;
|
|
for(let i = 0; i < bookmarks.length; i++){
|
|
if(((bookmarks[i] - currentLine) < next) && (bookmarks[i] < currentLine)){
|
|
next = bookmarks[i];
|
|
}
|
|
}
|
|
// move to previous bookmark
|
|
if((next == Infinity) && (bookmarks.length > 0)){
|
|
editor.setPosition({ lineNumber: bookmarks[bookmarks.length - 1], column: 1 });
|
|
}
|
|
else{
|
|
editor.setPosition({ lineNumber: next, column: 1 });
|
|
editor.revealLineInCenter(next);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function used to build monaco editor shortcuts
|
|
* @param id : id required to identify the action
|
|
* @param label: the name of the shortcut
|
|
* @param keybindings: the keys that must be pressed to trigger the shortcut
|
|
* @param runFunction: the function that specify what must happens when the shortcut is triggered
|
|
* @returns
|
|
*/
|
|
function createEditorAction(id, label, keybindings, runFunction) {
|
|
return {
|
|
id: id,
|
|
label: label,
|
|
keybindings: keybindings,
|
|
run: runFunction
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Function used to create the complete list of functions and events for a specified portlet control on the fly
|
|
* @param position: necessary to know where to insert the suggestion once selected
|
|
* @param item: the portlet control whose suggestions we want to obtain
|
|
* @param controls: the full list of controls in the portlet
|
|
* @param objClass: the list of the default functions and events
|
|
*/
|
|
function createSuggestions(position, item, controls, objClass){
|
|
let monaco = getMonaco();
|
|
let completionItems = [];
|
|
let events, functs;
|
|
// if an item is NOT provided (i.e. 'this.' case) return list of items + default functions/events
|
|
// if an item is provided return item-specific functions/events
|
|
if(!item){
|
|
// items
|
|
for(let i = 0; i < controls.length; i++){
|
|
let suggestion = { label: controls[i].name,
|
|
detail: "(item)",
|
|
insertText: controls[i].name,
|
|
kind: monaco.languages.CompletionItemKind.Field
|
|
}
|
|
completionItems.push(suggestion);
|
|
}
|
|
events = objClass.events;
|
|
functs = objClass.functions;
|
|
} else {
|
|
events = item.objClass.events;
|
|
functs = item.objClass.functions;
|
|
}
|
|
|
|
// events
|
|
for(let i = 0; i < events?.length; i++){
|
|
let codeText, eventLabel, eventDoc, goBack;
|
|
switch(typeof events[i]){
|
|
case 'string':
|
|
eventLabel = events[i];
|
|
break;
|
|
case 'object':
|
|
if(events[i].name){ // event format type 1
|
|
eventLabel = events[i].name;
|
|
eventDoc = events[i].tooltip;
|
|
} else { // event format type 2
|
|
eventLabel = events[i][0];
|
|
}
|
|
break;
|
|
}
|
|
if(eventLabel.endsWith(")")){
|
|
if(!item) {
|
|
codeText = "function this_" + eventLabel + "{\n\n}";
|
|
goBack = 5;
|
|
} else {
|
|
codeText = "function " + item.name + "_" + eventLabel + "{\n\n}";
|
|
goBack = 5 + item.name.length + 1;
|
|
}
|
|
} else {
|
|
if(!item) {
|
|
codeText = "function this_" + eventLabel + "(){\n\n}";
|
|
goBack = 5;
|
|
} else {
|
|
codeText = "function " + item.name + "_" + eventLabel + "(){\n\n}";
|
|
goBack = 5 + item.name.length + 1;
|
|
}
|
|
}
|
|
let suggestion = { label: eventLabel.replace(/\(([^)]*)\)/,""),
|
|
insertText: codeText,
|
|
detail: "(event) ",
|
|
documentation: eventDoc,
|
|
kind: monaco.languages.CompletionItemKind.Event,
|
|
// additionalTextEdits used to remove 'this.'
|
|
additionalTextEdits: [{
|
|
range: {
|
|
startLineNumber: position.lineNumber,
|
|
startColumn: (position.column - goBack),
|
|
endLineNumber: position.lineNumber,
|
|
endColumn: position.column
|
|
},
|
|
text: ""
|
|
}]
|
|
}
|
|
completionItems.push(suggestion);
|
|
}
|
|
|
|
// functions
|
|
for(let i = 0; i < functs?.length; i++){
|
|
let funcLabel, funcDoc, funcDetail, parameters;
|
|
switch(typeof functs[i]){
|
|
case 'string':
|
|
funcLabel = functs[i];
|
|
funcDetail = "(function)";
|
|
funcDoc = "";
|
|
let funcParams = funcLabel ? funcLabel.match(/\(([^)]*)\)/)?.[1]?.split(",") : [];
|
|
funcDetail = (funcLabel ? "(function) Item." + funcLabel.replace(/\(([^)]*)\)/,"") + "(" : "");
|
|
parameters = [];
|
|
if(funcParams && funcParams[0] != ""){
|
|
for (let j = 0; j < funcParams.length; j++){
|
|
let p = funcParams[j];
|
|
// remove #
|
|
if(p.startsWith("#") && p.endsWith("#")){
|
|
p = p.replace(/#/g,"");
|
|
}
|
|
let type = null;
|
|
// optional parameters checkA
|
|
if(p.substring(0,3) == "opt"){
|
|
p = p.replace(/opt_/, "");
|
|
funcDetail = funcDetail + "?";
|
|
}
|
|
// type of parameters
|
|
if((p[1]?.toUpperCase() == p[1]) && (p[0].toLowerCase() == p[0])){
|
|
let typeChar = p[0];
|
|
switch(typeChar){
|
|
case "c":
|
|
type = "String";
|
|
break;
|
|
case "n":
|
|
type = "number";
|
|
break;
|
|
case "b":
|
|
type = "boolean";
|
|
break;
|
|
}
|
|
if(typeChar != "x"){
|
|
p = (p.length > 1) ? p.slice(1) : p; // remove type char
|
|
funcDetail = funcDetail + p + ": " + type;
|
|
} else {
|
|
p = p.slice(1);
|
|
funcDetail = funcDetail + p;
|
|
}
|
|
} else {
|
|
funcDetail = funcDetail + p;
|
|
}
|
|
parameters.push({name: p, type: type, tooltip:'No tooltip available'});
|
|
if(j < (funcParams.length-1)){
|
|
funcDetail = funcDetail + ", ";
|
|
}
|
|
}
|
|
}
|
|
funcDetail = funcDetail + ")";
|
|
break;
|
|
case 'object':
|
|
if(functs[i].name){ // function format type 1
|
|
funcLabel = functs[i].name;
|
|
funcDoc = functs[i].usage;
|
|
funcDetail = "(function) Item." + funcLabel + "(";
|
|
if(functs[i].parameters){
|
|
for (let j = 0; j < functs[i].parameters.length; j++){
|
|
let p = functs[i].parameters[j];
|
|
if(p.optional){
|
|
funcDetail = funcDetail + "?";
|
|
}
|
|
funcDetail = funcDetail + p.name + ": " + p.type;
|
|
if(j < (functs[i].parameters.length-1)){
|
|
funcDetail = funcDetail + ", ";
|
|
}
|
|
}
|
|
}
|
|
funcDetail = funcDetail + ")";
|
|
} else { // function format type 2
|
|
funcLabel = functs[i][1];
|
|
funcDoc = functs[i][0]?.split("->")[1] || functs[1].usage;
|
|
let funcParams = funcLabel ? funcLabel.match(/\(([^)]*)\)/)?.[1]?.split(",") : [];
|
|
funcDetail = (funcLabel ? "(function) Item." + funcLabel.replace(/\(([^)]*)\)/,"") + "(" : "");
|
|
functs[i].parameters = [];
|
|
if(funcParams && funcParams[0] != ""){
|
|
for (let j = 0; j < funcParams.length; j++){
|
|
let p = funcParams[j];
|
|
// remove #
|
|
if(p.startsWith("#") && p.endsWith("#")){
|
|
p = p.replace(/#/g,"");
|
|
}
|
|
let type = null;
|
|
// optional parameters check
|
|
if(p.substring(0,3) == "opt"){
|
|
p = p.replace(/opt_/, "");
|
|
funcDetail = funcDetail + "?";
|
|
}
|
|
// type of parameters
|
|
if((p[1].toUpperCase() == p[1]) && (p[0].toLowerCase() == p[0])){
|
|
let typeChar = p[0];
|
|
switch(typeChar){
|
|
case "c":
|
|
type = "String";
|
|
break;
|
|
case "n":
|
|
type = "number";
|
|
break;
|
|
case "b":
|
|
type = "boolean";
|
|
break;
|
|
}
|
|
if(typeChar != "x"){
|
|
p = p.slice(1); // remove type char
|
|
funcDetail = funcDetail + p + ": " + type;
|
|
} else {
|
|
p = p.slice(1);
|
|
funcDetail = funcDetail + p;
|
|
}
|
|
} else {
|
|
funcDetail = funcDetail + p;
|
|
}
|
|
functs[i].parameters.push({name: p, type: type, tooltip:'No tooltip available'});
|
|
if(j < (funcParams.length-1)){
|
|
funcDetail = funcDetail + ", ";
|
|
}
|
|
}
|
|
}
|
|
funcDetail = funcDetail + ")";
|
|
}
|
|
break;
|
|
}
|
|
|
|
if(funcLabel){
|
|
let suggestion = { label: funcLabel.replace(/\(([^)]*)\)/,""),
|
|
insertText: funcLabel.replace(/\(([^)]*)\)/,""),
|
|
detail: funcDetail,
|
|
documentation: funcDoc,
|
|
parameters: functs[i].parameters || parameters,
|
|
kind: monaco.languages.CompletionItemKind.Function
|
|
}
|
|
completionItems.push(suggestion);
|
|
}
|
|
|
|
}
|
|
return completionItems;
|
|
}
|
|
|
|
/**
|
|
* Functions that remove from the command list (ctrl+shift+P) the default functions and keep only the ones defined in the editor
|
|
*/
|
|
function disableDefaultCommands(additionalCommands){
|
|
const originalFunc = CommandsQuickAccessProvider.prototype.getCommandPicks;
|
|
CommandsQuickAccessProvider.prototype.getCommandPicks = function (token) {
|
|
const filteredCommands = new Promise((resolve) => {
|
|
const allCommandsPromise = originalFunc.call(this, token);
|
|
allCommandsPromise.then((commands) => {
|
|
// the only commands allowed are the one defined in the editor (i.e. the ones that start with 'vs.editor.ICodeEditor:1:')
|
|
var allowedCommands = commands.reduce((acc, item) => {
|
|
if (item.commandId.startsWith("vs.editor.ICodeEditor:1:")) {
|
|
acc.push(item.commandId);
|
|
}
|
|
return acc;
|
|
}, []);
|
|
allowedCommands = allowedCommands.concat(additionalCommands);
|
|
resolve(commands.filter((command) => allowedCommands.includes(command.commandId)));
|
|
});
|
|
});
|
|
return filteredCommands;
|
|
};
|
|
}
|
|
|
|
// EDITORS DEFINITIONS
|
|
|
|
/**
|
|
* Function that allows to initialize the action code editor
|
|
* @param setup: object that contains informations like the html container, the code to put into the editor, the theme selected by the user,
|
|
* the font size, the saved position of the bookmarks, the tab size
|
|
* @param controls: the full list of controls in the portlet
|
|
* @param objClass: the list of the default functions and events
|
|
* @returns the action code editor set up
|
|
*/
|
|
export const runActionCodeEditor = (setup, controls, objClass) => {
|
|
return new Promise(async (resolve, reject) => {
|
|
// const code = '';
|
|
// const fileSystemProvider = new RegisteredFileSystemProvider(false);
|
|
// fileSystemProvider.registerFile(
|
|
// new RegisteredMemoryFile(Uri.file("/workspace/actionCode.js"), code)
|
|
// );
|
|
// registerFileSystemOverlay(1, fileSystemProvider);
|
|
try {
|
|
let monacoSetup = {
|
|
startLangServer: false,
|
|
language: "javascript",
|
|
editorOptions: { theme: setup.theme, hover: {above: false, enabled: true}, parameterHints: { enabled: true }, fontSize: setup.fontSize}
|
|
};
|
|
const userConfig = createUserConfig(monacoSetup); // change second parameter to TRUE if you want to start the language server
|
|
const htmlElement = document.getElementById(setup.htmlContainer);
|
|
await startEditor(userConfig, htmlElement);
|
|
|
|
let editor = getEditor();
|
|
let monaco = getMonaco();
|
|
|
|
disableDefaultCommands();
|
|
|
|
editor.setValue(setup.code); // set the action code
|
|
editor.getModel().updateOptions({tabSize: setup.tabSize}); // set the tab size equals to 2 spaces
|
|
editor.setPosition({ lineNumber: 1, column: 1 });
|
|
editor.focus();
|
|
|
|
// load bookmarks
|
|
editor.bookmarks = setup.bookmarks;
|
|
for(let i=0; i<editor.bookmarks.length; i++){
|
|
toggleBookmark(editor.bookmarks[i]);
|
|
}
|
|
|
|
// function to add bookmark via gutter click
|
|
editor.onMouseDown((e) => {
|
|
if (e.target.type === monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN) {
|
|
toggleBookmark(e.target.position.lineNumber);
|
|
}
|
|
})
|
|
|
|
// add shortcuts
|
|
const actions = [
|
|
// toggle bookmark
|
|
createEditorAction('toggleBookmark', 'Toggle Bookmark',
|
|
[monaco.KeyMod.CtrlCmd | monaco.KeyCode.F8], () => toggleBookmark(editor.getSelection().startLineNumber)),
|
|
// move to next bookmakr
|
|
createEditorAction('nextBookmark', 'Move To Next Bookmark',
|
|
[monaco.KeyCode.F8], () => toNextBookmark(editor.getSelection().startLineNumber)),
|
|
// move to previus bookmark
|
|
createEditorAction('previousBookmark', 'Move to Previous Bookmark',
|
|
[monaco.KeyMod.Shift | monaco.KeyCode.F8], () => toPreviousBookmark(editor.getSelection().startLineNumber)),
|
|
// save
|
|
createEditorAction('save', 'Save', [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
|
|
() => {
|
|
parent.parent.saveTool ? parent.parent.saveTool() : parent.save ? parent.save(): void 0;
|
|
parent.managed = true;
|
|
}),
|
|
// comment line
|
|
createEditorAction('commentQ', 'Add Line Comment Alternative', [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyQ],
|
|
() => {
|
|
editor.trigger('keyboard', 'editor.action.commentLine');
|
|
})
|
|
];
|
|
actions.forEach(action => editor.addAction(action));
|
|
|
|
// COMPLETION ITEM PROVIDER
|
|
// create suggestions for 'this.'
|
|
var suggestions = {};
|
|
for(let i = 0; i < controls.length; i++){
|
|
suggestions[controls[i].name] = createSuggestions({lineNumber:0, column:0}, controls[i], controls, objClass);
|
|
}
|
|
monaco.languages.registerCompletionItemProvider('javascript', {
|
|
triggerCharacters: ['.'],
|
|
provideCompletionItems: function (model, position, context, token) {
|
|
// the completion provider is not triggere ONLY by pressing the trigger character but every time a character is written
|
|
// the following 'if' is therefore necessary to improve the performances
|
|
if(context.triggerCharacter == "."){
|
|
let word = model.getWordAtPosition({column: (position.column-1), lineNumber: position.lineNumber})?.word;
|
|
if(word == "this") { // Check if "this." is typed
|
|
// suggestion for 'this.'
|
|
let s = createSuggestions(position, null, controls, objClass);
|
|
suggestions["this"] = s;
|
|
return {
|
|
suggestions: s
|
|
};
|
|
} else {
|
|
// suggestions for specific items
|
|
for(let i = 0; i < controls.length; i++){
|
|
if (word == controls[i].name) {
|
|
let s = createSuggestions(position, controls[i], controls, objClass);
|
|
suggestions[controls[i].name] = s;
|
|
return {
|
|
suggestions: createSuggestions(position, controls[i], controls, objClass)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// HOVER HELPER PROVIDER
|
|
monaco.languages.registerHoverProvider('javascript', {
|
|
provideHover: function(model, position) {
|
|
let word = model.getWordAtPosition(position);
|
|
let suggKeys = Object.keys(suggestions);
|
|
try {
|
|
let prevPos = {lineNumber:position.lineNumber, column:(word.startColumn - 3)};
|
|
let prevWord = model.getWordAtPosition(prevPos);
|
|
for(let i = 0; i < suggKeys.length; i++){
|
|
if(prevWord.word == suggKeys[i]){
|
|
let vals = suggestions[suggKeys[i]];
|
|
for(let j = 0; j < vals.length; j++){
|
|
if(word && word.word === suggestions[suggKeys[i]][j].label){
|
|
return {
|
|
contents: [
|
|
{ value: "**" + suggestions[suggKeys[i]][j].detail + "**" },
|
|
{ value: suggestions[suggKeys[i]][j].documentation }
|
|
]
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch(err) {}
|
|
return null;
|
|
}
|
|
});
|
|
|
|
// SIGNATURE HELP
|
|
var currentlySuggesting = false;
|
|
var currentSignatureSuggestion = {};
|
|
monaco.languages.registerSignatureHelpProvider('javascript', {
|
|
signatureHelpTriggerCharacters: ['(',',',')'],
|
|
signatureHelpRetriggerCharacters: [',',')'],
|
|
provideSignatureHelp: function(model, position, context, token) {
|
|
let word = model.getWordAtPosition({lineNumber:position.lineNumber, column:(position.column -1)});
|
|
let commaCount = 0;
|
|
|
|
const getCurrentTextInfo = (model, position) => {
|
|
let fncSoFar = model.getValueInRange({
|
|
startLineNumber: position.lineNumber,
|
|
startColumn: 1,
|
|
endLineNumber: position.lineNumber,
|
|
endColumn: position.column
|
|
});
|
|
let currChar = fncSoFar[fncSoFar.length];
|
|
let i = 1;
|
|
let check = true;
|
|
while(check){
|
|
if(currChar == ","){
|
|
commaCount = commaCount + 1;
|
|
}
|
|
currChar = fncSoFar[fncSoFar.length - i];
|
|
if(currChar == "("){
|
|
if(/^[a-zA-Z]$/.test(fncSoFar[fncSoFar.length - i - 1])){
|
|
check = false;
|
|
}
|
|
} else if(currChar == undefined){
|
|
check = false;
|
|
}
|
|
i = i + 1;
|
|
}
|
|
return model.getWordAtPosition({lineNumber:position.lineNumber, column:(position.column - i)});
|
|
}
|
|
|
|
const setSuggestion = (method) => {
|
|
let suggKeys = Object.keys(suggestions);
|
|
let prevWord = method ? model.getWordAtPosition({lineNumber: position.lineNumber, column: method.startColumn-1}) : null;
|
|
for(let i = 0; i < suggKeys.length; i++){
|
|
if(prevWord?.word == suggKeys[i]){
|
|
let vals = suggestions[suggKeys[i]];
|
|
for(let j = 0; j < vals.length; j++){
|
|
if(word && word.word === suggestions[suggKeys[i]][j].label){
|
|
let l = suggestions[suggKeys[i]][j].label + "("
|
|
let params = [];
|
|
|
|
suggestions[suggKeys[i]][j].parameters?.forEach(p => {
|
|
l = l + p.name + ": " + p.type + ", "
|
|
params.push({label: (p.name + ": " + p.type), documentation: p.tooltip})
|
|
});
|
|
l = l = l.slice(-1) != '(' ? l.slice(0,-2) : l;;
|
|
l = l + ")";
|
|
currentlySuggesting = true;
|
|
currentSignatureSuggestion = {
|
|
activeParameter: commaCount,
|
|
activeSignature: 0,
|
|
signatures: [{
|
|
label: l,
|
|
parameters: params
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch(token.triggerCharacter){
|
|
case "(":
|
|
let c = 0;
|
|
do{
|
|
c = c + 1;
|
|
word = model.getWordAtPosition({lineNumber:position.lineNumber, column:(position.column - c)});
|
|
}while(word == null && (position.column - c)>0);
|
|
break;
|
|
case ",":
|
|
word = getCurrentTextInfo(model, position);
|
|
break;
|
|
case ")":
|
|
if(currentlySuggesting){
|
|
currentlySuggesting = false;
|
|
currentSignatureSuggestion = {};
|
|
}
|
|
return {
|
|
dispose: () => {},
|
|
value: currentSignatureSuggestion
|
|
};
|
|
default:
|
|
word = getCurrentTextInfo(model, position);
|
|
if(!word){
|
|
return {
|
|
dispose: () => {},
|
|
value: {}
|
|
};
|
|
}
|
|
}
|
|
setSuggestion(word);
|
|
return {
|
|
dispose: () => {},
|
|
value: currentSignatureSuggestion,
|
|
};
|
|
|
|
}
|
|
});
|
|
|
|
resolve();
|
|
} catch (e) {
|
|
console.error(e);
|
|
reject(e);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Function that allows to initialize the expression builder editor
|
|
* @param container: the html element that contains the editor
|
|
* @param tool: where the expression builder is being called from (e.g. visualquery editor, report editor, portlet control, etc.)
|
|
* @returns the expression builder editor set up
|
|
*/
|
|
export const runExpressionBuilderEditor = (container, tool) => {
|
|
return new Promise(async (resolve, reject) => {
|
|
// const code = '';
|
|
// const fileSystemProvider = new RegisteredFileSystemProvider(false);
|
|
// fileSystemProvider.registerFile(
|
|
// new RegisteredMemoryFile(Uri.file("/workspace/expressionBuilder.txt"), code)
|
|
// );
|
|
// registerFileSystemOverlay(1, fileSystemProvider);
|
|
try {
|
|
let monacoSetup = {
|
|
startLangServer: false,
|
|
language: "javascript", //set javascript for colorization
|
|
editorOptions: {minimap: {enabled: false}, lineNumbers: 'off', glyphMargin: false,
|
|
folding: false, lineDecorationsWidth: 0, lineNumbersMinChars: 0, wordWrap: 'on',
|
|
parameterHints: { enabled: true }, hover: { above: false, enabled: true }}
|
|
};
|
|
const userConfig = createUserConfig(monacoSetup);
|
|
const htmlElement = document.getElementById(container);
|
|
|
|
currentUserConfig = userConfig;
|
|
currentSetup = container;
|
|
await startEditor(userConfig, htmlElement);
|
|
|
|
let monaco = getMonaco();
|
|
|
|
disableDefaultCommands();
|
|
|
|
// add expression builder text formatter
|
|
if(tool == "visualquery"){
|
|
// visualquery formatter
|
|
monaco.languages.registerDocumentFormattingEditProvider('javascript', {
|
|
provideDocumentFormattingEdits(model, options) {
|
|
var text = model.getValue();
|
|
var formattedText = expBuilderFormatter(text);
|
|
return [
|
|
{
|
|
range: model.getFullModelRange(),
|
|
text: formattedText
|
|
}
|
|
];
|
|
}
|
|
});
|
|
}
|
|
|
|
// completion provider for TABLE FIELDS
|
|
monaco.languages.registerCompletionItemProvider('javascript', {
|
|
triggerCharacters: ['.'],
|
|
provideCompletionItems: function (model, position, context) {
|
|
let caller = model.getWordAtPosition({lineNumber: position.lineNumber, column: (position.column - 1)});
|
|
for (let i = 0; i < fieldsOfTable.length; i++) {
|
|
if (caller.word == fieldsOfTable[i].name) {
|
|
let completionItems = [];
|
|
let fields = fieldsOfTable[i].fields;
|
|
for (let j = 0; j < fields.length; j++) {
|
|
let suggestion = {
|
|
label: fields[j][0],
|
|
insertText: fields[j][0],
|
|
detail: "(field) " + fields[j][1] + " " + fields[j][2],
|
|
documentation: fields[j][1] + " " + fields[j][2],
|
|
kind: monaco.languages.CompletionItemKind.Field
|
|
};
|
|
completionItems.push(suggestion);
|
|
}
|
|
return {
|
|
suggestions: completionItems
|
|
};
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// completion provider for TABLE NAMES
|
|
monaco.languages.registerCompletionItemProvider('javascript', {
|
|
provideCompletionItems: function (model, position) {
|
|
let caller = model.getWordAtPosition({lineNumber: position.lineNumber, column: (position.column - 1)});
|
|
if (caller.word.length > 2) {
|
|
let completionItems = [];
|
|
for (let i = 0; i < fieldsOfTable.length; i++) {
|
|
if (fieldsOfTable[i].name.substring(0, 3) == caller.word) {
|
|
let suggestion = {
|
|
label: fieldsOfTable[i].name,
|
|
insertText: fieldsOfTable[i].name,
|
|
detail: "(table)",
|
|
kind: monaco.languages.CompletionItemKind.Struct
|
|
};
|
|
completionItems.push(suggestion);
|
|
}
|
|
}
|
|
return {
|
|
suggestions: completionItems
|
|
};
|
|
}
|
|
}
|
|
});
|
|
|
|
// completion provider for FUNCTIONS
|
|
monaco.languages.registerCompletionItemProvider('javascript', {
|
|
triggerCharacters: ['['],
|
|
provideCompletionItems: function (model, position, context) {
|
|
if (context.triggerCharacter == "[") {
|
|
let completionItems = [];
|
|
for (let i = 0; i < functionsToView.length; i++) {
|
|
for (let j = 0; j < functionsToView[i].functions.length; j++) {
|
|
let f = functionsToView[i].functions[j];
|
|
let suggestion = {
|
|
label: f[0],
|
|
insertText: f[0] + "(${1})",
|
|
detail: "(function) " + f[0] + f[1],
|
|
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
documentation: {supportHtml: true, isTrusted: true, value: f[2]},
|
|
kind: monaco.languages.CompletionItemKind.Function
|
|
};
|
|
completionItems.push(suggestion);
|
|
}
|
|
}
|
|
return {
|
|
suggestions: completionItems
|
|
};
|
|
}
|
|
}
|
|
});
|
|
|
|
// signature help for functions
|
|
var currentlySuggesting = false;
|
|
var currentSignatureSuggestion = {};
|
|
monaco.languages.registerSignatureHelpProvider('javascript', {
|
|
signatureHelpTriggerCharacters: ['(',',',')'],
|
|
signatureHelpRetriggerCharacters: [',',')'],
|
|
provideSignatureHelp: function(model, position, token, context){
|
|
let fncName;
|
|
let commaCount = 0;
|
|
let isClosed = false;
|
|
|
|
const getCurrentTextInfo = (model, position) => {
|
|
let fncSoFar = model.getValueInRange({
|
|
startLineNumber: position.lineNumber,
|
|
startColumn: 1,
|
|
endLineNumber: position.lineNumber,
|
|
endColumn: position.column
|
|
});
|
|
let currChar = fncSoFar[fncSoFar.length];
|
|
let i = 1;
|
|
let check = true;
|
|
while(check){
|
|
if(currChar == ","){
|
|
commaCount = commaCount + 1;
|
|
}
|
|
currChar = fncSoFar[fncSoFar.length - i];
|
|
if(currChar == "("){
|
|
if(/^[a-zA-Z]$/.test(fncSoFar[fncSoFar.length - i - 1])){
|
|
check = false;
|
|
}
|
|
} else if(currChar == undefined){
|
|
check = false;
|
|
isClosed = true;
|
|
}
|
|
i = i + 1;
|
|
}
|
|
return model.getWordAtPosition({lineNumber:position.lineNumber, column:(position.column - i)})?.word;
|
|
}
|
|
|
|
const setSuggestion = () => {
|
|
let fnc;
|
|
for(let i=0; i<functionsToView.length; i++){
|
|
let functionsGroup = functionsToView[i].functions;
|
|
if(functionsGroup.find(item => item[0] == fncName)){
|
|
fnc = functionsGroup.find(item => item[0] == fncName);
|
|
break;
|
|
}
|
|
}
|
|
if(fnc){
|
|
// get the list of parameters of the function
|
|
let parameterList = fnc[1].substring(1, fnc[1].length - 1).split(",");
|
|
parameterList = parameterList.map(param => ({ label: param.trim() }));
|
|
// find the active parameter
|
|
currentlySuggesting = true;
|
|
currentSignatureSuggestion = {
|
|
activeParameter: commaCount,
|
|
activeSignature: 0,
|
|
signatures: [{
|
|
label: fnc[0] + fnc[1],
|
|
parameters: parameterList,
|
|
documentation: {supportHtml: true, isTrusted: true, value: fnc[2]},
|
|
}]
|
|
};
|
|
}
|
|
}
|
|
switch(context.triggerCharacter){
|
|
case "(":
|
|
let c = 0;
|
|
do{
|
|
c = c + 1;
|
|
fncName = model.getWordAtPosition({lineNumber:position.lineNumber, column:(position.column - c)});
|
|
}while(fncName == null);
|
|
fncName = fncName.word;
|
|
break;
|
|
case ",":
|
|
fncName = getCurrentTextInfo(model, position);
|
|
break;
|
|
case ")":
|
|
if(currentlySuggesting){
|
|
currentlySuggesting = false;
|
|
currentSignatureSuggestion = {};
|
|
}
|
|
return {
|
|
dispose: () => {},
|
|
value: currentSignatureSuggestion
|
|
};
|
|
default:
|
|
fncName = getCurrentTextInfo(model, position);
|
|
if(!fncName){
|
|
return {
|
|
dispose: () => {},
|
|
value: {}
|
|
};
|
|
}
|
|
setSuggestion();
|
|
return {
|
|
dispose: () => {},
|
|
value: currentSignatureSuggestion
|
|
};
|
|
}
|
|
setSuggestion();
|
|
return {
|
|
dispose: () => {},
|
|
value: currentSignatureSuggestion,
|
|
};
|
|
}
|
|
});
|
|
|
|
// hover help for functions
|
|
monaco.languages.registerHoverProvider('javascript', {
|
|
provideHover: function(model, position) {
|
|
let word = model.getWordAtPosition(position)?.word;
|
|
if(word){
|
|
for(let i=0; i<functionsToView.length; i++){
|
|
for(let j=0; j<functionsToView[i].functions.length; j++){
|
|
if(word == functionsToView[i].functions[j][0]){
|
|
let fnc = functionsToView[i].functions[j];
|
|
return {
|
|
contents: [
|
|
{ value: "**(function) " + fnc[0] + fnc[1] + "**" },
|
|
{ value: fnc[2] }
|
|
]
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
resolve();
|
|
} catch (e) {
|
|
console.error(e);
|
|
reject(e);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Function that allows to initialize the sql colorization editor (non-editable)
|
|
* @param container: the html element that contains the editor
|
|
* @returns the SQL colorizer editor set up
|
|
*/
|
|
export const runSQLColorization = (container, dbIndex) => {
|
|
return new Promise(async (resolve, reject) => {
|
|
// const code = '';
|
|
// const fileSystemProvider = new RegisteredFileSystemProvider(false);
|
|
// fileSystemProvider.registerFile(
|
|
// new RegisteredMemoryFile(Uri.file("/workspace/actionCode.js"), code)
|
|
// );
|
|
// registerFileSystemOverlay(1, fileSystemProvider);
|
|
try {
|
|
let monacoSetup = {
|
|
startLangServer: false,
|
|
language: "javascript",
|
|
editorOptions: { minimap: {enabled: false}, lineNumbers: 'off', glyphMargin: false,
|
|
folding: false, lineDecorationsWidth: 0, tabSize: 2, wordWrap: 'on'}
|
|
};
|
|
const userConfig = createUserConfig(monacoSetup); // change second parameter to TRUE if you want to start the language server
|
|
const htmlElement = document.getElementById(container);
|
|
await startEditor(userConfig, htmlElement);
|
|
|
|
function getDbName(dbIndex){
|
|
switch(dbIndex){
|
|
case 2:
|
|
return "plsq";
|
|
case 4:
|
|
return "db2";
|
|
case 10:
|
|
return "postgresql";
|
|
case 13:
|
|
return "mysql";
|
|
default:
|
|
return "sql";
|
|
}
|
|
}
|
|
|
|
let monaco = getMonaco();
|
|
// add expression builder text formatter
|
|
monaco.languages.registerDocumentFormattingEditProvider('javascript', {
|
|
provideDocumentFormattingEdits(model, options) {
|
|
let text = model.getValue();
|
|
let dbType = getDbName(dbIndex);
|
|
var formattedText = SQLformatter(text, {language: dbType});
|
|
return [
|
|
{
|
|
range: model.getFullModelRange(),
|
|
text: formattedText
|
|
}
|
|
];
|
|
}
|
|
});
|
|
|
|
resolve();
|
|
} catch (e) {
|
|
console.error(e);
|
|
reject(e);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Function to get the editor environment
|
|
|
|
*/
|
|
export const getEditor = () => {
|
|
return getWrapper().editorApp.editor;
|
|
};
|
|
|
|
/**
|
|
* Function to get the monaco environment
|
|
*/
|
|
export const getMonaco = () => {
|
|
return api2;
|
|
};
|
|
|
|
export const restartEditor = () => {
|
|
return new Promise(async (resolve, reject) => {
|
|
try{
|
|
let editor = getEditor();
|
|
let currText = editor.getValue();
|
|
|
|
let wrapper = getWrapper();
|
|
await wrapper.dispose()
|
|
await wrapper.initAndStart(currentUserConfig,document.getElementById(currentSetup));
|
|
|
|
editor = getEditor();
|
|
editor.setValue(currText || '');
|
|
resolve(editor);
|
|
} catch(error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
} |