aboutsummaryrefslogtreecommitdiff
path: root/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'plugin')
-rw-r--r--plugin/package.json46
-rw-r--r--plugin/src/brackets.js25
-rw-r--r--plugin/src/extension.js24
-rw-r--r--plugin/test/brackets.test.js29
4 files changed, 109 insertions, 15 deletions
diff --git a/plugin/package.json b/plugin/package.json
index 003a866..60cf9da 100644
--- a/plugin/package.json
+++ b/plugin/package.json
@@ -10,20 +10,32 @@
"url": "https://github.com/ovfor4/cie-pseudocode-compiler.git",
"directory": "plugin"
},
- "engines": { "vscode": "^1.85.0" },
+ "engines": {
+ "vscode": "^1.85.0"
+ },
"main": "./src/extension.js",
- "categories": ["Programming Languages", "Formatters"],
+ "categories": [
+ "Programming Languages",
+ "Formatters"
+ ],
"scripts": {
- "check": "node --check src/extension.js && node --check src/indentation.js && node --check src/lexical.js",
+ "check": "node --check src/extension.js && node --check src/indentation.js && node --check src/lexical.js && node --check src/brackets.js",
"test": "node --test test/*.test.js"
},
"contributes": {
- "languages": [{
- "id": "cie-pseudocode",
- "aliases": ["CIE Pseudocode", "cie-pseudocode"],
- "extensions": [".cps"],
- "configuration": "./language-configuration.json"
- }],
+ "languages": [
+ {
+ "id": "cie-pseudocode",
+ "aliases": [
+ "CIE Pseudocode",
+ "cie-pseudocode"
+ ],
+ "extensions": [
+ ".cps"
+ ],
+ "configuration": "./language-configuration.json"
+ }
+ ],
"configurationDefaults": {
"[cie-pseudocode]": {
"editor.insertSpaces": false,
@@ -31,14 +43,18 @@
"editor.detectIndentation": false,
"editor.autoIndent": "full",
"editor.formatOnType": true,
- "editor.defaultFormatter": "ovfor4.cie-pseudocode"
+ "editor.defaultFormatter": "ovfor4.cie-pseudocode",
+ "editor.bracketPairColorization.enabled": true,
+ "editor.bracketPairColorization.independentColorPoolPerBracketType": false
}
},
- "grammars": [{
- "language": "cie-pseudocode",
- "scopeName": "source.cie-pseudocode",
- "path": "./syntaxes/cie-pseudocode.tmLanguage.json"
- }]
+ "grammars": [
+ {
+ "language": "cie-pseudocode",
+ "scopeName": "source.cie-pseudocode",
+ "path": "./syntaxes/cie-pseudocode.tmLanguage.json"
+ }
+ ]
},
"devDependencies": {
"vscode-oniguruma": "^2.0.1",
diff --git a/plugin/src/brackets.js b/plugin/src/brackets.js
new file mode 100644
index 0000000..ad79ea5
--- /dev/null
+++ b/plugin/src/brackets.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const { codeOnly } = require('./lexical');
+const pairs = { ')': '(', ']': '[' };
+
+function unmatchedBrackets(text) {
+ const code = codeOnly(text);
+ const stack = [];
+ const unmatched = [];
+ for (let offset = 0; offset < code.length; offset++) {
+ const char = code[offset];
+ if (char === '(' || char === '[') {
+ stack.push({ char, offset });
+ } else if (pairs[char]) {
+ const opening = stack.pop();
+ if (!opening || opening.char !== pairs[char]) {
+ if (opening) unmatched.push(opening.offset);
+ unmatched.push(offset);
+ }
+ }
+ }
+ return unmatched.concat(stack.map(opening => opening.offset)).sort((a, b) => a - b);
+}
+
+module.exports = { unmatchedBrackets };
diff --git a/plugin/src/extension.js b/plugin/src/extension.js
index a7b4834..68bb7fd 100644
--- a/plugin/src/extension.js
+++ b/plugin/src/extension.js
@@ -3,6 +3,7 @@
const vscode = require('vscode');
const { indentationEdits } = require('./indentation');
const { codeOnly } = require('./lexical');
+const { unmatchedBrackets } = require('./brackets');
const languageId = 'cie-pseudocode';
function formatLines(document, startLine, endLine, keepBlankIndent = false) {
@@ -12,7 +13,29 @@ function formatLines(document, startLine, endLine, keepBlankIndent = false) {
}
function activate(context) {
+ const invalidBracket = vscode.window.createTextEditorDecorationType({
+ dark: { color: '#F44747' }
+ });
+ function refreshBrackets(document) {
+ const rangesByDocument = new Map();
+ for (const editor of vscode.window.visibleTextEditors) {
+ if (document && editor.document !== document) continue;
+ if (editor.document.languageId !== languageId) {
+ editor.setDecorations(invalidBracket, []);
+ continue;
+ }
+ if (!rangesByDocument.has(editor.document)) {
+ const ranges = unmatchedBrackets(editor.document.getText()).map(offset =>
+ new vscode.Range(editor.document.positionAt(offset), editor.document.positionAt(offset + 1)));
+ rangesByDocument.set(editor.document, ranges);
+ }
+ editor.setDecorations(invalidBracket, rangesByDocument.get(editor.document));
+ }
+ }
context.subscriptions.push(
+ invalidBracket,
+ vscode.workspace.onDidChangeTextDocument(event => refreshBrackets(event.document)),
+ vscode.window.onDidChangeVisibleTextEditors(() => refreshBrackets()),
vscode.languages.registerDocumentRangeFormattingEditProvider(languageId, {
provideDocumentRangeFormattingEdits(document, range) {
const endLine = range.end.character === 0 && range.end.line > range.start.line
@@ -28,6 +51,7 @@ function activate(context) {
}
}, '\n', 'E', 'F', 'L', 'T', 'N')
);
+ refreshBrackets();
}
module.exports = { activate };
diff --git a/plugin/test/brackets.test.js b/plugin/test/brackets.test.js
new file mode 100644
index 0000000..822a98f
--- /dev/null
+++ b/plugin/test/brackets.test.js
@@ -0,0 +1,29 @@
+'use strict';
+
+const assert = require('node:assert/strict');
+const { test } = require('node:test');
+const { unmatchedBrackets } = require('../src/brackets');
+
+test('mixed nesting matches across lines and repaired brackets stop being errors', () => {
+ assert.deepEqual(unmatchedBrackets('A[(B + C)\n + D[1]]'), []);
+ assert.deepEqual(unmatchedBrackets('A['), [1]);
+ assert.deepEqual(unmatchedBrackets('A[1]'), []);
+ assert.deepEqual(unmatchedBrackets('(\n)'), []);
+});
+
+test('unpaired opening, closing and crossed bracket types are all reported', () => {
+ assert.deepEqual(unmatchedBrackets('(['), [0, 1]);
+ assert.deepEqual(unmatchedBrackets(')]'), [0, 1]);
+ assert.deepEqual(unmatchedBrackets('([)]'), [0, 1, 2, 3]);
+ assert.deepEqual(unmatchedBrackets('(]'), [0, 1]);
+});
+
+test('comments and quotes are ignored with correct UTF-16 offsets after Unicode and CRLF', () => {
+ assert.deepEqual(unmatchedBrackets('OUTPUT "[( //" // )]\nOUTPUT \'[\''), []);
+ const unfinished = 'OUTPUT "unfinished [\r\nA[';
+ assert.deepEqual(unmatchedBrackets(unfinished), [unfinished.length - 1]);
+ const unicode = 'OUTPUT "你好😀"\r\nA[';
+ assert.deepEqual(unmatchedBrackets(unicode), [unicode.length - 1]);
+ // Backslashes do not escape CIE quotes.
+ assert.deepEqual(unmatchedBrackets('"x\\"['), [4]);
+});