echasnovski / mini.nvim

Library of 40+ independent Lua modules improving overall Neovim (version 0.8 and higher) experience with minimal effort
MIT License
4.74k stars 179 forks source link

mini.move: less intrusive blockwise movement #838

Closed matu3ba closed 2 weeks ago

matu3ba commented 4 months ago

Contributing guidelines

Module(s)

mini.move

Description

Use case Drawing block selections up and down breaks right-wards indentation, which can make virtual edits for adjusting graphics unnecessary painful. I hope below drawings show the problem. Any kind of ideas would be appreciated.

Problem Description

got

                           ┌──────────────┐
                           │              │
                           │              │
          ┌────────────┐   │              │
          │    upsii   │   │              │
          │     123    │   │              │
          │            │   └──────────────┘
          │            │
          │            │
          └────────────┘

want without redundant steps 1 box selection copy 2 selection replace whitespace 3 selection wanted position and paste

                           ┌──────────────┐
                           │              │
                           │              │
          ┌────────────┐   │              │
          │            │   │              │
          │            │   │              │
          │    upsii   │   └──────────────┘
          │     123    │
          │            │
          └────────────┘

current behavior: 1 line selection movements dont work for multiple lines 2 minimove.move_selection up/down breaks right-hand intendation

                           ┌──────────────┐
                           │              │
                           │              │
          ┌────────────┐   │              │
          │       │   │              │
          │       │   │              │
          │    upsii        │   └──────────────┘
          │     123         │
          │            │
          └────────────┘

It would be nice, if move_selection would have another mode so that a move is an actual swap of text elements freely into the buffer positions, so moving 1 upwards does

                           ┌──────────────┐
                           │              │
                           │              │
          ┌────upsii───┐   │              │
          │     123    │   │              │
          │    ─────   │   │              │
          │            │   └──────────────┘
          │            │
          │            │
          └────────────┘

and moving 1 more upwards does

                           ┌──────────────┐
                           │              │
               upsii       │              │
          ┌──── 123 ───┐   │              │
          │    ─────   │   │              │
          │            │   │              │
          │            │   └──────────────┘
          │            │
          │            │
          └────────────┘

until

               upsii       ┌──────────────┐
                123        │              │
                           │              │
          ┌────────────┐   │              │
          │            │   │              │
          │            │   │              │
          │            │   └──────────────┘
          │            │
          │            │
          └────────────┘

with the internal position of the collision range be kept. To me it looks like you have implemented collision detection, but nothing to keep track of correct fixups after nothig collides. For example, left and right movements have the same behavior:

                           ┌──────────────┐
                           │              │
                           │              │
          ┌────────────┐   │              │
  upsii        │       │   │              │
   123         │       │   │              │
          │            │   └──────────────┘
          │            │
          │            │
          └────────────┘
```lua local selmove_hint = [[ Arrow^^^^^^ ^ ^ _k_ ^ ^ _h_ ^ ^ _l_ ^ ^ _j_ ^ ^ __ ]] local ok_minimove, minimove = pcall(require, 'mini.move') assert(ok_minimove) if ok_minimove == true then local opts = { mappings = { left = '', right = '', down = '', up = '', line_left = '', line_right = '', line_down = '', line_up = '', }, } minimove.setup(opts) -- setup here prevents needless global vars for opts required by `move_selection()/moveline()` M.minimove_box_hydra = Hydra { name = 'Move Box Selection', hint = selmove_hint, config = { color = 'pink', invoke_on_body = true, }, mode = 'v', body = 'vb', heads = { { 'h', function() minimove.move_selection('left', opts) end, }, { 'j', function() minimove.move_selection('down', opts) end, }, { 'k', function() minimove.move_selection('up', opts) end, }, { 'l', function() minimove.move_selection('right', opts) end, }, { '', nil, { exit = true } }, }, } M.minimove_line_hydra = Hydra { name = 'Move Line Selection', hint = selmove_hint, config = { color = 'pink', invoke_on_body = true, }, mode = 'n', body = 'vl', heads = { { 'h', function() minimove.move_line('left', opts) end, }, { 'j', function() minimove.move_line('down', opts) end, }, { 'k', function() minimove.move_line('up', opts) end, }, { 'l', function() minimove.move_line('right', opts) end, }, { '', nil, { exit = true } }, }, } end ```
echasnovski commented 4 months ago

Thanks for the suggestion!

I can reproduce. At first glance, it is indeed not a very good behavior. However, blockwise movement is kind of lesser tier of support for 'mini.move' because of how complicated it is to implement.

I'll look more into why this happens.

echasnovski commented 2 weeks ago

I've taken another look at this and it seems to work as expected.

It would be nice, if move_selection would have another mode so that a move is an actual swap of text elements freely into the buffer positions, so moving 1 upwards does

This is the core of the issue: 'mini.move' actually moves selection/line, not swaps. It may look like swapping when moving to the nearest neighboor (i.e. with v:count 1), but it actually is not. Here moving is more or less equivalent to "cut current selection -> move to target position -> paste selection". And provided diagrams show exactly this behavior.

My best suggestion is to use exchange gx operator from 'mini.operators'. So in this case it would be something like:

Closing as not planned.