From e52266d8ce8823866b2dad3e04ffc78438e08397 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 13 Oct 2026 05:58:39 +0000 Subject: feat(plugin): add keyword Tab indentation and selection formatting Co-Authored-By: Claude Fable 5.1 --- plugin/src/indentation.js | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 plugin/src/indentation.js (limited to 'plugin/src/indentation.js') 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 }; -- cgit v1.3.1-4-g156e