helix-editor / helix

A post-modern modal text editor.
https://helix-editor.com
Mozilla Public License 2.0
33.49k stars 2.48k forks source link

Move lines or blocks up or down #2245

Closed gregorriegler closed 1 year ago

gregorriegler commented 2 years ago

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.

archseer commented 2 years ago

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

workingj commented 2 years ago

@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 commented 2 years ago

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.

ic4-y commented 2 years ago

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

gregorriegler commented 2 years ago

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

workingj commented 2 years ago

@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

zakaria-chahboun commented 2 years ago

Also duplicate the line up/down

hustcer commented 2 years ago

I need this feature too

zakaria-chahboun commented 2 years ago

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"]
ic4-y commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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)

sireliah commented 2 years ago

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 commented 1 year ago

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:

What can be improved:

sireliah commented 1 year ago

preview.webm

Chickenkeeper commented 1 year ago

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 commented 1 year ago

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

Chickenkeeper commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

robrecord commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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"]
eugenesvk commented 1 year ago

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

pi314ever commented 1 year ago

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

jannschu commented 1 year ago

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 commented 1 year ago

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:

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 commented 1 year ago

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 commented 1 year ago

https://github.com/helix-editor/helix/pull/4545#issuecomment-1597793528

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.

vwkd commented 12 months ago

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.

carlosvigil commented 11 months ago

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

ic4-y commented 11 months ago

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 commented 11 months ago

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.

ic4-y commented 11 months ago

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? :smile: So select->act can be understood in many ways.

jfaz1 commented 11 months ago

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

https://github.com/helix-editor/helix/assets/56184947/e9a344b0-89ad-4a5d-8bfe-957ad2f7f983

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 :laughing:

milesgranger commented 11 months ago

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 commented 11 months ago

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 commented 11 months ago

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

ic4-y commented 11 months ago

@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 commented 8 months ago

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 commented 8 months ago

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 commented 8 months ago

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 commented 8 months ago

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

j03-dev commented 4 months ago
[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 commented 4 months ago

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 commented 4 months ago

@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 commented 4 months ago

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

Mnior commented 1 month ago

becomes disorienting if auto-indent is enabled

You paid attention not so much to the formatting principle, indents, but to something more: it is desirable that the entire text be in an absurd state as rarely as possible. Therefore, moving even one line will also be absurd. For example, in program code, line-by-line moving a line from the body of one function to another, in the interval will go beyond the functions (between classes). In ordinary text, moving one sentence in a paragraph is also absurd - the sentence will be arbitrarily located in the middle of other sentences and words. And @igor-ramazanov wrote about this, that this already falls into the area of interest of LSP/ TreeSitter, and this can be observed in the Zed editor, where TreeSitter is even more dominant. It is desirable that the line/block when moving immediately jumps to the body of the next function.

At the same time, I agree with you that moving more than one block - almost completely loses its meaning, the sizes of different functions are random, if the first block fell into place, then the second is unlikely, it is difficult to predict.

The distinctive feature of command editors from ordinary ones is precisely the reduction of all sorts of meaningless repetitions, not pressing the j key 15 times in a row, but pressing three keys 15j. It's the same principle, if we need to be 15 lines lower, we don't need to be in ridiculous intermediate states.

It's annoying when a colleague stubbornly hits the same keys because he doesn't want to automate himself, but gets nervous himself when he doesn't have the skill to do the same thing in a few clicks.

How does it sound? Let's call moving a line up as ё, if we need to move a line or a block of lines 5 lines higher (after all, all commands have a repetition prefix), then how will differ fromxd+5k+P? Only 3 more clicks? And what about other movements? In order to reduce the command by 3 characters, should we add and learn new ones for all commands (up/down, jump to, etc., there are thousands of them)? After all, the template is simple xd+jump+P.

So I have a counter question: How to set the command so that it would be possible to pass quantity to the desired sub-command? `xd`+N{cmd}+`P` - where N is the quantity parameter? Is there any parameterization anywhere?

Line-by-line movement is rather a slow "enjoyment" of the process of contemplation - how a line, like something physical, overcomes obstacles in the form of other lines.

Why does the desire to move lines arise at all? We must return to those first moments when some meaningful need made using this command a pleasant way out of the situation, which later resulted in a bad habit. I suspect that this is a limitation in buffers (registers). I judge by myself. As I did in ordinary clumsy editors (where pressing the same keys is a necessity), in order not to lose the contents of the only one buffer (system), I moved the text with the mouse. And in this case, how?

How to move a block of text without touching the default buffer? Let's complicate the formula xd+jump+P => x"ld+jump+"lP, where l is a buffer/register for lines. And the command has acquired 4 more keystrokes. For developers, the issue of parameterization can be clarified here, to the concept of a "decorator" of commands.

There are three blocks in this design:

Let's say we define the decorator through #, (we can even choose different ones, like registers, or from the register), in which we specified "ld and "lP. Then the clicks will be like this: 2x + #5j or # + 2x + 5j + Ctrl+# (turn off the decorator).

Depending on the behavior of the decorator:

This design, as a "command decorator", can solve not only the author's problems, but also many others, and at the same time there will be no need to write many plugins. I think this is also connected with why Kakuone appeared. The inconvenience of the order of pressing keys led to its appearance and resulted in such a wonderful thing as Helix.

I would like to draw attention to how a structure of several cursors is perceived. Moving several cursors can be implemented in different ways:

What does dependent behavior mean? When there is only one cursor, it stops moving at the boundaries, for example, at the beginning/end of the file. So it is visually perceived that it does not move, it is stuck, but does not "move" in place. If there are several cursors, then the "stuck" property can be extended to all at once, as a common single solid structure. Their relative position always remains the same. It would be good if there was a cursor behavior switch: through <-> relative.

Very often, I would say, less than always, this lack of integrity is very annoying, if you make a mistake in your actions, then the cursors are connected/merged and sometimes you have to start the work from the beginning. After all, there is no such command u, but not for actions, but for cursor movements. After all, there isn't, right? But these two things are very much lacking.

And here are two problems:

I would approach the logical problem in the same way (at the same time remembering TreeSitter): you need to move to the next place available for this structure, even if it is a hundred lines higher/lower. But from a software point of view, this is even worse, and visually it is difficult to predict: 5j will turn into 105j. Especially if there are thousands of cursors, which I sometimes abuse.

But this is not necessary. When working with tabular text, basically moving a lot of single cursors. In order not to remember in your head that you cut 57 elements, and not to lose the 57 cursors that have already been created, you move them entirely to the desired place up/down, having previously parked them at the beginning of the line, position the column (not linearly) and insert. And in this movement you are afraid of losing these cursors by accidental pressing and are ready to tolerate the slow work of the editor, for the sake of safety.


To summarize. What should be considered for implementation:

PS: Sorry for my English.