3

How can I indent lines selected in Visual mode using space granularity instead of than TABs (i.e. < or >)?

Sometimes when coding Python, I need to indent lines to a level that is not a TAB boundary.

Selecting the lines by pressing V to enter Visual mode and then using < or > to indent them doesn't let me indent the lines to the anticipated level.

Any ideas?

Update:

Suppose after some edits, the dots ('.') are not aligned, which they must be in order to satisfy the Flake8 Python linter:

    # Flake8: All dots ('.') should be aligned over each other
    proj_object = self.session.query(Project) \
                            .filter(Project.id == parsed_proj.id) \
                            .one_or_none()

In particular, all dots must have the same indentation as the topmost dot, which is not on a TAB boundary. It's very cumbersome to manually ident each line individually using SPACEs... I'd like to indent all lines in Visual mode at once using SPACEs (not TABs):

    # Flake8: No complains
    proj_object = self.session.query(Project) \
                              .filter(Project.id == parsed_proj.id) \
                              .one_or_none()

NB: I have looked at expandtab, but that doesn't really suit my needs.

6
  • 1
    @Biggybi was right in his comment expandtab sounds like what you are looking for. Could you explain why that doesn't suit your needs? It would be nice that you edit your question with a snippet of code and what you actually want to do with it otherwise it will be hard to help you.
    – statox
    Commented Jul 22, 2020 at 11:35
  • 1
    expandtab + shiftwidth + softtabstop sounds like what you need
    – D. Ben Knoble
    Commented Jul 22, 2020 at 11:41
  • Please see my update with example code
    – Shuzheng
    Commented Jul 22, 2020 at 11:44
  • @statox I removed my comment because I had a feeling that the indentation would not match a whole tab length. After thoughts, it seems to me that it would be the role of autoindent and smartindent or maybe the lsp for python to handle this gracefully.
    – Biggybi
    Commented Jul 22, 2020 at 13:23
  • 1
    OP, you might just want to run black or similar on save (I do this via ALE for python)—works wonders for me.
    – D. Ben Knoble
    Commented Jul 22, 2020 at 15:06

3 Answers 3

4

A working, hacky solution

function! Visual_indent_with_space() range abort
  '<,'>g/./exe "normal! " v:count1 . "I "
endfunction
vnoremap <leader><space> :call Visual_indent_with_space()<cr>

Breaking down: '<,'>g/./exe "normal! " v:count1 . "I "

  • '<,'>: use the selected range
  • g/./: apply a global command to each selected line (. matches each line)
  • exe "normal! " execute a normal command
  • v:count1 . "I " insert v:count1 spaces at the beginning of these lines

From the :h v:count and :h v:count1`:

v:count     The count given for the last Normal mode command.  Can be used
            to get the count before a mapping.  Read-only.
v:count1    Just like "v:count", but defaults to one when no count is
            used.

Also see:

  • :h :global
  • :h :range
  • :h :execute

An elegant solution that does not work as expected

This would be an elegant solution, but it only works on the first line.

Type [N]<leader><space> to insert [N] spaces at the beginning of selected lines.

vnoremap <leader><space> @='I <C-V><Esc>'<CR>
  • @: execute the content of the register
  • =: use the expression register
  • '': boundaries of the content to load in the expression register
  • I : insert a space at the start of the line
  • <c-v><esc>: input a <escape character (<c-v> escapes the character)
  • <cr>: validate the command

Not an elegant solution

With this, you can select your lines and hit the mapping to insert a space at the beginning of the selected lines:

vnoremap <leader><space> :norm I<space><cr>gv
  • :norm: start a normal command (i.e. like in normal mode) from command-line
  • I: start inserting at the beginning of the line.
  • <space>: insert a space
  • <cr>: validate the command
  • gv: go back to visual mode (so you can repeat the process)

If your leader is space, you can mash space to insert as many as you want.

If it is not, you could map with <space><space>:

vnoremap <space><space> :norm I<space><cr>gv
8
  • Don't you mean I can insert a SPACE using that mapping? What does :norm and I do?
    – Shuzheng
    Commented Jul 22, 2020 at 13:23
  • Indeed, I'm editing the post for more clarity.
    – Biggybi
    Commented Jul 22, 2020 at 13:24
  • I love your solution. Would it be possible to supply a count, i.e. [N]<leader><space>? I don't have sufficient Vim scripting skills to accomplish that.
    – Shuzheng
    Commented Jul 22, 2020 at 13:27
  • @Shuzheng I got it!
    – Biggybi
    Commented Jul 22, 2020 at 15:39
  • Thank you, awesome work! Why do you call it a hacky solution? Also, why does v:count work? It gets the count for the last Normal mode command, but in this case you are executing a Visual mode command? By “command-line” you mean Ex mode, right?
    – Shuzheng
    Commented Jul 22, 2020 at 16:40
1

I just do column selection + copy for such unaligned movement of my code.

So... first I have expandtab (et) as part of my settings at the bottom of my file like so:

// vim: ts=4 sw=4 et

This means the tabstop is at 4 characters, the shiftwidth is also at 4 characters, and expandtab is also turned on (so no tabs anywhere, just spaces).

When I have a problem like yours above, so this code:

proj_object = self.session.query(Project) \
                        .filter(Project.id == parsed_proj.id) \
                        .one_or_none()

I go under the first line and select using Ctrl-V (column select), in this case go down once, do y to yank that column and then p to copy the column. I repeat the p until the alignment is correct (twice in your example).

Here is a screenshot showing the selection of the column:

enter image description here

As long as all the characters before the . are spaces, the Ctrl-V can happen at any location.

Note: when the selection is rather large, I use my mouse. Although it's possible to enter the column selection with the mouse, it require a quadruple click which I find annoying. However, with just one left click and selection, then Ctrl-V problem solved! You can actually use v and Ctrl-V any number of times to switch between the two selection modes (and V too [capital], which is used to select whole lines).

3
  • 1
    I think you mean y to yank and not Y
    – D. Ben Knoble
    Commented Jul 23, 2020 at 12:14
  • 1
    This is indeed the way to go if you don't want any config. Besides, I think you could use a motion instead of the mouse, in the case of large selection. <c-d> could be a solution, or } if you happen to have an empty line after the block, or even better: /^[^ ] (find next line starting with a character that is not a space), which you could map to something!
    – Biggybi
    Commented Jul 23, 2020 at 18:49
  • 1
    Yes, indeed, you can use any kind of movement include a search. When I have an array, for example, which I have aligned, I can use [] to reach the }; at the end. (I do mostly C++). Commented Jul 24, 2020 at 7:40
0

Three steps to success:

  1. Ctrl + V and select the lines you want to add indentation to using arrow keys,
  2. Shift + i and add the number of spaces require to align the first line,
  3. Hit ESC and watch the magic happen!!!
2
  • Welcome to Vi and Vim! <C-v> is visual block (no shift needed). Also you can use ` backticks for code formatting, or <kbd> tags if you want to highlight a key like <kbd>Ctrl</kbd>-<kbd>v</kbd>. Feel free to edit :)
    – D. Ben Knoble
    Commented Jul 28, 2020 at 21:07
  • Thanks! I don't know why I kept using shift :P
    – Canute S
    Commented Jul 29, 2020 at 5:24

Your Answer

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

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