Skip to content

Commit d8033b7

Browse files
committed
Improve algorithm to avoid excess allocations
1 parent 972e04e commit d8033b7

File tree

1 file changed

+69
-47
lines changed

1 file changed

+69
-47
lines changed

src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs

+69-47
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)