Skip to content

Support hierarchical document symbols in LSP #4661

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 13 commits into from
Aug 23, 2018
Merged

Support hierarchical document symbols in LSP #4661

merged 13 commits into from
Aug 23, 2018

Conversation

MikhailArkhipov
Copy link

Per LSP 4.4+, VS Code 1.26+
microsoft/vscode-python#2384

Questions:

  • Do we need old DocumentSymbol method? VS does not appear to use it.
  • Just change IServer or keep method and make IServer2?
  • Better way to establish child-parent relations?

@zooba
Copy link
Member

zooba commented Aug 17, 2018

OutOfProcProjectAnalyzer.GetNavigations() should be reimplemented in terms of DocumentSymbol, but currently it's not using it.

It might be nice to add a recursion limit parameter here, firstly because it is probably possible to get an infinitely recursive structure (from analysis), but also just to save time/space when returning the symbols. VS is unlikely to request more than two levels deep, for example.

case PythonMemberType.Unknown: return SymbolKind.None;
case PythonMemberType.Class: return SymbolKind.Class;
case PythonMemberType.Instance: return SymbolKind.Object;
case PythonMemberType.Class: return m.Scope is FunctionScope ? SymbolKind.Variable : SymbolKind.Class;
Copy link
Member

@zooba zooba Aug 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a constant mapping. If nestedinner classes have an incorrect member type, that is probably its own bug.

Alternatively, handle this extra check earlier, so that Scope doesn't have to be passed around here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It a minor nit. In class method first argument comes as class although technically it is an argument in the signature. It looks a bit odd in the outline as a class inside a function. Or maybe its OK?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you'd certainly want it to look like a class in this case, so perhaps the answer is to explicitly handle arguments differently?

def make_type():
    class MyClass:
        pass
    return MyClass

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@@ -50,6 +51,14 @@ await GetModuleVariablesAsync(entry as ProjectEntry, opts, @params.query, 50)
.ToArray();
}

public async Task<DocumentSymbol[]> NewDocumentSymbol(DocumentSymbolParams @params, CancellationToken cancellationToken) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see, this is the newer one. Personally I'd prefer a suffix 2 or 3_10 (for the LSP version), or a descriptive prefix (HierarchyDocumentSymbol?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

@MikhailArkhipov MikhailArkhipov changed the title WIP: Support hierarchical document symbols Support hierarchical document symbols in LSP Aug 20, 2018
@MikhailArkhipov
Copy link
Author

There are some problems rewriting GetNavigations since LS does not return types like staticmethod or classmethod, it returns SymbolKind. We can return them as additional custom fields though.

Copy link
Member

@zooba zooba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the logic for finding the hierarchy could be simpler if you start with an AST walk and then just go to the analysis for names you can't tell the type of. This is fundamentally what we do for analysis classifications - you may even be able to reuse that walker.


public interface IServer2 : IServer {
Task<DocumentSymbol[]> HierarchicalDocumentSymbol(DocumentSymbolParams @params, CancellationToken cancellationToken);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're likely to end up with a lot of IServer# interfaces if they only gain one method at a time... but maybe that's how we have to do it given how frequently this releases?

Arguably, if there's no user for it, maybe we could batch up interface additions?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extension (IC) will be using it most probably since they need symbols with types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they need to use it via the interface? Or are they downcasting to the Server class anyway?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They use IServer except there is some odd code that casts it to Server to see it is actual Python.


private static string GetFunctionKind(MemberResult m) {
if (m.MemberType == PythonMemberType.Function) {
var funcDef = m.Values.FirstOrDefault(x => x is IPythonFunction)?.PythonType as FunctionDefinition;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This always results in null - you will want m.Values.OfType<FunctionInfo>() and then check their .IsStatic/.IsClassMethod/.IsProperty member.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

var topLevel = new List<MemberResult>();

foreach (var m in members) {
var parent = members.FirstOrDefault(x => x.Scope?.Node == m.Scope?.OuterScope?.Node && x.Name == m.Scope?.Name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to avoid this check by more carefully collecting values from scopes, yes? And save adding scope references to the MemberResult struct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or arguably we can extract nearly enough information from an AST walk (the names at least), and then use a direct analysis lookup to resolve those that we don't know for sure (like we do in the analysis classifier).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am actually going to write document symbols fetch using AST, but that is going to be September. microsoft/vscode-python#2430

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MemberResult is a public API, so adding/removing a field will be a breaking change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to revisit public surface going forward, add more interfaces since it gets harder to make changes as we go.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can make MemberResult2 deriving from the base technically

var opts = GetMemberOptions.ExcludeBuiltins | GetMemberOptions.DeclaredOnly;
var entry = ProjectFiles.GetEntry(@params.textDocument);

var members = await GetModuleVariablesAsync(entry as ProjectEntry, opts, string.Empty, 50);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancellationToken

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants