3

In a script, I want to delete a rectangular block of text. For example, I want to convert this:

ABCD 
EFGH
IJKL
MNOP

into this:

ABCD
EH
IL
MNOP

One way to do this is with visual block mode. Move the cursor to "F", enter visual block mode with Ctrl+V, move the cursor to "K", and press d.

However, this has the side effect of changing the value of the '< and '> marks. It there a way to avoid doing this? I want my Vimscript function to have as few side-effects as possible, other than deleting the desired text.

I wonder if there is a way to restore the '< and '> marks to the value they had before my function was called. Or, alternatively, if there is a way to delete a rectangular selection without entering visual-block mode in the first place.

3
  • 2
    I’m fairly confident there’s a way to save and restore marks. On a nix, you could use cut or similar commands to do the processing... or you could some sort of line-based command like :s/..\zs.*// to keep only the first two characters in the line (use a range to control which lines)... many options. I’ll try to write an answer when i find out about the marks.
    – D. Ben Knoble
    Jul 8, 2020 at 0:38
  • I would just delete it without using rectangular selection using simple loop over lines of text. vpaste.net/GDVbS
    – Maxim Kim
    Jul 8, 2020 at 8:03
  • I have missed you want to delete 2 chars in the middle: vpaste.net/DkXJR
    – Maxim Kim
    Jul 8, 2020 at 8:09

2 Answers 2

2

You can use getpos() to save those marks.

You could restore them using straight setpos() on "'<" and "'>" directly, but that might not be enough, since you're not restoring the Visual mode you were in, so using a command such as gv will restore the initial selection but use blockwise visual mode (assuming that's what you used in the interim.)

One possible solution to this is to also save the visualmode() and then use that (inside a :normal!) to reproduce the original selection.

You might also want to use winsaveview() and winrestview() to restore the cursor location and window position as well.

Putting it all together:

function! PerformOperation()
    let save_view = winsaveview()
    let save_mode = visualmode()
    let save_start = getpos("'<")
    let save_end = getpos("'>")
    " Perform an operation here,
    " possibly using visual mode.
    " Then restore original selection.
    call setpos('.', save_start)
    execute "normal!" save_mode
    call setpos('.', save_end)
    execute "normal! \<Esc>"
    call winrestview(save_view)
endfunction

After this operation, using gv should be able to restore the previous visual selection correctly. (Assuming your operation in the middle of the function didn't affect the lines and columns in the selection, otherwise it might be off, but I don't think you can do much about that particular case...)

2

A couple of other non-visual options:

[range]!cut -c1,4-

Removes the 2nd and 3rd characters of each line in range using cut(1) (*nix only).

[range]substitute/^.\zs..//

Does the same, but using the :substitute command instead.

There is a solution using getbufline and setbufline in a loop or with map, but it's a bit nasty and not worth it when these will do.

By avoiding visual mode, we don't worry about screwing up the marks or the mode of the last visual mode (regular, linewise, blockwise, etc.).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.