@@ -18,27 +18,27 @@ const tm = getCoreNodeModule("vscode-textmate");
18
18
* Returns a node module installed with VSCode, or null if it fails.
19
19
*/
20
20
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 ) { }
24
24
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 ) { }
28
28
29
- return null ;
29
+ return null ;
30
30
}
31
31
32
32
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 [ ] ;
34
34
}
35
35
36
36
interface IExtensionPackage {
37
37
contributes ?: {
38
- languages ?: { id : string , configuration : string } [ ] ,
39
- grammars ?: IExtensionGrammar [ ] ,
38
+ languages ?: { id : string ; configuration : string } [ ] ,
39
+ grammars ?: IExtensionGrammar [ ] ,
40
40
}
41
- }
41
+ }
42
42
43
43
// Need to reproduce the IToken interface from vscode-textmate due to
44
44
// the odd way it has to be required
@@ -61,20 +61,38 @@ class MatchedToken {
61
61
public endline : number ;
62
62
public matchType : MatchType ;
63
63
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 ) {
65
70
this . start = start ;
66
71
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
+ }
69
82
this . matchType = matchType ;
70
83
}
71
84
72
85
public isValidRange ( ) : boolean {
73
- return ( this . endline - this . startline >= 2 ) ;
86
+ return ( this . endline - this . startline >= 1 ) ;
74
87
}
75
88
76
89
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 ) ;
78
96
}
79
97
}
80
98
@@ -83,9 +101,6 @@ interface IMatchedTokenList extends Array<MatchedToken> {}
83
101
export class FoldingProvider implements vscode . FoldingRangeProvider {
84
102
private powershellGrammar ;
85
103
86
- // constructor(
87
- // ) { }
88
-
89
104
public async provideFoldingRanges (
90
105
document : vscode . TextDocument ,
91
106
context : vscode . FoldingContext ,
@@ -99,16 +114,16 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
99
114
const tokens = this . grammar ( ) . tokenizeLine ( document . getText ( ) ) . tokens ;
100
115
// Parse the token list looking for matching tokens and return
101
116
// 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
103
118
const foldableTokens = this . matchGrammarTokens ( tokens , document )
104
- . filter ( ( item ) => item . isValidRange ( ) ) ;
119
+ . filter ( ( item ) => item . isValidRange ( ) ) ;
105
120
106
121
// Sort the list of matched tokens to start at the top of the document,
107
122
// and ensure that in the case of multiple ranges starting the same line,
108
123
// that the largest range (i.e. most number of lines spanned) is sorted
109
124
// first. This is needed as vscode will just ignore any duplicate folding
110
125
// ranges
111
- foldableTokens . sort ( ( a : MatchedToken , b : MatchedToken ) => {
126
+ foldableTokens . sort ( ( a : MatchedToken , b : MatchedToken ) => {
112
127
// Initially look at the start line
113
128
if ( a . startline > b . startline ) { return 1 ; }
114
129
if ( a . startline < b . startline ) { return - 1 ; }
@@ -121,9 +136,7 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
121
136
} ) ;
122
137
123
138
// 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 ( ) ) ; } ) ;
127
140
128
141
return foldingRanges ;
129
142
}
@@ -132,13 +145,13 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
132
145
const result = [ ] ;
133
146
const tokenStack = [ ] ;
134
147
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
+ }
142
155
} ) ;
143
156
144
157
return result . reverse ( ) ;
@@ -148,19 +161,72 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
148
161
const result = [ ] ;
149
162
let startToken ;
150
163
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 = [ ] ;
154
195
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
+ }
160
223
}
161
- }
162
224
} ) ;
163
225
226
+ if ( startLine !== - 1 ) {
227
+ result . push ( new MatchedToken ( null , null , startLine , nextLine - 1 , MatchType . Comment , document ) ) ;
228
+ }
229
+
164
230
return result ;
165
231
}
166
232
@@ -170,11 +236,14 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
170
236
// Find matching Braces { -> }
171
237
this . matchScopeElements ( tokens , "punctuation.section.braces.begin.powershell" , "punctuation.section.braces.end.powershell" , MatchType . Region , document ) . forEach ( ( x ) => { matchedTokens . push ( x ) ; } ) ;
172
238
// 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 ) ; } ) ;
174
240
175
241
// 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 ) ; } ) ;
178
247
179
248
return matchedTokens ;
180
249
}
@@ -184,11 +253,11 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
184
253
try {
185
254
const psGrammars =
186
255
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" ) ;
192
261
if ( psGrammars . length === 0 ) { return "" ; }
193
262
return path . join ( psGrammars [ 0 ] . extensionPath , psGrammars [ 0 ] . path ) ;
194
263
} catch ( err ) { return "" ; }
0 commit comments