Skip to content

Commit 7519e9d

Browse files
committed
Add SortDocumentSymbols to make the outline hierarchical (again)
This is probably what OmniSharp was doing for us.
1 parent 55f7172 commit 7519e9d

File tree

1 file changed

+85
-6
lines changed

1 file changed

+85
-6
lines changed

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

+85-6
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,79 @@ 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)
47+
{
48+
// Sort by the start of the symbol definition.
49+
symbols.Sort((x1, x2) => x1.Range.Start.CompareTo(x2.Range.Start));
50+
51+
List<DocumentSymbol> parents = new();
52+
53+
foreach (DocumentSymbol symbol in symbols)
54+
{
55+
// This async method is pretty dense with synchronous code
56+
// so it's helpful to add some yields.
57+
await Task.Yield();
58+
if (cancellationToken.IsCancellationRequested)
59+
{
60+
return symbols;
61+
}
62+
63+
// Base case.
64+
if (parents.Count == 0)
65+
{
66+
parents.Add(symbol);
67+
}
68+
// Symbol starts after end of last symbol parsed.
69+
else if (symbol.Range.Start > parents[parents.Count - 1].Range.End)
70+
{
71+
parents.Add(symbol);
72+
}
73+
// Find where it fits.
74+
else
75+
{
76+
for (int i = 0; i < parents.Count; i++)
77+
{
78+
DocumentSymbol parent = parents[i];
79+
if (parent.Range.Start <= symbol.Range.Start && symbol.Range.End <= parent.Range.End)
80+
{
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 };
88+
break;
89+
}
90+
}
91+
}
92+
}
93+
94+
// Recursively sort the children.
95+
for (int i = 0; i < parents.Count; i++)
96+
{
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+
}
104+
}
105+
106+
return parents;
107+
}
108+
43109
// AKA the outline feature
44110
public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(DocumentSymbolParams request, CancellationToken cancellationToken)
45111
{
46112
_logger.LogDebug($"Handling document symbols for {request.TextDocument.Uri}");
47113

48114
ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri);
49-
List<SymbolInformationOrDocumentSymbol> symbols = new();
115+
List<DocumentSymbol> symbols = new();
50116

51117
foreach (SymbolReference r in ProvideDocumentSymbols(scriptFile))
52118
{
@@ -71,18 +137,31 @@ public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(Do
71137
// symbols, and we don't have the information nor algorithm to do that currently.
72138
// OmniSharp was previously doing this for us based on the range, perhaps we can
73139
// find that logic and reuse it.
74-
symbols.Add(new SymbolInformationOrDocumentSymbol(new DocumentSymbol
140+
symbols.Add(new DocumentSymbol
75141
{
76142
Kind = SymbolTypeUtils.GetSymbolKind(r.Type),
77143
Range = r.ScriptRegion.ToRange(),
78144
SelectionRange = r.NameRegion.ToRange(),
79145
Name = r.Name
80-
}));
146+
});
147+
}
148+
149+
// Short-circuit if we have no symbols.
150+
if (symbols.Count == 0)
151+
{
152+
return s_emptySymbolInformationOrDocumentSymbolContainer;
81153
}
82154

83-
return symbols.Count == 0
84-
? s_emptySymbolInformationOrDocumentSymbolContainer
85-
: new SymbolInformationOrDocumentSymbolContainer(symbols);
155+
// Otherwise slowly sort them into a hierarchy.
156+
symbols = await SortDocumentSymbols(symbols, cancellationToken).ConfigureAwait(false);
157+
158+
// And finally convert them to the silly SymbolInformationOrDocumentSymbol wrapper.
159+
List<SymbolInformationOrDocumentSymbol> container = new();
160+
foreach (DocumentSymbol symbol in symbols)
161+
{
162+
container.Add(new SymbolInformationOrDocumentSymbol(symbol));
163+
}
164+
return container;
86165
}
87166

88167
private IEnumerable<SymbolReference> ProvideDocumentSymbols(ScriptFile scriptFile)

0 commit comments

Comments
 (0)