1
0
mirror of https://github.com/salexdv/bsl_console.git synced 2024-11-28 08:48:48 +02:00

Поддержка пользовательских функций.

Функционал для загрузки пользовательских сниппетов и функций.
Исправление ситуации, когда в автокомплит не попадали функции, в названии которых есть цифры.
This commit is contained in:
salexdv 2020-07-24 12:42:36 +03:00
parent cccc52c554
commit 7f1c39262f
4 changed files with 255 additions and 87 deletions

View File

@ -33127,6 +33127,7 @@ define([], function () {
}, },
"Прочее": "" "Прочее": ""
} }
} },
"customFunctions": {}
} }
}); });

View File

@ -136,7 +136,7 @@ class bslHelper {
let index = expArray.length - 1; let index = expArray.length - 1;
while (!exp && 0 <= index) { while (!exp && 0 <= index) {
if (/^[^\(\)\[\]=\+\*/%<>"\.\,;:][a-zA-Z\u0410-\u044F_\.]*$/.test(expArray[index])) if (/^[^\(\)\[\]=\+\*/%<>"\.\,;:][a-zA-Z0-9\u0410-\u044F_\.]*$/.test(expArray[index]))
exp = expArray[index] exp = expArray[index]
else { else {
if (expArray[index].trim() !== '' && !this.lastOperator) if (expArray[index].trim() !== '' && !this.lastOperator)
@ -188,7 +188,7 @@ class bslHelper {
let index = expArray.length - 1; let index = expArray.length - 1;
while (!exp && 0 <= index) { while (!exp && 0 <= index) {
if (/^(?!новый |new )[^\(\)\[\]=\+\*/%<>"][a-zA-Z\u0410-\u044F_\.]*$/.test(expArray[index])) { if (/^(?!новый |new )[^\(\)\[\]=\+\*/%<>"][a-zA-Z0-9\u0410-\u044F_\.]*$/.test(expArray[index])) {
exp = expArray[index] exp = expArray[index]
} }
index--; index--;
@ -265,8 +265,11 @@ class bslHelper {
postfix = '()'; postfix = '()';
} }
values.push({ name: value.name, detail: value.description, description: value.hasOwnProperty('returns') ? value.returns : '', postfix: postfix }); let template = value.hasOwnProperty('template') ? value.template : '';
values.push({ name: value.name_en, detail: value.description, description: value.hasOwnProperty('returns') ? value.returns : '', postfix: postfix });
values.push({ name: value.name, detail: value.description, description: value.hasOwnProperty('returns') ? value.returns : '', postfix: postfix, template: template });
if (value.hasOwnProperty('name_en'))
values.push({ name: value.name_en, detail: value.description, description: value.hasOwnProperty('returns') ? value.returns : '', postfix: postfix, template: template });
} }
else { else {
@ -275,7 +278,7 @@ class bslHelper {
let postfix = ''; let postfix = '';
if (invalue.hasOwnProperty('postfix')) if (invalue.hasOwnProperty('postfix'))
postfix = invalue.postfix; postfix = invalue.postfix;
values.push({ name: inkey, detail: '', description: '', postfix: postfix }); values.push({ name: inkey, detail: '', description: '', postfix: postfix, template: '' });
} }
} }
@ -285,7 +288,7 @@ class bslHelper {
suggestions.push({ suggestions.push({
label: value.name, label: value.name,
kind: kind, kind: kind,
insertText: value.name + value.postfix, insertText: value.template ? value.template : value.name + value.postfix,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: value.detail, detail: value.detail,
documentation: value.description documentation: value.description
@ -483,71 +486,77 @@ class bslHelper {
let itemExists = false; let itemExists = false;
let exp = this.getLastRawExpression(); let exp = this.getLastRawExpression();
let fullText = this.getFullTextBeforePosition();
let regex = new RegExp(exp + '\\s?=\\s?(.*)\\(.*\\);', 'gi'); if (exp) {
regex = regex.exec(fullText);
if (regex && 1 < regex.length) {
regex = /(.+?)(?:\.(.*?))?\.?(?:\.(.*?))?\(?$/.exec(regex[1]); let fullText = this.getFullTextBeforePosition();
let regex = new RegExp(exp + '\\s?=\\s?(.*)\\(.*\\);', 'gi');
regex = regex.exec(fullText);
if (regex && 1 < regex.length) {
let metadataName = regex && 1 < regex.length ? regex[1] : ''; regex = /(.+?)(?:\.(.*?))?\.?(?:\.(.*?))?\(?$/.exec(regex[1]);
let metadataItem = regex && 2 < regex.length ? regex[2] : '';
let metadataFunc = regex && 3 < regex.length ? regex[3] : '';
if (metadataName && metadataItem && metadataFunc) { let metadataName = regex && 1 < regex.length ? regex[1] : '';
let metadataItem = regex && 2 < regex.length ? regex[2] : '';
let metadataFunc = regex && 3 < regex.length ? regex[3] : '';
for (const [key, value] of Object.entries(data)) { if (metadataName && metadataItem && metadataFunc) {
if (value.name.toLowerCase() == metadataName || value.name_en.toLowerCase() == metadataName) { for (const [key, value] of Object.entries(data)) {
for (const [ikey, ivalue] of Object.entries(value.items)) { if (value.name.toLowerCase() == metadataName || value.name_en.toLowerCase() == metadataName) {
if (ikey.toLowerCase() == metadataItem) { for (const [ikey, ivalue] of Object.entries(value.items)) {
itemExists = true; if (ikey.toLowerCase() == metadataItem) {
for (const [pkey, pvalue] of Object.entries(ivalue.properties)) { itemExists = true;
suggestions.push({
label: pkey,
kind: monaco.languages.CompletionItemKind.Field,
insertText: pkey,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: pvalue
});
}
if (value.hasOwnProperty('objMethods')) {
let postfix = '';
let signatures = [];
for (const [mkey, mvalue] of Object.entries(value.objMethods)) {
signatures = this.getMethodsSignature(mvalue);
if (signatures.length == 0 || (signatures.length == 1 && signatures[0].parameters.length == 0))
postfix = '()';
if (this.hasRu(metadataName)) {
suggestions.push({
label: mvalue.name,
kind: monaco.languages.CompletionItemKind.Function,
insertText: mvalue.name + postfix,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: mvalue.description
});
}
else {
suggestions.push({
label: mvalue.name_en,
kind: monaco.languages.CompletionItemKind.Function,
insertText: mvalue.name + postfix,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: mvalue.description
});
}
for (const [pkey, pvalue] of Object.entries(ivalue.properties)) {
suggestions.push({
label: pkey,
kind: monaco.languages.CompletionItemKind.Field,
insertText: pkey,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: pvalue
});
} }
if (value.hasOwnProperty('objMethods')) {
let postfix = '';
let signatures = [];
for (const [mkey, mvalue] of Object.entries(value.objMethods)) {
signatures = this.getMethodsSignature(mvalue);
if (signatures.length == 0 || (signatures.length == 1 && signatures[0].parameters.length == 0))
postfix = '()';
if (this.hasRu(metadataName)) {
suggestions.push({
label: mvalue.name,
kind: monaco.languages.CompletionItemKind.Function,
insertText: mvalue.name + postfix,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: mvalue.description
});
}
else {
suggestions.push({
label: mvalue.name_en,
kind: monaco.languages.CompletionItemKind.Function,
insertText: mvalue.name + postfix,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: mvalue.description
});
}
}
}
} }
} }
@ -557,8 +566,8 @@ class bslHelper {
} }
} }
} }
} }
return itemExists; return itemExists;
@ -743,6 +752,7 @@ class bslHelper {
this.getCommonCompletition(suggestions, bslGlobals.globalfunctions, monaco.languages.CompletionItemKind.Function, true); this.getCommonCompletition(suggestions, bslGlobals.globalfunctions, monaco.languages.CompletionItemKind.Function, true);
this.getCommonCompletition(suggestions, bslGlobals.globalvariables, monaco.languages.CompletionItemKind.Class, false); this.getCommonCompletition(suggestions, bslGlobals.globalvariables, monaco.languages.CompletionItemKind.Class, false);
this.getCommonCompletition(suggestions, bslGlobals.systemEnum, monaco.languages.CompletionItemKind.Enum, false); this.getCommonCompletition(suggestions, bslGlobals.systemEnum, monaco.languages.CompletionItemKind.Enum, false);
this.getCommonCompletition(suggestions, bslGlobals.customFunctions, monaco.languages.CompletionItemKind.Function, true);
} }
this.getSnippets(suggestions, snippets); this.getSnippets(suggestions, snippets);
@ -917,45 +927,50 @@ class bslHelper {
let helper = null; let helper = null;
let exp = this.getLastNExpression(4); let exp = this.getLastNExpression(4);
let fullText = this.getFullTextBeforePosition();
let regex = new RegExp(exp + '\\s?=\\s?(.*)\\(.*\\);', 'gi');
regex = regex.exec(fullText);
if (regex && 1 < regex.length) { if (exp) {
regex = /(.+?)(?:\.(.*?))?\.?(?:\.(.*?))?\(?$/.exec(regex[1]); let fullText = this.getFullTextBeforePosition();
let regex = new RegExp(exp + '\\s?=\\s?(.*)\\(.*\\);', 'gi');
regex = regex.exec(fullText);
let metadataName = regex && 1 < regex.length ? regex[1] : ''; if (regex && 1 < regex.length) {
let metadataItem = regex && 2 < regex.length ? regex[2] : '';
let metadataFunc = regex && 3 < regex.length ? regex[3] : '';
if (metadataName && metadataItem && metadataFunc) { regex = /(.+?)(?:\.(.*?))?\.?(?:\.(.*?))?\(?$/.exec(regex[1]);
metadataFunc = this.lastRawExpression; let metadataName = regex && 1 < regex.length ? regex[1] : '';
let metadataItem = regex && 2 < regex.length ? regex[2] : '';
let metadataFunc = regex && 3 < regex.length ? regex[3] : '';
if (metadataFunc) { if (metadataName && metadataItem && metadataFunc) {
for (const [key, value] of Object.entries(data)) { metadataFunc = this.lastRawExpression;
if (value.name.toLowerCase() == metadataName || value.name_en.toLowerCase() == metadataName) { if (metadataFunc) {
for (const [ikey, ivalue] of Object.entries(value.items)) { for (const [key, value] of Object.entries(data)) {
if (ikey.toLowerCase() == metadataItem) { if (value.name.toLowerCase() == metadataName || value.name_en.toLowerCase() == metadataName) {
if (value.hasOwnProperty('objMethods')) { for (const [ikey, ivalue] of Object.entries(value.items)) {
for (const [mkey, mvalue] of Object.entries(value.objMethods)) { if (ikey.toLowerCase() == metadataItem) {
if (mvalue.name.toLowerCase() == metadataFunc || mvalue.name_en.toLowerCase() == metadataFunc) { if (value.hasOwnProperty('objMethods')) {
let signatures = this.getMethodsSignature(mvalue); for (const [mkey, mvalue] of Object.entries(value.objMethods)) {
if (signatures.length) {
helper = { if (mvalue.name.toLowerCase() == metadataFunc || mvalue.name_en.toLowerCase() == metadataFunc) {
activeParameter: this.textBeforePosition.split(',').length - 1,
activeSignature: 0, let signatures = this.getMethodsSignature(mvalue);
signatures: signatures, if (signatures.length) {
helper = {
activeParameter: this.textBeforePosition.split(',').length - 1,
activeSignature: 0,
signatures: signatures,
}
} }
} }
} }
@ -1122,6 +1137,9 @@ class bslHelper {
if (!helper) if (!helper)
helper = this.getCommonSigHelp(bslGlobals.globalfunctions); helper = this.getCommonSigHelp(bslGlobals.globalfunctions);
if (!helper)
helper = this.getCommonSigHelp(bslGlobals.customFunctions);
if (helper) if (helper)
return new SignatureHelpResult(helper); return new SignatureHelpResult(helper);
@ -1155,6 +1173,84 @@ class bslHelper {
} }
}
/**
* Escapes special character in json-string
* before parsing
*
* @param {string} jsonString string to parsing
*
* @returns {string} escaped string
*/
static escapeJSON(jsonString) {
return jsonString.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
}
/**
* Updates snippets from JSON-string which
* was received from 1C
*
* @param {string} data JSON-string with snippets info
* @param {boolean} replace whether or not to replace native snippents
*
* @returns {true|object} true - snippets was updated, {errorDescription} - not
*/
static updateSnippets(data, replace) {
try {
let snippetsObj = JSON.parse(this.escapeJSON(data));
if (snippetsObj.hasOwnProperty('snippets')) {
if (replace) {
snippets = snippetsObj.snippets;
}
else {
for (const [key, value] of Object.entries(snippetsObj.snippets)) {
snippets[key] = value;
}
}
return true;
}
else {
throw new TypeError("Wrong structure of snippets");
}
}
catch (e) {
return { errorDescription: e.message };
}
}
/**
* Updates custom functions JSON-string which
* was received from 1C
*
* @param {string} snips JSON-string with function's definition
*
* @returns {true|object} true - functions was updated, {errorDescription} - not
*/
static updateCustomFunctions(data) {
try {
let funcObj = JSON.parse(data);
if (funcObj.hasOwnProperty('customFunctions')) {
bslGlobals.customFunctions = funcObj.customFunctions;
return true;
}
else {
throw new TypeError("Wrong structure of custom functions");
}
}
catch (e) {
return { errorDescription: e.message };
}
} }
/** /**

View File

@ -58,6 +58,18 @@ define(['bslGlobals', 'bslMetadata', 'snippets', 'bsl_language', 'vs/editor/edit
} }
updateSnippets = function (snips, replace = false) {
return bslHelper.updateSnippets(snips, replace);
}
updateCustomFunctions = function (data) {
return bslHelper.updateCustomFunctions(data);
}
setTheme = function (theme) { setTheme = function (theme) {
monaco.editor.setTheme(theme); monaco.editor.setTheme(theme);

View File

@ -26,6 +26,21 @@ describe("Проверка автокомлита и подсказок реда
return new bslHelper(model, position); return new bslHelper(model, position);
} }
function helperToConsole(helper) {
console.log('line number:', helper.column);
console.log('column:', helper.lineNumber);
console.log('word:', helper.word);
console.log('last operator:', helper.lastOperator);
console.log('whitespace:', helper.hasWhitespace);
console.log('last expr:', helper.lastExpression);
console.log('expr array:', helper.getExpressioArray());
console.log('last raw expr:', helper.lastRawExpression);
console.log('raw array:', helper.getRawExpressioArray());
console.log('text before:', helper.textBeforePosition);
}
let bsl = helper(''); let bsl = helper('');
let bslLoaded = (bslGlobals != undefined); let bslLoaded = (bslGlobals != undefined);
@ -58,7 +73,7 @@ describe("Проверка автокомлита и подсказок реда
}); });
it("проверка подсказки параметров для глобальной функции Найти(", function () { it("проверка подсказки параметров для глобальной функции Найти(", function () {
bsl = helper('Найти('); bsl = helper('Найти(');
let suggestions = []; let suggestions = [];
let help = bsl.getCommonSigHelp(bslGlobals.globalfunctions); let help = bsl.getCommonSigHelp(bslGlobals.globalfunctions);
expect(help).to.have.property('activeParameter'); expect(help).to.have.property('activeParameter');
@ -247,6 +262,30 @@ describe("Проверка автокомлита и подсказок реда
bslMetadata = JSON.parse(JSON.stringify(mCopy)); bslMetadata = JSON.parse(JSON.stringify(mCopy));
}); });
it("проверка обновления сниппетов", function () {
let sCopy = JSON.parse(JSON.stringify(snippets));
assert.notEqual(updateSnippets(123), true);
let strJSON = '{"snippets": { "ЕслиЧто": { "prefix": "Если", "body": "Если ${1:Условие} Тогда\n\t$0\nКонецЕсли;", "description": "ЕслиЧто"}}}';
assert.equal(updateSnippets(strJSON), true);
bsl = helper('ЕслиЧто');
let suggestions = [];
bsl.getSnippets(suggestions, snippets);
expect(suggestions).to.be.an('array').that.not.is.empty;
assert.equal(suggestions.some(suggest => suggest.detail === "ЕслиЧто"), true);
snippets = JSON.parse(JSON.stringify(sCopy));
});
it("проверка замены сниппетов", function () {
let sCopy = JSON.parse(JSON.stringify(snippets));
let strJSON = '{"snippets": { "ЕслиЧто": { "prefix": "Если", "body": "Если ${1:Условие} Тогда\n\t$0\nКонецЕсли;", "description": "ЕслиЧто"}}}';
assert.equal(updateSnippets(strJSON, true), true);
bsl = helper('Если');
let suggestions = [];
bsl.getSnippets(suggestions, snippets);
assert.equal(suggestions.length, 1);
snippets = JSON.parse(JSON.stringify(sCopy));
});
it("проверка всплывающей подсказки", function () { it("проверка всплывающей подсказки", function () {
let model = getModel("Найти("); let model = getModel("Найти(");
let position = new monaco.Position(1, 2); let position = new monaco.Position(1, 2);
@ -269,6 +308,26 @@ describe("Проверка автокомлита и подсказок реда
assert.equal(getFormatString(), null); assert.equal(getFormatString(), null);
}); });
it("проверка загрузки пользовательских функций", function () {
let strJSON = '{ "customFunctions":{ "МояФункция1":{ "name":"МояФункция1", "name_en":"MyFuntion1", "description":"Получает из строки закодированной по алгоритму base64 двоичные данные.", "returns":"Тип: ДвоичныеДанные. ", "signature":{ "default":{ "СтрокаПараметров":"(Строка: Строка): ДвоичныеДанные", "Параметры":{ "Строка":"Строка, закодированная по алгоритму base64." } } } }, "МояФункция2":{ "name":"МояФункция2", "name_en":"MyFuntion2", "description":"Выполняет сериализацию значения в формат XML.", "template":"МояФункция2(ВызовЗависимойФункции(${1:ПервыйЗависимыйПараметр}, ${2:ВторойЗависимыйПараметр}), ${0:ПараметрМоейФункции}))", "signature":{ "ЗаписатьБезИмени":{ "СтрокаПараметров":"(ЗаписьXML: ЗаписьXML, Значение: Произвольный, НазначениеТипа?: НазначениеТипаXML)", "Параметры":{ "ЗаписьXML":"Объект, через который осуществляется запись XML, полученный через зависимою функцию", "Значение":"Записываемое в поток XML значение. Тип параметра определяется совокупностью типов, для которых определена XML-сериализация." } }, "ЗаписатьСПолнымИменем":{ "СтрокаПараметров":"(ЗаписьXML: ЗаписьXML, Значение: Произвольный, ПолноеИмя: Строка, НазначениеТипа?: НазначениеТипаXML)", "Параметры":{ "ЗаписьXML":"Объект, через который осуществляется запись XML.", "Значение":"Записываемое в поток XML значение. Тип параметра определяется совокупностью типов, для которых определена XML-сериализация.", "ПолноеИмя":"Полное имя элемента XML, в который будет записано значение.", "НазначениеТипа":"Определяет необходимость назначения типа элементу XML. Значение по умолчанию: Неявное." } }, "ЗаписатьСЛокальнымИменемИПространствомИмен":{ "СтрокаПараметров":"(ЗаписьXML: ЗаписьXML, Значение: Произвольный, ЛокальноеИмя: Строка, URIПространстваИмен: Строка, НазначениеТипа?: НазначениеТипаXML)", "Параметры":{ "ЗаписьXML":"Объект, через который осуществляется запись XML.", "Значение":"Записываемое в поток XML значение. Тип параметра определяется совокупностью типов, для которых определена XML-сериализация.", "ЛокальноеИмя":"Локальное имя элемента XML, в который будет записано значение.", "URIПространстваИмен":"URI пространства имен, к которому принадлежит указанное ЛокальноеИмя.", "НазначениеТипа":"Определяет необходимость назначения типа элементу XML. Значение по умолчанию: Неявное." } } } } } }';
assert.notEqual(updateCustomFunctions(123), true);
assert.equal(updateCustomFunctions(strJSON), true);
});
it("проверка автокомплита для пользовательской функции МояФункция2", function () {
bsl = helper('мояфу');
let suggestions = [];
bsl.getCommonCompletition(suggestions, bslGlobals.customFunctions, monaco.languages.CompletionItemKind.Function)
expect(suggestions).to.be.an('array').that.not.is.empty;
});
it("проверка подсказки параметров для пользовательской функции МояФункция2", function () {
bsl = helper('МояФункция2');
let suggestions = [];
let help = bsl.getCommonSigHelp(bslGlobals.customFunctions);
expect(help).to.have.property('activeParameter');
});
} }
mocha.run(); mocha.run();