|
2 | 2 | // Licensed under the MIT License.
|
3 | 3 |
|
4 | 4 | using System.Collections.Generic;
|
| 5 | +using System.Diagnostics; |
5 | 6 | using System.Threading;
|
6 | 7 | using System.Threading.Tasks;
|
7 | 8 | using Microsoft.Extensions.Logging;
|
@@ -40,63 +41,75 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp
|
40 | 41 | DocumentSelector = LspUtils.PowerShellDocumentSelector
|
41 | 42 | };
|
42 | 43 |
|
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) |
45 | 46 | {
|
46 | 47 | // 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. |
48 | 51 | symbols.Sort((x1, x2) => x1.Range.Start.CompareTo(x2.Range.Start));
|
| 52 | + return SortHierarchicalSymbolsImpl(symbols, cancellationToken); |
| 53 | + } |
49 | 54 |
|
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++) |
53 | 58 | {
|
54 | 59 | // This async method is pretty dense with synchronous code
|
55 | 60 | // so it's helpful to add some yields.
|
56 | 61 | await Task.Yield();
|
57 | 62 | if (cancellationToken.IsCancellationRequested)
|
58 | 63 | {
|
59 |
| - return parents; |
| 64 | + return; |
60 | 65 | }
|
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) |
63 | 72 | {
|
64 |
| - parents.Add(symbol); |
| 73 | + continue; |
65 | 74 | }
|
66 | 75 | // 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) |
68 | 77 | {
|
69 |
| - parents.Add(symbol); |
| 78 | + continue; |
70 | 79 | }
|
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). |
72 | 81 | else
|
73 | 82 | {
|
74 |
| - foreach (HierarchicalSymbol parent in parents) |
| 83 | + for (int j = 0; j <= i; j++) |
75 | 84 | {
|
| 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]; |
76 | 90 | // If the symbol starts after the parent starts and ends before the parent
|
77 | 91 | // ends then its a child.
|
78 | 92 | if (symbol.Range.Start > parent.Range.Start && symbol.Range.End < parent.Range.End)
|
79 | 93 | {
|
| 94 | + // Add it to the parent's list. |
80 | 95 | 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--; |
81 | 100 | break;
|
82 | 101 | }
|
83 | 102 | }
|
84 |
| - // TODO: If we somehow exist the list of parents and didn't find a place for the |
85 |
| - // child...we have a problem. |
86 | 103 | }
|
87 | 104 | }
|
88 | 105 |
|
89 | 106 | // Now recursively sort the children into nested buckets of children too.
|
90 |
| - foreach (HierarchicalSymbol parent in parents) |
| 107 | + foreach (HierarchicalSymbol parent in symbols) |
91 | 108 | {
|
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); |
97 | 112 | }
|
98 |
| - |
99 |
| - return parents; |
100 | 113 | }
|
101 | 114 |
|
102 | 115 | // 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
|
174 | 187 | return s_emptySymbolInformationOrDocumentSymbolContainer;
|
175 | 188 | }
|
176 | 189 |
|
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); |
179 | 192 |
|
180 | 193 | // And finally convert them to the silly SymbolInformationOrDocumentSymbol wrapper.
|
181 | 194 | List<SymbolInformationOrDocumentSymbol> container = new();
|
|
0 commit comments