Martin Drlík
Martin Drlík

Reputation: 1454

How to convert leading spaces to tabs?

Many people use spaces rather than tabs. I use both of them. Tabs at the beginning of line and spaces from the first non-whitespace character. No problem for starting new document and in case I have to modify one better adapt to using format. Still sometimes I need to fix the spaces issue anyway.

According to Search and replace I can just do :%s/spaces_for_tab/tab/g. It is simple and it will work for many cases. Anyway I want to refactor only spaces at the beginning of line.

Upvotes: 5

Views: 6326

Answers (4)

Mike Armstrong
Mike Armstrong

Reputation: 1

David's response is very elegant but it doesn't address leading whitespace that has a mixture of tabs and spaces. For example to convert a line like:

<SPACE><SPACE><TAB>something...

you have to know the position of the tab to determine the number of spaces needed to replace the <TAB> and reach the next tabstop. My solution below, although not as compact as David's, addresses this. It also allows me to select which way to use leading whitespace without depending upon &expandtab. I would appreciate any suggestions to improve my code...

function! TabsToSpaces(...)
  let ts = &tabstop
  let pos = getpos('.')
  while search('^ *\zs\t', "w") != 0
    let l:curpos = getcharpos('.')
    " The number of spaces needed depends upon the position of the <TAB>
    let numsp = 1 + ts - ( curpos[2] % ts )
    if numsp == 9
      let numsp = 1
    endif
    silent execute ':s/^ *\zs\t/'.repeat(' ', numsp).'/'
  endwhile
  if a:0 == 0
    echo 'Changed leading tabs to spaces'
  endif
  call setpos('.', pos)
endfunction

function! SpacesToTabs(...)
  let ts = &tabstop
  let pos = getpos('.')
  " First normalize all tabs to spaces
  call TabsToSpaces("quiet")
  while search('^\t* \{'.ts.'}') != 0
    silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
  endwhile
  if a:0 == 0
    echo 'Changed leading spaces to tabs'
  endif
  call setpos('.', pos)                                                          
endfunction                                                                      

" Some keystrokes to implement the spaces/tabs functions
nmap <Leader>st :execute SpacesToTabs()<CR>
nmap <Leader>ts :execute TabsToSpaces()<CR>

Upvotes: 0

David Sanders
David Sanders

Reputation: 4119

I took Martin's answer and improved on it a bit if anyone's interested:

function Fixspaces()
  let ts = &tabstop
  let pos = getpos('.')

  if &expandtab
    while search('^ *\t') != 0
      silent execute ':%s/^ *\zs\t/'.repeat(' ', ts).'/g'
    endwhile

    echo 'Changed tabs to spaces'
  else
    while search('^\t* \{'.ts.'}') != 0
      silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
    endwhile

    echo 'Changed spaces to tabs'
  endif

  call setpos('.', pos)
endfunction

This function does the appropriate thing depending on the values of the expandtab and tabstop settings and also remembers where the cursor is.

Upvotes: -1

Martin Drl&#237;k
Martin Drl&#237;k

Reputation: 1454

I've written a simple func for it. Anyway it will work only for 4-space tab.

fu! Fixspaces()
        while search('^\t* \{4}') != 0
                execute ':%s/^\t*\zs \{4}/\t/g'
        endwhile
endfu

You can suggest better solution, if exists, and I will use it with pleasure. The issue is that this func replaces spaces in strings as well.

Upvotes: 2

Jo So
Jo So

Reputation: 26501

This is more of a regex issue. To anchor at the beginning of the line, use the caret, e.g.

s/^        /\t/

Or do it using vim's builtin functionality:

:set tabstop=4  "four spaces will make up for one tab
:set noexpandtab  "tell vim to keep tabs instead of inserting spaces
:retab            "let vim handle your case

By the way, I too prefer tabs for indentation and spaces for alignment. Unfortunately, vim doesn't handle this well (and I don't know what other editors do), so I mostly use :set expandtab (maybe see :set softtabstop).

Upvotes: 6

Related Questions