Skip to content

Add SortDocumentSymbols to make the outline hierarchical (again) #2084

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 19, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,79 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp
DocumentSelector = LspUtils.PowerShellDocumentSelector
};

// This turns a flat list of symbols into a hierarchical list. It's ugly because we're
// dealing with records and so sadly must slowly copy and replace things whenever need to do
// a modification, but it seems to work.
private static async Task<List<DocumentSymbol>> SortDocumentSymbols(List<DocumentSymbol> symbols, CancellationToken cancellationToken)
{
// Sort by the start of the symbol definition.
symbols.Sort((x1, x2) => x1.Range.Start.CompareTo(x2.Range.Start));

List<DocumentSymbol> parents = new();

foreach (DocumentSymbol symbol in symbols)
{
// This async method is pretty dense with synchronous code
// so it's helpful to add some yields.
await Task.Yield();
if (cancellationToken.IsCancellationRequested)
{
return symbols;
}

// Base case.
if (parents.Count == 0)
{
parents.Add(symbol);
}
// Symbol starts after end of last symbol parsed.
else if (symbol.Range.Start > parents[parents.Count - 1].Range.End)
{
parents.Add(symbol);
}
// Find where it fits.
else
{
for (int i = 0; i < parents.Count; i++)
{
DocumentSymbol parent = parents[i];
if (parent.Range.Start <= symbol.Range.Start && symbol.Range.End <= parent.Range.End)
{
List<DocumentSymbol> children = new();
if (parent.Children is not null)
{
children.AddRange(parent.Children);
}
children.Add(symbol);
parents[i] = parent with { Children = children };
break;
}
}
}
}

// Recursively sort the children.
for (int i = 0; i < parents.Count; i++)
{
DocumentSymbol parent = parents[i];
if (parent.Children is not null)
{
List<DocumentSymbol> children = new(parent.Children);
children = await SortDocumentSymbols(children, cancellationToken).ConfigureAwait(false);
parents[i] = parent with { Children = children };
}
}

return parents;
}

// AKA the outline feature
public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(DocumentSymbolParams request, CancellationToken cancellationToken)
{
_logger.LogDebug($"Handling document symbols for {request.TextDocument.Uri}");

ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri);
List<SymbolInformationOrDocumentSymbol> symbols = new();
List<DocumentSymbol> symbols = new();

foreach (SymbolReference r in ProvideDocumentSymbols(scriptFile))
{
Expand All @@ -71,18 +137,31 @@ public override async Task<SymbolInformationOrDocumentSymbolContainer> Handle(Do
// symbols, and we don't have the information nor algorithm to do that currently.
// OmniSharp was previously doing this for us based on the range, perhaps we can
// find that logic and reuse it.
symbols.Add(new SymbolInformationOrDocumentSymbol(new DocumentSymbol
symbols.Add(new DocumentSymbol
{
Kind = SymbolTypeUtils.GetSymbolKind(r.Type),
Range = r.ScriptRegion.ToRange(),
SelectionRange = r.NameRegion.ToRange(),
Name = r.Name
}));
});
}

// Short-circuit if we have no symbols.
if (symbols.Count == 0)
{
return s_emptySymbolInformationOrDocumentSymbolContainer;
}

return symbols.Count == 0
? s_emptySymbolInformationOrDocumentSymbolContainer
: new SymbolInformationOrDocumentSymbolContainer(symbols);
// Otherwise slowly sort them into a hierarchy.
symbols = await SortDocumentSymbols(symbols, cancellationToken).ConfigureAwait(false);

// And finally convert them to the silly SymbolInformationOrDocumentSymbol wrapper.
List<SymbolInformationOrDocumentSymbol> container = new();
foreach (DocumentSymbol symbol in symbols)
{
container.Add(new SymbolInformationOrDocumentSymbol(symbol));
}
return container;
}

private IEnumerable<SymbolReference> ProvideDocumentSymbols(ScriptFile scriptFile)
Expand Down