@@ -40,6 +40,38 @@ interface IExtensionPackage {
4040 }
4141 }
4242
43+ // Need to reproduce the IToken interface from vscode-textmate due to
44+ // the odd way it has to be required
45+ export interface IToken {
46+ startIndex : number ;
47+ readonly endIndex : number ;
48+ readonly scopes : string [ ] ;
49+ }
50+
51+ class MatchedToken {
52+ public start : IToken ;
53+ public startline : number ;
54+ public end : IToken ;
55+ public endline : number ;
56+
57+ constructor ( start , end , document : vscode . TextDocument ) {
58+ this . start = start ;
59+ this . end = end ;
60+ this . startline = document . positionAt ( start . startIndex ) . line ;
61+ this . endline = document . positionAt ( end . startIndex ) . line ;
62+ }
63+
64+ public isValidRange ( ) : boolean {
65+ return ( this . endline - this . startline >= 2 ) ;
66+ }
67+
68+ public toFoldingRange ( ) : vscode . FoldingRange {
69+ return new vscode . FoldingRange ( this . startline , this . endline , vscode . FoldingRangeKind . Region ) ;
70+ }
71+ }
72+
73+ interface IMatchedTokenList extends Array < MatchedToken > { }
74+
4375export class FoldingProvider implements vscode . FoldingRangeProvider {
4476 private powershellGrammar ;
4577
@@ -54,46 +86,65 @@ export class FoldingProvider implements vscode.FoldingRangeProvider {
5486
5587 const foldingRanges = [ ] ;
5688
57- console . log ( document ) ;
58- console . log ( context ) ;
59- console . log ( token ) ;
89+ // Convert the document text into a series of grammar tokens
90+
91+ const tokens = this . grammar ( ) . tokenizeLine ( document . getText ( ) ) . tokens ;
92+ // Parse the token list looking for matching tokens and return
93+ // a list of <start, end>. Then filter the list and only return token matches
94+ // that are a valid text range e.g. Needs to span at least 3 lines
95+ const foldableTokens = this . matchGrammarTokens ( tokens , document )
96+ . filter ( ( item ) => item . isValidRange ( ) ) ;
97+
98+ // Sort the list of matched tokens to start at the top of the document,
99+ // and ensure that in the case of multiple ranges starting the same line,
100+ // that the largest range (i.e. most number of lines spanned) is sorted
101+ // first. This is needed as vscode will just ignore any duplicate folding
102+ // ranges
103+ foldableTokens . sort ( ( a : MatchedToken , b : MatchedToken ) => {
104+ // Initially look at the start line
105+ if ( a . startline > b . startline ) { return 1 ; }
106+ if ( a . startline < b . startline ) { return - 1 ; }
107+ // They have the same start line so now consider the end line.
108+ // The biggest line range is sorted first
109+ if ( a . endline > b . endline ) { return - 1 ; }
110+ if ( a . endline < b . endline ) { return 1 ; }
111+ // They're the same
112+ return 0 ;
113+ } ) ;
114+
115+ // Convert the matched token list into a FoldingRange[]
116+ foldableTokens . forEach ( ( item ) => { foldingRanges . push ( item . toFoldingRange ( ) ) ; } ) ;
117+
118+ // console.log(foldableTokens);
60119
61- const content = document . getText ( ) ;
120+ return foldingRanges ;
121+ }
122+
123+ private matchScopeElements ( tokens , startScopeName , endScopeName , document : vscode . TextDocument ) : IMatchedTokenList {
124+ const result = [ ] ;
125+ const tokenStack = [ ] ;
62126
63- let tokens = this . grammar ( ) . tokenizeLine ( content ) . tokens ;
64- for ( var i = 0 ; i < tokens . length ; i ++ ) {
65- tokens [ i ] [ 'value' ] = content . substring ( tokens [ i ] [ 'startIndex' ] , tokens [ i ] [ 'endIndex' ] ) ;
66- tokens [ i ] [ 'startLine' ] = document . positionAt ( tokens [ i ] [ "startIndex" ] ) . line ;
67- tokens [ i ] [ 'endLine' ] = document . positionAt ( tokens [ i ] [ "endIndex" ] ) . line ;
127+ tokens . forEach ( ( token ) => {
128+ if ( token . scopes . includes ( startScopeName ) ) {
129+ tokenStack . push ( token ) ;
68130 }
131+ if ( token . scopes . includes ( endScopeName ) ) {
132+ result . push ( new MatchedToken ( tokenStack . pop ( ) , token , document ) ) ;
133+ }
134+ } ) ;
69135
70- // Just force a fixed folding range to see what happens
71- foldingRanges . push ( new vscode . FoldingRange ( 1 , 3 , vscode . FoldingRangeKind . Region ) ) ;
72-
73- console . log ( tokens ) ;
74- // const tocProvider = new TableOfContentsProvider(this.engine, document);
75- // let toc = await tocProvider.getToc();
76- // if (toc.length > rangeLimit) {
77- // toc = toc.slice(0, rangeLimit);
78- // }
79-
80- // const foldingRanges = toc.map((entry, startIndex) => {
81- // const start = entry.line;
82- // let end: number | undefined = undefined;
83- // for (let i = startIndex + 1; i < toc.length; ++i) {
84- // if (toc[i].level <= entry.level) {
85- // end = toc[i].line - 1;
86- // if (document.lineAt(end).isEmptyOrWhitespace && end >= start + 1) {
87- // end = end - 1;
88- // }
89- // break;
90- // }
91- // }
92- // return new vscode.FoldingRange(
93- // start,
94- // typeof end === 'number' ? end : document.lineCount - 1);
95- // });
96- return foldingRanges ;
136+ return result . reverse ( ) ;
137+ }
138+
139+ private matchGrammarTokens ( tokens , document : vscode . TextDocument ) : IMatchedTokenList {
140+ const matchedTokens = [ ] ;
141+
142+ // Find matching Braces { -> }
143+ this . matchScopeElements ( tokens , "punctuation.section.braces.begin.powershell" , "punctuation.section.braces.end.powershell" , document ) . forEach ( ( x ) => { matchedTokens . push ( x ) ; } ) ;
144+ // Find matching brackets ( -> )
145+ this . matchScopeElements ( tokens , "punctuation.section.group.begin.powershell" , "punctuation.section.group.end.powershell" , document ) . forEach ( ( x ) => { matchedTokens . push ( x ) ; } ) ;
146+
147+ return matchedTokens ;
97148 }
98149
99150 private powerShellGrammarPath ( ) : string {
0 commit comments