Skip to content

Commit 45e51a3

Browse files
committed
(todo) added block comments
1 parent 85d4988 commit 45e51a3

File tree

1 file changed

+118
-49
lines changed

1 file changed

+118
-49
lines changed

src/features/Folding.ts

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,27 @@ const tm = getCoreNodeModule("vscode-textmate");
1818
* Returns a node module installed with VSCode, or null if it fails.
1919
*/
2020
function getCoreNodeModule(moduleName: string) {
21-
try {
22-
return require(`${vscode.env.appRoot}/node_modules.asar/${moduleName}`);
23-
} catch (err) { }
21+
try {
22+
return require(`${vscode.env.appRoot}/node_modules.asar/${moduleName}`);
23+
} catch (err) { }
2424

25-
try {
26-
return require(`${vscode.env.appRoot}/node_modules/${moduleName}`);
27-
} catch (err) { }
25+
try {
26+
return require(`${vscode.env.appRoot}/node_modules/${moduleName}`);
27+
} catch (err) { }
2828

29-
return null;
29+
return null;
3030
}
3131

3232
interface IExtensionGrammar {
33-
language?: string, scopeName?: string, path?: string, embeddedLanguages?: {[scopeName:string]:string}, injectTo?: string[]
33+
language?: string; scopeName?: string; path?: string; embeddedLanguages?: { [scopeName: string]: string }; injectTo?: string[];
3434
}
3535

3636
interface IExtensionPackage {
3737
contributes?: {
38-
languages?: {id: string, configuration: string }[],
39-
grammars?: IExtensionGrammar[],
38+
languages?: { id: string; configuration: string }[],
39+
grammars?: IExtensionGrammar[],
4040
}
41-
}
41+
}
4242

4343
// Need to reproduce the IToken interface from vscode-textmate due to
4444
// the odd way it has to be required
@@ -61,20 +61,38 @@ class MatchedToken {
6161
public endline: number;
6262
public matchType: MatchType;
6363

64-
constructor(start, end, matchType: MatchType, document: vscode.TextDocument) {
64+
constructor(start: IToken = null,
65+
end: IToken = null,
66+
startLine: number = null,
67+
endLine: number = null,
68+
matchType: MatchType,
69+
document: vscode.TextDocument) {
6570
this.start = start;
6671
this.end = end;
67-
this.startline = document.positionAt(start.startIndex).line;
68-
this.endline = document.positionAt(end.startIndex).line;
72+
if (startLine === null) {
73+
this.startline = document.positionAt(start.startIndex).line;
74+
} else {
75+
this.startline = startLine;
76+
}
77+
if (endLine === null) {
78+
this.endline = document.positionAt(end.startIndex).line;
79+
} else {
80+
this.endline = endLine;
81+
}
6982
this.matchType = matchType;
7083
}
7184

7285
public isValidRange(): boolean {
73-
return (this.endline - this.startline >= 2);
86+
return (this.endline - this.startline >= 1);
7487
}
7588

7689
public toFoldingRange(): vscode.FoldingRange {
77-
return new vscode.FoldingRange(this.startline, this.endline, vscode.FoldingRangeKind.Region);
90+
let rk: vscode.FoldingRangeKind = vscode.FoldingRangeKind.Region;
91+
switch (this.matchType) {
92+
case MatchType.Comment: { rk = vscode.FoldingRangeKind.Comment; break; }
93+
case MatchType.Imports: { rk = vscode.FoldingRangeKind.Imports; break; }
94+
}
95+
return new vscode.FoldingRange(this.startline, this.endline, rk);
7896
}
7997
}
8098

@@ -83,9 +101,6 @@ interface IMatchedTokenList extends Array<MatchedToken> {}
83101
export class FoldingProvider implements vscode.FoldingRangeProvider {
84102
private powershellGrammar;
85103

86-
// constructor(
87-
// ) { }
88-
89104
public async provideFoldingRanges(
90105
document: vscode.TextDocument,
91106
context: vscode.FoldingContext,
@@ -99,16 +114,16 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
99114
const tokens = this.grammar().tokenizeLine(document.getText()).tokens;
100115
// Parse the token list looking for matching tokens and return
101116
// a list of <start, end>. Then filter the list and only return token matches
102-
// that are a valid text range e.g. Needs to span at least 3 lines
117+
// that are a valid text range e.g. Needs to span at least 2 lines
103118
const foldableTokens = this.matchGrammarTokens(tokens, document)
104-
.filter( (item) => item.isValidRange());
119+
.filter((item) => item.isValidRange());
105120

106121
// Sort the list of matched tokens to start at the top of the document,
107122
// and ensure that in the case of multiple ranges starting the same line,
108123
// that the largest range (i.e. most number of lines spanned) is sorted
109124
// first. This is needed as vscode will just ignore any duplicate folding
110125
// ranges
111-
foldableTokens.sort( (a: MatchedToken, b: MatchedToken) => {
126+
foldableTokens.sort((a: MatchedToken, b: MatchedToken) => {
112127
// Initially look at the start line
113128
if (a.startline > b.startline) { return 1; }
114129
if (a.startline < b.startline) { return -1; }
@@ -121,9 +136,7 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
121136
});
122137

123138
// Convert the matched token list into a FoldingRange[]
124-
foldableTokens.forEach( (item) => { foldingRanges.push(item.toFoldingRange()); });
125-
126-
// console.log(foldableTokens);
139+
foldableTokens.forEach((item) => { foldingRanges.push(item.toFoldingRange()); });
127140

128141
return foldingRanges;
129142
}
@@ -132,13 +145,13 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
132145
const result = [];
133146
const tokenStack = [];
134147

135-
tokens.forEach( (token) => {
136-
if (token.scopes.includes(startScopeName)) {
137-
tokenStack.push(token);
138-
}
139-
if (token.scopes.includes(endScopeName)) {
140-
result.push(new MatchedToken(tokenStack.pop(), token, matchType, document));
141-
}
148+
tokens.forEach((token) => {
149+
if (token.scopes.includes(startScopeName)) {
150+
tokenStack.push(token);
151+
}
152+
if (token.scopes.includes(endScopeName)) {
153+
result.push(new MatchedToken(tokenStack.pop(), token, null, null, matchType, document));
154+
}
142155
});
143156

144157
return result.reverse();
@@ -148,19 +161,72 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
148161
const result = [];
149162
let startToken;
150163

151-
tokens.forEach( (token, index) => {
152-
if (token.scopes.includes(scopeName)) {
153-
if (startToken === undefined) { startToken = token; }
164+
tokens.forEach((token, index) => {
165+
if (token.scopes.includes(scopeName)) {
166+
if (startToken === undefined) { startToken = token; }
167+
168+
// If we're at the end of the token list, or the next token does not include the scopeName
169+
// we've reached the end of the contiguous block.
170+
if (((index + 1) >= tokens.length) || (!tokens[index + 1].scopes.includes(scopeName))) {
171+
result.push(new MatchedToken(startToken, token, null, null, matchType, document));
172+
startToken = undefined;
173+
}
174+
}
175+
});
176+
177+
return result;
178+
}
179+
180+
// Given a zero based offset, find the line text preceeding it
181+
private preceedingText(offset: number, document: vscode.TextDocument) {
182+
const endPos = document.positionAt(offset);
183+
const startPos = endPos.translate(0, -endPos.character);
184+
185+
return document.getText(new vscode.Range(startPos, endPos));
186+
}
187+
188+
// Given a zero based offset, return the line number
189+
private lineAtOffest(offset, document): number {
190+
return document.positionAt(offset).line;
191+
}
192+
193+
private matchBlockCommentScopeElements(tokens, document: vscode.TextDocument) {
194+
const result = [];
154195

155-
// If we're at the end of the token list, or the next token does not include the scopeName
156-
// we've reached the end of the contiguous block.
157-
if (((index + 1) >= tokens.length) || (!tokens[index + 1].scopes.includes(scopeName))) {
158-
result.push(new MatchedToken(startToken, token, matchType, document));
159-
startToken = undefined;
196+
const emptyLine = new RegExp("^[\\s]+$");
197+
198+
let startLine: number = -1;
199+
let nextLine: number = -1;
200+
201+
tokens.forEach((token, index) => {
202+
if (token.scopes.includes("punctuation.definition.comment.powershell")) {
203+
// The punctuation.definition.comment.powershell token matches new-line comments
204+
// and inline comments e.g. `$x = 'foo' # inline comment`. We are only interested
205+
// in comments which begin the line i.e. no preceeding text
206+
if (emptyLine.test(this.preceedingText(token.startIndex, document))) {
207+
const lineNum = this.lineAtOffest(token.startIndex, document);
208+
// A simple pattern for keeping track of contiguous numbers in a known
209+
// sorted array
210+
if (startLine === -1) {
211+
startLine = lineNum;
212+
nextLine = lineNum + 1;
213+
} else {
214+
if (lineNum === nextLine) {
215+
nextLine = lineNum + 1;
216+
} else {
217+
result.push(new MatchedToken(null, null, startLine, nextLine - 1, MatchType.Comment, document));
218+
startLine = lineNum;
219+
nextLine = lineNum + 1;
220+
}
221+
}
222+
}
160223
}
161-
}
162224
});
163225

226+
if (startLine !== -1) {
227+
result.push(new MatchedToken(null, null, startLine, nextLine - 1, MatchType.Comment, document));
228+
}
229+
164230
return result;
165231
}
166232

@@ -170,11 +236,14 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
170236
// Find matching Braces { -> }
171237
this.matchScopeElements(tokens, "punctuation.section.braces.begin.powershell", "punctuation.section.braces.end.powershell", MatchType.Region, document).forEach((x) => { matchedTokens.push(x); });
172238
// Find matching brackets ( -> )
173-
this.matchScopeElements(tokens, "punctuation.section.group.begin.powershell", "punctuation.section.group.end.powershell", MatchType.Region, document).forEach( (x) => { matchedTokens.push(x); });
239+
this.matchScopeElements(tokens, "punctuation.section.group.begin.powershell", "punctuation.section.group.end.powershell", MatchType.Region, document).forEach((x) => { matchedTokens.push(x); });
174240

175241
// Find contiguous here strings @' -> '@ and @" -> "@
176-
this.matchContiguousScopeElements(tokens, "string.quoted.single.heredoc.powershell", MatchType.Region, document).forEach( (x) => { matchedTokens.push(x); });
177-
this.matchContiguousScopeElements(tokens, "string.quoted.double.heredoc.powershell", MatchType.Region, document).forEach( (x) => { matchedTokens.push(x); });
242+
this.matchContiguousScopeElements(tokens, "string.quoted.single.heredoc.powershell", MatchType.Region, document).forEach((x) => { matchedTokens.push(x); });
243+
this.matchContiguousScopeElements(tokens, "string.quoted.double.heredoc.powershell", MatchType.Region, document).forEach((x) => { matchedTokens.push(x); });
244+
245+
// Find blocks of line comments # foo
246+
this.matchBlockCommentScopeElements(tokens, document).forEach((x) => { matchedTokens.push(x); });
178247

179248
return matchedTokens;
180249
}
@@ -184,11 +253,11 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
184253
try {
185254
const psGrammars =
186255
vscode.extensions.all
187-
.filter( (x) => x.packageJSON && x.packageJSON.contributes && x.packageJSON.contributes.grammars)
188-
.reduce((a: (IExtensionGrammar&{extensionPath: string})[], b) =>
189-
[...a, ...(b.packageJSON as IExtensionPackage).contributes.grammars
190-
.map( (x) => Object.assign({extensionPath: b.extensionPath}, x))], [])
191-
.filter( (x) => x.language === "powershell");
256+
.filter((x) => x.packageJSON && x.packageJSON.contributes && x.packageJSON.contributes.grammars)
257+
.reduce((a: (IExtensionGrammar & { extensionPath: string })[], b) =>
258+
[...a, ...(b.packageJSON as IExtensionPackage).contributes.grammars
259+
.map((x) => Object.assign({ extensionPath: b.extensionPath }, x))], [])
260+
.filter((x) => x.language === "powershell");
192261
if (psGrammars.length === 0) { return ""; }
193262
return path.join(psGrammars[0].extensionPath, psGrammars[0].path);
194263
} catch (err) { return ""; }

0 commit comments

Comments
 (0)