Skip to content

"Dumb" auto import assist #762

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 5 commits into from
Feb 10, 2019
Merged

"Dumb" auto import assist #762

merged 5 commits into from
Feb 10, 2019

Conversation

eulerdisk
Copy link
Contributor

@eulerdisk eulerdisk commented Feb 7, 2019

This adds a new assist to "add xxx::yyy to the current file" when the cursor is on a PATH. It manages correctly nested imports,self keyword and creates new nested imports if necessary. [See the tests]
It doesn't use name resolution so in that sense is 'dumb', but I have plans to do that. That in the future will be useful to auto import trait names in autocompletion for example.

It can easily be extended to provide multiple actions to select in which scope to import. That's another thing I plan to do.

@matklad I copied some indentation code from ide_light, I don't know at the moment if/how you want to refactor that code. This assist was meant to be in ide_light.

@eulerdisk eulerdisk changed the title Dumb auto import assit Dumb auto import assist Feb 7, 2019
@eulerdisk eulerdisk changed the title Dumb auto import assist "Dumb" auto import assist Feb 7, 2019
@matklad
Copy link
Member

matklad commented Feb 7, 2019

Haven't looked at the code yet, but the idea is 100% brilliant!

SyntaxKind::{ PATH, PATH_SEGMENT, COLONCOLON, COMMA }
};
use crate::assist_ctx::{AssistCtx, Assist, AssistBuilder};
use itertools::{ Itertools, EitherOrBoth };
Copy link
Member

Choose a reason for hiding this comment

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

An interesting next step along the lines of this PR is "optimize imports" action, which reformats imports, sorts crate:: paths last and merges prefixes.

Though perhaps to do that we'll need a "structured editing API", that is, something like "remove this import" rather than "replace this sub string with that sub string". There's no such API in ra_syntax yet, it would be fun to design and implement it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe having something like a mutable AST should be enough?

let mut mtreelist = treelist.get_mutable();
mtreelist.add_path(MutPath.from_str("foo::bar"))?
indent(mtreelist);
let diffs = diff(trellis, mstreelist); // get text insertions/deletions

Apart from ident and diff, I thing everything else can be autogenerated.

use itertools::{ Itertools, EitherOrBoth };

// TODO: refactor this before merge
mod formatting {
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, formatting module in ra_ide_api_light is kinda random at the moment. Really, this should be a separate crate which is as powerful as rustfmt.

You can use formatting from ra_ide_api_light, but I would also love a separate PR which creates a new ra_fmt crate and moves this module there. I think we then would be able to drop dependency from assists on ide_api_light.

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'll go for another PR then, introducing ra_fmt.

Copy link
Member

Choose a reason for hiding this comment

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

Awesome! Feel free to add/remove stuff from fmt module, it's currently a somewhat ad-hoc collections of functions, I imagine it should grow into a better "all formatting" API in the future!

return Some(v);
}

fn collect_path_segments_raw<'b, 'a: 'b>(
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 it's OK to only declare 'a lifetime and left 'b to elision.


fn compare_path_segment(a: &ast::PathSegment, b: &ast::PathSegment) -> bool {
if let (Some(ka), Some(kb)) = (a.kind(), b.kind()) {
return match (ka, kb) {
Copy link
Member

Choose a reason for hiding this comment

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

Don't need this return. That's actually also a warning we can already implement.

}

#[derive(Copy, Clone)]
enum PathSegmentsMatch {
Copy link
Member

Choose a reason for hiding this comment

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

Hm, I wonder if instead of enum we could return a single usize -- length of the common prefix. It sort-of has the same amount of info as this enum, and doesn't suffer the problem that Empy and Partial(0) are the same.

// Add a brand new use statement.
AddNewUse(
Option<&'a SyntaxNode>, // anchor node
bool, // true if we want to add the new statement after the anchor
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps it makes sense to make this a struct variant, if we need a comment anyway? Positional bools are awkward .


// Find out the best ImportAction to import target path against current_use_tree.
// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList.
fn walk_use_tree_for_best_action<'b, 'c, 'a: 'b + 'c>(
Copy link
Member

Choose a reason for hiding this comment

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

ditto, 'b and 'c are not required I think.

@matklad
Copy link
Member

matklad commented Feb 7, 2019

Haven't reviewed the bulk of the logic yet, but the general structure and tests look good to me!

One alternative approach here would be to always add a simple "fully-qualified" path first, and then invoking a separate "optimize imports" action to rewrite & merge all the imports.

@eulerdisk
Copy link
Contributor Author

eulerdisk commented Feb 8, 2019

Currently this assist is designed to modify existing code as little as possible.
I never thought around "optimize imports" deeply, but I think it's a bit more complicated. It seems to me that it involves a lot of heuristics, user preferences and indentation capabilities, so I think it's a job for rustfmt (Maybe they are already doing it?).

[The best solution in the long run in having rustfmt implemented on top of ra_syntax anyway]

@matklad
Copy link
Member

matklad commented Feb 8, 2019

It seems to me that it involves a lot of heuristics, user preferences and indentation capabilities, so I think it's a job for rustfmt (Maybe they are already doing it?).

Agree, but we totally need to rewrite rustfmt on top of ra_syntax, and "optimizing imports" in particular seems an interesting hard+isolated task for prototyping editing API. We musn't do it in this PR of course, just something to keep in mind for future work.

@eulerdisk
Copy link
Contributor Author

eulerdisk commented Feb 8, 2019

Another thing "optimize imports" should do is to remove unused imports, so in the end it's not entirely a rustfmt job.

@eulerdisk
Copy link
Contributor Author

@matklad I addressed your comments. I removed PathSegmentsMatch entirely, I'm not sure if it's better for you now. The match in walk_use... gets messier, so tell me if you want me to revert that.

bors bot added a commit that referenced this pull request Feb 9, 2019
766: Formatting code into ra_fmt r=matklad a=eulerdisk

As discussed #762 (comment)

I did only move the code without other improvements.

Co-authored-by: Andrea Pretto <[email protected]>
@matklad
Copy link
Member

matklad commented Feb 10, 2019

bors r+

Awesome!

bors bot added a commit that referenced this pull request Feb 10, 2019
762: "Dumb" auto import assist r=matklad a=eulerdisk

This adds a new assist to "add xxx::yyy to the current file" when the cursor is on a PATH. It manages correctly nested imports,`self` keyword and creates new nested imports if necessary. [See the tests]
It doesn't use name resolution so in that sense is 'dumb', but I have plans to do that. That in the future will be useful to auto import trait names in autocompletion for example.

It can easily be extended to provide multiple actions to select in which scope to import. That's another thing I plan to do.

@matklad I copied some indentation code from `ide_light`, I don't know at the moment if/how you want to refactor that code. This assist was meant to be in `ide_light`.

Co-authored-by: Andrea Pretto <[email protected]>
@bors
Copy link
Contributor

bors bot commented Feb 10, 2019

Build succeeded

@bors bors bot merged commit 1a4faaf into rust-lang:master Feb 10, 2019
@matklad
Copy link
Member

matklad commented Feb 10, 2019

Opened #779 about well-typed editing API.

@matklad
Copy link
Member

matklad commented Feb 25, 2020

Hey, @eulerdisk just want to let you know that this code have effectively grown to a full-blown auto import recently, thanks for writing it more than a year ago!

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.

2 participants