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 };
|