@@ -40,81 +40,106 @@ 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 )
43+ // This turns a flat list of symbols into a hierarchical list.
44+ private static async Task < List < HierarchicalSymbol > > SortHierarchicalSymbols ( List < HierarchicalSymbol > symbols , CancellationToken cancellationToken )
4745 {
48- // Sort by the start of the symbol definition.
46+ // Sort by the start of the symbol definition (they're probably sorted but we need to be
47+ // certain otherwise this algorithm won't work).
4948 symbols . Sort ( ( x1 , x2 ) => x1 . Range . Start . CompareTo ( x2 . Range . Start ) ) ;
5049
51- List < DocumentSymbol > parents = new ( ) ;
50+ List < HierarchicalSymbol > parents = new ( ) ;
5251
53- foreach ( DocumentSymbol symbol in symbols )
52+ foreach ( HierarchicalSymbol symbol in symbols )
5453 {
5554 // This async method is pretty dense with synchronous code
5655 // so it's helpful to add some yields.
5756 await Task . Yield ( ) ;
5857 if ( cancellationToken . IsCancellationRequested )
5958 {
60- return symbols ;
59+ return parents ;
6160 }
62-
63- // Base case.
61+ // Base case where we haven't found any parents yet.
6462 if ( parents . Count == 0 )
6563 {
6664 parents . Add ( symbol ) ;
6765 }
68- // Symbol starts after end of last symbol parsed.
66+ // If the symbol starts after end of last symbol parsed then it's a new parent .
6967 else if ( symbol . Range . Start > parents [ parents . Count - 1 ] . Range . End )
7068 {
7169 parents . Add ( symbol ) ;
7270 }
73- // Find where it fits .
71+ // Otherwise it's a child, we just need to figure out whose child it is .
7472 else
7573 {
76- for ( int i = 0 ; i < parents . Count ; i ++ )
74+ foreach ( HierarchicalSymbol parent in parents )
7775 {
78- DocumentSymbol parent = parents [ i ] ;
79- if ( parent . Range . Start <= symbol . Range . Start && symbol . Range . End <= parent . Range . End )
76+ // If the symbol starts after the parent starts and ends before the parent
77+ // ends then its a child.
78+ if ( symbol . Range . Start > parent . Range . Start && symbol . Range . End < parent . Range . End )
8079 {
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 } ;
80+ parent . Children . Add ( symbol ) ;
8881 break ;
8982 }
9083 }
84+ // TODO: If we somehow exist the list of parents and didn't find a place for the
85+ // child...we have a problem.
9186 }
9287 }
9388
94- // Recursively sort the children.
95- for ( int i = 0 ; i < parents . Count ; i ++ )
89+ // Now recursively sort the children into nested buckets of children too .
90+ foreach ( HierarchicalSymbol parent in parents )
9691 {
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- }
92+ List < HierarchicalSymbol > sortedChildren = await SortHierarchicalSymbols ( parent . Children , cancellationToken ) . ConfigureAwait ( false ) ;
93+ // Since this is a foreach we can't just assign to parent.Children and have to do
94+ // this instead.
95+ parent . Children . Clear ( ) ;
96+ parent . Children . AddRange ( sortedChildren ) ;
10497 }
10598
10699 return parents ;
107100 }
108101
109- // AKA the outline feature
102+ // This struct and the mapping function below exist to allow us to skip a *bunch* of
103+ // unnecessary allocations when sorting the symbols since DocumentSymbol (which this is
104+ // pretty much a mirror of) is an immutable record...but we need to constantly modify the
105+ // list of children when sorting.
106+ private struct HierarchicalSymbol
107+ {
108+ public SymbolKind Kind ;
109+ public Range Range ;
110+ public Range SelectionRange ;
111+ public string Name ;
112+ public List < HierarchicalSymbol > Children ;
113+ }
114+
115+ // Recursively turn our HierarchicalSymbol struct into OmniSharp's DocumentSymbol record.
116+ private static List < DocumentSymbol > GetDocumentSymbolsFromHierarchicalSymbols ( IEnumerable < HierarchicalSymbol > hierarchicalSymbols )
117+ {
118+ List < DocumentSymbol > documentSymbols = new ( ) ;
119+ foreach ( HierarchicalSymbol symbol in hierarchicalSymbols )
120+ {
121+ documentSymbols . Add ( new DocumentSymbol
122+ {
123+ Kind = symbol . Kind ,
124+ Range = symbol . Range ,
125+ SelectionRange = symbol . SelectionRange ,
126+ Name = symbol . Name ,
127+ Children = GetDocumentSymbolsFromHierarchicalSymbols ( symbol . Children )
128+ } ) ;
129+ }
130+ return documentSymbols ;
131+ }
132+
133+ // AKA the outline feature!
110134 public override async Task < SymbolInformationOrDocumentSymbolContainer > Handle ( DocumentSymbolParams request , CancellationToken cancellationToken )
111135 {
112136 _logger . LogDebug ( $ "Handling document symbols for { request . TextDocument . Uri } ") ;
113137
114138 ScriptFile scriptFile = _workspaceService . GetFile ( request . TextDocument . Uri ) ;
115- List < DocumentSymbol > symbols = new ( ) ;
116139
117- foreach ( SymbolReference r in ProvideDocumentSymbols ( scriptFile ) )
140+ List < HierarchicalSymbol > hierarchicalSymbols = new ( ) ;
141+
142+ foreach ( SymbolReference symbolReference in ProvideDocumentSymbols ( scriptFile ) )
118143 {
119144 // This async method is pretty dense with synchronous code
120145 // so it's helpful to add some yields.
@@ -128,36 +153,33 @@ public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(Do
128153 //
129154 // TODO: We should also include function invocations that are part of DSLs (like
130155 // Invoke-Build etc.).
131- if ( ! r . IsDeclaration || r . Type is SymbolType . Parameter )
156+ if ( ! symbolReference . IsDeclaration || symbolReference . Type is SymbolType . Parameter )
132157 {
133158 continue ;
134159 }
135160
136- // TODO: This now needs the Children property filled out to support hierarchical
137- // symbols, and we don't have the information nor algorithm to do that currently.
138- // OmniSharp was previously doing this for us based on the range, perhaps we can
139- // find that logic and reuse it.
140- symbols . Add ( new DocumentSymbol
161+ hierarchicalSymbols . Add ( new HierarchicalSymbol
141162 {
142- Kind = SymbolTypeUtils . GetSymbolKind ( r . Type ) ,
143- Range = r . ScriptRegion . ToRange ( ) ,
144- SelectionRange = r . NameRegion . ToRange ( ) ,
145- Name = r . Name
163+ Kind = SymbolTypeUtils . GetSymbolKind ( symbolReference . Type ) ,
164+ Range = symbolReference . ScriptRegion . ToRange ( ) ,
165+ SelectionRange = symbolReference . NameRegion . ToRange ( ) ,
166+ Name = symbolReference . Name ,
167+ Children = new List < HierarchicalSymbol > ( )
146168 } ) ;
147169 }
148170
149171 // Short-circuit if we have no symbols.
150- if ( symbols . Count == 0 )
172+ if ( hierarchicalSymbols . Count == 0 )
151173 {
152174 return s_emptySymbolInformationOrDocumentSymbolContainer ;
153175 }
154176
155177 // Otherwise slowly sort them into a hierarchy.
156- symbols = await SortDocumentSymbols ( symbols , cancellationToken ) . ConfigureAwait ( false ) ;
178+ hierarchicalSymbols = await SortHierarchicalSymbols ( hierarchicalSymbols , cancellationToken ) . ConfigureAwait ( false ) ;
157179
158180 // And finally convert them to the silly SymbolInformationOrDocumentSymbol wrapper.
159181 List < SymbolInformationOrDocumentSymbol > container = new ( ) ;
160- foreach ( DocumentSymbol symbol in symbols )
182+ foreach ( DocumentSymbol symbol in GetDocumentSymbolsFromHierarchicalSymbols ( hierarchicalSymbols ) )
161183 {
162184 container . Add ( new SymbolInformationOrDocumentSymbol ( symbol ) ) ;
163185 }
0 commit comments