aboutsummaryrefslogtreecommitdiff
path: root/plugin/src/indentation.js
blob: fd3f5850e9a0a48fa6501ea8d4f52f7dfeb4fd96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

'use strict';

const { codeOnly } = require('./lexical');
const onTypeCharacters = ['\n', 'E', 'F', 'L', 'T', 'N', 'H', 'Y'];

function shouldFormatOnType(line, character) {
	return character === '\n' || /^\s*(?:ELSE|ENDIF|ENDWHILE|UNTIL|NEXT|ENDFUNCTION|ENDPROCEDURE|ENDTYPE|CATCH|ENDTRY)\b/.test(codeOnly(line));
}

function selectionEndLine(startLine, endLine, endColumn) {
	return endColumn === 0 && endLine > startLine ? endLine - 1 : endLine;
}
const closers = {
	ENDIF: 'IF', ENDWHILE: 'WHILE', UNTIL: 'REPEAT', NEXT: 'FOR',
	ENDFUNCTION: 'FUNCTION', ENDPROCEDURE: 'PROCEDURE', ENDTYPE: 'TYPE', ENDTRY: 'TRY'
};

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];
		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 (branch) stack.push({ kind, 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', '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 });
		}
		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, onTypeCharacters, shouldFormatOnType, selectionEndLine };