Skip to content

Commit 993a54c

Browse files
committed
Optimize algorithm by sorting in-place
This finishes the optimizations by relying on the fact that we're passing a reference to the list which means we can modify it in-place. We sort the whole list of symbols on the first pass only, and then as sort children into parents' buckets we *move* them by adding them to `parent.Children` and removing them from the current list, and then recurse and modify those lists until we're done. I don't normally like relying on side-effects such as modifying in the called function, but it makes the most sense in this case for optimizations and is duly noted.
1 parent d8033b7 commit 993a54c

File tree

1 file changed

+39
-26
lines changed

1 file changed

+39
-26
lines changed

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

+39-26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.Collections.Generic;
5+
using System.Diagnostics;
56
using System.Threading;
67
using System.Threading.Tasks;
78
using Microsoft.Extensions.Logging;
@@ -40,63 +41,75 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp
4041
DocumentSelector = LspUtils.PowerShellDocumentSelector
4142
};
4243

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)
44+
// Modifies a flat list of symbols into a hierarchical list.
45+
private static Task SortHierarchicalSymbols(List<HierarchicalSymbol> symbols, CancellationToken cancellationToken)
4546
{
4647
// 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).
48+
// certain otherwise this algorithm won't work). We only need to sort the list once, and
49+
// since the implementation is recursive, it's easiest to use the stack to track that
50+
// this is the first call.
4851
symbols.Sort((x1, x2) => x1.Range.Start.CompareTo(x2.Range.Start));
52+
return SortHierarchicalSymbolsImpl(symbols, cancellationToken);
53+
}
4954

50-
List<HierarchicalSymbol> parents = new();
51-
52-
foreach (HierarchicalSymbol symbol in symbols)
55+
private static async Task SortHierarchicalSymbolsImpl(List<HierarchicalSymbol> symbols, CancellationToken cancellationToken)
56+
{
57+
for (int i = 0; i < symbols.Count; i++)
5358
{
5459
// This async method is pretty dense with synchronous code
5560
// so it's helpful to add some yields.
5661
await Task.Yield();
5762
if (cancellationToken.IsCancellationRequested)
5863
{
59-
return parents;
64+
return;
6065
}
61-
// Base case where we haven't found any parents yet.
62-
if (parents.Count == 0)
66+
67+
HierarchicalSymbol symbol = symbols[i];
68+
69+
// Base case where we haven't found any parents yet (the first symbol must be a
70+
// parent by definition).
71+
if (i == 0)
6372
{
64-
parents.Add(symbol);
73+
continue;
6574
}
6675
// If the symbol starts after end of last symbol parsed then it's a new parent.
67-
else if (symbol.Range.Start > parents[parents.Count - 1].Range.End)
76+
else if (symbol.Range.Start > symbols[i - 1].Range.End)
6877
{
69-
parents.Add(symbol);
78+
continue;
7079
}
71-
// Otherwise it's a child, we just need to figure out whose child it is.
80+
// Otherwise it's a child, we just need to figure out whose child it is and move it there (which also means removing it from the current list).
7281
else
7382
{
74-
foreach (HierarchicalSymbol parent in parents)
83+
for (int j = 0; j <= i; j++)
7584
{
85+
// While we should only check up to j < i, we iterate up to j <= i so that
86+
// we can check this assertion that we didn't exhaust the parents.
87+
Debug.Assert(j != i, "We didn't find the child's parent!");
88+
89+
HierarchicalSymbol parent = symbols[j];
7690
// If the symbol starts after the parent starts and ends before the parent
7791
// ends then its a child.
7892
if (symbol.Range.Start > parent.Range.Start && symbol.Range.End < parent.Range.End)
7993
{
94+
// Add it to the parent's list.
8095
parent.Children.Add(symbol);
96+
// Remove it from this "parents" list (because it's a child) and adjust
97+
// our loop counter because it's been removed.
98+
symbols.RemoveAt(i);
99+
i--;
81100
break;
82101
}
83102
}
84-
// TODO: If we somehow exist the list of parents and didn't find a place for the
85-
// child...we have a problem.
86103
}
87104
}
88105

89106
// Now recursively sort the children into nested buckets of children too.
90-
foreach (HierarchicalSymbol parent in parents)
107+
foreach (HierarchicalSymbol parent in symbols)
91108
{
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);
109+
// Since this modifies in place we just recurse, no re-assignment or clearing from
110+
// parent.Children necessary.
111+
await SortHierarchicalSymbols(parent.Children, cancellationToken).ConfigureAwait(false);
97112
}
98-
99-
return parents;
100113
}
101114

102115
// This struct and the mapping function below exist to allow us to skip a *bunch* of
@@ -174,8 +187,8 @@ public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(Do
174187
return s_emptySymbolInformationOrDocumentSymbolContainer;
175188
}
176189

177-
// Otherwise slowly sort them into a hierarchy.
178-
hierarchicalSymbols = await SortHierarchicalSymbols(hierarchicalSymbols, cancellationToken).ConfigureAwait(false);
190+
// Otherwise slowly sort them into a hierarchy (this modifies the list).
191+
await SortHierarchicalSymbols(hierarchicalSymbols, cancellationToken).ConfigureAwait(false);
179192

180193
// And finally convert them to the silly SymbolInformationOrDocumentSymbol wrapper.
181194
List<SymbolInformationOrDocumentSymbol> container = new();

0 commit comments

Comments
 (0)