forked from atom/atom
-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathtree-indenter.js
155 lines (136 loc) · 5.19 KB
/
tree-indenter.js
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// const log = console.debug // in dev
const log = () => {}; // in production
module.exports = class TreeIndenter {
constructor(languageMode, scopes = undefined) {
this.languageMode = languageMode;
this.scopes =
scopes ||
languageMode.config.get('editor.scopes', {
scope: this.languageMode.rootScopeDescriptor
});
log('[TreeIndenter] constructor', this.scopes);
}
/** tree indenter is configured for this language */
get isConfigured() {
return !!this.scopes;
}
// Given a position, walk up the syntax tree, to find the highest level
// node that still starts here. This is to identify the column where this
// node (e.g., an HTML closing tag) ends.
_getHighestSyntaxNodeAtPosition(row, column = null) {
if (column == null) {
// Find the first character on the row that is not whitespace + 1
column = this.languageMode.buffer.lineForRow(row).search(/\S/);
}
let syntaxNode;
if (column >= 0) {
syntaxNode = this.languageMode.getSyntaxNodeAtPosition({ row, column });
while (
syntaxNode &&
syntaxNode.parent &&
syntaxNode.parent.startPosition.row === syntaxNode.startPosition.row &&
syntaxNode.parent.endPosition.row === syntaxNode.startPosition.row &&
syntaxNode.parent.startPosition.column ===
syntaxNode.startPosition.column
) {
syntaxNode = syntaxNode.parent;
}
return syntaxNode;
}
}
/** Walk up the tree. Everytime we meet a scope type, check whether we
are coming from the first (resp. last) child. If so, we are opening
(resp. closing) that scope, i.e., do not count it. Otherwise, add 1.
This is the core function.
It might make more sense to reverse the direction of this walk, i.e.,
go from root to leaf instead.
*/
_treeWalk(node, lastScope = null) {
if (node == null || node.parent == null) {
return 0;
} else {
let increment = 0;
const notFirstOrLastSibling =
node.previousSibling != null && node.nextSibling != null;
const isScope = this.scopes.indent[node.parent.type];
notFirstOrLastSibling && isScope && increment++;
const isScope2 = this.scopes.indentExceptFirst[node.parent.type];
!increment && isScope2 && node.previousSibling != null && increment++;
const isScope3 = this.scopes.indentExceptFirstOrBlock[node.parent.type];
!increment && isScope3 && node.previousSibling != null && increment++;
// apply current row, single line, type-based rules, e.g., 'else' or 'private:'
let typeDent = 0;
this.scopes.types.indent[node.type] && typeDent++;
this.scopes.types.outdent[node.type] && increment && typeDent--;
increment += typeDent;
// check whether the last (lower) indentation happened due to a scope that
// started on the same row and ends directly before this.
if (
lastScope &&
increment > 0 &&
// previous (lower) scope was a two-sided scope, reduce if starts on
// same row and ends right before
// TODO: this currently only works for scopes that have a single-character
// closing delimiter (like statement_blocks, but not HTML, for instance).
((node.parent.startPosition.row === lastScope.node.startPosition.row &&
node.parent.endIndex <= lastScope.node.endIndex + 1) ||
// or this is a special scope (like if, while) and it's ends coincide
(isScope3 &&
(lastScope.node.endIndex === node.endIndex ||
node.parent.endIndex === node.endIndex)))
) {
log('ignoring repeat', node.parent.type, lastScope);
increment = 0;
} else {
lastScope &&
log(
node.parent.startPosition.row,
lastScope.node.startPosition.row,
node.parent.endIndex,
lastScope.node.endIndex,
isScope3,
node.endIndex
);
}
log('treewalk', {
node,
notFirstOrLastSibling,
type: node.parent.type,
increment
});
const newLastScope =
isScope || isScope2 ? { node: node.parent } : lastScope;
return this._treeWalk(node.parent, newLastScope) + increment;
}
}
suggestedIndentForBufferRow(row, tabLength, options) {
// get current indentation for row
const line = this.languageMode.buffer.lineForRow(row);
const currentIndentation = this.languageMode.indentLevelForLine(
line,
tabLength
);
const syntaxNode = this._getHighestSyntaxNodeAtPosition(row);
if (!syntaxNode) {
const previousRow = Math.max(row - 1, 0);
const previousIndentation = this.languageMode.indentLevelForLine(
this.languageMode.indentLevelForLine(previousRow),
tabLength
);
return previousIndentation;
}
let indentation = this._treeWalk(syntaxNode);
// Special case for comments
if (
(syntaxNode.type === 'comment' || syntaxNode.type === 'description') &&
syntaxNode.startPosition.row < row &&
syntaxNode.endPosition.row > row
) {
indentation += 1;
}
if (options && options.preserveLeadingWhitespace) {
indentation -= currentIndentation;
}
return indentation;
}
};