helix icon indicating copy to clipboard operation
helix copied to clipboard

Move lines or blocks up or down

Open gregorriegler opened this issue 3 years ago • 27 comments

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 avatar Apr 23 '22 22:04 gregorriegler

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

archseer avatar Apr 24 '22 04:04 archseer

@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

workingj avatar Apr 24 '22 10:04 workingj

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.

gregorriegler avatar Apr 24 '22 14:04 gregorriegler

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

ic4-y avatar Apr 24 '22 16:04 ic4-y

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

gregorriegler avatar Apr 24 '22 17:04 gregorriegler

@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

workingj avatar Apr 24 '22 20:04 workingj

Also duplicate the line up/down

zakaria-chahboun avatar May 27 '22 03:05 zakaria-chahboun

I need this feature too

hustcer avatar May 28 '22 14:05 hustcer

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"]

zakaria-chahboun avatar Jun 02 '22 05:06 zakaria-chahboun

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.

ic4-y avatar Jun 07 '22 07:06 ic4-y

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.

pedronasser avatar Jul 12 '22 18:07 pedronasser

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.

workingj avatar Jul 13 '22 11:07 workingj

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)

Byteron avatar Jul 21 '22 14:07 Byteron

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 avatar Oct 22 '22 14:10 sireliah

Here we go: https://github.com/helix-editor/helix/pull/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 avatar Oct 31 '22 22:10 sireliah

preview.webm

sireliah avatar Oct 31 '22 22:10 sireliah

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.

Chickenkeeper avatar Nov 01 '22 22:11 Chickenkeeper

I've added some unit tests for the functionality and I'm ready for the review of the PR https://github.com/helix-editor/helix/pull/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).

sireliah avatar Nov 09 '22 12:11 sireliah

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.

Chickenkeeper avatar Nov 10 '22 17:11 Chickenkeeper

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.

sireliah avatar Nov 27 '22 15:11 sireliah

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?

https://github.com/helix-editor/helix/pull/4545#pullrequestreview-1195055143

dead10ck avatar Nov 28 '22 02:11 dead10ck

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"]

robrecord avatar Jan 13 '23 14:01 robrecord

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 avatar Mar 30 '23 14:03 igor-ramazanov

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.

igor-ramazanov avatar Mar 30 '23 14:03 igor-ramazanov

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"]

themixednuts avatar May 12 '23 06:05 themixednuts

The proposed solutions unfortunately don't preserve cursor position within the line, so aren't true line movements

eugenesvk avatar May 17 '23 07:05 eugenesvk

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

pi314ever avatar May 18 '23 22:05 pi314ever

I am also looking forward to this :-)

I also like the horizontal movement that is implemented by vim-move. Although this is not part of this issue I would like this—should it be implemented at some point—to work nicely together with vertical movement. Horizontal movement would undoubtedly only move the selected content. On this regard: I think a comment on the merge request already asks about selections of partial lines and how these are dealt with. The current implementation always moves the full line as soon as some selection is on the line. It might be worth to explore the idea of actually only moving what is selected. This would be more consistent with other functionality and would also complement the behavior of how potential horizontal movements would be implemented.

With horizontal and vertical movement implemented like that we could very naturally move selected content around while keeping it selected which is the key advantage over one of the proposed key bindings.

I imagine that moving only what is selected could work by cutting the selection and then pasting it at the same column one line above or below. In the event of the line above or below being too short the behavior could be similar to how j and k move cursors, i.e. the column position is clipped to the line length. Since moving cursors with j and k already works like that the necessary information might tracked already. Moving a full line is then a special case of this. We would need to take care that a selection is not moved into another selection at which point it could just not be moved or merged for example (latter matches j/k behavior if cursors are moved into each other the beginning of the file). This should also cover the conflict resolution logic that is part of the merge request.

jannschu avatar Jun 04 '23 19:06 jannschu

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).

jannschu avatar Jun 04 '23 21:06 jannschu

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.

sireliah avatar Jun 04 '23 21:06 sireliah