PortaleOrdiniGruppo/PortalStudio/monaco/monacoLoader.js
2025-03-24 15:28:26 +01:00

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);
}
});
}