@@ -40,81 +40,106 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp
40
40
DocumentSelector = LspUtils . PowerShellDocumentSelector
41
41
} ;
42
42
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 )
47
45
{
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).
49
48
symbols . Sort ( ( x1 , x2 ) => x1 . Range . Start . CompareTo ( x2 . Range . Start ) ) ;
50
49
51
- List < DocumentSymbol > parents = new ( ) ;
50
+ List < HierarchicalSymbol > parents = new ( ) ;
52
51
53
- foreach ( DocumentSymbol symbol in symbols )
52
+ foreach ( HierarchicalSymbol symbol in symbols )
54
53
{
55
54
// This async method is pretty dense with synchronous code
56
55
// so it's helpful to add some yields.
57
56
await Task . Yield ( ) ;
58
57
if ( cancellationToken . IsCancellationRequested )
59
58
{
60
- return symbols ;
59
+ return parents ;
61
60
}
62
-
63
- // Base case.
61
+ // Base case where we haven't found any parents yet.
64
62
if ( parents . Count == 0 )
65
63
{
66
64
parents . Add ( symbol ) ;
67
65
}
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 .
69
67
else if ( symbol . Range . Start > parents [ parents . Count - 1 ] . Range . End )
70
68
{
71
69
parents . Add ( symbol ) ;
72
70
}
73
- // Find where it fits .
71
+ // Otherwise it's a child, we just need to figure out whose child it is .
74
72
else
75
73
{
76
- for ( int i = 0 ; i < parents . Count ; i ++ )
74
+ foreach ( HierarchicalSymbol parent in parents )
77
75
{
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 )
80
79
{
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 ) ;
88
81
break ;
89
82
}
90
83
}
84
+ // TODO: If we somehow exist the list of parents and didn't find a place for the
85
+ // child...we have a problem.
91
86
}
92
87
}
93
88
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 )
96
91
{
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 ) ;
104
97
}
105
98
106
99
return parents ;
107
100
}
108
101
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!
110
134
public override async Task < SymbolInformationOrDocumentSymbolContainer > Handle ( DocumentSymbolParams request , CancellationToken cancellationToken )
111
135
{
112
136
_logger . LogDebug ( $ "Handling document symbols for { request . TextDocument . Uri } ") ;
113
137
114
138
ScriptFile scriptFile = _workspaceService . GetFile ( request . TextDocument . Uri ) ;
115
- List < DocumentSymbol > symbols = new ( ) ;
116
139
117
- foreach ( SymbolReference r in ProvideDocumentSymbols ( scriptFile ) )
140
+ List < HierarchicalSymbol > hierarchicalSymbols = new ( ) ;
141
+
142
+ foreach ( SymbolReference symbolReference in ProvideDocumentSymbols ( scriptFile ) )
118
143
{
119
144
// This async method is pretty dense with synchronous code
120
145
// so it's helpful to add some yields.
@@ -128,36 +153,33 @@ public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(Do
128
153
//
129
154
// TODO: We should also include function invocations that are part of DSLs (like
130
155
// Invoke-Build etc.).
131
- if ( ! r . IsDeclaration || r . Type is SymbolType . Parameter )
156
+ if ( ! symbolReference . IsDeclaration || symbolReference . Type is SymbolType . Parameter )
132
157
{
133
158
continue ;
134
159
}
135
160
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
141
162
{
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 > ( )
146
168
} ) ;
147
169
}
148
170
149
171
// Short-circuit if we have no symbols.
150
- if ( symbols . Count == 0 )
172
+ if ( hierarchicalSymbols . Count == 0 )
151
173
{
152
174
return s_emptySymbolInformationOrDocumentSymbolContainer ;
153
175
}
154
176
155
177
// Otherwise slowly sort them into a hierarchy.
156
- symbols = await SortDocumentSymbols ( symbols , cancellationToken ) . ConfigureAwait ( false ) ;
178
+ hierarchicalSymbols = await SortHierarchicalSymbols ( hierarchicalSymbols , cancellationToken ) . ConfigureAwait ( false ) ;
157
179
158
180
// And finally convert them to the silly SymbolInformationOrDocumentSymbol wrapper.
159
181
List < SymbolInformationOrDocumentSymbol > container = new ( ) ;
160
- foreach ( DocumentSymbol symbol in symbols )
182
+ foreach ( DocumentSymbol symbol in GetDocumentSymbolsFromHierarchicalSymbols ( hierarchicalSymbols ) )
161
183
{
162
184
container . Add ( new SymbolInformationOrDocumentSymbol ( symbol ) ) ;
163
185
}
0 commit comments