|
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