-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Augmentation-related navigation #54742
Comments
As far as go to definition (and related features), I think it should by default go to the augmented declaration (the original one, without an |
Related to the first item, I think it would be ideal if you could tell that a declaration is being augmented at a glance, without requiring any hovering over the declaration to see the list of augmentations. I don't have a specific UX proposal here, I just want something to indicate that it is augmented. |
There are at least two ways in LSP that I can think of to show this information: semantic highlighting and inlay hints. I'm sure there are others. The other is currently supported in IntelliJ, but we might be able to add the second. |
I think we should probably return both (or all) results here rather than just one, so that the user can see them all and easily jump between them (it would behave like "Go to References" when that shows multiple results).
We could add a semantic token modifier, but I think the only thing we could do with that in the client is change the colour (and only in limited ways because of custom themes).. I don't know if that would be an obvious indicator (it also doesn't wouldn't give a way to navigate to them). Inlay Hints are also quite limited right now (the standard ones are assumed to either by Type or Parameter names) and we couldn't style them very well. VS Code does have "decorations" which are very similar, but not standard LSP - that's what we use for Closing Labels which gives us some more flexibility (at the expense or requiring custom messages and code in the client). Something that comes up now and then is using CodeLens to show references to symbols: I haven't tried to implement this yet because it feels expensive (we have to tell the client where these code lenses will appear for an entire document in one go, and that either means potentially having lots of 0-reference CodeLens, or I think doing a lot of slow searching). But maybe if we know how many augmentations there are quickly, we could show them in this way? We'd probably still need a small amount of custom client code (because we'd want clicking them to do a search, and LSP doesn't define a way to trigger something like definitions), but that should be fairly minimal. |
Yes. We have that information in the element model directly without any searching, so that sounds like a viable option worth considering. |
Something I just remembered is that LSP has both a "definition" and a "declaration" request. We don't currently implement declaration, but perhaps we should implement it now (going to the original class definition without any This would allow definition to include all the augmentations and the user can either jump directly to the original class, or trigger the peek window with all of the definitions. |
We had an in-office discussion yesterday and had come to the conclusion that "go to definition" should, at least for invocations, take the user to the last augmentation in the merge list. If other augmentations in the chain, or the un-augmented function, are invoked (via We didn't discusses class-like declarations. But I agree that the ability to implement "go to declaration" might open some interesting possibilities. @jwren Does IntelliJ have a similar distinction between "definition" and "declaration"? |
For LSP, I think returning only this option might not be the best choice. We can return multiple results and (in VS Code, at least), the user can set whether they're prefer to jump to the "primary" result or see a peek list of all (or both): If we return them all, the user can choose which behaviour they want - but if we only provide one, they cannot choose to see them all. |
Good to know. I don't think any of us were aware of that setting. The argument to make the choice available to the user is a valid one. If the user chooses 'goto', can we control which one of the multiple options will be selected? I'd expect it to either be the first or the last, but the LSP spec doesn't describe the effect of the user setting on the UX. Ideally I'd like to have them ordered based on the "merge" order, because I believe that the order matters to users. In our discussion, the primary argument was an assumption that most users wouldn't want to have to choose, and would be more interested in seeing the code that will actually be executed. This seems especially compelling given that some of the augmentations might never be invoked. It's hard to see why a user, when looking at an invocation site, would want to see anything other than the code being invoked, especially given that the entire method signature must be consistent and that some of the potential augmentations shown by 'peek' might be unreachable. Is there a way that we could mark the unreachable augmentations in the peek window? I don't see anything in the protocol, but that doesn't mean it doesn't exist. If we think that users might want to see all of the augmentations, is there any way, other than the location of the augmentation, for us to distinguish them in the UI? Of course, the argument only applies to function augmentations. Augmentations of class-like declarations are all always applied, and the "header" can be different in every augmentation. For that case it does seem likely that a user would want to be able to navigate to every augmentation, though the question of how to clearly distinguish the possible destinations is still valid. |
LSP doesn't define options like this so this setting is VS Code-specific (other editors could choose to do similar, or not). VS Code also doesn't seem to specify it in the API docs, but the code shows that it uses the first: We could presumably sort the list in either direction to achieve what you want, although I think the UI groups by file, so if they can span multiple files (I'm not sure if they can?), that order might not be obvious.
That sounds reasonable to me. When I wrote some of the above I think I was just thinking about
I don't think so, but I'm also unsure what you mean that some might be unreachable. Do you mean if there's no call to
Unfortunately I don't think so, at least not for Definition. There are of course other potential features for navigation though (like CodeLens, hierarchy, Go-to-Super etc.). |
Yes, the augmentations can span multiple files, at least for user-defined augmentations. For example:
import augment 'a.dart';
import augment 'b.dart';
void f() {}
library augment 'test.dart';
augment void f() {
print('from a');
}
library augment 'test.dart';
augment void f() {
print('from b');
}
Yes, just like the augmentation in |
I don't know if I agree with what I wrote above now.. For classes, showing all of the agumentation classes makes sense to me, but if we're talking about invocations of methods/functions I'm less certain. If they're essentially the same as overridden methods with (and I think we should implement "Go to Declaration" to get straight to the declaration if that might be where the docs etc. are but Definition is not going there) |
They're mostly the same, and close enough that I think users will expect the same behavior. The difference is that with overrides we can't know which method will be executed, only the method that would be executed if lookup started in the class corresponding to the static type. With augmentations we know exactly which method will be executed (assuming there are no overrides of the base method).
That seems reasonable, but
|
It doesn't seem like many languages currently support "Go to Declaration" (I could only find C++ where I think it's going to headers). It feels like a missing feature to me though - we don't have a quick way to jump to the declaration of a method, we always go to the most overridden version. If you want to get to the base you'd have to keep jumping to the (note: Go to Declaration is a separate command with no keybindings, so it wouldn't affect F12/Ctrl+click, it would just be an extra option on the context menu/command palette). |
When I first saw that both were supported, C++ was what immediately came to mind as a language where the declaration and definition can be separate.
I'm not sure whether that distinction really makes a lot of sense in Dart. Let me give a couple of examples for why I have to question the value. First, it's possible for the method found by starting lookup at the static type to have a different signature than the base method (the method that does not override any other). Consider class A {
void m(String a) {}
}
class B extends A {
@override
void m(String a, [int b = 0]) {}
} It isn't clear to me that in this case that it's useful for the user to be able to navigate to Second, there isn't necessarily a single base method. Consider class A {
void m(String a, {int b = 0}) {}
}
abstract class I {
void m(String a, {int c = 0}) {}
}
class B extends A implements I {
@override
void m(String a, {int b = 0, int c = 0}) {}
} Would it be more appropriate to navigate to I'm not saying that we shouldn't implement something for Go To Declaration, only that the choice for how to implement it doesn't seem as straightforward to me as the design of Go To Definition. |
You're right, it is less clear than I thought :) We can indeed return multiple results for Go-to-Declaration too, but you're right that it doesn't help answer all of the questions. |
This is a P1 issue, so it needs an assignee and weekly progress. @bwilkerson should that assignee be you? |
I had a little dig into the VS Code APIs for "Peek Location" to see how they'd work triggered from CodeLens. Unfortunately this isn't standard LSP so would require some custom code in Dart-Code. Here I added an "x augmentations" link above declarations with augmentations (though only those that are not themselves augmentations) which when clicked will show the locations of all augmentations. I think it works reasonable well, but it's unfortunate that the list of results on the right is basically the same for everything (unfortunately we don't get to choose what appears here, we're just providing a list of locations and VS Code is extracting the relevant line). I also added CodeLens for "Augments x" on augmentations and "Augmented by x" for things with augmentations that jump between them directly. I don't know if there's a better way to word this and I'm not sure if taking up this extra space with CodeLens is worth it in that case (for example for Super we just have a "Go to Super" command that's on the context menu... I wonder if we should just have "Go to Augmented" and "Go to Augmentation" commands.. although I'll note that we can't make these conditional, so they'd always appear in the context menu, just like Go to Super does). augmentation.codelens.mp4 |
Thanks for looking into this.
That is unfortunate. It makes it really hard to know which one to select. I'm not sure whether it actually adds value for the user. Kind of seems like we'd be better off with just the 'Go to annotated' and 'Go to annotation' style navigation.
That's also unfortunate. It wouldn't be so bad if we could disable the menu item when it doesn't apply, but I'm guessing that we can't. I do think it would be good for us to treat these two forms of implementation-chain navigation in a similar way. The concepts are similar enough that it seems like having a similar UI would be helpful. (And we might consider having similar support for redeclarations in extension types, though it isn't clear how often users will use extension types so it might not be worth adding.) |
This is unfortunately correct. For the context menu we have to declare all the items up-front, and we have no context of where it will be invoked (because showing the context menu is synchronous in the VS code UI and it will never wait on async calls to the extension host for showing a menu). For some kinds of things we can set conditions based on something like the cursor (caret?) location within the editor, however this doesn't work for context menus because being async we'd always be "late" and the user would see menu items enabled based on where the caret was before they right-clicked. |
I hacked up an example of using CodeLens for various kinds of navigation like this (Super, Overrides, Augmented, Augmentation): I realised that Super/Overrides and Augmented/Augmentations are not quite as similar as I initially imagined. Whereas the augmentation chain is a one-to-one relationship, overrides are not (that is, there is only ever 0 or 1 direct augmentations of a member, but because multiple classes can extend a class, there can be multiple overrides). That means if we did something like the screenshot above, "Overrides" would potentially trigger a search that uses a peek window (as shown above), whereas "Augmentation" would always just jump to the next item in the chain. I don't think that difference is a reason not to make them otherwise consistent though. Some questions I had while looking at this:
|
These are all good questions.
Technically, neither are 'Super' (or 'Overridden'), at least not in my mental model. I tend to think of concrete members in classes as overriding abstract members in either a superclass or an interface, and they can override members from multiple interfaces. I'm not sure what we currently use as the target for 'Go To Super'; maybe it only ever goes to an overridden member from a superclass.
I don't know that I have any concrete answer, but my view of this is likely to be influenced by my mental model. I tend to think of the two chains graphically as
That is, the augmentation chain is effectively a single member in the override chain. As opposed to
where the override attaches to the last element in the augmentation chain. Of course, the real question here is what will users want. This probably depends in part on what's convenient, but also on the mental model that we're going to encourage users to have (via documentation, discussions, etc.) @MaryaBelanger
Should "Go to Super" go to the same place as navigating from the keyword I usually use navigation from But "Go To Super" might be different, it might want to walk up to the head of the augmentation chain in the superclass (which could very well be in a library augmentation).
I think the answer is 'yes'. I think we have enough information today to make that efficient, but if not I'm reasonably confident that we're already computing all the necessary information and just need to capture it. |
My intention when I added this was to go to the same place as I don't know whether my idea is what most users would expect though - this command hasn't had a lot of use/feedback because until recently (when it was added to the context menu) it wasn't particularly easy to discover. If we're not sure, we could ofc offer both options - but since they'd always be on the context menu everywhere, we'd need to make sure the labels are clear enough that users understand why they both exist (I suspect in the overwhelming majority of cases they will be the same target). My comment about augmentations and overrides being different (which I typo'd a bit) was that a method can have multiple overrides (eg. So if we had both overrides and augmentations as CodeLenses, it might be that one always jumps directly to a target (augmentation) whereas the other may need to show a set of results. |
(I'd probably say 'last'; it seems more consistent with the notion of a merge order because the augmentation that will be invoked is always the last augmentation in the merge order.) I think I agree with that. It's worth noting that |
How does the following sound:
Maybe for classes things should be a little different though. For example "Overrides" would probably be replaced by "Subclasses"? Should we also have an "Augmentations" on the original class declaration, or just Augmented/Augmentation so you move along the chain? I feel like both could be useful but having three CodeLenses with very similar text doesn't feel great). I guess as a start, I can ensure Go to Super does the same as |
I like the idea of implementing them as commands available from the command palette first. That would allow us to experiment a bit to get the UX right, though I do think we're close to knowing what we want. I also like that code lenses are configurable.
5-8. Yes.
Yes, I think we should match the standard terminology. We already have a "Go to Implementations" on the context menu. I assume that that we'd use the same command for both "Subclasses" and "Overrides".
If it's useful to have an "Augmentations" (I assume it would open a peek window when there's more than one), which I'm not sure that it is, then it should be available on every augmented declaration, and maybe on every augmentation as well. I think I'd wait on implementing this until we have evidence that it's useful, and even then I think we should consider making it available on the command palette without making it a code lens until there's evidence that it's really desired. I'm a little concerned about having too many code lenses and having the list of commands get too long. Maybe it'll be fine, but ... |
This adds a custom command similar to `dart/textDocument/super` but for augmentation targets ("augmented"). This won't show up anywhere on its own, but it will be added to the command palette in Dart-Code based on the server capabilities (and in the future, maybe CodeLens - although that will require some additional work in the server first since it will be the source of CodeLens even if they trigger client-side actions). See #54742 Change-Id: I11ca115c61ade40bf51478f58b39b7caedac19c9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358451 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Phil Quitslund <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
This adds a custom command similar to `dart/textDocument/super` but for augmentations. This won't show up anywhere on its own, but it will be added to the command palette in Dart-Code based on the server capabilities (and in the future, maybe CodeLens - although that will require some additional work in the server first since it will be the source of CodeLens even if they trigger client-side actions). See #54742 Change-Id: Ib3a3fd08702e14b8bd251991035ef0b8d06ad996 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358452 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Phil Quitslund <[email protected]>
As this is a P1 issue, I have to ask, @DanTup , @bwilkerson , any updates? Is this complete with the commits landed 2 weeks ago? |
The changes above support "Go to Augment(ed|ation)" CodeLens. We don't yet have CodeLenses for other things (like Go to Super), and I don't know if we want to do that now or iterate a little on the augmentation CodeLenses first (anyone have opinions on this?). There are some other things noted above about things like Find References.. I don't know if those are all working as expected for augmentations, so I suspect there might at least be some tests that need adding to verify the behaviour of those where they haven't already. |
I would vote for iterating on the augmentation CodeLenses first. I also wouldn't include work on Go to Super in the work for this particular issue because I see them as being independent from each other. Find References, on the other hand, does seem to fit under this issue. |
Just a status update: Danny has a prototype of CodeLens support for navigating along the augmentation chain. It looks promising (see Dart-Code/Dart-Code#5063). |
Any fresh updates here? Is this still a P1? |
I downgraded the priority. I think it's critical that we have a good design, but it's kind of odd to have a P1 for a design discussion. |
There are some interesting design questions related to library augmentations (and hence macros) and navigation in IDEs. I've included them all in a single issue because I think it would be good to think about them holistically, at least initially. If there are additional questions, please add them to the list.
While the limitations of the major supported platforms will need to be considered, I'm more interested initially in identifying the UX that will be best for users. If we have to compromise because of those limitations then at least we'll know what the ideal is.
Note that any augmented declaration might have multiple augmentations. There is a well-defined "merge order" that we could use to linearize the augmentations, but it isn't clear whether that's the right UX or whether it would be better to be able to navigate from any augmentation to any other augmentation in a single click.
m
, do we want 'Go to declaration' to navigate to the augmented method, to one of the augmentations, or to present multiple targets? The potentially impacted existing navigations I can think of are (please add to this list if it's incomplete):The text was updated successfully, but these errors were encountered: