aboutsummaryrefslogtreecommitdiff
path: root/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'plugin')
-rw-r--r--plugin/AGENTS/ARCH.md7
-rw-r--r--plugin/src/extension.js4
-rw-r--r--plugin/src/indentation.js11
-rw-r--r--plugin/test/indentation.test.js39
4 files changed, 49 insertions, 12 deletions
diff --git a/plugin/AGENTS/ARCH.md b/plugin/AGENTS/ARCH.md
index 1a1c076..8215615 100644
--- a/plugin/AGENTS/ARCH.md
+++ b/plugin/AGENTS/ARCH.md
@@ -13,7 +13,8 @@ there is no compiler library or cpptools integration.
comments, brackets, quote pairs and identifiers.
- `syntaxes/cie-pseudocode.tmLanguage.json` adapts small C++ comment, quote and
comma rules and uses the same general token scope categories. CIE keyword,
- builtin, number and operator patterns are locally defined.
+ builtin, number and operator patterns are locally defined. Exception keywords
+ reuse C++ try/catch/throw scopes, with ENDTRY sharing the TRY scope.
- cpptools was consulted for provider registration. Its formatter and language
server are not called, imported or bundled. This extension is not a cpptools
fork. The formatting API accepts locally computed edits; it does not implement
@@ -78,7 +79,9 @@ Follow the current compiler, rather than implementing every PDF feature:
- Double-quoted strings and single-quoted characters have no escapes or multiline
continuation. The compiler accepts `'''` as a quote character literal.
- Blocks: IF/THEN/ELSE/ENDIF, WHILE/DO/ENDWHILE, REPEAT/UNTIL, FOR/NEXT,
- FUNCTION/ENDFUNCTION, PROCEDURE/ENDPROCEDURE and record TYPE/ENDTYPE.
+ FUNCTION/ENDFUNCTION, PROCEDURE/ENDPROCEDURE, record TYPE/ENDTYPE and
+ TRY/CATCH/ENDTRY. CATCH aligns with TRY and starts another indented body;
+ ENDTRY closes the block. Bare THROW does not change indentation depth.
- Separate THEN and DO lines activate the pending block once. Enum and pointer
`TYPE Name = ...` declarations do not open a record block.
- Builtins: LENGTH, MID, RIGHT, LEFT, LCASE, UCASE, ASC, CHR, IS_NUM, NUM_TO_STR,
diff --git a/plugin/src/extension.js b/plugin/src/extension.js
index 70c8622..6bd1255 100644
--- a/plugin/src/extension.js
+++ b/plugin/src/extension.js
@@ -49,11 +49,11 @@ function activate(context) {
}),
vscode.languages.registerOnTypeFormattingEditProvider(languageId, {
provideOnTypeFormattingEdits(document, position, char) {
- if (char !== '\n' && !/^\s*(?:ELSE|ENDIF|ENDWHILE|UNTIL|NEXT|ENDFUNCTION|ENDPROCEDURE|ENDTYPE)\b/.test(
+ if (char !== '\n' && !/^\s*(?:ELSE|ENDIF|ENDWHILE|UNTIL|NEXT|ENDFUNCTION|ENDPROCEDURE|ENDTYPE|CATCH|ENDTRY)\b/.test(
codeOnly(document.lineAt(position.line).text))) return [];
return formatLines(document, position.line, position.line, char === '\n');
}
- }, '\n', 'E', 'F', 'L', 'T', 'N')
+ }, '\n', 'E', 'F', 'L', 'T', 'N', 'H', 'Y')
);
refreshBrackets();
}
diff --git a/plugin/src/indentation.js b/plugin/src/indentation.js
index c8af770..b7fca78 100644
--- a/plugin/src/indentation.js
+++ b/plugin/src/indentation.js
@@ -3,7 +3,7 @@
const { codeOnly } = require('./lexical');
const closers = {
ENDIF: 'IF', ENDWHILE: 'WHILE', UNTIL: 'REPEAT', NEXT: 'FOR',
- ENDFUNCTION: 'FUNCTION', ENDPROCEDURE: 'PROCEDURE', ENDTYPE: 'TYPE'
+ ENDFUNCTION: 'FUNCTION', ENDPROCEDURE: 'PROCEDURE', ENDTYPE: 'TYPE', ENDTRY: 'TRY'
};
function lineDepths(lines) {
@@ -12,13 +12,14 @@ function lineDepths(lines) {
return lines.map(line => {
const code = codeOnly(line).trim();
const keyword = /^([A-Za-z][A-Za-z0-9_]*)\b/.exec(code)?.[1];
- if (closers[keyword] || keyword === 'ELSE') {
- const kind = keyword === 'ELSE' ? 'IF' : closers[keyword];
+ const branch = keyword === 'ELSE' ? 'IF' : keyword === 'CATCH' ? 'TRY' : null;
+ if (closers[keyword] || branch) {
+ const kind = branch || closers[keyword];
const index = stack.findLastIndex(block => block.kind === kind);
if (index >= 0) {
stack.length = index;
const current = depth();
- if (keyword === 'ELSE') stack.push({ kind: 'IF', active: true });
+ if (branch) stack.push({ kind, active: true });
return current;
}
// Incomplete code should still format without negative indentation.
@@ -32,7 +33,7 @@ function lineDepths(lines) {
} else if (keyword === 'IF' || keyword === 'WHILE') {
stack.push({ kind: keyword, active: new RegExp(
keyword === 'IF' ? '\\bTHEN\\b' : '\\bDO\\b').test(code) });
- } else if (['FOR', 'REPEAT', 'FUNCTION', 'PROCEDURE'].includes(keyword)) {
+ } else if (['FOR', 'REPEAT', 'FUNCTION', 'PROCEDURE', 'TRY'].includes(keyword)) {
stack.push({ kind: keyword, active: true });
} else if (/^TYPE\s+[A-Za-z][A-Za-z0-9_]*$/.test(code)) {
stack.push({ kind: 'TYPE', active: true });
diff --git a/plugin/test/indentation.test.js b/plugin/test/indentation.test.js
index 9a50cb3..1a9d91e 100644
--- a/plugin/test/indentation.test.js
+++ b/plugin/test/indentation.test.js
@@ -24,6 +24,36 @@ test('split THEN/DO, single-line TYPE, comments and strings do not add extra lev
assert.deepEqual(lineDepths(lines), [0, 0, 0, 0, 1, 1, 2, 2, 1, 0, 0, 0]);
});
+test('try and catch bodies nest with existing blocks and THROW keeps their depth', () => {
+ const lines = [
+ 'PROCEDURE Demo()', 'TRY', 'IF Ready THEN', 'THROW', 'ENDIF',
+ 'CATCH', 'TRY', 'THROW', 'CATCH', 'WHILE Ready DO', 'THROW',
+ 'ENDWHILE', 'ENDTRY', 'ENDTRY', 'OUTPUT "Done"', 'ENDPROCEDURE'
+ ];
+ assert.deepEqual(lineDepths(lines), [0, 1, 2, 3, 2, 1, 2, 3, 2, 3, 4, 3, 2, 1, 1, 0]);
+ assert.deepEqual(lineDepths([
+ 'CATCH', 'ENDTRY', 'try', 'TRYValue <- 1', 'TRY // CATCH ENDTRY',
+ '// ENDTRY', 'OUTPUT "CATCH ENDTRY"', 'CATCH // TRY', 'THROW',
+ 'ENDTRY', 'OUTPUT "Done"'
+ ]), [0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0]);
+});
+
+test('catch selection uses earlier TRY context and formatting is idempotent', () => {
+ const lines = ['IF Ready THEN', ' TRY', ' THROW', ' CATCH',
+ ' OUTPUT "Caught"', ' ENDTRY', 'ENDIF'];
+ const edits = indentationEdits(lines, 3, 5);
+ assert.deepEqual(edits, [
+ { line: 3, length: 4, text: '\t' },
+ { line: 4, length: 1, text: '\t\t' },
+ { line: 5, length: 3, text: '\t' }
+ ]);
+ for (const edit of edits) lines[edit.line] = edit.text + lines[edit.line].slice(edit.length);
+ assert.equal(lines[1], ' TRY');
+ assert.equal(lines[2], ' THROW');
+ assert.equal(lines[4], '\t\tOUTPUT "Caught"');
+ assert.deepEqual(indentationEdits(lines, 3, 5), []);
+});
+
test('range formatting reads earlier context, preserves text and is idempotent', () => {
const lines = ['IF X THEN', ' WHILE Y DO', ' OUTPUT "x" // keep spaces', ' ENDWHILE', 'ENDIF'];
const edits = indentationEdits(lines, 2, 3);
@@ -38,7 +68,10 @@ test('range formatting reads earlier context, preserves text and is idempotent',
});
test('blank lines are empty when formatted and indented at the caret after Enter', () => {
- const lines = ['IF X THEN', ' '];
- assert.deepEqual(indentationEdits(lines, 1, 1), [{ line: 1, length: 3, text: '' }]);
- assert.deepEqual(indentationEdits(lines, 1, 1, true), [{ line: 1, length: 3, text: '\t' }]);
+ for (const header of [['IF X THEN'], ['TRY'], ['TRY', 'THROW', 'CATCH']]) {
+ const lines = [...header, ' '];
+ const line = header.length;
+ assert.deepEqual(indentationEdits(lines, line, line), [{ line, length: 3, text: '' }]);
+ assert.deepEqual(indentationEdits(lines, line, line, true), [{ line, length: 3, text: '\t' }]);
+ }
});