@@ -12,6 +12,11 @@ interface PackageSymbolsCommandResult {
1212 Symbols : PackageSymbolData [ ] ;
1313}
1414
15+ enum PackageOutlineSortOrder {
16+ Position = 'position' ,
17+ Name = 'name'
18+ }
19+
1520export class GoPackageOutlineProvider implements vscode . TreeDataProvider < PackageSymbol > {
1621 private _onDidChangeTreeData : vscode . EventEmitter < PackageSymbol | undefined > = new vscode . EventEmitter <
1722 PackageSymbol | undefined
@@ -21,13 +26,30 @@ export class GoPackageOutlineProvider implements vscode.TreeDataProvider<Package
2126
2227 public result ?: PackageSymbolsCommandResult ;
2328 public activeDocument ?: vscode . TextDocument ;
29+ public view ?: vscode . TreeView < PackageSymbol > ;
30+
31+ private readonly collator = new Intl . Collator ( undefined , { numeric : true , sensitivity : 'base' } ) ;
32+ private packageSymbols : PackageSymbol [ ] = [ ] ;
33+ private packageItem = this . createPackageItem ( ) ;
34+ private sortOrder = PackageOutlineSortOrder . Position ;
35+ private lastRevealedSymbol ?: PackageSymbol ;
2436
2537 static setup ( ctx : vscode . ExtensionContext ) {
2638 const provider = new this ( ctx ) ;
27- const {
28- window : { registerTreeDataProvider }
29- } = vscode ;
30- ctx . subscriptions . push ( registerTreeDataProvider ( 'go.package.outline' , provider ) ) ;
39+ provider . view = vscode . window . createTreeView ( 'go.package.outline' , {
40+ treeDataProvider : provider ,
41+ showCollapseAll : true
42+ } ) ;
43+ ctx . subscriptions . push ( provider . view ) ;
44+ ctx . subscriptions . push (
45+ vscode . commands . registerCommand ( 'go.packageOutline.sortByName' , ( ) =>
46+ provider . setSortOrder ( PackageOutlineSortOrder . Name )
47+ ) ,
48+ vscode . commands . registerCommand ( 'go.packageOutline.sortByPosition' , ( ) =>
49+ provider . setSortOrder ( PackageOutlineSortOrder . Position )
50+ )
51+ ) ;
52+ provider . updateContextKeys ( ) ;
3153 return provider ;
3254 }
3355
@@ -51,60 +73,43 @@ export class GoPackageOutlineProvider implements vscode.TreeDataProvider<Package
5173 this . reload ( e ?. document ) ;
5274 } )
5375 ) ;
76+ ctx . subscriptions . push (
77+ vscode . window . onDidChangeTextEditorSelection ( ( e ) => {
78+ void this . revealActiveSymbol ( e . textEditor ) ;
79+ } )
80+ ) ;
5481 }
5582
5683 getTreeItem ( element : PackageSymbol ) {
5784 return element ;
5885 }
5986
87+ // TreeView.reveal uses getParent to expand the path to nested symbols.
88+ getParent ( element : PackageSymbol ) : PackageSymbol | undefined {
89+ return element . parent ;
90+ }
91+
6092 rootItems ( ) : Promise < PackageSymbol [ ] > {
61- const list = Array < PackageSymbol > ( ) ;
62- // Add a tree item to display the current package name. Its "command" value will be undefined and thus
63- // will not link anywhere when clicked
64- list . push (
65- new PackageSymbol (
66- {
67- name : this . result ?. PackageName ? 'Current Package: ' + this . result . PackageName : '' ,
68- detail : '' ,
69- kind : 0 ,
70- range : new vscode . Range ( 0 , 0 , 0 , 0 ) ,
71- selectionRange : new vscode . Range ( 0 , 0 , 0 , 0 ) ,
72- children : [ ] ,
73- file : 0
74- } ,
75- [ ] ,
76- vscode . TreeItemCollapsibleState . None
77- )
78- ) ;
79- const res = this . result ;
80- if ( res ) {
81- res . Symbols ?. forEach ( ( d ) =>
82- list . push (
83- new PackageSymbol (
84- d ,
85- res . Files ?? [ ] ,
86- d . children ?. length > 0
87- ? vscode . TreeItemCollapsibleState . Collapsed
88- : vscode . TreeItemCollapsibleState . None
89- )
90- )
91- ) ;
92- }
93- return new Promise ( ( resolve ) => resolve ( list ) ) ;
93+ return Promise . resolve ( [ this . packageItem , ...this . sortSymbols ( this . packageSymbols ) ] ) ;
9494 }
9595
9696 getChildren ( element ?: PackageSymbol ) : Thenable < PackageSymbol [ ] | undefined > {
9797 // getChildren is called with null element when TreeDataProvider first loads
9898 if ( ! element ) {
9999 return this . rootItems ( ) ;
100100 }
101- return Promise . resolve ( element . children ) ;
101+ return Promise . resolve ( this . sortSymbols ( element . children ) ) ;
102102 }
103103
104104 async reload ( e ?: vscode . TextDocument ) {
105105 if ( e ?. languageId !== 'go' || e ?. uri ?. scheme !== 'file' ) {
106106 this . result = undefined ;
107107 this . activeDocument = undefined ;
108+ this . packageSymbols = [ ] ;
109+ this . packageItem = this . createPackageItem ( ) ;
110+ this . lastRevealedSymbol = undefined ;
111+ vscode . commands . executeCommand ( 'setContext' , 'go.showPackageOutline' , false ) ;
112+ this . _onDidChangeTreeData . fire ( undefined ) ;
108113 return ;
109114 }
110115 this . activeDocument = e ;
@@ -113,15 +118,135 @@ export class GoPackageOutlineProvider implements vscode.TreeDataProvider<Package
113118 URI : e . uri . toString ( )
114119 } ) ) as PackageSymbolsCommandResult ;
115120 this . result = res ;
121+ this . packageSymbols = this . createPackageSymbols ( res ) ;
122+ this . packageItem = this . createPackageItem ( res . PackageName ) ;
123+ this . lastRevealedSymbol = undefined ;
116124 // Show the Package Outline explorer if the request returned symbols for the current package
117125 vscode . commands . executeCommand ( 'setContext' , 'go.showPackageOutline' , res ?. Symbols ?. length > 0 ) ;
118126 this . _onDidChangeTreeData . fire ( undefined ) ;
127+ await this . revealActiveSymbol ( vscode . window . activeTextEditor ) ;
119128 } catch ( e ) {
129+ this . result = undefined ;
130+ this . packageSymbols = [ ] ;
131+ this . packageItem = this . createPackageItem ( ) ;
132+ this . lastRevealedSymbol = undefined ;
120133 // Hide the Package Outline explorer
121134 vscode . commands . executeCommand ( 'setContext' , 'go.showPackageOutline' , false ) ;
135+ this . _onDidChangeTreeData . fire ( undefined ) ;
122136 console . log ( 'ERROR' , e ) ;
123137 }
124138 }
139+
140+ private createPackageSymbols ( res : PackageSymbolsCommandResult ) : PackageSymbol [ ] {
141+ return ( res . Symbols ?? [ ] ) . map (
142+ ( symbol ) =>
143+ new PackageSymbol (
144+ symbol ,
145+ res . Files ?? [ ] ,
146+ symbol . children ?. length > 0
147+ ? vscode . TreeItemCollapsibleState . Collapsed
148+ : vscode . TreeItemCollapsibleState . None
149+ )
150+ ) ;
151+ }
152+
153+ private createPackageItem ( packageName ?: string ) : PackageSymbol {
154+ return new PackageSymbol (
155+ {
156+ name : packageName ? 'Current Package: ' + packageName : '' ,
157+ detail : '' ,
158+ kind : 0 ,
159+ range : new vscode . Range ( 0 , 0 , 0 , 0 ) ,
160+ selectionRange : new vscode . Range ( 0 , 0 , 0 , 0 ) ,
161+ children : [ ] ,
162+ file : 0
163+ } ,
164+ [ ] ,
165+ vscode . TreeItemCollapsibleState . None
166+ ) ;
167+ }
168+
169+ private sortSymbols ( symbols : readonly PackageSymbol [ ] ) : PackageSymbol [ ] {
170+ return [ ...symbols ] . sort ( ( a , b ) => this . compareSymbols ( a , b ) ) ;
171+ }
172+
173+ // Sort alphabetically when requested, otherwise preserve source order.
174+ private compareSymbols ( a : PackageSymbol , b : PackageSymbol ) : number {
175+ if ( this . sortOrder === PackageOutlineSortOrder . Name ) {
176+ const byName = this . collator . compare ( a . symbolName , b . symbolName ) ;
177+ if ( byName !== 0 ) {
178+ return byName ;
179+ }
180+ return this . compareByPosition ( a , b ) ;
181+ }
182+ const byPosition = this . compareByPosition ( a , b ) ;
183+ if ( byPosition !== 0 ) {
184+ return byPosition ;
185+ }
186+ return this . collator . compare ( a . symbolName , b . symbolName ) ;
187+ }
188+
189+ private compareByPosition ( a : PackageSymbol , b : PackageSymbol ) : number {
190+ if ( a . fileIndex !== b . fileIndex ) {
191+ return a . fileIndex - b . fileIndex ;
192+ }
193+ if ( a . range . start . line !== b . range . start . line ) {
194+ return a . range . start . line - b . range . start . line ;
195+ }
196+ return a . range . start . character - b . range . start . character ;
197+ }
198+
199+ private setSortOrder ( sortOrder : PackageOutlineSortOrder ) {
200+ if ( this . sortOrder === sortOrder ) {
201+ return ;
202+ }
203+ this . sortOrder = sortOrder ;
204+ vscode . commands . executeCommand ( 'setContext' , 'go.packageOutline.sortOrder' , sortOrder ) ;
205+ this . lastRevealedSymbol = undefined ;
206+ this . _onDidChangeTreeData . fire ( undefined ) ;
207+ void this . revealActiveSymbol ( vscode . window . activeTextEditor ) ;
208+ }
209+
210+ private updateContextKeys ( ) {
211+ vscode . commands . executeCommand ( 'setContext' , 'go.packageOutline.sortOrder' , this . sortOrder ) ;
212+ }
213+
214+ private async revealActiveSymbol ( editor ?: vscode . TextEditor ) {
215+ if ( ! this . view || ! editor || editor . document !== this . activeDocument ) {
216+ return ;
217+ }
218+ const symbol = this . findSymbolAtPosition ( this . packageSymbols , editor . document . uri , editor . selection . active ) ;
219+ if ( ! symbol ) {
220+ this . lastRevealedSymbol = undefined ;
221+ return ;
222+ }
223+ if ( symbol === this . lastRevealedSymbol ) {
224+ return ;
225+ }
226+ this . lastRevealedSymbol = symbol ;
227+ try {
228+ await this . view . reveal ( symbol , { expand : true , select : true } ) ;
229+ } catch ( e ) {
230+ console . log ( 'ERROR' , e ) ;
231+ }
232+ }
233+
234+ private findSymbolAtPosition (
235+ symbols : readonly PackageSymbol [ ] ,
236+ uri : vscode . Uri ,
237+ position : vscode . Position
238+ ) : PackageSymbol | undefined {
239+ for ( const symbol of symbols ) {
240+ const childMatch = this . findSymbolAtPosition ( symbol . children , uri , position ) ;
241+ if ( childMatch ) {
242+ return childMatch ;
243+ }
244+ if ( symbol . contains ( uri , position ) ) {
245+ return symbol ;
246+ }
247+ }
248+ return undefined ;
249+ }
125250}
126251
127252interface PackageSymbolData {
@@ -168,12 +293,26 @@ interface PackageSymbolData {
168293}
169294
170295export class PackageSymbol extends vscode . TreeItem {
296+ public readonly children : PackageSymbol [ ] ;
297+
171298 constructor (
172299 private readonly data : PackageSymbolData ,
173300 private readonly files : string [ ] ,
174- public readonly collapsibleState : vscode . TreeItemCollapsibleState
301+ public readonly collapsibleState : vscode . TreeItemCollapsibleState ,
302+ public readonly parent ?: PackageSymbol
175303 ) {
176304 super ( data . name , collapsibleState ) ;
305+ this . children = ( data . children ?? [ ] ) . map (
306+ ( child ) =>
307+ new PackageSymbol (
308+ child ,
309+ files ,
310+ child . children ?. length > 0
311+ ? vscode . TreeItemCollapsibleState . Collapsed
312+ : vscode . TreeItemCollapsibleState . None ,
313+ this
314+ )
315+ ) ;
177316 const file = files [ data . file ?? 0 ] ;
178317 this . resourceUri = files && files . length > 0 ? vscode . Uri . parse ( file ) : undefined ;
179318 const [ icon , kind ] = this . getSymbolInfo ( ) ;
@@ -195,17 +334,28 @@ export class PackageSymbol extends vscode.TreeItem {
195334 : undefined ;
196335 }
197336
198- get children ( ) : PackageSymbol [ ] | undefined {
199- return this . data . children ?. map (
200- ( c ) =>
201- new PackageSymbol (
202- c ,
203- this . files ,
204- c . children ?. length > 0
205- ? vscode . TreeItemCollapsibleState . Collapsed
206- : vscode . TreeItemCollapsibleState . None
207- )
208- ) ;
337+ get range ( ) : vscode . Range {
338+ return this . data . range ;
339+ }
340+
341+ get fileIndex ( ) : number {
342+ return this . data . file ?? 0 ;
343+ }
344+
345+ get symbolName ( ) : string {
346+ return this . data . name ;
347+ }
348+
349+ contains ( uri : vscode . Uri , position : vscode . Position ) : boolean {
350+ if ( this . resourceUri ?. toString ( ) !== uri . toString ( ) ) {
351+ return false ;
352+ }
353+ const { start, end } = this . range ;
354+ const afterStart =
355+ start . line < position . line || ( start . line === position . line && start . character <= position . character ) ;
356+ const beforeEnd =
357+ end . line > position . line || ( end . line === position . line && end . character >= position . character ) ;
358+ return afterStart && beforeEnd ;
209359 }
210360
211361 private getSymbolInfo ( ) : [ vscode . ThemeIcon | undefined , string ] {
0 commit comments