113

I've some codes copied from the Internet that have 2-space indenting and I want to change it into 4-space indenting. I wonder if there is a short vim routine to accomplish the task without having to write vim script? Here is how I'm currently doing it with an HTML file:

  • Record a macro
  • Go to the beginning of a line
  • Visual select all whitespaces until the first occurrence of "<"
  • Yank and paste all whitespaces (basically to double them)
  • Replay the macro till the end of the file

In short qa0vt<yp<esc>jq

Pitfalls:

The macro fails for a blank line or a line that doesn't start with "<". And I have no idea how to extend this solution to non-HTML file.

Community
  • 1
  • 1
Lim H.
  • 9,870
  • 9
  • 48
  • 74

8 Answers8

197

A general way of changing the indent is by changing the tabstop:

Paste your file into an empty buffer, then:

:set ts=2 sts=2 noet
:retab!

This changes every 2 spaces to a TAB character, then:

:set ts=4 sts=4 et
:retab

This changes every TAB to 4 spaces.

The advantage of this method is that you can also use it the other way around, to convert from 4 to 2 spaces for example.

Daan Bakker
  • 6,122
  • 3
  • 24
  • 23
  • 20
    @XavierT.I actually think this answer is pretty easy to understand. And maybe it will be more clear if the short command name is replace with their full name: `ts` := `tabstop`, `sts` := `softtabstop` and `[no]et` := `[no]expandtab`. – YaOzI Jun 01 '14 at 08:28
  • In what situation would `retab`'s argument be useful, and why can't we use it for this task? – joeytwiddle Mar 11 '16 at 10:25
  • @joeytwiddle As I understand it, you *could* use that and achieve the same effect as this answer by using `:set sts=2 noet` and `:retab! 2` so it would be a bit shorter (albeit perhaps less readable) that way. – Daan Bakker Mar 12 '16 at 23:47
  • This method also changes 2-spaces to 4-spaces within string literals and other places, rather than just the start of the line. – nacitar sevaht Mar 29 '16 at 14:25
  • 3
    Put them all together: `:set ts=2 sts=2 noet | retab! | set ts=4 sts=4 et | retab!` and then use `@:` in different files to quickly apply the same action. – L__ Jun 02 '16 at 16:33
  • I had to reverse the commands since I had to change from 4 space to 2 spaces. – SenG Nov 12 '17 at 12:54
  • Also see https://stackoverflow.com/a/15971630/414125 for how to copy the result to the clipboard – Leo Jan 20 '20 at 02:08
69

It may be possible with :set shiftwidth=4 and gg=G.

perreal
  • 94,503
  • 21
  • 155
  • 181
  • Note that if this doesn't work, you may have a mode enabled that has some other influence on indentation. – Dav Clark Jun 03 '13 at 00:47
  • 1
    Although the question isn't about a general solution, please note that this doesn't work always, specially with python code. – 0xc0de Dec 29 '13 at 20:59
  • 8
    What does `gg=G` mean exatly? I understand that `gg` means "move to 1,1", and `G` means "move to last line". – Jonathon Reinhart Oct 20 '15 at 02:26
  • 8
    @JonathonReinhart `=` does indenting, so it just means to indent from start to end of text – alex Dec 14 '15 at 15:03
  • worked better than `%retab`, which incorrectly converted spaces embedded inside string literals. – Alnitak May 12 '19 at 13:43
40

What I do is very similar to esneider and cforbish's approaches, but a bit quicker to type:

:%s/^\s*/&&

Simply replaces leading space (spaces or tabs) with twice as much leading space (& is substituted with the matched expression).

spro
  • 1,553
  • 2
  • 14
  • 17
7

This is a very old question, however all the answers are ... wrong ... Vim has a very easy way to reindent the entire file. I learned this after writing my own function to do it, so I'm in the same ignorance boat ;)

type

gg=G

this is assuming that you have your tabstop set to what you like, (so for the OP it would be ts=4)

I learned this from http://vim.wikia.com/wiki/Fix_indentation , which mentions

In normal mode, typing gg=G will reindent the entire file. This is a special case; = is an operator. Just like d or y, it will act on any text that you move over with a cursor motion command. In this case, gg positions the cursor on the first line, then =G re-indents from the current cursor position to the end of the buffer.

Chase Vasic
  • 91
  • 1
  • 1
  • 3
    1. If you scroll down all the way to the bottom, you will see an answer mentioning gg=G. 2. It doesn't always work. – Lim H. Feb 19 '15 at 05:47
3

I used this regular expression (it doubles the number of leading spaces):

%s;^\(\s\+\);\=repeat(' ', len(submatch(0))*2);g
cforbish
  • 8,567
  • 3
  • 28
  • 32
  • doesn't `%s/\s\+/&&/` do the same thing? Btw, I think the `g` flag is unnecessary (for one, there would be only 1 match with `^pattern` in every line, no?) – doubleDown Jun 03 '13 at 17:44
  • Thanks for pointing out '&'. I have a slight correction to your's mainly because you forgot the '^' `%s/^\s\+/&&/g` is closer. – cforbish Jun 04 '13 at 02:25
  • 1
    This is similar to spro's answer but I appreciate the answer as it allows you to tweek it easier if you need to do something slightly more complicated. – stephenmm May 11 '15 at 20:48
1

Similar (but somewhat simpler) to cforbish's answer, this regex will duplicate the leading spaces

:%s/^\( \+\)/\1\1

Or you can use this other regex to transform 2-spaces into 4-spaces, preserving single spaces (and odd amounts in general)

:%s/^\(\(  \)\+\)/\1\1

That is,

  • 1 space ⇢ 1 space
  • 2 spaces ⇢ 4 spaces
  • 3 spaces ⇢ 5 spaces
  • 4 spaces ⇢ 8 spaces
esneider
  • 589
  • 4
  • 12
1

In addition to @spro's answer, I put this in my .vimrc

command! -range=% Format :<line1>,<line2>s/^\s*/&&

Just type :Format.

With visual selection, this only formats the selected lines.

Without visual selection, this formats the whole file.

yihui.dev
  • 602
  • 8
  • 10
0

This is a variant of the regex based answers.

I have a bash script in my local bin directory that will double the amount of whitespace at the start of a line. Input can be stdin or a file:

$ cat ~/bin/dblsp
#!/bin/bash

file=${1--}

while IFS= read -r line; do
    echo "$line" | sed 's/\s*/&&/'
done < <(cat -- "$file")

I use this within vim by visually selecting a line and issuing the following command:

:'<,'>!dblsp

This saves me the need to type (or remember) the regex.

I also use it in maps like the following:

nnoremap <leader>] `[V`]!dblsp<CR>

which will apply it to a block of recently pasted text. I usually use the following map to paste rather than :set paste

nnoremap <leader>p :r !xclip -o<CR>

My usual workflow is:

  • select code snippet (eg the example code on this page is 2 spaces but I want 4)
  • paste code snippet (,p)
  • change spacing (,])

or simply changing the indent on yanked blocks pasted from another buffer.

htaccess
  • 2,800
  • 26
  • 31