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

Move lines or blocks up or down #2245

Closed
gregorriegler opened this issue Apr 23, 2022 · 49 comments
Closed

Move lines or blocks up or down #2245

gregorriegler opened this issue Apr 23, 2022 · 49 comments
Labels
A-plugin Area: Plugin system C-enhancement Category: Improvements R-wontfix Not planned: Won't fix

Comments

@gregorriegler
Copy link

IMO one of the most important features for a text editor is the ability to quickly move lines, but also blocks up and down.

In VIM I achieve this using the https://github.com/matze/vim-move plugin. However it would be awesome if helix did something like this out of the box.

@gregorriegler gregorriegler added the C-enhancement Category: Improvements label Apr 23, 2022
@archseer
Copy link
Member

xdp/xdP is already very short and you can always create a mapping for it if you want.

@workingj
Copy link
Contributor

@gregorriegler

# line-up
C-e = ["keep_primary_selection","extend_line","yank","move_line_up","open_above","normal_mode","replace_with_yanked", "move_line_down", "move_line_down", "extend_line", "delete_selection", "move_line_up", "move_line_up"]
# line-down
C-d = ["keep_primary_selection","extend_line","delete_selection", "paste_after", "move_line_down"]

may be that helps

@gregorriegler
Copy link
Author

Thank you for the tips!
That works good for a single line, yes.

I wish I could just select a complete function and move it around, maybe down below the next function.
Or lets say I have a function in a function, and I want to move it out to the top level, and it automatically unindents.
Yes I know I can cut and paste. But once the moving is possible, you don't want back.

@icodeforyou-dot-net
Copy link

I agree, that is a functionality I was looking for as well and couldn't find: moving whole code blocks as described.

@gregorriegler
Copy link
Author

It's also viable for non-code. Think paragraphs, config sections, lists. So many cases, I use it regularely.

@workingj
Copy link
Contributor

workingj commented Apr 24, 2022

@icodeforyou-dot-net @gregorriegler ,well you can achive that also with these commands.

But I agree, moving text blocks that way with auto indentation is nice to have.
I am used to this from vscodium

@kirawi kirawi added the A-helix-term Area: Helix term improvements label Apr 24, 2022
@zakaria-chahboun
Copy link

Also duplicate the line up/down

@hustcer
Copy link
Contributor

hustcer commented May 28, 2022

I need this feature too

@zakaria-chahboun
Copy link

You can do:
Ctrl+Alt+Up / Ctrl+Alt+Down to duplicate lines in insert mode
Alt+Up / Alt+Down to move lines in insert mode

By adding this to the config file:

[keys.insert]
# move line up
"A-up" = ["extend_line","yank","move_line_up","open_above","replace_with_yanked","move_line_down", "move_line_down", "extend_line", "delete_selection", "move_line_up", "move_line_up"]
# move line down
"A-down" = ["extend_line","delete_selection", "paste_after", "move_line_down"]
# duplicate line up
"C-A-up" = ["extend_line","yank","open_above","normal_mode","replace_with_yanked", "insert_mode"]
# duplicate line down
"C-A-down" = ["extend_line","yank","move_line_down","open_above","normal_mode","replace_with_yanked", "insert_mode"]

@icodeforyou-dot-net
Copy link

You can do: Ctrl+Alt+Up / Ctrl+Alt+Down to duplicate lines in insert mode Alt+Up / Alt+Down to move lines in insert mode

By adding this to the config file:

[keys.insert]
# move line up
"A-up" = ["extend_line","yank","move_line_up","open_above","replace_with_yanked","move_line_down", "move_line_down", "extend_line", "delete_selection", "move_line_up", "move_line_up"]
# move line down
"A-down" = ["extend_line","delete_selection", "paste_after", "move_line_down"]
# duplicate line up
"C-A-up" = ["extend_line","yank","open_above","normal_mode","replace_with_yanked", "insert_mode"]
# duplicate line down
"C-A-down" = ["extend_line","yank","move_line_down","open_above","normal_mode","replace_with_yanked", "insert_mode"]

While better than nothing, this is bugging out quite a bit if you mark more than one line and change into insert mode to move them. Duplicating multiple lines works like a charm. Duplicating a single line multiple times on the other hand will not work properly without re-selecting.

@pedronasser
Copy link

Clearly we need some kind of builtin command for doing selection moving easily and properly.
Neither of the above solutions or other solutions that I've tried reached a good enough behavior.

@workingj
Copy link
Contributor

Clearly we need some kind of builtin command for doing selection moving easily and properly. Neither of the above solutions or other solutions that I've tried reached a good enough behavior.

think so too.

@Byteron
Copy link

Byteron commented Jul 21, 2022

cutting, moving, then pasting is a 3 step process, while moving a line is a 1 step process.
same with line duplication.

I'd definitely think this is worth having a built-in for. I heavily used it in vscode and am really missing it in helix. (though the rest is top notch)

@kirawi kirawi added the E-good-first-issue Call for participation: Issues suitable for new contributors label Sep 29, 2022
@sireliah
Copy link

Hi, I think I have good idea how to implement function for moving lines/selections up and down. For the starters I'll cover just lines and selections and if this works well enough, we can make the command indentation aware (similarly as in vscode).

@sireliah
Copy link

Here we go: #4545

This is draft PR that is not intended to be merged yet, but to show the behavior of the feature.

What is included:

  • move line
  • move selection
  • move multiple lines
  • move multiple selections
  • support for UNIX and Windows style line endings

What can be improved:

  • edge case when multiple cursors occupy adjacent lines - in such case I remove the conflicting cursor/selection, but it's still rough around the edges. There is no symmetry when cursors go up vs. down. I could spend ages trying to deal with that, but decided to ask for initial review first.
  • comprehensive unit tests - I'm going to add them, because of the high risk of panic when there is a bug in the changes computation

@sireliah
Copy link

preview.webm

@Chickenkeeper
Copy link
Contributor

For the time being you can almost get that functionality with this key mapping:

[keys.normal]
C-A-j = ['ensure_selections_forward', 'extend_to_line_bounds', 'extend_char_right', 'extend_char_left', 'delete_selection', 'add_newline_below', 'move_line_down', 'replace_with_yanked']
C-A-k = ['ensure_selections_forward', 'extend_to_line_bounds', 'extend_char_right', 'extend_char_left', 'delete_selection', 'move_line_up', 'add_newline_above', 'move_line_up', 'replace_with_yanked']

It works with multiple lines (even disjoint) as well as single lines.

It does get a little funky if you try and move lines past the top/bottom line, and it will overwrite your yank register and selection, so I would still prefer it being built-in. But I've had lots of success using that mapping so far.

@sireliah
Copy link

sireliah commented Nov 9, 2022

I've added some unit tests for the functionality and I'm ready for the review of the PR #4545.

Please also give me feedback on the default key bindings. C-up and C-down are the ones I used for testing, but I'm open for suggestions (especially from the maintainers of this repo).

@Chickenkeeper
Copy link
Contributor

I'd prefer a keybinding that uses the j & k or u & d keys for up and down, since it is more consistent with existing movement keys and closer to all the other keys Helix uses. Using the arrow keys often involves repositioning one of your hands, which defeats the purpose of Vim-like controls.

@sireliah
Copy link

Hi, would anyone be able to review the PR? I'm regularly resolving conflicts in the files (against master), but it's a perpetual struggle.

@dead10ck
Copy link
Member

Hi, would anyone be able to review the PR? I'm regularly resolving conflicts in the files (against master), but it's a perpetual struggle.

It looks like it was?

#4545 (review)

@robrecord
Copy link

robrecord commented Jan 13, 2023

I'd prefer a keybinding that uses the j & k or u & d keys for up and down...

This works:

[keys.normal]
C-j = ["extend_to_line_bounds", "delete_selection", "paste_after"]
C-k = ["extend_to_line_bounds", "delete_selection", "move_line_up", "paste_before"]

@igor-ramazanov
Copy link

igor-ramazanov commented Mar 30, 2023

I really like and enjoy the simplicity and compositionality of commands in Helix.
Also, appreciate it being simple and easy to configure with sane defaults.

I a bit worried if it could sorta "blow up" in configurability and becoming an inconsistent mess if maintainers attempt to fulfull everyone's desires.

That is generally speaking, which has nothing to do with this particular issue.

Speaking concretely, I am personally, do not have any issues with cutting out and pasting blocks of code first selecting them with tree-sitter/LSP grammars (Alt + Up/Down by default).

That is already simple enough for me, I would not like to overcomplicate it and to eventually turn Helix into a configurability mess.

I believe it is important to provide fundamentals or basics, convenient enough to use out of the box, and the ways to compose them into more complex commands if users would want them by themselves.

@igor-ramazanov
Copy link

igor-ramazanov commented Mar 30, 2023

And I also believe that, of course, if this feature will be meant to be implemented and merged then it will be convenient to automatically preserve identation.

However, and again generally speaking, I think this functionality should reside outside of the editor.

Code quality and its representation must not depend on the editor a particular developer uses, but instead should be defined somewhere outside.

It is already achievable nowadays with LSP/tree-sitter + auto-format or delegating formatting to an external tool.

Then it is possible to write the code in any way possible, but it will be autoformatted consistently upon saving and irrelevantly from an editor everyone uses.

@themixednuts
Copy link

themixednuts commented May 12, 2023

I'd prefer a keybinding that uses the j & k or u & d keys for up and down...

This works:

[keys.normal]
C-j = ["extend_to_line_bounds", "delete_selection", "paste_after"]
C-k = ["extend_to_line_bounds", "delete_selection", "move_line_up", "paste_before"]

Thank you! I also added with this help.

A-C-j = ["extend_to_line_bounds", "yank", "paste_after"]
A-C-k = ["extend_to_line_bounds", "yank", "paste_before"]

@jannschu
Copy link

jannschu commented Jun 4, 2023

I played a bit with the following bindings which work with partial selections in some cases but have their annoyances:

[keys.normal]
C-j = ["delete_selection", "move_line_down", "paste_before"]
C-k = ["delete_selection", "move_line_up", "paste_before"]
C-h = ["delete_selection", "move_char_left", "paste_before"]
C-l = ["delete_selection", "move_char_right", "paste_before"]

Issues with those:

  • If the file does not end with a line break the last line is moved to the beginning of the previous line and no line break is added. Sublime Text and vscode both handle this special case but replicating this would (rightfully IMHO) deviate a bit from only moving what is selected because no line break was in fact selected.
  • If multiple selections are moved some content can become discarded. For example go the the first character of the file, then press C and then C-k. The second selection's content is now deleted.
  • Moving content using C-j/C-k into a line that is too short and then back does not keep the column as j/k would.
  • The " register is polluted. Another register could be used though.
  • If the previous line contains tab characters moving up selected content may result in unexpected positioning: Say the cursor is at column 3 and the first character in the previous line is a tab, then the selected content is added at the beginning of the line. I would also expect column 3 (this might be considered a move_line_<up/down> bug maybe?).
  • Selection directions are not kept, the cursor will always be at the end of the selection.
  • Technically not part of this issue but: Horizontal movement allows leaving the line which may or may not be desirable. This at least mimics the h/l behavior.

For me that makes an extra command desirable for moving selected content because key bindings can not cover these cases I think (@the-mikedavis rightfully brought this question up in a review comment).

@sireliah
Copy link

sireliah commented Jun 4, 2023

Any idea on if this will be implemented in core? The PR (#4545) for this issue hasn't had any progress since November 2022.

Hi, I'm the author of the PR. Sorry for no progress. To be absolutely honest, I got demotivated to finish it because of the some of the negative feedback (this is not a feature that helix should support) here and in HN. Also I don't use helix anymore that much because of its lack of support for the permanent state.

As the matter of fact, I have solved all the edge cases in the code, but haven't pushed the changes yet. I'll rebase and push an update soon.

@the-mikedavis
Copy link
Member

#4545 (comment)

This kind of editing breaks with select-then-act so I don't see this fitting into core, though it'd be fine as something in a plugin when the plugin system exists.

@the-mikedavis the-mikedavis closed this as not planned Won't fix, can't repro, duplicate, stale Jun 19, 2023
@the-mikedavis the-mikedavis added A-plugin Area: Plugin system R-wontfix Not planned: Won't fix and removed A-helix-term Area: Helix term improvements E-good-first-issue Call for participation: Issues suitable for new contributors labels Jun 19, 2023
@vwkd
Copy link
Contributor

vwkd commented Oct 27, 2023

Sad that this was rejected.

The argument that it violates a "select-then-act" principle seems to be more ideological than practical. The fact that this feature is widely used in other editors and requested by many helix users should be a hint that maybe the principle isn't perfect and maybe an exception is warranted.

As echoed by others before, remapping the respective cut-and-paste commands isn't a sufficient substitute. It adds a newline to the end of the file if not already, overwrites the registers, doesn't respect indentation, doesn't accept numbers, doesn't work with repetition (press number before), etc.

This should be reopened.

omentic added a commit to omentic/helix-ext that referenced this issue Nov 1, 2023
@carlosvigil
Copy link

carlosvigil commented Nov 20, 2023

How is moving a selection not "select then act"?

@icodeforyou-dot-net
Copy link

I don't quite get it either, what is the big deal? Just require entire lines to be selected before moving stuff around if that's what makes everybody happy.

@jfaz1
Copy link

jfaz1 commented Nov 20, 2023

The maintainer pointed out that the behavior on fully selected lines was indeed fine, the issue that breaks select->act is when you have a line partially selected and then it ends up moving it anyways. And if you get rid of that feature, you can basically end up mimicking this behavior with one of the config settings posted above.

@icodeforyou-dot-net
Copy link

The reports from above appear to indicate to me that mimicking this behavior is still more complicated than one might expect it to be. I haven't tried myself since the very beginning of this topic though.

But what would be the alternative? Using Neovim until we get a plugin system for Helix?

It's a bit sad that philosophy trumps functionality in this. Helix is missing quite an essential piece here I feel.

Or maybe instead of insisting on abstract principles, casuistry should be given a chance: whenever my cursor is on a line, I in a way select it mentally, don't I? 😄 So select->act can be understood in many ways.

@jfaz1
Copy link

jfaz1 commented Nov 20, 2023

@icodeforyou-dot-net I can't give you a satisfying response, but if not having a dedicated shortcut for moving lines up/down is enough of a difference to make you feel like you have to use Neovim then I recommend you do so until Helix gets a plugin system. In my case, while I might make use of this feature, simply cutting and pasting a line/block somewhere else takes no time at all. You just select a line (or multiple) by pressing x/ ]p, d to cut, and p to paste after you moved the cursor to the target location. I know that's technically more operations, but in practice it feels fast enough coming from Neovim/VScode/Emacs (though I still use Emacs).

cut.mp4

Or maybe instead of insisting on abstract principles, casuistry should be given a chance: whenever my cursor is on a line, I in a way select it mentally, don't I? 😄 So select->act can be understood in many ways.

Well that would require more code changes than you probably might think 😆

@milesgranger
Copy link

milesgranger commented Nov 20, 2023

the issue that breaks select->act is when you have a line partially selected and then it ends up moving it anyways.

Just skimming this, but one can currently select part of a line (not all of it) and de/indent the whole thing. Why is that okay then? Should it (technically) only de/indent the portion that's selected? If that's the argument for not doing it in this case.

Will also note, I'm mostly fine w/ the select/yank/paste flow. Just seems like a minor inconsistency to a naive user to allow that while moving side to side, but not up or down.

@jfaz1
Copy link

jfaz1 commented Nov 20, 2023

Just skimming this, but one can currently select part of a line (not all of it) and de/indent the whole thing. Why is that okay then? Should it (technically) only de/indent the portion that's selected? If that's the argument for not doing it in this case.

That's actually a really good point, you should raise it in the PR. I was just conveying what the maintainers stated in regards to this feature, I'm personally not opposed to it either way.

@omentic
Copy link
Contributor

omentic commented Nov 20, 2023

If anybody wants it, I have this patch merged into my fork: https://aur.archlinux.org/packages/helix-ext

@icodeforyou-dot-net
Copy link

icodeforyou-dot-net commented Nov 21, 2023

@milesgranger really interesting point. Just shows that principles aren't always fixed.

@jfaz1 since you mention VSCode. That one has a very interesting mechanism for this. It has multiple ways to understand select so to speak. Just click anywhere on a line and the whole line gets highlighted. Unless you select the individual characters the highlight stays there and VSCode assumes that you selected the entire line. Ctrl+x to cut will operate on the entire line for instance unless you specifically select characters within it. So in a way it has two concepts of select, select characters in lines or select lines. (Actually it has more than two, words also get selected for example)

The operation I was looking to get in Helix is very easy to use in VSCode: just select any number of characters in adjacent lines. VSCode will assume that you also select the lines in a way. Then you can use alt+up or alt+down to move the entire block of lines up and down. This operates only on the entirety of selected lines, not on the selected characters within them. VSCode basically assumes that this operation only ever makes sense on entire lines.

Vim/Neovim with plugins is obviously a bit more versatile here. But VSCode doesn't need any plugins for that basic version of moving blocks of lines around. Which is great I think.

@7ombie
Copy link
Contributor

7ombie commented Feb 6, 2024

Thinking about how this feature is used in VSCode etc, it's doing a couple of distinct things from a Helix user's perspective:

  1. Move a single line up or down by a line or two.
  2. Move one or more lines by many lines.

The second case doesn't really make sense in Helix. We don't want to hold a keybinding to mash a command with auto-repeat, while a block of code crawls awkwardly through the file. This is fundamentally at odds with how Helix works, and quickly becomes disorienting if auto-indent is enabled as well, as the lines also jump left and right, as each line moves through various levels of indentation. We would just select the block, cut it, move the cursor to where the block needs to be, and paste it there.

The first case is much more compelling though. Being able to quickly move a line up or down by a line or two is really useful. Partly just for reorganizing lines, but it's also helpful when you need to swap around parts of a line (as you can break the parts into lines, move them around, then join them back into one line again).


On keybindings, I've been using Shift with the Arrow Keys: Right and Left indent and unindent, while Up and Down move lines vertically. This allows me to hold Shift and freely (two-dimensionally) move a line to any position. This works especially well in Helix, as it doesn't auto-indent the code (but still infers tab-stops when you move left and right, so you don't need to mash right to indent a line).

Ultimately, I'm not sure what the correct solution is, but feel like this feature request was completely abandoned when it should have probably been partly adopted. It just needed rethinking a bit. I'd be grateful if we could revisit this. Thanks.

@gregorriegler
Copy link
Author

We would just select the block, cut it, move the cursor to where the block needs to be, and paste it there.

How is that different from moving a block of lines down by one line?

@7ombie
Copy link
Contributor

7ombie commented Feb 28, 2024

How is that different from moving a block of lines down by one line?

@gregorriegler - I'm not precisely sure what you're asking. Admittedly, the point I was making was a bit vague, but I already had a pretty good run at making it. I doubt that fully reiterating the same point would make it any clearer.

I was basically saying that moving lines up or down, one line at a time, would feel pretty natural in Helix when only moving one or two lines up or down by a few lines or so, but moving bigger blocks, especially by many lines (one line at a time), would feel unnatural. On the other hand, in VSCode, that distinction would just seem far less relevant (just due to a different way of doing things).

I'm not sure that you could exactly support one and not the other, but you can at least focus on providing something that does the thing that seems natural in Helix, and just kind of assume that users use yank and paste to move larger things farther. You'd be right that, in practice, it may not make much difference to how any eventual commands and bindings etc actually work. I was really trying to encourage reopening this issue with a more limited conceptual scope, focussed only on how they'd actually be used.

@7ombie
Copy link
Contributor

7ombie commented Feb 28, 2024

On bindings, I ended up using Alt (instead of Shift) with the cursor keys to move blocks around "two-dimensionally". That felt more conventional, and freed up Shift with Left and Right for switching buffers, and Shift with Up and Down for making selections uppercase or lowercase (which just worked better for my configuration).

omentic added a commit to omentic/helix-ext that referenced this issue May 1, 2024
omentic added a commit to omentic/helix-ext that referenced this issue May 1, 2024
@j03-dev
Copy link

j03-dev commented Jun 7, 2024

[keys.select]
K = [
  "yank_main_selection_to_clipboard",
  "delete_selection",
  "move_line_up",
  "paste_clipboard_before",
  "select_mode",
]

J = [
  "yank_main_selection_to_clipboard",
  "delete_selection",
  "move_line_down",
  "paste_clipboard_before",
  "select_mode",
]

@H4ckint0sh
Copy link

None of the above suggested solutions is working for me! Am i missing something? I use to build the project myself with some goodies I merge like inline diagnostics, maybe something has gone wrong when I merged those branches.
Does this work for you people?

@7ombie
Copy link
Contributor

7ombie commented Jun 20, 2024

@H4ckint0sh - I had some trouble with some of the suggestions, but have been using this for a while:

up = [ # scroll selections up one line
    "ensure_selections_forward",
    "extend_to_line_bounds",
    "extend_char_right",
    "extend_char_left",
    "delete_selection",
    "move_line_up",
    "add_newline_above",
    "move_line_up",
    "replace_with_yanked"
]

down = [ # scroll selections down one line
    "ensure_selections_forward",
    "extend_to_line_bounds",
    "extend_char_right",
    "extend_char_left",
    "delete_selection",
    "add_newline_below",
    "move_line_down",
    "replace_with_yanked"
]

You'll need to change which keys you're binding to, but that snippet should work. I'm not sure it's perfect. For example, it may will get confused if you try to move lines past the end of the buffer, that kind of thing. I've used it for a few months, and not had any issues with it, but haven't really tested it either. It does the job. Hope it helps.

P.S. I'm still +1 for this feature being built in.

@omentic
Copy link
Contributor

omentic commented Jun 20, 2024

(I'll mention I've been keeping my fork updated - it has this, among other patches, built-in and bugfixed and doesn't have any issues with moving past the end of the buffer: https://github.com/omentic/helix-ext)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-plugin Area: Plugin system C-enhancement Category: Improvements R-wontfix Not planned: Won't fix
Projects
None yet
Development

No branches or pull requests