@@ -40,13 +40,79 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp
4040 DocumentSelector = LspUtils . PowerShellDocumentSelector
4141 } ;
4242
43+ // This turns a flat list of symbols into a hierarchical list. It's ugly because we're
44+ // dealing with records and so sadly must slowly copy and replace things whenever need to do
45+ // a modification, but it seems to work.
46+ private static async Task < List < DocumentSymbol > > SortDocumentSymbols ( List < DocumentSymbol > symbols , CancellationToken cancellationToken )
47+ {
48+ // Sort by the start of the symbol definition.
49+ symbols . Sort ( ( x1 , x2 ) => x1 . Range . Start . CompareTo ( x2 . Range . Start ) ) ;
50+
51+ List < DocumentSymbol > parents = new ( ) ;
52+
53+ foreach ( DocumentSymbol symbol in symbols )
54+ {
55+ // This async method is pretty dense with synchronous code
56+ // so it's helpful to add some yields.
57+ await Task . Yield ( ) ;
58+ if ( cancellationToken . IsCancellationRequested )
59+ {
60+ return symbols ;
61+ }
62+
63+ // Base case.
64+ if ( parents . Count == 0 )
65+ {
66+ parents . Add ( symbol ) ;
67+ }
68+ // Symbol starts after end of last symbol parsed.
69+ else if ( symbol . Range . Start > parents [ parents . Count - 1 ] . Range . End )
70+ {
71+ parents . Add ( symbol ) ;
72+ }
73+ // Find where it fits.
74+ else
75+ {
76+ for ( int i = 0 ; i < parents . Count ; i ++ )
77+ {
78+ DocumentSymbol parent = parents [ i ] ;
79+ if ( parent . Range . Start <= symbol . Range . Start && symbol . Range . End <= parent . Range . End )
80+ {
81+ List < DocumentSymbol > children = new ( ) ;
82+ if ( parent . Children is not null )
83+ {
84+ children . AddRange ( parent . Children ) ;
85+ }
86+ children . Add ( symbol ) ;
87+ parents [ i ] = parent with { Children = children } ;
88+ break ;
89+ }
90+ }
91+ }
92+ }
93+
94+ // Recursively sort the children.
95+ for ( int i = 0 ; i < parents . Count ; i ++ )
96+ {
97+ DocumentSymbol parent = parents [ i ] ;
98+ if ( parent . Children is not null )
99+ {
100+ List < DocumentSymbol > children = new ( parent . Children ) ;
101+ children = await SortDocumentSymbols ( children , cancellationToken ) . ConfigureAwait ( false ) ;
102+ parents [ i ] = parent with { Children = children } ;
103+ }
104+ }
105+
106+ return parents ;
107+ }
108+
43109 // AKA the outline feature
44110 public override async Task < SymbolInformationOrDocumentSymbolContainer > Handle ( DocumentSymbolParams request , CancellationToken cancellationToken )
45111 {
46112 _logger . LogDebug ( $ "Handling document symbols for { request . TextDocument . Uri } ") ;
47113
48114 ScriptFile scriptFile = _workspaceService . GetFile ( request . TextDocument . Uri ) ;
49- List < SymbolInformationOrDocumentSymbol > symbols = new ( ) ;
115+ List < DocumentSymbol > symbols = new ( ) ;
50116
51117 foreach ( SymbolReference r in ProvideDocumentSymbols ( scriptFile ) )
52118 {
@@ -71,18 +137,31 @@ public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(Do
71137 // symbols, and we don't have the information nor algorithm to do that currently.
72138 // OmniSharp was previously doing this for us based on the range, perhaps we can
73139 // find that logic and reuse it.
74- symbols . Add ( new SymbolInformationOrDocumentSymbol ( new DocumentSymbol
140+ symbols . Add ( new DocumentSymbol
75141 {
76142 Kind = SymbolTypeUtils . GetSymbolKind ( r . Type ) ,
77143 Range = r . ScriptRegion . ToRange ( ) ,
78144 SelectionRange = r . NameRegion . ToRange ( ) ,
79145 Name = r . Name
80- } ) ) ;
146+ } ) ;
147+ }
148+
149+ // Short-circuit if we have no symbols.
150+ if ( symbols . Count == 0 )
151+ {
152+ return s_emptySymbolInformationOrDocumentSymbolContainer ;
81153 }
82154
83- return symbols . Count == 0
84- ? s_emptySymbolInformationOrDocumentSymbolContainer
85- : new SymbolInformationOrDocumentSymbolContainer ( symbols ) ;
155+ // Otherwise slowly sort them into a hierarchy.
156+ symbols = await SortDocumentSymbols ( symbols , cancellationToken ) . ConfigureAwait ( false ) ;
157+
158+ // And finally convert them to the silly SymbolInformationOrDocumentSymbol wrapper.
159+ List < SymbolInformationOrDocumentSymbol > container = new ( ) ;
160+ foreach ( DocumentSymbol symbol in symbols )
161+ {
162+ container . Add ( new SymbolInformationOrDocumentSymbol ( symbol ) ) ;
163+ }
164+ return container ;
86165 }
87166
88167 private IEnumerable < SymbolReference > ProvideDocumentSymbols ( ScriptFile scriptFile )
0 commit comments