rust-analyzer
rust-analyzer copied to clipboard
'Move item' refactoring
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.
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...
filed https://github.com/rust-analyzer/rust-analyzer/issues/2180 for auto-import
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:
(and another possibility in that 'toolbox' would then be an inline import assist to get rid of the reexport...)
Ohhh, I like these ideas!
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
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.
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.
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.
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.
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? ;)
For prior art, TypeScript has a code action for moving any item to an existing/new file:
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.
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
renameaction 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
renameonToMoveand typing:crate::mod_one::finish::FinalWould create
finish.rsas a sibling tostart.rs, and insert the struct, renamed toFinal.While typing:
crate::mod_two::finish::FinalWould create the
mod_twodirectory if needed, amod.rsfile (or a top-levelmod_two.rsfile, presumably preference-dependent), and would create thefinish.rsfile in there with theFinalstruct.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.