0

I have the following function that inserts tabs if cursor is at the start of a line and required amount of spaces otherwise.

function! SpecialTab() abort
    if (col('.') == 1) || (matchstr(getline('.'), '\%'.(col('.') - 1).'c.') =~ '\t')
        return "\<Tab>"
    else
        return repeat("\<Space>", (&tabstop - (virtcol('.') % &tabstop) + 1))
    endif
endfunction
inoremap <expr> <Tab> SpecialTab()

If you use :set backspace=indent,eol,start with :set expandtab, one backspace can delete a tabs worth of spaces if you originally inserted a tabs worth of spaces by tapping the key in the first place. But if you manually typed required amount of spaces, one backspace just deletes one space.

I want to get that with my function. I don't have enough experience to understand C source code and I don't understand how backspace characteristics is maintained. Any help would be appreciated.

Edit

I have just checked that vim actually doesn't check whether the spaces were inserted with pressing <Tab> or <Space>. It just behaves that way until you escape from the insert mode. After you have escaped from insert mode and returned back to insert mode, if you try to use <BS> you'll see that it is deleting &tabstop amount (i.e. assuming the spaces are integer multiple of &tabstop) of spaces no matter how you inserted it.

  • An alternative is to turn on expandtab, return \<c-v>\<tab> instead of \<tab> . But i'm afread that will create a new series of problem. – dedowsdi Apr 9 at 5:51
  • Do you mean turn on et in the function? How would that help? Tabs are not the problem here, I can delete one tab (visualizing :h ts worth of spaces) with one backspace key in insert mode. My problem is with deleting tabstop worth of spaces when pressing backspace in insert mode. – klaus Apr 9 at 6:03
  • Does setting shiftwidth to the value of tabstop help? What about if you turn on shiftround? What if you use i_CTRL-T and i_CTRL-D instead? – D. Ben Knoble Apr 9 at 6:18
  • What if you set smarttab ? – D. Ben Knoble Apr 9 at 6:21
  • 1
    I did some experiment, with non indention space. During inserting, if you backspace after a series of tab and space (mixed), vim only check the last key stroke, if it's tab, it delete softtabstop number of spaces, other wise only 1 space deleted. If you backspace immediately after enter insert mode, vim delete softtabstop number of spaces. I don't believe vim knows if existing space are created by tab or space, it's a bit crazy. – dedowsdi Apr 9 at 8:21
0

We discuss only non indention space here.

During inserting, if you backspace after a series of tab and space (mixed), vim only check the last key stroke, if it's tab, it delete softtabstop number of spaces, other wise only 1 space deleted. If you backspace immediately after enter insert mode, vim delete softtabstop number of spaces. I don't believe vim knows if existing space are created by tab or space, it's a bit crazy.

Your backspace behaves different with default because you send 4 space to vim instead of a real tab. To fix that, you must fake a tab by adding an extra tab and backspace after 4 spaces, so vim will believe it's a tab, not space.

" vim: tabstop=4 shiftwidth=4 softtabstop=4 smarttab shiftround noexpandtab noet list listchars=tab\:+.,space\:-

function! SpecialTab() abort
    if (col('.') == 1) || (matchstr(getline('.'), '\%'.(col('.') - 1).'c.') =~ '\t')
        return "\<Tab>"
    else
        return repeat("\<Space>", (&tabstop - (virtcol('.') % &tabstop) + 1)) . "\<Tab>\<Backspace>"
    endif
endfunction
inoremap <expr> <Tab> SpecialTab()

update

I missed something, it's not working.

update

for unknow resason send \<space>\<space>\<space>\<space>\<tab>\<backspace> will insert a real tab. I have no idea why it behaves like this. But if i send \<space>\<space>\<space>\<space>a\<tab>\<backspace>\<backspace>, it works.

" vim: tabstop=4 shiftwidth=4 softtabstop=4 smarttab shiftround noexpandtab noet list listchars=tab\:+.,space\:-

function! SpecialTab() abort
    if (col('.') == 1) || (matchstr(getline('.'), '\%'.(col('.') - 1).'c.') =~ '\t')
        return "\<Tab>"
    else
        return repeat("\<Space>", (&tabstop - (virtcol('.') % &tabstop) + 1)) . "a\<tab>\<backspace>\<backspace>"
    endif
endfunction
inoremap <expr> <Tab> SpecialTab()

update

check comment for the reason why \<space>\<space>\<space>\<space>\<tab>\<backspace> result in a tab.

  • You function doesn't return space in the else clause. I have copy-pasted it. It just inserts tab characters everywhere. – klaus Apr 9 at 9:13
  • 1
    I think it's because "\<Tab>" converts the previous loose spaces into tabs for some reason. I checked that with vanilla vim and :set noet while I tried to insert Tab after >--->---" hello++ and the text converted to >---->----" hello>->----. Here, >--- are tabs and + are spaces. – klaus Apr 9 at 9:34
  • 1
    fixed. that make sense. – dedowsdi Apr 9 at 9:46
  • Hmm. This works as expected. But it's unusual and I would have liked to know why it works like this! – klaus Apr 9 at 9:53
  • 1
    I'm afraid esc changes dot behavior. – dedowsdi Apr 9 at 9:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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