rust-analyzer icon indicating copy to clipboard operation
rust-analyzer copied to clipboard

'Move item' refactoring

Open flodiebold opened this issue 6 years ago • 11 comments
trafficstars

This is a feature that I wish we could support very often: Selecting a top-level item and moving it to another module, or selecting an item in an impl block and moving it to another impl block, while fixing up all imports and references. Obviously this would not be easy to implement, but I actually think we could do a useful implementation of this with the analysis we're currently capable of :slightly_smiling_face:

The biggest question for me is how to support this UI-wise, since the LSP doesn't offer much for this (code actions are too limited). An alternative would also be to fix up imports when copy/pasting, but LSP doesn't allow that either as far as I know.

flodiebold avatar Nov 04 '19 21:11 flodiebold

oh yeah, this could have saved me a lot of work when splitting hir... The way it works in IntelliJ is that you can basically copy-paste and drag'n'drop stuff everywhere, and things magically work. We can't have that with LSP I think, and, even if we write our custom extensions for this, plugging it into the editor would be a lot of work. I think an 80% solution here is auto-import.

I've been avoiding writing an issue about it for fear that someone would send a 1000 lines PR with an implementation which I wouldn't be able to review on time, but heh, let me just jot down some mentoring instructions...

matklad avatar Nov 04 '19 22:11 matklad

filed https://github.com/rust-analyzer/rust-analyzer/issues/2180 for auto-import

matklad avatar Nov 04 '19 22:11 matklad

Btw, I've been thinking a possible UI for this (within the constraints of LSP) could be a 'move item here' assist on use items... so you have something like

use super::Foo;

, apply the assist to that, and it replaces the whole use by the declaration of the struct itself, and replaces the declaration of the struct in the super module by a reexport :thinking:

flodiebold avatar Jan 17 '20 15:01 flodiebold

(and another possibility in that 'toolbox' would then be an inline import assist to get rid of the reexport...)

flodiebold avatar Jan 17 '20 15:01 flodiebold

Ohhh, I like these ideas!

matklad avatar Jan 17 '20 15:01 matklad

Another idea is to have the reverse of Replace qualified path with use which would produce absolute paths for all items in the selection. Then it would be possible to have that plus copy to clipboard on crtl+shift+c and the reverse on crtl+shift+v

ssendev avatar Jan 03 '22 15:01 ssendev

Just throwing another UI option into the mix, because I gave it a shot to see if it would work without knowing whether or not this was implemented.

One could potentially update the rename action to take a fully-qualified path, which, if it differed from the item's current fully-qualified path, could be a "move and potentially rename" instruction, moving the item to the appropriate module, creating the module if needed, renaming the module if the name is different from the original, and then updating all imports.

e.g. if I'm in src/mod_one/start.rs, and I've got:

struct ToMove;

Running rename on ToMove and typing:

crate::mod_one::finish::Final

Would create finish.rs as a sibling to start.rs, and insert the struct, renamed to Final.

While typing:

crate::mod_two::finish::Final

Would create the mod_two directory if needed, a mod.rs file (or a top-level mod_two.rs file, presumably preference-dependent), and would create the finish.rs file in there with the Final struct.

I'd imagine this working kind of like the various options for opening, moving, and copying files in emacs, where the necessary files/directories are created based on your input.

mplanchard avatar Apr 06 '22 19:04 mplanchard

In addition to the previous responses using code actions, could we have something close to the intellij cut-paste/drag-and-drop everywhere and-everything-magically-works experience by adding code actions based on recent changes? So, for example, the user could cut-paste some code from one module to another (rust-analyzer would see a series of didChange messages) then invokes a codeAction request at the new location of the code and sees an assist like "Complete move from [] to []", which updates all the references. Or they just move a parameter from one position to another (#8340) and then invoke a codeAction request and see an assist like "Move argument [] to []", which updates all the call sites of this function.

One reason I really like this approach is it integrates very well with a keyboard based, in editor workflow. I can just vim around moving code, using my plugins for swapping, moving and shifting code around and (hopefully) rust-analyzer automagically sees it and adds assists to help. I've often found myself going ciw on the name of some variable to change it and then remembering that this wont change it everywhere so i have to undo and do the lsp rename. Making these work would make the experience much nicer.

Discoverability may be a problem. Maybe a diagnostic or an inlay hint could be used (after the 'manual' move) to inform the user that the lsp can help complete this refactor using a code action. For discovering this before doing a refactor, I think most people would first look at the code action menu right? So maybe adding a (mostly dummy) assist when the user opens the code action menu on something that can be refactored thats says "If you move this elsewhere RA can update the references".

Another problem is that these moves may not be a single didChange notification depending on how it was done, so how to know what is the scope of the refactor, the complete refactor assist shouldn't show up forever, and also should not be missing by some fluke. I could imagine a lsp extension where the user can explicitly start a refactor by sending a willRefactor message, and then every change after that is considered as one big refactor edit and assists are added appropriately, disappearing when doneRefactor is sent. But only adding refactoring assists in this context would mean the user has to remember everytime to start (and stop) refactoring mode, slightly less convenient and magical than the intellij experience.

IndianBoy42 avatar Jul 13 '23 11:07 IndianBoy42

An action like "Extract file module to folder" would be great. This:

src
└── compiler.rs

to this:

src
└── compiler
    └── mod.rs

Where compiler/mod.rs has the same content as compiler.rs. I don’t think this is that hard, but I’m not sure whether this is doable with only the LSP.

TheBlckbird avatar Dec 16 '23 14:12 TheBlckbird

An action like "Extract file module to folder" would be great.

RA has that already, it's in crates/ide-assists/src/handlers/move_to_mod_rs.rs, select all content in current module could trigger it. image

Young-Flash avatar Dec 16 '23 15:12 Young-Flash

This issue is now almost five years old and I am respectfully asking for an update on when we will see actual progress.

I would love to be able to Move a class or implementation into its own file with a click. I can do this with C#, but not rust; Am I to conclude that rust is simply not as advanced a language? ;)

duaneking avatar Aug 25 '24 23:08 duaneking

For prior art, TypeScript has a code action for moving any item to an existing/new file:

Image

Recently, it also gained a new feature that automatically updates imports as you paste blocks around:

https://github.com/user-attachments/assets/85e9b551-d69b-465a-8903-4ae5fdee7228

Having something like this for rust-analyzer would be an insane productivity boost.

musjj avatar Jan 12 '25 14:01 musjj

Just throwing another UI option into the mix, because I gave it a shot to see if it would work without knowing whether or not this was implemented.

One could potentially update the rename action to take a fully-qualified path, which, if it differed from the item's current fully-qualified path, could be a "move and potentially rename" instruction, moving the item to the appropriate module, creating the module if needed, renaming the module if the name is different from the original, and then updating all imports.

e.g. if I'm in src/mod_one/start.rs, and I've got:

struct ToMove;

Running rename on ToMove and typing:

crate::mod_one::finish::Final

Would create finish.rs as a sibling to start.rs, and insert the struct, renamed to Final.

While typing:

crate::mod_two::finish::Final

Would create the mod_two directory if needed, a mod.rs file (or a top-level mod_two.rs file, presumably preference-dependent), and would create the finish.rs file in there with the Final struct.

I'd imagine this working kind of like the various options for opening, moving, and copying files in emacs, where the necessary files/directories are created based on your input.

Personally I find this to be the most intuitive option out of the ones presented. If that is the general consensus, I may give this a shot, but can't promise anything, I am quite scheduled at the moment.

OD-tpeko avatar Apr 17 '25 17:04 OD-tpeko