Skip to content
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

Allow assists with multiple selectable actions #2716

Merged
merged 4 commits into from
Jan 15, 2020

Conversation

SomeoneToIgnore
Copy link
Contributor

This PR prepares an infra for #2180 task by adding a possibility to specify multiple actions in one assist as multiple edit parameters to the applySourceChange command.

When this is done, the command opens a selection dialog, allowing the user to pick the edit to be applied.

I have no working example to test in this PR, but here's a demo of an auto import feature (a separate PR coming later for that one) using this functionality:

out

The PR is not that massive as it may seem: all the assist files' changes are very generic and similar.

@SomeoneToIgnore SomeoneToIgnore force-pushed the action-with-multiple-changes branch from 540c1de to 3e352e3 Compare December 31, 2019 20:50
@SomeoneToIgnore
Copy link
Contributor Author

SomeoneToIgnore commented Dec 31, 2019

I had to use #[allow(dead_code)] twice for methods that I'm using in the auto import assist branch to add an assist with multiple actions.

I wanted to include those metods in this PR nevertheless since they are more related to this change and showcase the API for the assist with multiple actions.

@SomeoneToIgnore SomeoneToIgnore changed the title Allow actions with multiple selectable edits Allow assists with multiple selectable actions Dec 31, 2019
@SomeoneToIgnore
Copy link
Contributor Author

cargo test passes for me locally on two different machines and the CI does not show the logs currently, so I have no idea what's wrong.

@lnicola
Copy link
Member

lnicola commented Dec 31, 2019

The CI logs load for me, there are two failing heavy tests (test_missing_module_code_action). I think those are only run on CI.

@SomeoneToIgnore
Copy link
Contributor Author

TIL we have heavy tests, thank you.
I've found the way to download the logs and fix the issue.

@SomeoneToIgnore SomeoneToIgnore force-pushed the action-with-multiple-changes branch 3 times, most recently from 7c9719b to ebfd47f Compare December 31, 2019 23:22
@kiljacken
Copy link
Contributor

For future reference you can enable the slow test by setting RUN_SLOW_TESTS=1 in environment variables :)

@flodiebold
Copy link
Member

If I understand it correctly, this change would break other existing clients like the emacs integration, until they're adapted. Maybe there's a way of extending the protocol in a compatible way?

@SomeoneToIgnore
Copy link
Contributor Author

SomeoneToIgnore commented Jan 1, 2020

Good point. What are the compatibility guarantees RA promises to keep currently?
I have thought that there are none, since it's an experimental tool built from sources.

There are obviously ways to keep the compatibility, we can either create a new command or fiddle with the parameters of an existing one, for instance.
Just not sure if it's worth it.

What if we fix the emacs client instead?
We should pass a list as a parameter now, I think it might be a one-liner fix somewhere around here:
https://github.com/rust-analyzer/rust-analyzer/blob/master/editors/emacs/ra-emacs-lsp.el#L41

Copy link
Member

@matklad matklad left a comment

Choose a reason for hiding this comment

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

Left high-level design advice!

@@ -77,20 +77,40 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
f(ctx)
}

pub(crate) fn add_assist(
pub(crate) fn add_assist_with_single_action(
Copy link
Member

Choose a reason for hiding this comment

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

Assists with a single action will always be a majority. For this reason, it makes sense to organize the API such that it builds a single-action assist by default, and you need extra steps to add more actions.

That means that, ideally, existing assists and tests should not not see any changes at tall, as the existing API is already optimized for single action use-case.

So, I suggest leaving add_assist / check_assist APIs as is, and instead adding a separate add_assist_group API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Evaluating this direction further, since currently this is the only way of grouping that works for sure.

What if we add an extra Vec<AssistAction> field to the Assist struct, something like:

pub(crate) enum Assist {
    Unresolved { label: AssistLabel },
    Resolved { label: AssistLabel, action: AssistAction, alternative_actions: Vec<AssistAction> },
}

and modify the protocol part correspondingly:

export async function applySourceChange(ctx: Ctx, change: SourceChange, alternative_changes: SourceChange[] | undefined)

?

That way the backward compatibility is preserved (checked by adjusting the protocol part only on a fresh master) and we enforce at least one assist presence on a compile time.

editors/code/src/commands/index.ts Outdated Show resolved Hide resolved
editors/code/src/source_change.ts Outdated Show resolved Hide resolved
@@ -63,7 +64,7 @@ where
///
/// Assists are returned in the "resolved" state, that is with edit fully
/// computed.
pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)>
pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, Vec<AssistAction>)>
Copy link
Member

Choose a reason for hiding this comment

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

I thin the return type should look like this:

pub fn assists<H>(db: &H, range: FileRange) -> 
  (
    Vec<WithLabel<
      Either<
        AssistId, 
        Vec<WithLabel<AssistId>>
      >
    >>, 
    FxHashMap<AssistId, AssistAction>
  ) 

{ ... }

struct WithLabel<T> {
  label: String,
  value: T,
}

but we probably should introduce some named types to avoid >>>>>.

In other words, with lhs we should be able to draw a depth-one tree

do foo (id1)
do baz (id2)
  with spam (id3)
  with eggs (id4)
do quux (id5)

and with rhs we shoudl be able to map each id to an action!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've tried this approach btw and it's not that straightforward, I think:

  1. Currently AssistAction has an optional label that should be set in the builder, so we cannot really return Vec<WithLabel<AssistId>> currently.
    Forcing the users to input the label every time means changing all current assists and duplicating the Assist label for a single AsssistAction.

  2. There's no FxHashMap crate in ra_assists and it feels like we don't really need it that much: it looks like we should rather return Vec<Assist> where Assist will be something like

struct Assist {
  label: String,
  id: AssistId,
  action_data: Either<AssistAction, Vec<AssistAction>>,
}

or leave it as it is currently (with alternative_actions).
Former appoach will also require some additional changes in tests again, since now they are relying on a single action to be present.

So it feels like maximum I can do here is to add some wrapper named struct to hold all data returned and a few methods to it.

@matklad
Copy link
Member

matklad commented Jan 1, 2020

What if we fix the emacs client instead?

I believe vim client(s) also support this extension. Really, we should "just" upstream our extensions for the assists into the protocol (I think the relevant disucssion is microsoft/language-server-protocol#724)

@kjeremy
Copy link
Contributor

kjeremy commented Jan 1, 2020

Why doesn't returning multiple items in the reply handle this? See: https://microsoft.github.io/language-server-protocol/specifications/specification-3-15/#textDocument_codeAction

@SomeoneToIgnore
Copy link
Contributor Author

@kjeremy , do you mean returning multiple actions instead of one action with multiple edits?

I keep the auto import action case in my head and from its perspective my thoughts to go this paths are the following:

  • it's the same action semantically, auto import with multiple options, not many separate actions
  • the list of options for importing might be big for some cases and scrolling through it is not that pleasant (afaik, there are no ways to filter out the list of actions or jump to any of those via any keystrokes)

So, the current way is more like a convenient grouping, allowing the user to quickly select the needed action and then to think on the options provided by it.

Your proposal is great in a sense that it's easier to implement, another thing that concerns me there (besides the bullets above) is the AssistIds I need to come up with for those import actions.
Here's how it looks like:

zzz

Does anybody have a preference on which approach to go with?

@matklad
Copy link
Member

matklad commented Jan 1, 2020

I feel that we'll need assist groups eventually, so I'd love to add the required plumbing! But just returning, say, the first three candidates also seems fine!

@kjeremy
Copy link
Contributor

kjeremy commented Jan 1, 2020

@SomeoneToIgnore yes I think that's what I mean grouped by CodeActionKind (I think we can add our own too).

@SomeoneToIgnore
Copy link
Contributor Author

SomeoneToIgnore commented Jan 1, 2020

I'd like to continue with grouping after all the time I've put into it, but have no idea how to achieve this with the way @kjeremy proposes.

I've added the custom code action name to the action creation and to the server capabilities and to the client capabilities, but still get the non-grouped actions listed.

Did I miss anything?

@SomeoneToIgnore SomeoneToIgnore force-pushed the action-with-multiple-changes branch 2 times, most recently from 6192ef4 to 0cb9092 Compare January 2, 2020 00:04
@SomeoneToIgnore
Copy link
Contributor Author

I've implemented the #2716 (comment) idea of mine, no more breaking changes in the protocol, double checked that on master with client changes only.

The #2716 (comment) is not addressed yet, let's pick the final approach first.

@@ -55,3 +55,13 @@ export async function applySourceChange(ctx: Ctx, change: SourceChange) {
);
}
}

export async function applySourceChange(ctx: Ctx, change: SourceChange, alternativeChanges: SourceChange[] | undefined) {
Copy link
Member

Choose a reason for hiding this comment

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

It still seems to me that overloading this function is wrong. Depending on the arguments you pass to it, it can be either just apply some side-effect, or it can interactively ask user for some input. I'd rather avoid mixing interactive and non-interactive usages in the same function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the client side split is great, I like it.
The question is how to pass the data from a backend, that what I wanted to discuss in
#2716 (comment)

So far it seems that I'd better go with the option #2 from #2716 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

I think you'll need to pick a different action name in conv_rs, but other than that, the current backend setup looks reasonable to me.

I can't say that I feel that it is unquestionably perfect, but I can't suggest any immediate obvious improvements :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I've come up with the changes that hopefully make the overall thing better.

@SomeoneToIgnore SomeoneToIgnore force-pushed the action-with-multiple-changes branch 3 times, most recently from d93f75e to 7c6a71b Compare January 12, 2020 22:02
Copy link
Member

@matklad matklad left a comment

Choose a reason for hiding this comment

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

I like this now!

I'd love to try one other way to encode groups, but I'll do this after this PR is merged, as the idea is vague, and I don't want to block on it.

crates/ra_assists/src/lib.rs Outdated Show resolved Hide resolved
@matklad
Copy link
Member

matklad commented Jan 15, 2020

r=me with itertools replaced with either

bors delegate+

@bors
Copy link
Contributor

bors bot commented Jan 15, 2020

✌️ SomeoneToIgnore can now approve this pull request. To approve and merge a pull request, simply reply with bors r+. More detailed instructions are available here.

@SomeoneToIgnore SomeoneToIgnore force-pushed the action-with-multiple-changes branch from 7c6a71b to 79b7740 Compare January 15, 2020 18:21
@SomeoneToIgnore
Copy link
Contributor Author

Thanks for spending the time on reviewing it!

bors r+

bors bot added a commit that referenced this pull request Jan 15, 2020
2716: Allow assists with multiple selectable actions r=SomeoneToIgnore a=SomeoneToIgnore

This PR prepares an infra for #2180 task by adding a possibility to specify multiple actions in one assist as multiple edit parameters to the `applySourceChange` command.

When this is done, the command opens a selection dialog, allowing the user to pick the edit to be applied.

I have no working example to test in this PR, but here's a demo of an auto import feature (a separate PR coming later for that one) using this functionality:

![out](https://user-images.githubusercontent.com/2690773/71633614-f8ea4d80-2c1d-11ea-9b15-0e13611a7aa4.gif)

The PR is not that massive as it may seem: all the assist files' changes are very generic and similar.

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
@bors
Copy link
Contributor

bors bot commented Jan 15, 2020

Build succeeded

  • Rust (macos-latest)
  • Rust (ubuntu-latest)
  • TypeScript

@bors bors bot merged commit 79b7740 into rust-lang:master Jan 15, 2020
@SomeoneToIgnore SomeoneToIgnore deleted the action-with-multiple-changes branch January 15, 2020 18:55
@matklad
Copy link
Member

matklad commented Jan 16, 2020

I'd love to try one other way to encode groups, but I'll do this after this PR is merged, as the idea is vague, and I don't want to block on it.

Actually, I'll hold up until the auto-import PR is submitted and merged, to avoid merge conflicts and to have a better example to work from

@SomeoneToIgnore SomeoneToIgnore mentioned this pull request Jan 17, 2020
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.

6 participants