diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb7380..8926613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,16 @@ -## 0.1.2 (20.07.2020) +## 0.1.2 (21.07.2020) ### Новое: * Добавлена обработка подсказок для предопределенных значений элементов +* Сворачивание циклов, условий и текстов запросов +* Простые всплювающие подсказки для глобальных функций, перечислений и классов ### Улучшения: * Отказ от хранения описаний конструкций языка и метаданных в файлах JSON из-за CORS policy +* Добавление **;** после ключевых слов *КонецЦикла*, *КонецЕсли* (поле Prefix в bslGlobals) ### Исправления: * Выделение ключевого слова *Выполнить* -### Исправления: -* Исправлен сниппет для выборки из регистра накопления - ## 0.1.1 (19.07.2020) ### Новое: diff --git a/src/bslGlobals.js b/src/bslGlobals.js index d4b6502..62006f3 100644 --- a/src/bslGlobals.js +++ b/src/bslGlobals.js @@ -5656,11 +5656,15 @@ define([], function () { "Если": {}, "Тогда": {}, "ИначеЕсли": {}, - "КонецЦикла": {}, + "КонецЦикла": { + "postfix": ";\n" + }, "Иначе": {}, "Исключение": {}, "КонецПопытки": {}, - "КонецЕсли": {}, + "КонецЕсли": { + "postfix": ";\n" + }, "Попытка": {}, "Пока": {}, "Для": {}, @@ -5693,7 +5697,9 @@ define([], function () { "Else": {}, "ElsIf": {}, "Then": {}, - "EndIf": {}, + "EndIf": { + "postfix": ";\n" + }, "Try": {}, "Except": {}, "EndTry": {}, @@ -5704,7 +5710,9 @@ define([], function () { "In": {}, "To": {}, "Do": {}, - "EndDo": {}, + "EndDo": { + "postfix": ";\n" + }, "NOT": {}, "AND": {}, "OR": {}, diff --git a/src/bsl_helper.js b/src/bsl_helper.js index a429ebc..ac3a323 100644 --- a/src/bsl_helper.js +++ b/src/bsl_helper.js @@ -21,7 +21,8 @@ class bslHelper { constructor(model, position) { this.model = model; - this.position = position; + this.lineNumber = position.lineNumber; + this.column = position.column; let wordData = model.getWordAtPosition(position); this.word = wordData ? wordData.word.toLowerCase() : ''; @@ -31,7 +32,7 @@ class bslHelper { this.textBeforePosition = this.getTextBeforePosition(); this.lastExpression = this.getLastExpression(); - this.lastRawExpression = this.getLastRawExpression(); + this.lastRawExpression = this.getLastRawExpression(); } @@ -83,7 +84,7 @@ class bslHelper { */ getFullTextBeforePosition() { - return this.model.getValueInRange({ startLineNumber: 1, startColumn: 1, endLineNumber: this.position.lineNumber, endColumn: this.position.column }).trim().toLowerCase(); + return this.model.getValueInRange({ startLineNumber: 1, startColumn: 1, endLineNumber: this.lineNumber, endColumn: this.column }).trim().toLowerCase(); } @@ -94,7 +95,7 @@ class bslHelper { */ getTextBeforePosition() { - let text = this.model.getValueInRange({ startLineNumber: this.position.lineNumber, startColumn: 1, endLineNumber: this.position.lineNumber, endColumn: this.position.column }); + let text = this.model.getValueInRange({ startLineNumber: this.lineNumber, startColumn: 1, endLineNumber: this.lineNumber, endColumn: this.column }); this.hasWhitespace = (text.substr(-1) == ' '); return text.trim().toLowerCase(); @@ -271,7 +272,10 @@ class bslHelper { else { for (const [inkey, invalue] of Object.entries(value)) { - values.push({ name: inkey, detail: '', description: '', postfix: '' }); + let postfix = ''; + if (invalue.hasOwnProperty('postfix')) + postfix = invalue.postfix; + values.push({ name: inkey, detail: '', description: '', postfix: postfix }); } } @@ -699,6 +703,48 @@ class bslHelper { } + /** + * Completition provider + * + * @returns {array} array of completition + */ + getCompletition() { + + let suggestions = []; + + if (!this.getClassCompletition(suggestions, bslGlobals.classes)) { + + if (!this.getClassCompletition(suggestions, bslGlobals.systemEnum)) { + + if (!this.getMetadataCompletition(suggestions, bslMetadata)) { + + this.getCommonCompletition(suggestions, bslGlobals.keywords, monaco.languages.CompletionItemKind.Keyword.ru, true); + this.getCommonCompletition(suggestions, bslGlobals.keywords, monaco.languages.CompletionItemKind.Keyword.en, true); + + if (this.requireClass()) { + this.getCommonCompletition(suggestions, bslGlobals.classes, monaco.languages.CompletionItemKind.Constructor, false); + } + else { + this.getCommonCompletition(suggestions, bslGlobals.globalfunctions, monaco.languages.CompletionItemKind.Function, true); + this.getCommonCompletition(suggestions, bslGlobals.globalvariables, monaco.languages.CompletionItemKind.Class, false); + this.getCommonCompletition(suggestions, bslGlobals.systemEnum, monaco.languages.CompletionItemKind.Enum, false); + } + + this.getSnippets(suggestions, snippets); + + } + + } + + } + + if (suggestions.length) + return { suggestions: suggestions } + else + return []; + + } + /** * Returns array of parametrs as described in JSON-dictionary * for current node (method) @@ -1046,6 +1092,26 @@ class bslHelper { } + /** + * Signature help provider + * + * @returns {object} helper + */ + getSigHelp() { + + let helper = this.getMetadataSigHelp(bslMetadata); + + if (!helper) + helper = this.getClassSigHelp(bslGlobals.classes); + + if (!helper) + helper = this.getCommonSigHelp(bslGlobals.globalfunctions); + + if (helper) + return new SignatureHelpResult(helper); + + } + /** * Updates bslMetadata from JSON-string which * was received from 1C @@ -1076,4 +1142,183 @@ class bslHelper { } + /** + * Finds blocks like conditions (if...endif) and loops (while...enddo) + * when start column startString equal start column endString + * + * @param {ITextModel} current model of editor + * @param {string} regexp to detect opening construction + * @param {string} regexp to detect closing construction + * + * @returns {array} - array of folding ranges + */ + static getRangesForConstruction(model, startString, endString) { + + let ranges = []; + + const startMatches = model.findMatches("(?:^|\\b)?(" + startString + ") ", false, true) + let startMatch = null; + + const endMatches = model.findMatches("(?:^|\\b)?(" + endString + ") ?;", false, true) + let endMatch = null; + + let structFound = false; + let subidx = 0; + + if (startMatches && endMatches) { + + for (let idx = 0; idx < startMatches.length; idx++) { + + structFound = false; + startMatch = startMatches[idx]; + + subidx = 0; + + while (!structFound && subidx < endMatches.length) { + + endMatch = endMatches[subidx]; + + if (endMatch.range.startColumn == startMatch.range.startColumn && startMatch.range.startLineNumber < endMatch.range.startLineNumber) { + structFound = true; + ranges.push( + { + kind: monaco.languages.FoldingRangeKind.Region, + start: startMatch.range.startLineNumber, + end: endMatch.range.startLineNumber + } + ) + } + + subidx++; + } + + } + + } + + return ranges; + + } + + /** + * Finds blocks like functions by regexp + * + * @param {ITextModel} current model of editor + * @param {string} regexp to detect block + * + * @returns {array} - array of folding ranges + */ + static getRangesForRegexp(model, regexp) { + + let ranges = []; + let match = null; + const matches = model.findMatches(regexp, false, true, false, null, true) + + if (matches) { + + for (let idx = 0; idx < matches.length; idx++) { + match = matches[idx]; + ranges.push( + { + kind: monaco.languages.FoldingRangeKind.Region, + start: match.range.startLineNumber, + end: match.range.endLineNumber + } + ) + } + + } + + return ranges; + + } + + /** + * Provider for folding blocks + * @param {ITextModel} current model of editor + * + * @returns {array} - array of folding ranges + */ + static getFoldingRanges(model) { + + let ranges = this.getRangesForRegexp(model, "\"(?:\\n|\\r|\\|)*(?:выбрать|select)(?:(?:.|\\n|\\r)*?)?\""); + ranges = ranges.concat(this.getRangesForRegexp(model, "(?:^|\\b)(?:функция|процедура).*\\((?:.|\\n|\\r)*?(?:конецпроцедуры|конецфункции)")); + ranges = ranges.concat(this.getRangesForRegexp(model, "(?:^|\\b)#.+(?:.|\\n|\\r)*?#.+$")); + ranges = ranges.concat(this.getRangesForConstruction(model, "пока|while", "конеццикла|enddo")); + ranges = ranges.concat(this.getRangesForConstruction(model, "для .*(?:по|из) .*|for .* (?:to|each) .*", "конеццикла|enddo")); + ranges = ranges.concat(this.getRangesForConstruction(model, "если|if", "конецесли|endif")); + + return ranges; + + } + + /** + * Provider for hover popoup + * + * @returns {object} - hover object or null + */ + getHover() { + + for (const [key, value] of Object.entries(bslGlobals)) { + + for (const [ikey, ivalue] of Object.entries(value)) { + + if (ivalue.hasOwnProperty('name')) { + + if (ivalue.name.toLowerCase() == this.word) { + + let contents = [ + { value: '**' + ivalue.name + '**' }, + { value: ivalue.description } + ] + + if (ivalue.hasOwnProperty('returns')) { + contents.push( + { value: 'Возвращает: ' + ivalue.returns } + ) + } + + return { + range: new monaco.Range(this.lineNumber, this.column, this.lineNumber, this.model.getLineMaxColumn(this.lineNumber)), + contents: contents + }; + } + + } + + } + + } + + return null; + + } + + /** + * Returns query's text from current position + * + * @returns {object} object with text and range or null + */ + getQuery() { + + const matches = this.model.findMatches("\"(?:\\n|\\r|\\|)*(?:выбрать|select)(?:(?:.|\\n|\\r)*?)?\"", false, true, false, null, true) + + let idx = 0; + let match = null; + let queryFound = false; + + if (matches) { + + while (idx < matches.length && !queryFound) { + match = matches[idx]; + queryFound = (match.range.startLineNumber <= this.lineNumber && this.lineNumber <= match.range.endLineNumber); + idx++; + } + + } + + return queryFound ? { text: match.matches[0], range: match.range } : null; + + } + } \ No newline at end of file diff --git a/src/bsl_language.js b/src/bsl_language.js new file mode 100644 index 0000000..e433362 --- /dev/null +++ b/src/bsl_language.js @@ -0,0 +1,165 @@ +define([], function () { + + language = { + + id: 'bsl', + rules: { + defaultToken: '', + tokenPostfix: 'bsl', + ignoreCase: true, + brackets: [ + { open: '[', close: ']', token: 'delimiter.square' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, + ], + keywords: [ + 'КонецПроцедуры', 'EndProcedure', 'КонецФункции', 'EndFunction', + 'Прервать', 'Break', 'Продолжить', 'Continue', 'Возврат', 'Return', + 'Если', 'If', 'Иначе', 'Else', 'ИначеЕсли', 'ElsIf', 'Тогда', 'Then', + 'КонецЕсли', 'EndIf', 'Попытка', 'Try', 'Исключение', 'Except', + 'КонецПопытки', 'EndTry', 'Raise', 'ВызватьИсключение', 'Пока', + 'While', 'Для', 'For', 'Каждого', 'Each', 'Из', 'In', 'По', 'To', 'Цикл', + 'Do', 'КонецЦикла', 'EndDo', 'НЕ', 'NOT', 'И', 'AND', 'ИЛИ', 'OR', 'Новый', + 'New', 'Процедура', 'Procedure', 'Функция', 'Function', 'Перем', 'Var', + 'Экспорт', 'Export', 'Знач', 'Val', 'Неопределено', 'Выполнить' + ], + namespaceFollows: [ + 'namespace', 'using', + ], + parenFollows: [ + 'if', 'for', 'while', 'switch', 'foreach', 'using', 'catch', 'when' + ], + operators: ['=', '<=', '>=', '<>', '<', '>', '+', '-', '*', '/', '%'], + symbols: /[=>=', '<>', '<', '>', '+', '-', '*', '/', '%'], - symbols: /[=> { - let bsl = new bslHelper(model, position); - let helper = bsl.getMetadataSigHelp(bslMetadata); - - if (!helper) - helper = bsl.getClassSigHelp(bslGlobals.classes); - - if (!helper) - helper = bsl.getCommonSigHelp(bslGlobals.globalfunctions); - - if (helper) - return new SignatureHelpResult(helper); - + return bsl.getSigHelp(); } }); - monaco.editor.defineTheme('bsl-white', { - base: 'vs', - inherit: true, - rules: [ - { token: 'commentbsl', foreground: '008000' }, - { token: 'keywordbsl', foreground: 'ff0000' }, - { token: 'delimiterbsl', foreground: 'ff0000' }, - { token: 'delimiter.squarebsl', foreground: 'ff0000' }, - { token: 'delimiter.parenthesisbsl', foreground: 'ff0000' }, - { token: 'identifierbsl', foreground: '0000ff' }, - { token: 'stringbsl', foreground: '000000' }, - { token: 'string.quotebsl', foreground: '000000' }, - { token: 'string.invalidbsl', foreground: '000000' }, - { token: 'numberbsl', foreground: '000000' }, - { token: 'number.floatbsl', foreground: '000000' }, - { token: 'preprocbsl', foreground: '963200' }, - ] - }); + monaco.languages.registerHoverProvider(language.id, { - monaco.editor.defineTheme('bsl-dark', { - base: 'vs', - inherit: true, - colors: { - 'foreground': '#d4d4d4', - 'editor.background': '#1e1e1e', - 'editor.selectionBackground': '#062f4a', - 'editorCursor.foreground': '#d4d4d4', - 'editorSuggestWidget.background': '#252526', - 'editorSuggestWidget.foreground': '#d4d4d4', - 'editorSuggestWidget.selectedBackground': '#062f4a', - 'editorWidget.background': '#252526', - 'editorWidget.foreground': '#d4d4d4', - 'editorWidget.border': '#d4d4d4' - }, - rules: [ - { token: 'commentbsl', foreground: '6A9955' }, - { token: 'keywordbsl', foreground: '499caa' }, - { token: 'delimiterbsl', foreground: 'd4d4d4' }, - { token: 'delimiter.squarebsl', foreground: 'd4d4d4' }, - { token: 'delimiter.parenthesisbsl', foreground: 'd4d4d4' }, - { token: 'identifierbsl', foreground: 'd4d4d4' }, - { token: 'stringbsl', foreground: 'c3602c' }, - { token: 'string.quotebsl', foreground: 'c3602c' }, - { token: 'string.invalidbsl', foreground: 'c3602c' }, - { token: 'numberbsl', foreground: 'b5cea8' }, - { token: 'number.floatbsl', foreground: 'b5cea8' }, - { token: 'preprocbsl', foreground: '963200' }, - { background: '#1e1e1e' } - ] - }); + provideHover: function (model, position) { + let bsl = new bslHelper(model, position); + return bsl.getHover(); + } - monaco.editor.setTheme('bsl-dark'); + }); + + for (const [key, value] of Object.entries(language.themes)) { + monaco.editor.defineTheme(value.name, value); + monaco.editor.setTheme(value.name); + } editor = monaco.editor.create(document.getElementById("container"), { theme: "bsl-white", value: getCode(), - language: 'bsl' + language: language.id }); }); \ No newline at end of file diff --git a/src/test.js b/src/test.js index c9947da..3d7fe42 100644 --- a/src/test.js +++ b/src/test.js @@ -239,6 +239,16 @@ describe("Проверка автокомлита и подсказок реда bslMetadata = JSON.parse(JSON.stringify(mCopy)); }); + it("проверка всплывающей подсказки", function () { + let model = getModel("Найти("); + let position = new monaco.Position(1, 2); + bsl = new bslHelper(model, position); + assert.notEqual(bsl.getHover(), null); + model = getModel("НайтиЧтоНибудь("); + bsl = new bslHelper(model, position); + assert.equal(bsl.getHover(), null); + }); + } mocha.run();