patch 8.1.0105: all tab stops are the same

Problem:    All tab stops are the same.
Solution:   Add the variable tabstop feature. (Christian Brabandt,
            closes #2711)
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 6a0126b..94e527b 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -147,8 +147,8 @@
 	    test_perl.res \
 	    test_plus_arg_edit.res \
 	    test_preview.res \
-	    test_prompt_buffer.res \
 	    test_profile.res \
+	    test_prompt_buffer.res \
 	    test_python2.res \
 	    test_python3.res \
 	    test_pyx2.res \
@@ -180,6 +180,7 @@
 	    test_undo.res \
 	    test_user_func.res \
 	    test_usercommands.res \
+	    test_vartabs.res \
 	    test_viminfo.res \
 	    test_vimscript.res \
 	    test_visual.res \
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index 9d12ea0..a742153 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -136,6 +136,8 @@
       \ 'toolbariconsize': [['', 'tiny', 'huge'], ['xxx']],
       \ 'ttymouse': [['', 'xterm'], ['xxx']],
       \ 'ttytype': [[], []],
+      \ 'varsofttabstop': [['8', '4,8,16,32'], ['xxx', '-1', '4,-1,20']],
+      \ 'vartabstop': [['8', '4,8,16,32'], ['xxx', '-1', '4,-1,20']],
       \ 'viewoptions': [['', 'cursor', 'unix,slash'], ['xxx']],
       \ 'viminfo': [['', '''50', '"30'], ['xxx']],
       \ 'virtualedit': [['', 'all', 'all,block'], ['xxx']],
diff --git a/src/testdir/test_breakindent.vim b/src/testdir/test_breakindent.vim
index 7deffbe..ae05479 100644
--- a/src/testdir/test_breakindent.vim
+++ b/src/testdir/test_breakindent.vim
@@ -12,135 +12,267 @@
 
 let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
 
-function s:screen_lines(lnum, width) abort
+func s:screen_lines(lnum, width) abort
   return ScreenLines([a:lnum, a:lnum + 2], a:width)
-endfunction
+endfunc
 
-function! s:compare_lines(expect, actual)
+func s:compare_lines(expect, actual)
   call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
-endfunction
+endfunc
 
-function s:test_windows(...)
+func s:test_windows(...)
   call NewWindow(10, 20)
   setl ts=4 sw=4 sts=4 breakindent
   put =s:input
   exe get(a:000, 0, '')
-endfunction
+endfunc
 
-function s:close_windows(...)
+func s:close_windows(...)
   call CloseWindow()
   exe get(a:000, 0, '')
-endfunction
+endfunc
 
-function Test_breakindent01()
+func Test_breakindent01()
   " simple breakindent test
   call s:test_windows('setl briopt=min:0')
-  let lines=s:screen_lines(line('.'),8)
-  let expect=[
-\ "    abcd",
-\ "    qrst",
-\ "    GHIJ",
-\ ]
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "    qrst",
+	\ "    GHIJ",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows()
-endfunction
+endfunc
 
-function Test_breakindent02()
+func Test_breakindent01_vartabs()
+  " like 01 but with vartabs feature
+  if !has("vartabs")
+    return
+  endif
+  call s:test_windows('setl briopt=min:0 vts=4')
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "    qrst",
+	\ "    GHIJ",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent02()
   " simple breakindent test with showbreak set
   call s:test_windows('setl briopt=min:0 sbr=>>')
-  let lines=s:screen_lines(line('.'),8)
-  let expect=[
-\ "    abcd",
-\ "    >>qr",
-\ "    >>EF",
-\ ]
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "    >>qr",
+	\ "    >>EF",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows('set sbr=')
-endfunction
+endfunc
 
-function Test_breakindent03()
+func Test_breakindent02_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " simple breakindent test with showbreak set
+  call s:test_windows('setl briopt=min:0 sbr=>> vts=4')
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "    >>qr",
+	\ "    >>EF",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent03()
   " simple breakindent test with showbreak set and briopt including sbr
   call s:test_windows('setl briopt=sbr,min:0 sbr=++')
-  let lines=s:screen_lines(line('.'),8)
-  let expect=[
-\ "    abcd",
-\ "++  qrst",
-\ "++  GHIJ",
-\ ]
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "++  qrst",
+	\ "++  GHIJ",
+	\ ]
   call s:compare_lines(expect, lines)
   " clean up
   call s:close_windows('set sbr=')
-endfunction
+endfunc
 
-function Test_breakindent04()
+func Test_breakindent03_vartabs()
+  " simple breakindent test with showbreak set and briopt including sbr
+  if !has("vartabs")
+    return
+  endif
+  call s:test_windows('setl briopt=sbr,min:0 sbr=++ vts=4')
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "++  qrst",
+	\ "++  GHIJ",
+	\ ]
+  call s:compare_lines(expect, lines)
+  " clean up
+  call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent04()
   " breakindent set with min width 18
   call s:test_windows('setl sbr= briopt=min:18')
-  let lines=s:screen_lines(line('.'),8)
-  let expect=[
-\ "    abcd",
-\ "  qrstuv",
-\ "  IJKLMN",
-\ ]
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "  qrstuv",
+	\ "  IJKLMN",
+	\ ]
   call s:compare_lines(expect, lines)
   " clean up
   call s:close_windows('set sbr=')
-endfunction
+endfunc
 
-function Test_breakindent05()
+func Test_breakindent04_vartabs()
+  " breakindent set with min width 18
+  if !has("vartabs")
+    return
+  endif
+  call s:test_windows('setl sbr= briopt=min:18 vts=4')
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "  qrstuv",
+	\ "  IJKLMN",
+	\ ]
+  call s:compare_lines(expect, lines)
+  " clean up
+  call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent05()
   " breakindent set and shift by 2
   call s:test_windows('setl briopt=shift:2,min:0')
-  let lines=s:screen_lines(line('.'),8)
-  let expect=[
-\ "    abcd",
-\ "      qr",
-\ "      EF",
-\ ]
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "      qr",
+	\ "      EF",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows()
-endfunction
+endfunc
 
-function Test_breakindent06()
+func Test_breakindent05_vartabs()
+  " breakindent set and shift by 2
+  if !has("vartabs")
+    return
+  endif
+  call s:test_windows('setl briopt=shift:2,min:0 vts=4')
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "      qr",
+	\ "      EF",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent06()
   " breakindent set and shift by -1
   call s:test_windows('setl briopt=shift:-1,min:0')
-  let lines=s:screen_lines(line('.'),8)
-  let expect=[
-\ "    abcd",
-\ "   qrstu",
-\ "   HIJKL",
-\ ]
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "   qrstu",
+	\ "   HIJKL",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows()
-endfunction
+endfunc
 
-function Test_breakindent07()
+func Test_breakindent06_vartabs()
+  " breakindent set and shift by -1
+  if !has("vartabs")
+    return
+  endif
+  call s:test_windows('setl briopt=shift:-1,min:0 vts=4')
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "    abcd",
+	\ "   qrstu",
+	\ "   HIJKL",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent07()
   " breakindent set and shift by 1, Number  set sbr=? and briopt:sbr
   call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n')
-  let lines=s:screen_lines(line('.'),10)
-  let expect=[
-\ "  2     ab",
-\ "?        m",
-\ "?        x",
-\ ]
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2     ab",
+	\ "?        m",
+	\ "?        x",
+	\ ]
   call s:compare_lines(expect, lines)
   " clean up
   call s:close_windows('set sbr= cpo-=n')
-endfunction
+endfunc
 
-function Test_breakindent07a()
+func Test_breakindent07_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " breakindent set and shift by 1, Number  set sbr=? and briopt:sbr
+  call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n vts=4')
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2     ab",
+	\ "?        m",
+	\ "?        x",
+	\ ]
+  call s:compare_lines(expect, lines)
+  " clean up
+  call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
+
+func Test_breakindent07a()
   " breakindent set and shift by 1, Number  set sbr=? and briopt:sbr
   call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4')
-  let lines=s:screen_lines(line('.'),10)
-  let expect=[
-\ "  2     ab",
-\ "    ?    m",
-\ "    ?    x",
-\ ]
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2     ab",
+	\ "    ?    m",
+	\ "    ?    x",
+	\ ]
   call s:compare_lines(expect, lines)
   " clean up
   call s:close_windows('set sbr=')
-endfunction
+endfunc
 
-function Test_breakindent08()
+func Test_breakindent07a_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " breakindent set and shift by 1, Number  set sbr=? and briopt:sbr
+  call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 vts=4')
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2     ab",
+	\ "    ?    m",
+	\ "    ?    x",
+	\ ]
+  call s:compare_lines(expect, lines)
+  " clean up
+  call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent08()
   " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
   call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4')
   " make sure, cache is invalidated!
@@ -148,43 +280,96 @@
   redraw!
   set ts=4
   redraw!
-  let lines=s:screen_lines(line('.'),10)
-  let expect=[
-\ "  2 ^Iabcd",
-\ "#      opq",
-\ "#      BCD",
-\ ]
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2 ^Iabcd",
+	\ "#      opq",
+	\ "#      BCD",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows('set sbr= cpo-=n')
-endfunction
+endfunc
 
-function Test_breakindent08a()
+func Test_breakindent08_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
+  call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4 vts=4')
+  " make sure, cache is invalidated!
+  set ts=8
+  redraw!
+  set ts=4
+  redraw!
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2 ^Iabcd",
+	\ "#      opq",
+	\ "#      BCD",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
+
+func Test_breakindent08a()
   " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
   call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list')
-  let lines=s:screen_lines(line('.'),10)
-  let expect=[
-\ "  2 ^Iabcd",
-\ "    #  opq",
-\ "    #  BCD",
-\ ]
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2 ^Iabcd",
+	\ "    #  opq",
+	\ "    #  BCD",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows('set sbr=')
-endfunction
+endfunc
 
-function Test_breakindent09()
+func Test_breakindent08a_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
+  call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list vts=4')
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2 ^Iabcd",
+	\ "    #  opq",
+	\ "    #  BCD",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent09()
   " breakindent set and shift by 1, Number and list set sbr=#
   call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list')
-  let lines=s:screen_lines(line('.'),10)
-  let expect=[
-\ "  2 ^Iabcd",
-\ "       #op",
-\ "       #AB",
-\ ]
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2 ^Iabcd",
+	\ "       #op",
+	\ "       #AB",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows('set sbr=')
-endfunction
+endfunc
 
-function Test_breakindent10()
+func Test_breakindent09_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " breakindent set and shift by 1, Number and list set sbr=#
+  call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list vts=4')
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2 ^Iabcd",
+	\ "       #op",
+	\ "       #AB",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent10()
   " breakindent set, Number set sbr=~
   call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0')
   " make sure, cache is invalidated!
@@ -192,41 +377,91 @@
   redraw!
   set ts=4
   redraw!
-  let lines=s:screen_lines(line('.'),10)
-  let expect=[
-\ "  2     ab",
-\ "~       mn",
-\ "~       yz",
-\ ]
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2     ab",
+	\ "~       mn",
+	\ "~       yz",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows('set sbr= cpo-=n')
-endfunction
+endfunc
 
-function Test_breakindent11()
+func Test_breakindent10_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " breakindent set, Number set sbr=~
+  call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0 vts=4')
+  " make sure, cache is invalidated!
+  set ts=8
+  redraw!
+  set ts=4
+  redraw!
+  let lines = s:screen_lines(line('.'),10)
+  let expect = [
+	\ "  2     ab",
+	\ "~       mn",
+	\ "~       yz",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
+
+func Test_breakindent11()
   " test strdisplaywidth()
   call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4')
-  let text=getline(2)
+  let text = getline(2)
   let width = strlen(text[1:])+indent(2)+strlen(&sbr)*3 " text wraps 3 times
   call assert_equal(width, strdisplaywidth(text))
   call s:close_windows('set sbr=')
-endfunction
+endfunc
 
-function Test_breakindent12()
+func Test_breakindent11_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " test strdisplaywidth()
+  call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4 vts=4')
+  let text = getline(2)
+  let width = strlen(text[1:])+indent(2)+strlen(&sbr)*3 " text wraps 3 times
+  call assert_equal(width, strdisplaywidth(text))
+  call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent12()
   " test breakindent with long indent
-  let s:input="\t\t\t\t\t{"
+  let s:input = "\t\t\t\t\t{"
   call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>-')
-  let lines=s:screen_lines(2,16)
-  let expect=[
-\ " 2 >--->--->--->",
-\ "          ---{  ",
-\ "~               ",
-\ ]
+  let lines = s:screen_lines(2,16)
+  let expect = [
+	\ " 2 >--->--->--->",
+	\ "          ---{  ",
+	\ "~               ",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows('set nuw=4 listchars=')
-endfunction
+endfunc
 
-function Test_breakindent13()
-  let s:input=""
+func Test_breakindent12_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " test breakindent with long indent
+  let s:input = "\t\t\t\t\t{"
+  call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>- vts=4')
+  let lines = s:screen_lines(2,16)
+  let expect = [
+	\ " 2 >--->--->--->",
+	\ "          ---{  ",
+	\ "~               ",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set nuw=4 listchars= vts&')
+endfunc
+
+func Test_breakindent13()
+  let s:input = ""
   call s:test_windows('setl breakindent briopt=min:10 ts=8')
   vert resize 20
   call setline(1, ["    a\tb\tc\td\te", "    z   y       x       w       v"])
@@ -237,62 +472,146 @@
   call assert_equal('d', @a)
   call assert_equal('w', @b)
   call s:close_windows()
-endfunction
+endfunc
 
-function Test_breakindent14()
-  let s:input=""
+func Test_breakindent13_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  let s:input = ""
+  call s:test_windows('setl breakindent briopt=min:10 ts=8 vts=8')
+  vert resize 20
+  call setline(1, ["    a\tb\tc\td\te", "    z   y       x       w       v"])
+  1
+  norm! fbgj"ayl
+  2
+  norm! fygj"byl
+  call assert_equal('d', @a)
+  call assert_equal('w', @b)
+  call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent14()
+  let s:input = ""
   call s:test_windows('setl breakindent briopt= ts=8')
   vert resize 30
   norm! 3a1234567890
   norm! a    abcde
   exec "norm! 0\<C-V>tex"
-  let lines=s:screen_lines(line('.'),8)
-  let expect=[
-\ "e       ",
-\ "~       ",
-\ "~       ",
-\ ]
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "e       ",
+	\ "~       ",
+	\ "~       ",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows()
-endfunction
+endfunc
 
-function Test_breakindent15()
-  let s:input=""
+func Test_breakindent14_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  let s:input = ""
+  call s:test_windows('setl breakindent briopt= ts=8 vts=8')
+  vert resize 30
+  norm! 3a1234567890
+  norm! a    abcde
+  exec "norm! 0\<C-V>tex"
+  let lines = s:screen_lines(line('.'),8)
+  let expect = [
+	\ "e       ",
+	\ "~       ",
+	\ "~       ",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent15()
+  let s:input = ""
   call s:test_windows('setl breakindent briopt= ts=8 sw=8')
   vert resize 30
   norm! 4a1234567890
   exe "normal! >>\<C-V>3f0x"
-  let lines=s:screen_lines(line('.'),20)
-  let expect=[
-\ "        1234567890  ",
-\ "~                   ",
-\ "~                   ",
-\ ]
+  let lines = s:screen_lines(line('.'),20)
+  let expect = [
+	\ "        1234567890  ",
+	\ "~                   ",
+	\ "~                   ",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows()
-endfunction
+endfunc
 
-function Test_breakindent16()
+func Test_breakindent15_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  let s:input = ""
+  call s:test_windows('setl breakindent briopt= ts=8 sw=8 vts=8')
+  vert resize 30
+  norm! 4a1234567890
+  exe "normal! >>\<C-V>3f0x"
+  let lines = s:screen_lines(line('.'),20)
+  let expect = [
+	\ "        1234567890  ",
+	\ "~                   ",
+	\ "~                   ",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent16()
   " Check that overlong lines are indented correctly.
-  let s:input=""
+  let s:input = ""
   call s:test_windows('setl breakindent briopt=min:0 ts=4')
   call setline(1, "\t".repeat("1234567890", 10))
   resize 6
   norm! 1gg$
   redraw!
-  let lines=s:screen_lines(1,10)
-  let expect=[
-\ "    789012",
-\ "    345678",
-\ "    901234",
-\ ]
+  let lines = s:screen_lines(1,10)
+  let expect = [
+	\ "    789012",
+	\ "    345678",
+	\ "    901234",
+	\ ]
   call s:compare_lines(expect, lines)
-  let lines=s:screen_lines(4,10)
-  let expect=[
-\ "    567890",
-\ "    123456",
-\ "    7890  ",
-\ ]
+  let lines = s:screen_lines(4,10)
+  let expect = [
+	\ "    567890",
+	\ "    123456",
+	\ "    7890  ",
+	\ ]
   call s:compare_lines(expect, lines)
   call s:close_windows()
-endfunction
+endfunc
+
+func Test_breakindent16_vartabs()
+  if !has("vartabs")
+    return
+  endif
+  " Check that overlong lines are indented correctly.
+  let s:input = ""
+  call s:test_windows('setl breakindent briopt=min:0 ts=4 vts=4')
+  call setline(1, "\t".repeat("1234567890", 10))
+  resize 6
+  norm! 1gg$
+  redraw!
+  let lines = s:screen_lines(1,10)
+  let expect = [
+	\ "    789012",
+	\ "    345678",
+	\ "    901234",
+	\ ]
+  call s:compare_lines(expect, lines)
+  let lines = s:screen_lines(4,10)
+  let expect = [
+	\ "    567890",
+	\ "    123456",
+	\ "    7890  ",
+	\ ]
+  call s:compare_lines(expect, lines)
+  call s:close_windows('set vts&')
+endfunc
diff --git a/src/testdir/test_vartabs.vim b/src/testdir/test_vartabs.vim
new file mode 100644
index 0000000..85914f6
--- /dev/null
+++ b/src/testdir/test_vartabs.vim
@@ -0,0 +1,257 @@
+" Test for variable tabstops
+
+if !has("vartabs")
+  finish
+endif
+
+func! Test_vartabs()
+  new
+  %d
+
+  " Test normal operation of tabstops ...
+  set ts=4
+  call setline(1, join(split('aaaaa', '\zs'), "\t"))
+  retab 8
+  let expect = "a   a\<tab>a   a\<tab>a"
+  call assert_equal(expect, getline(1))
+
+  " ... and softtabstops
+  set ts=8 sts=6
+  exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b"
+  let expect = "b     b\<tab>    b\<tab>  b\<tab>b"
+  call assert_equal(expect, getline(1))
+
+  " Test variable tabstops.
+  set sts=0 vts=4,8,4,8
+  exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c"
+  retab 8
+  let expect = "c   c\<tab>    c\<tab>c\<tab>c\<tab>c"
+  call assert_equal(expect, getline(1))
+
+  set et vts=4,8,4,8
+  exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d"
+  let expect = "d   d       d   d       d       d"
+  call assert_equal(expect, getline(1))
+
+  " Changing ts should have no effect if vts is in use.
+  call cursor(1, 1)
+  set ts=6
+  exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e"
+  let expect = "e   e       e   e       e       e"
+  call assert_equal(expect, getline(1))
+
+  " Clearing vts should revert to using ts.
+  set vts=
+  exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f"
+  let expect = "f     f     f     f     f     f"
+  call assert_equal(expect, getline(1))
+
+  " Test variable softtabstops.
+  set noet ts=8 vsts=12,2,6
+  exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g"
+  let expect = "g\<tab>    g g\<tab>    g\<tab>  g\<tab>g"
+  call assert_equal(expect, getline(1))
+
+  " Variable tabstops and softtabstops combined.
+  set vsts=6,12,8 vts=4,6,8
+  exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h"
+  let expect = "h\<tab>  h\<tab>\<tab>h\<tab>h\<tab>h"
+  call assert_equal(expect, getline(1))
+
+  " Retab with a single value, not using vts.
+  set ts=8 sts=0 vts= vsts=
+  exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i"
+  retab 4
+  let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i"
+  call assert_equal(expect, getline(1))
+
+  " Retab with a single value, using vts.
+  set ts=8 sts=0 vts=6 vsts=
+  exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j"
+  retab 4
+  let expect = "j\<tab>  j\<tab>\<tab>j\<tab>  j\<tab>\<tab>j"
+  call assert_equal(expect, getline(1))
+
+  " Retab with multiple values, not using vts.
+  set ts=6 sts=0 vts= vsts=
+  exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k"
+  retab 4,8
+  let expect = "k\<tab>  k\<tab>k     k\<tab>    k\<tab>  k"
+  call assert_equal(expect, getline(1))
+
+  " Retab with multiple values, using vts.
+  set ts=8 sts=0 vts=6 vsts=
+  exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l"
+  retab 4,8
+  let expect = "l\<tab>  l\<tab>l     l\<tab>    l\<tab>  l"
+  call assert_equal(expect, getline(1))
+
+  " Check that global and local values are set.
+  set ts=4 vts=6 sts=8 vsts=10
+  call assert_equal(&ts, 4)
+  call assert_equal(&vts, '6')
+  call assert_equal(&sts, 8)
+  call assert_equal(&vsts, '10')
+  new
+  call assert_equal(&ts, 4)
+  call assert_equal(&vts, '6')
+  call assert_equal(&sts, 8)
+  call assert_equal(&vsts, '10')
+  bwipeout!
+
+  " Check that local values only are set.
+  setlocal ts=5 vts=7 sts=9 vsts=11
+  call assert_equal(&ts, 5)
+  call assert_equal(&vts, '7')
+  call assert_equal(&sts, 9)
+  call assert_equal(&vsts, '11')
+  new
+  call assert_equal(&ts, 4)
+  call assert_equal(&vts, '6')
+  call assert_equal(&sts, 8)
+  call assert_equal(&vsts, '10')
+  bwipeout!
+
+  " Check that global values only are set.
+  setglobal ts=6 vts=8 sts=10 vsts=12
+  call assert_equal(&ts, 5)
+  call assert_equal(&vts, '7')
+  call assert_equal(&sts, 9)
+  call assert_equal(&vsts, '11')
+  new
+  call assert_equal(&ts, 6)
+  call assert_equal(&vts, '8')
+  call assert_equal(&sts, 10)
+  call assert_equal(&vsts, '12')
+  bwipeout!
+
+  set ts& vts& sts& vsts& et&
+  bwipeout!
+endfunc
+
+func! Test_vartabs_breakindent()
+  if !exists("+breakindent")
+    return
+  endif
+  new
+  %d
+
+  " Test normal operation of tabstops ...
+  set ts=4
+  call setline(1, join(split('aaaaa', '\zs'), "\t"))
+  retab 8
+  let expect = "a   a\<tab>a   a\<tab>a"
+  call assert_equal(expect, getline(1))
+
+  " ... and softtabstops
+  set ts=8 sts=6
+  exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b"
+  let expect = "b     b\<tab>    b\<tab>  b\<tab>b"
+  call assert_equal(expect, getline(1))
+
+  " Test variable tabstops.
+  set sts=0 vts=4,8,4,8
+  exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c"
+  retab 8
+  let expect = "c   c\<tab>    c\<tab>c\<tab>c\<tab>c"
+  call assert_equal(expect, getline(1))
+
+  set et vts=4,8,4,8
+  exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d"
+  let expect = "d   d       d   d       d       d"
+  call assert_equal(expect, getline(1))
+
+  " Changing ts should have no effect if vts is in use.
+  call cursor(1, 1)
+  set ts=6
+  exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e"
+  let expect = "e   e       e   e       e       e"
+  call assert_equal(expect, getline(1))
+
+  " Clearing vts should revert to using ts.
+  set vts=
+  exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f"
+  let expect = "f     f     f     f     f     f"
+  call assert_equal(expect, getline(1))
+
+  " Test variable softtabstops.
+  set noet ts=8 vsts=12,2,6
+  exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g"
+  let expect = "g\<tab>    g g\<tab>    g\<tab>  g\<tab>g"
+  call assert_equal(expect, getline(1))
+
+  " Variable tabstops and softtabstops combined.
+  set vsts=6,12,8 vts=4,6,8
+  exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h"
+  let expect = "h\<tab>  h\<tab>\<tab>h\<tab>h\<tab>h"
+  call assert_equal(expect, getline(1))
+
+  " Retab with a single value, not using vts.
+  set ts=8 sts=0 vts= vsts=
+  exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i"
+  retab 4
+  let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i"
+  call assert_equal(expect, getline(1))
+
+  " Retab with a single value, using vts.
+  set ts=8 sts=0 vts=6 vsts=
+  exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j"
+  retab 4
+  let expect = "j\<tab>  j\<tab>\<tab>j\<tab>  j\<tab>\<tab>j"
+  call assert_equal(expect, getline(1))
+
+  " Retab with multiple values, not using vts.
+  set ts=6 sts=0 vts= vsts=
+  exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k"
+  retab 4,8
+  let expect = "k\<tab>  k\<tab>k     k\<tab>    k\<tab>  k"
+  call assert_equal(expect, getline(1))
+
+  " Retab with multiple values, using vts.
+  set ts=8 sts=0 vts=6 vsts=
+  exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l"
+  retab 4,8
+  let expect = "l\<tab>  l\<tab>l     l\<tab>    l\<tab>  l"
+  call assert_equal(expect, getline(1))
+
+  " Check that global and local values are set.
+  set ts=4 vts=6 sts=8 vsts=10
+  call assert_equal(&ts, 4)
+  call assert_equal(&vts, '6')
+  call assert_equal(&sts, 8)
+  call assert_equal(&vsts, '10')
+  new
+  call assert_equal(&ts, 4)
+  call assert_equal(&vts, '6')
+  call assert_equal(&sts, 8)
+  call assert_equal(&vsts, '10')
+  bwipeout!
+
+  " Check that local values only are set.
+  setlocal ts=5 vts=7 sts=9 vsts=11
+  call assert_equal(&ts, 5)
+  call assert_equal(&vts, '7')
+  call assert_equal(&sts, 9)
+  call assert_equal(&vsts, '11')
+  new
+  call assert_equal(&ts, 4)
+  call assert_equal(&vts, '6')
+  call assert_equal(&sts, 8)
+  call assert_equal(&vsts, '10')
+  bwipeout!
+
+  " Check that global values only are set.
+  setglobal ts=6 vts=8 sts=10 vsts=12
+  call assert_equal(&ts, 5)
+  call assert_equal(&vts, '7')
+  call assert_equal(&sts, 9)
+  call assert_equal(&vsts, '11')
+  new
+  call assert_equal(&ts, 6)
+  call assert_equal(&vts, '8')
+  call assert_equal(&sts, 10)
+  call assert_equal(&vsts, '12')
+  bwipeout!
+
+  bwipeout!
+endfunc