aboutsummaryrefslogtreecommitdiff
path: root/plugin/src/indentation.js
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-13 05:58:39 +0000
committer_ov4 <ov4@ov4.org>2026-10-13 05:58:39 +0000
commite52266d8ce8823866b2dad3e04ffc78438e08397 (patch)
tree6fbda74ac31a1d5db2e765d5ca086ae45df861ff /plugin/src/indentation.js
parentbe8154fac8a0ea5eb3dddef4053f12d67102009d (diff)
feat(plugin): add keyword Tab indentation and selection formatting
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat (limited to 'plugin/src/indentation.js')
-rw-r--r--plugin/src/indentation.js58
1 files changed, 58 insertions, 0 deletions
diff --git a/plugin/src/indentation.js b/plugin/src/indentation.js
new file mode 100644
index 0000000..c8af770
--- /dev/null
+++ b/plugin/src/indentation.js
@@ -0,0 +1,58 @@
+'use strict';
+
+const { codeOnly } = require('./lexical');
+const closers = {
+ ENDIF: 'IF', ENDWHILE: 'WHILE', UNTIL: 'REPEAT', NEXT: 'FOR',
+ ENDFUNCTION: 'FUNCTION', ENDPROCEDURE: 'PROCEDURE', ENDTYPE: 'TYPE'
+};
+
+function lineDepths(lines) {
+ const stack = [];
+ const depth = () => stack.filter(block => block.active).length;
+ 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 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 });
+ return current;
+ }
+ // Incomplete code should still format without negative indentation.
+ return depth();
+ }
+
+ const current = depth();
+ if (keyword === 'THEN' || keyword === 'DO') {
+ const top = stack[stack.length - 1];
+ if (top?.kind === (keyword === 'THEN' ? 'IF' : 'WHILE')) top.active = true;
+ } 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)) {
+ 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 });
+ }
+ return current;
+ });
+}
+
+function indentationEdits(lines, startLine, endLine, keepBlankIndent = false) {
+ const depths = lineDepths(lines.slice(0, endLine + 1));
+ const edits = [];
+ for (let line = startLine; line <= endLine; line++) {
+ const oldIndent = /^[\t ]*/.exec(lines[line])[0];
+ const blank = lines[line].trim() === '';
+ const newIndent = blank && !keepBlankIndent ? '' : '\t'.repeat(depths[line]);
+ if (oldIndent !== newIndent) {
+ edits.push({ line, length: oldIndent.length, text: newIndent });
+ }
+ }
+ return edits;
+}
+
+module.exports = { lineDepths, indentationEdits };