Hi all!

When options breakindent, wrap and vartabstop are used all together, there are 
erroneous indentation offsets of continuation lines, as if 
breakindentopt=shift:N would specify nonzero values of N depending on number of 
tab characters at start of line, even if vartabstop is set to a single number, 
e.g. “set vartabstop=4”.

Otherwise the patch works for me as advertised.

Many thanks to Christian and all the other contributors for this patch, 
vartabstop is a very useful feature!


vim -u NONE -U NONE -c ':language en_US.utf8| set columns=80| version| quit'
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Oct 23 2014 19:38:34)
Included patches: 1-488
Extra patches: variable tabstops
Modified by Gentoo-7.4.488-<odvx1@systomanalyson.not_s/o/e/g>
Compiled by <odvx1@systomanalyson.not_s/o/e/g>
Huge version without GUI.  Features included (+) or not (-):
+acl             -farsi           -mouse_sgr       +tag_old_static
-arabic          +file_in_path    -mouse_sysmouse  -tag_any_white
+autocmd         +find_in_path    -mouse_urxvt     -tcl
-balloon_eval    +float           -mouse_xterm     +terminfo
-browse          +folding         +multi_byte      +termresponse
++builtin_terms  -footer          +multi_lang      +textobjects
+byte_offset     +fork()          -mzscheme        +title
+cindent         +gettext         -netbeans_intg   -toolbar
-clientserver    -hangul_input    +path_extra      +user_commands
-clipboard       +iconv           +perl            +vartabs
+cmdline_compl   +insert_expand   -persistent_undo +vertsplit
+cmdline_hist    +jumplist        -printer         +virtualedit
+cmdline_info    +keymap          +profile         +visual
+comments        +langmap         +python          +visualextra
+conceal         +libcall         -python3         +viminfo
-cryptv          +linebreak       +quickfix        +vreplace
+cscope          +lispindent      +reltime         +wildignore
+cursorbind      +listcmds        -rightleft       +wildmenu
+cursorshape     +localmap        -ruby            +windows
+dialog_con      -lua             +scrollbind      +writebackup
+diff            -menu            +signs           -X11
+digraphs        +mksession       +smartindent     -xfontset
-dnd             +modify_fname    -sniff           -xim
-ebcdic          -mouse           +startuptime     -xsmp
+emacs_tags      -mouse_dec       +statusline      -xterm_clipboard
+eval            -mouse_gpm       -sun_workshop    -xterm_save
+ex_extra        -mouse_jsbterm   +syntax          -xpm
+extra_search    -mouse_netterm   +tag_binary
   system vimrc file: "/etc/vim/vimrc"
     user vimrc file: "$HOME/.vimrc"
 2nd user vimrc file: "~/.vim/vimrc"
      user exrc file: "$HOME/.exrc"
  fall-back for $VIM: "/usr/share/vim"
Compilation: x86_64-pc-linux-gnu-gcc -c -I. -Iproto -DHAVE_CONFIG_H     -O2 -mar
ch=native -pipe -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1
Linking: x86_64-pc-linux-gnu-gcc   -Wl,-E  -Wl,-O1 -L/usr/local/lib -Wl,--as-nee
ded -o vim        -lm   -lcurses -ldl   -Wl,-E -Wl,-O1 -Wl,--as-needed  -L/usr/l
ib64/perl5/5.12.3/x86_64-linux/CORE -lperl -lnsl -ldl -lm -lcrypt -lutil -lc -L/
usr/lib64/python2.7/config -lpython2.7 -lpthread -ldl -lutil -lm -Xlinker -expor
t-dynamic

-- 
Roland Eggner
# HG changeset patch
# Date 1414065606 -7200
# User Roland Eggner < o...@systomanalyson.not s/o/e/g >
# Parent 007b01253cb85ce967bc7c747278036e4232fd86
Christian_Brabandt.vim-mq-patches/var_tabstops_orig

Roland Eggner 2014-09-09  vim-7.4.430
Readded  src/testdir/test_vartabs.{in,ok}  based on an older version of this 
patch.
Fixed rejections of  src/testdir/Make{_{{amiga,dos,ming,os2}.mak,vms.mms},file} 
.

Roland Eggner 2014-10-23  vim-7.4.488
Fixed insertion of 4 lines after src/option.c:5439 -- due to context change in
vim-7.4.456, “hg qrefresh” inserted erroneously with fuzz 2 and -111 lines 
offset.
Fixed rejections of  src/testdir/Make{_{{amiga,dos,ming,os2}.mak,vms.mms},file} 
.

diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -903,6 +903,11 @@ 4.4 Changing tabs                                  
*change-tabs*
                        {not in Vi}
                        Not available when |+ex_extra| feature was disabled at
                        compile time.
+                       If the |+vartabs| feature is enabled then a list of
+                       tab widths separated by commas may be used in place of
+                       a single tabstop.  Each value in the list represents
+                       the width of one tabstop, except the final value which
+                       applies to all following tabstops.
 
                                                        *retab-example*
 Example for using autocommands and ":retab" to edit a file which is stored
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -6604,6 +6604,10 @@ A jump table for the options with a short description 
can be found at |Q_op|.
        set.
        NOTE: This option is set to 0 when 'compatible' is set.
 
+       If Vim is compiled with the |+vartabs| feature then the value of
+       'softtabstop' will be ignored if |'varsofttabstop'| is set to
+       anything other than an empty string.
+
                                                *'spell'* *'nospell'*
 'spell'                        boolean (default off)
                        local to window
@@ -7151,6 +7155,10 @@ A jump table for the options with a short description 
can be found at |Q_op|.
           though.  Otherwise aligned comments will be wrong when 'tabstop' is
           changed.
 
+       If Vim is compiled with the |+vartabs| feature then the value of
+       'tabstop' will be ignored if |'vartabstop'| is set to anything other
+       than an empty string.
+
                        *'tagbsearch'* *'tbs'* *'notagbsearch'* *'notbs'*
 'tagbsearch' 'tbs'     boolean (default on)
                        global
@@ -7787,6 +7795,44 @@ A jump table for the options with a short description 
can be found at |Q_op|.
        written to disk (see |crash-recovery|).  Also used for the
        |CursorHold| autocommand event.
 
+                                       *'varsofttabstop'* *'vsts'*
+'varsofttabstop' 'vsts'        string  (default "")
+                       local to buffer
+                       {only available when compiled with the |+vartabs|
+                       feature}
+                       {not in Vi}
+       A list of the number of spaces that a <Tab> counts for while editing,
+       such as inserting a <Tab> or using <BS>.  It "feels" like variable-
+       width <Tab>s are being inserted, while in fact a mixture of spaces
+       and <Tab>s is used.  Tab widths are separated with commas, with the
+       final value applying to all subsequent tabs.
+       
+       For example, when editing assembly language files where statements
+       start in the 8th column and comments in the 40th, it may be useful
+       to use the following: >
+               :set varsofttabstop=8,32,8
+<      This will set soft tabstops at the 8th and 40th columns, and at every
+       8th column thereafter.
+
+       Note that the value of |'softtabstop'| will be ignored while
+       'varsofttabstop' is set.
+
+                                               *'vartabstop'* *'vts'*
+'vartabstop' 'vts'     string  (default "")
+                       local to buffer
+                       {only available when compiled with the |+vartabs|
+                       feature}
+                       {not in Vi}
+       A list of the number of spaces that a <Tab> in the file counts for,
+       separated by commas.  Each value corresponds to one tab, with the
+       final value applying to all subsequent tabs. For example: >
+               :set vartabstop=4,20,10,8
+<      This will make the first tab 4 spaces wide, the second 20 spaces,
+       the third 10 spaces, and all following tabs 8 spaces.
+
+       Note that the value of |'tabstop'| will be ignored while 'vartabstop'
+       is set.
+
                                                *'verbose'* *'vbs'*
 'verbose' 'vbs'                number  (default 0)
                        global
diff --git a/runtime/doc/tags b/runtime/doc/tags
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -1063,6 +1063,8 @@
 'updatetime'   options.txt     /*'updatetime'*
 'ur'   options.txt     /*'ur'*
 'ut'   options.txt     /*'ut'*
+'varsofttabstop'       options.txt     /*'varsofttabstop'*
+'vartabstop'   options.txt     /*'vartabstop'*
 'vb'   options.txt     /*'vb'*
 'vbs'  options.txt     /*'vbs'*
 'vdir' options.txt     /*'vdir'*
@@ -1077,6 +1079,8 @@
 'virtualedit'  options.txt     /*'virtualedit'*
 'visualbell'   options.txt     /*'visualbell'*
 'vop'  options.txt     /*'vop'*
+'vsts' options.txt     /*'vsts'*
+'vts'  options.txt     /*'vts'*
 'w1200'        vi_diff.txt     /*'w1200'*
 'w300' vi_diff.txt     /*'w300'*
 'w9600'        vi_diff.txt     /*'w9600'*
@@ -1260,6 +1264,7 @@
 +title various.txt     /*+title*
 +toolbar       various.txt     /*+toolbar*
 +user_commands various.txt     /*+user_commands*
++vartabs       various.txt     /*+vartabs*
 +vertsplit     various.txt     /*+vertsplit*
 +viminfo       various.txt     /*+viminfo*
 +virtualedit   various.txt     /*+virtualedit*
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -433,6 +433,7 @@ N  *+textobjects*   |text-objects| selection
 N  *+title*            Setting the window 'title' and 'icon'
 N  *+toolbar*          |gui-toolbar|
 N  *+user_commands*    User-defined commands. |user-commands|
+B  *+vartabs*          Variable-width tabstops. |'vartabstop'|
 N  *+viminfo*          |'viminfo'|
 N  *+vertsplit*                Vertically split windows |:vsplit|
 N  *+virtualedit*      |'virtualedit'|
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -817,6 +817,14 @@ call <SID>OptionL("ts")
 call append("$", "shiftwidth\tnumber of spaces used for each step of 
(auto)indent")
 call append("$", "\t(local to buffer)")
 call <SID>OptionL("sw")
+if has("vartabs")
+  call append("$", "vartabstop\tlist of number of spaces a tab counts for")
+  call append("$", "\t(local to buffer)")
+  call <SID>OptionL("vts")
+  call append("$", "varsofttabstop\tlist of number of spaces a soft tabsstop 
counts for")
+  call append("$", "\t(local to buffer)")
+  call <SID>OptionL("vsts")
+endif
 call append("$", "smarttab\ta <Tab> in an indent inserts 'shiftwidth' spaces")
 call <SID>BinOptionG("sta", &sta)
 call append("$", "softtabstop\tif non-zero, number of spaces to insert for a 
<Tab>")
diff --git a/src/buffer.c b/src/buffer.c
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1940,6 +1940,19 @@ free_buf_options(buf, free_p_ff)
     clear_string_option(&buf->b_p_fo);
     clear_string_option(&buf->b_p_flp);
     clear_string_option(&buf->b_p_isk);
+#ifdef FEAT_VARTABS
+    clear_string_option(&buf->b_p_vsts);
+    if (buf->b_p_vsts_nopaste)
+       vim_free(buf->b_p_vsts_nopaste);
+    buf->b_p_vsts_nopaste = 0;
+    if (buf->b_p_vsts_ary)
+       vim_free(buf->b_p_vsts_ary);
+    buf->b_p_vsts_ary = 0;
+    clear_string_option(&buf->b_p_vts);
+    if (buf->b_p_vts_ary)
+       vim_free(buf->b_p_vts_ary);
+    buf->b_p_vts_ary = 0;
+#endif
 #ifdef FEAT_KEYMAP
     clear_string_option(&buf->b_p_keymap);
     ga_clear(&buf->b_kmap_ga);
diff --git a/src/charset.c b/src/charset.c
--- a/src/charset.c
+++ b/src/charset.c
@@ -815,6 +815,15 @@ vim_strnsize(s, len)
  * Also see getvcol() below.
  */
 
+#ifdef FEAT_VARTABS
+#define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \
+    if (*(p) == TAB && (!(wp)->w_p_list || lcs_tab1)) \
+    { \
+       return tabstop_padding(col, (buf)->b_p_ts, (buf)->b_p_vts_ary); \
+    } \
+    else \
+       return ptr2cells(p);
+#else
 #define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \
     if (*(p) == TAB && (!(wp)->w_p_list || lcs_tab1)) \
     { \
@@ -824,6 +833,7 @@ vim_strnsize(s, len)
     } \
     else \
        return ptr2cells(p);
+#endif
 
 #if defined(FEAT_VREPLACE) || defined(FEAT_EX_EXTRA) || defined(FEAT_GUI) \
        || defined(FEAT_VIRTUALEDIT) || defined(PROTO)
@@ -1081,6 +1091,9 @@ win_lbr_chartabsize(wp, line, s, col, headp)
     colnr_T    col_adj = 0; /* col + screen size of tab */
     colnr_T    colmax;
     int                added;
+# ifdef FEAT_VARTABS
+    colnr_T    orig_col = col;
+# endif
 # ifdef FEAT_MBYTE
     int                mb_added = 0;
 # else
@@ -1229,8 +1242,13 @@ win_nolbr_chartabsize(wp, s, col, headp)
 
     if (*s == TAB && (!wp->w_p_list || lcs_tab1))
     {
+# ifdef FEAT_VARTABS
+       return tabstop_padding(col, wp->w_buffer->b_p_ts,
+                                   wp->w_buffer->b_p_vts_ary);
+# else
        n = wp->w_buffer->b_p_ts;
        return (int)(n - (col % n));
+# endif
     }
     n = ptr2cells(s);
     /* Add one cell for a double-width character in the last column of the
@@ -1294,6 +1312,9 @@ getvcol(wp, pos, start, cursor, end)
     char_u     *line;          /* start of the line */
     int                incr;
     int                head;
+#ifdef FEAT_VARTABS
+    int                *vts = wp->w_buffer->b_p_vts_ary;
+#endif
     int                ts = wp->w_buffer->b_p_ts;
     int                c;
 
@@ -1333,7 +1354,11 @@ getvcol(wp, pos, start, cursor, end)
            }
            /* A tab gets expanded, depending on the current column */
            if (c == TAB)
+#ifdef FEAT_VARTABS
+               incr = tabstop_padding(vcol, ts, vts);
+#else
                incr = ts - (vcol % ts);
+#endif
            else
            {
 #ifdef FEAT_MBYTE
diff --git a/src/edit.c b/src/edit.c
--- a/src/edit.c
+++ b/src/edit.c
@@ -699,7 +699,13 @@ edit(cmdchar, startln, count)
            mincol = curwin->w_wcol;
            validate_cursor_col();
 
-           if ((int)curwin->w_wcol < mincol - curbuf->b_p_ts
+           if (
+#ifdef FEAT_VARTABS
+               (int)curwin->w_wcol < mincol
+                   - tabstop_at(get_nolist_virtcol(), curbuf->b_p_ts, 
curbuf->b_p_vts_ary)
+#else
+               (int)curwin->w_wcol < mincol - curbuf->b_p_ts
+#endif
                    && curwin->w_wrow == W_WINROW(curwin)
                                                 + curwin->w_height - 1 - p_so
                    && (curwin->w_cursor.lnum != curwin->w_topline
@@ -8970,23 +8976,31 @@ ins_bs(c, mode, inserted_space_p)
         */
        if (       mode == BACKSPACE_CHAR
                && ((p_sta && in_indent)
-                   || (get_sts_value() != 0
+                   || ((get_sts_value() != 0
+#ifdef FEAT_VARTABS
+                       || tabstop_count(curbuf->b_p_vsts_ary)
+#endif
+                       )
                        && curwin->w_cursor.col > 0
                        && (*(ml_get_cursor() - 1) == TAB
                            || (*(ml_get_cursor() - 1) == ' '
                                && (!*inserted_space_p
                                    || arrow_used))))))
        {
+#ifndef FEAT_VARTABS
            int         ts;
+#endif
            colnr_T     vcol;
            colnr_T     want_vcol;
            colnr_T     start_vcol;
 
            *inserted_space_p = FALSE;
+#ifndef FEAT_VARTABS
            if (p_sta && in_indent)
                ts = (int)get_sw_value(curbuf);
            else
                ts = (int)get_sts_value();
+#endif
            /* Compute the virtual column where we want to be.  Since
             * 'showbreak' may get in the way, need to get the last column of
             * the previous character. */
@@ -8995,7 +9009,15 @@ ins_bs(c, mode, inserted_space_p)
            dec_cursor();
            getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol);
            inc_cursor();
+#ifdef FEAT_VARTABS
+           if (p_sta && in_indent)
+               want_vcol = (want_vcol / curbuf->b_p_sw) * curbuf->b_p_sw;
+           else
+               want_vcol = tabstop_start(want_vcol, curbuf->b_p_sts,
+                                                    curbuf->b_p_vsts_ary);
+#else
            want_vcol = (want_vcol / ts) * ts;
+#endif
 
            /* delete characters until we are at or before want_vcol */
            while (vcol > want_vcol
@@ -9673,7 +9695,18 @@ ins_tab()
      * When nothing special, insert TAB like a normal character
      */
     if (!curbuf->b_p_et
+#ifdef FEAT_VARTABS
+           && !(p_sta && ind
+               /* These five lines mean 'tabstop' != 'shiftwidth' */
+               && ((tabstop_count(curbuf->b_p_vts_ary) > 1)
+                   || (tabstop_count(curbuf->b_p_vts_ary) == 1
+                       && tabstop_first(curbuf->b_p_vts_ary) != 
get_sw_value(curbuf))
+                   || (tabstop_count(curbuf->b_p_vts_ary) == 0
+                       && curbuf->b_p_ts != get_sw_value(curbuf))))
+           && tabstop_count(curbuf->b_p_vsts_ary) == 0
+#else
            && !(p_sta && ind && curbuf->b_p_ts != get_sw_value(curbuf))
+#endif
            && get_sts_value() == 0)
        return TRUE;
 
@@ -9688,6 +9721,20 @@ ins_tab()
 #endif
     AppendToRedobuff((char_u *)"\t");
 
+#ifdef FEAT_VARTABS
+    if (p_sta && ind)          /* insert tab in indent, use 'shiftwidth' */
+    {
+       temp = (int)curbuf->b_p_sw;
+       temp -= get_nolist_virtcol() % temp;
+    }
+    else if (tabstop_count(curbuf->b_p_vsts_ary) > 0 || curbuf->b_p_sts > 0)
+                               /* use 'softtabstop' when set */
+       temp = tabstop_padding(get_nolist_virtcol(), curbuf->b_p_sts,
+                                                    curbuf->b_p_vsts_ary);
+    else                       /* otherwise use 'tabstop' */
+       temp = tabstop_padding(get_nolist_virtcol(), curbuf->b_p_ts,
+                                                    curbuf->b_p_vts_ary);
+#else
     if (p_sta && ind)          /* insert tab in indent, use 'shiftwidth' */
        temp = (int)get_sw_value(curbuf);
     else if (curbuf->b_p_sts != 0) /* use 'softtabstop' when set */
@@ -9695,6 +9742,7 @@ ins_tab()
     else                       /* otherwise use 'tabstop' */
        temp = (int)curbuf->b_p_ts;
     temp -= get_nolist_virtcol() % temp;
+#endif
 
     /*
      * Insert the first space with ins_char(). It will delete one char in
@@ -9719,7 +9767,13 @@ ins_tab()
     /*
      * When 'expandtab' not set: Replace spaces by TABs where possible.
      */
+#ifdef FEAT_VARTABS
+    if (!curbuf->b_p_et && (tabstop_count(curbuf->b_p_vsts_ary) > 0
+                            || get_sts_value() > 0
+                           || (p_sta && ind)))
+#else
     if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind)))
+#endif
     {
        char_u          *ptr;
 #ifdef FEAT_VREPLACE
diff --git a/src/eval.c b/src/eval.c
--- a/src/eval.c
+++ b/src/eval.c
@@ -12800,6 +12800,9 @@ f_has(argvars, rettv)
 #ifdef FEAT_VIMINFO
        "viminfo",
 #endif
+#ifdef FEAT_VARTABS
+       "vartabs",
+#endif
 #ifdef FEAT_VERTSPLIT
        "vertsplit",
 #endif
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -592,12 +592,17 @@ ex_retab(eap)
     long       vcol;
     long       start_col = 0;          /* For start of white-space string */
     long       start_vcol = 0;         /* For start of white-space string */
-    int                temp;
     long       old_len;
     char_u     *ptr;
     char_u     *new_line = (char_u *)1;    /* init to non-NULL */
     int                did_undo;               /* called u_save for current 
line */
+#ifdef FEAT_VARTABS
+    int                *new_ts = 0;
+    char_u     *new_ts_str;            /* string value of tab argument */
+#else
+    int                temp;
     int                new_ts;
+#endif
     int                save_list;
     linenr_T   first_line = 0;         /* first changed line */
     linenr_T   last_line = 0;          /* last changed line */
@@ -605,6 +610,27 @@ ex_retab(eap)
     save_list = curwin->w_p_list;
     curwin->w_p_list = 0;          /* don't want list mode here */
 
+#ifdef FEAT_VARTABS
+    new_ts_str = eap->arg;
+    if (vim_isdigit(*(eap->arg)) && !tabstop_set(eap->arg, &new_ts))
+    {
+       EMSG(_(e_invarg));
+       return;
+    }
+    while (vim_isdigit(*(eap->arg)) || *(eap->arg) == ',')
+       ++(eap->arg);
+
+    /* This ensures that either new_ts and new_ts_str are freshly allocated,
+     * or new_ts points to an existing array and new_ts_str is null.
+     */
+    if (new_ts == 0)
+    {
+       new_ts = curbuf->b_p_vts_ary;
+       new_ts_str = NULL;
+    }
+    else
+       new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str);
+#else
     new_ts = getdigits(&(eap->arg));
     if (new_ts < 0)
     {
@@ -613,6 +639,7 @@ ex_retab(eap)
     }
     if (new_ts == 0)
        new_ts = curbuf->b_p_ts;
+#endif
     for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
     {
        ptr = ml_get(lnum);
@@ -645,6 +672,15 @@ ex_retab(eap)
                    num_tabs = 0;
                    if (!curbuf->b_p_et)
                    {
+#ifdef FEAT_VARTABS
+                       int t, s;
+                       tabstop_fromto(start_vcol, vcol,
+                                      tabstop_count(new_ts)? 0: curbuf->b_p_ts,
+                                      new_ts,
+                                      &t, &s);
+                       num_tabs = t;
+                       num_spaces = s;
+#else
                        temp = new_ts - (start_vcol % new_ts);
                        if (num_spaces >= temp)
                        {
@@ -653,6 +689,7 @@ ex_retab(eap)
                        }
                        num_tabs += num_spaces / new_ts;
                        num_spaces -= (num_spaces / new_ts) * new_ts;
+#endif
                    }
                    if (curbuf->b_p_et || got_tab ||
                                        (num_spaces + num_tabs < len))
@@ -710,14 +747,54 @@ ex_retab(eap)
     if (got_int)
        EMSG(_(e_interr));
 
+#ifdef FEAT_VARTABS
+    /* If a single value was given then it can be considered equal to
+     * either the value of 'tabstop' or the value of 'vartabstop'.
+     */
+    if (tabstop_count(curbuf->b_p_vts_ary) == 0
+       && tabstop_count(new_ts) == 1
+       && curbuf->b_p_ts == tabstop_first(new_ts))
+       ; /* not changed */
+    else if (tabstop_count(curbuf->b_p_vts_ary) > 0
+        && tabstop_eq(curbuf->b_p_vts_ary, new_ts))
+       ; /* not changed */
+    else
+       redraw_curbuf_later(NOT_VALID);
+#else
     if (curbuf->b_p_ts != new_ts)
        redraw_curbuf_later(NOT_VALID);
+#endif
     if (first_line != 0)
        changed_lines(first_line, 0, last_line + 1, 0L);
 
     curwin->w_p_list = save_list;      /* restore 'list' */
 
+#ifdef FEAT_VARTABS
+    if (new_ts_str != NULL)            /* set the new tabstop */
+    {
+       /* If 'vartabstop' is in use or if the value given to retab has more
+        * than one tabstop then update 'vartabstop'.
+        */
+       int* old_vts_ary = curbuf->b_p_vts_ary;
+       if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_ts) > 1) {
+           set_string_option_direct((char_u *)"vts", -1, new_ts_str,
+                                                   OPT_FREE|OPT_LOCAL, 0);
+           vim_free(new_ts_str);
+           curbuf->b_p_vts_ary = new_ts;
+           vim_free(old_vts_ary);
+       }
+       /* If 'vartabstop' wasn't in use and a single value was given to
+        * retab then update 'tabstop'.
+        */
+       else
+       {
+           curbuf->b_p_ts = tabstop_first(new_ts);
+           vim_free(new_ts);
+       }
+    }
+#else
     curbuf->b_p_ts = new_ts;
+#endif
     coladvance(curwin->w_curswant);
 
     u_clearline();
diff --git a/src/feature.h b/src/feature.h
--- a/src/feature.h
+++ b/src/feature.h
@@ -840,6 +840,13 @@
 #endif
 
 /*
+ * +vartabs            'vartabstop' and 'varsofttabstop' options.
+ */
+#ifdef FEAT_BIG
+# define FEAT_VARTABS
+#endif
+
+/*
  * Preferences:
  * ============
  */
diff --git a/src/gui_beval.c b/src/gui_beval.c
--- a/src/gui_beval.c
+++ b/src/gui_beval.c
@@ -230,6 +230,9 @@ gui_mch_create_beval_area(target, mesg, mesgCB, clientData)
        beval->msg = mesg;
        beval->msgCB = mesgCB;
        beval->clientData = clientData;
+#ifdef FEAT_VARTABS
+       beval->vts = 0;
+#endif
 
        /*
         * Set up event handler which will keep its eyes on the pointer,
@@ -273,6 +276,10 @@ gui_mch_destroy_beval_area(beval)
 # else
     XtDestroyWidget(beval->balloonShell);
 # endif
+# ifdef FEAT_VARTABS
+    if (beval->vts)
+       vim_free(beval->vts);
+# endif
     vim_free(beval);
 }
 #endif
@@ -412,6 +419,11 @@ get_beval_info(beval, getword, winp, lnump, textp, colp)
                *lnump = lnum;
                *textp = lbuf;
                *colp = col;
+#ifdef FEAT_VARTABS
+               if (beval->vts)
+                   vim_free(beval->vts);
+               beval->vts = tabstop_copy(wp->w_buffer->b_p_vts_ary);
+#endif
                beval->ts = wp->w_buffer->b_p_ts;
                return OK;
            }
diff --git a/src/gui_beval.h b/src/gui_beval.h
--- a/src/gui_beval.h
+++ b/src/gui_beval.h
@@ -58,6 +58,9 @@ typedef struct BalloonEvalStruct
     BeState            showState;      /* tells us whats currently going on */
 # endif
 #endif
+#ifdef FEAT_VARTABS
+    int                        *vts;           /* vartabstop setting for this 
buffer */
+#endif
     int                        ts;             /* tabstop setting for this 
buffer */
     char_u             *msg;
     void               (*msgCB)__ARGS((struct BalloonEvalStruct *, int));
diff --git a/src/gui_w32.c b/src/gui_w32.c
--- a/src/gui_w32.c
+++ b/src/gui_w32.c
@@ -4945,6 +4945,9 @@ gui_mch_create_beval_area(target, mesg, mesgCB, 
clientData)
        beval->msg = mesg;
        beval->msgCB = mesgCB;
        beval->clientData = clientData;
+#ifdef FEAT_VARTABS
+       beval->vts = 0;
+#endif
 
        InitCommonControls();
        cur_beval = beval;
@@ -5003,6 +5006,10 @@ TrackUserActivity(UINT uMsg)
 gui_mch_destroy_beval_area(beval)
     BalloonEval        *beval;
 {
+#ifdef FEAT_VARTABS
+    if (beval->vts)
+       vim_free(beval->vts);
+#endif
     vim_free(beval);
 }
 #endif /* FEAT_BEVAL */
diff --git a/src/hardcopy.c b/src/hardcopy.c
--- a/src/hardcopy.c
+++ b/src/hardcopy.c
@@ -900,7 +900,12 @@ hardcopy_line(psettings, page_line, ppos)
        if (line[col] == TAB || tab_spaces != 0)
        {
            if (tab_spaces == 0)
+#ifdef FEAT_VARTABS
+               tab_spaces = tabstop_padding(print_pos, curbuf->b_p_ts,
+                                                       curbuf->b_p_vts_ary);
+#else
                tab_spaces = (int)(curbuf->b_p_ts - (print_pos % 
curbuf->b_p_ts));
+#endif
 
            while (tab_spaces > 0)
            {
diff --git a/src/message.c b/src/message.c
--- a/src/message.c
+++ b/src/message.c
@@ -1719,7 +1719,11 @@ msg_prt_line(s, list)
            if (c == TAB && (!list || lcs_tab1))
            {
                /* tab amount depends on current column */
+#ifdef FEAT_VARTABS
+               n_extra = tabstop_padding(col, curbuf->b_p_ts, 
curbuf->b_p_vts_ary) - 1;
+#else
                n_extra = curbuf->b_p_ts - col % curbuf->b_p_ts - 1;
+#endif
                if (!list)
                {
                    c = ' ';
diff --git a/src/misc1.c b/src/misc1.c
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -32,7 +32,11 @@ static garray_T      ga_users;
     int
 get_indent()
 {
+#ifdef FEAT_VARTABS
+    return get_indent_str_vtab(ml_get_curline(), (int)curbuf->b_p_ts, 
curbuf->b_p_vts_ary);
+#else
     return get_indent_str(ml_get_curline(), (int)curbuf->b_p_ts, FALSE);
+#endif
 }
 
 /*
@@ -42,7 +46,11 @@ get_indent()
 get_indent_lnum(lnum)
     linenr_T   lnum;
 {
+#ifdef FEAT_VARTABS
+    return get_indent_str_vtab(ml_get(lnum), curbuf->b_p_ts, 
curbuf->b_p_vts_ary);
+#else
     return get_indent_str(ml_get(lnum), (int)curbuf->b_p_ts, FALSE);
+#endif
 }
 
 #if defined(FEAT_FOLDING) || defined(PROTO)
@@ -55,7 +63,11 @@ get_indent_buf(buf, lnum)
     buf_T      *buf;
     linenr_T   lnum;
 {
+#ifdef FEAT_VARTABS
+    return get_indent_str_vtab(ml_get_buf(buf, lnum, FALSE), 
(int)curbuf->b_p_ts, buf->b_p_vts_ary);
+#else
     return get_indent_str(ml_get_buf(buf, lnum, FALSE), (int)buf->b_p_ts, 
FALSE);
+#endif
 }
 #endif
 
@@ -90,6 +102,32 @@ get_indent_str(ptr, ts, list)
     return count;
 }
 
+#ifdef FEAT_VARTABS
+/*
+ * count the size (in window cells) of the indent in line "ptr", using
+ * variable tabstops
+ */
+    int
+get_indent_str_vtab(ptr, ts, vts)
+    char_u     *ptr;
+    int                ts;
+    int                *vts;
+{
+    int                count = 0;
+
+    for ( ; *ptr; ++ptr)
+    {
+       if (*ptr == TAB)    /* count a tab for what it is worth */
+           count += tabstop_padding(count, ts, vts);
+       else if (*ptr == ' ')
+           ++count;            /* count a space for one */
+       else
+           break;
+    }
+    return count;
+}
+#endif
+
 /*
  * Set the indent of the current line.
  * Leaves the cursor on the first non-blank in the line.
@@ -114,6 +152,9 @@ set_indent(size, flags)
     int                line_len;
     int                doit = FALSE;
     int                ind_done = 0;       /* measured in spaces */
+#ifdef FEAT_VARTABS
+    int                ind_col = 0;
+#endif
     int                tab_pad;
     int                retval = FALSE;
     int                orig_char_len = -1; /* number of initial whitespace 
chars when
@@ -146,8 +187,13 @@ set_indent(size, flags)
            {
                if (*p == TAB)
                {
+#ifdef FEAT_VARTABS
+                   tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
+                                                       curbuf->b_p_vts_ary);
+#else
                    tab_pad = (int)curbuf->b_p_ts
                                           - (ind_done % (int)curbuf->b_p_ts);
+#endif
                    /* stop if this tab will overshoot the target */
                    if (todo < tab_pad)
                        break;
@@ -164,23 +210,50 @@ set_indent(size, flags)
                ++p;
            }
 
+#ifdef FEAT_VARTABS
+           /* These diverge from this point. */
+           ind_col = ind_done;
+#endif
            /* Set initial number of whitespace chars to copy if we are
             * preserving indent but expandtab is set */
            if (curbuf->b_p_et)
                orig_char_len = ind_len;
 
            /* Fill to next tabstop with a tab, if possible */
+#ifdef FEAT_VARTABS
+           tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
+                                               curbuf->b_p_vts_ary);
+#else
            tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+#endif
            if (todo >= tab_pad && orig_char_len == -1)
            {
                doit = TRUE;
                todo -= tab_pad;
                ++ind_len;
                /* ind_done += tab_pad; */
+#ifdef FEAT_VARTABS
+               ind_col += tab_pad;
+#endif
            }
        }
 
        /* count tabs required for indent */
+#ifdef FEAT_VARTABS
+       for (;;)
+       {
+           tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts, 
curbuf->b_p_vts_ary);
+           if (todo < tab_pad)
+               break;
+           if (*p != TAB)
+               doit = TRUE;
+           else
+               ++p;
+           todo -= tab_pad;
+           ++ind_len;
+           ind_col += tab_pad;
+       }
+#else
        while (todo >= (int)curbuf->b_p_ts)
        {
            if (*p != TAB)
@@ -191,6 +264,7 @@ set_indent(size, flags)
            ++ind_len;
            /* ind_done += (int)curbuf->b_p_ts; */
        }
+#endif
     }
     /* count spaces required for indent */
     while (todo > 0)
@@ -265,8 +339,13 @@ set_indent(size, flags)
            {
                if (*p == TAB)
                {
+#ifdef FEAT_VARTABS
+                   tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
+                                                       curbuf->b_p_vts_ary);
+#else
                    tab_pad = (int)curbuf->b_p_ts
                                           - (ind_done % (int)curbuf->b_p_ts);
+#endif
                    /* stop if this tab will overshoot the target */
                    if (todo < tab_pad)
                        break;
@@ -282,21 +361,41 @@ set_indent(size, flags)
            }
 
            /* Fill to next tabstop with a tab, if possible */
+#ifdef FEAT_VARTABS
+           tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts,
+                                               curbuf->b_p_vts_ary);
+#else
            tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+#endif
            if (todo >= tab_pad)
            {
                *s++ = TAB;
                todo -= tab_pad;
+#ifdef FEAT_VARTABS
+               ind_done += tab_pad;
+#endif
            }
 
            p = skipwhite(p);
        }
 
+#ifdef FEAT_VARTABS
+       for (;;)
+       {
+           tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts, 
curbuf->b_p_vts_ary);
+           if (todo < tab_pad)
+               break;
+           *s++ = TAB;
+           todo -= tab_pad;
+           ind_done += tab_pad;
+       }
+#else
        while (todo >= (int)curbuf->b_p_ts)
        {
            *s++ = TAB;
            todo -= (int)curbuf->b_p_ts;
        }
+#endif
     }
     while (todo > 0)
     {
@@ -351,6 +450,9 @@ copy_indent(size, src)
     int                tab_pad;
     int                ind_done;
     int                round;
+#ifdef FEAT_VARTABS
+    int                ind_col;
+#endif
 
     /* Round 1: compute the number of characters needed for the indent
      * Round 2: copy the characters. */
@@ -359,6 +461,9 @@ copy_indent(size, src)
        todo = size;
        ind_len = 0;
        ind_done = 0;
+#ifdef FEAT_VARTABS
+       ind_col = 0;
+#endif
        s = src;
 
        /* Count/copy the usable portion of the source line */
@@ -366,18 +471,28 @@ copy_indent(size, src)
        {
            if (*s == TAB)
            {
+#ifdef FEAT_VARTABS
+               tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts, 
curbuf->b_p_vts_ary);
+#else
                tab_pad = (int)curbuf->b_p_ts
                                           - (ind_done % (int)curbuf->b_p_ts);
+#endif
                /* Stop if this tab will overshoot the target */
                if (todo < tab_pad)
                    break;
                todo -= tab_pad;
                ind_done += tab_pad;
+#ifdef FEAT_VARTABS
+               ind_col += tab_pad;
+#endif
            }
            else
            {
                --todo;
                ++ind_done;
+#ifdef FEAT_VARTABS
+               ++ind_col;
+#endif
            }
            ++ind_len;
            if (p != NULL)
@@ -386,22 +501,46 @@ copy_indent(size, src)
        }
 
        /* Fill to next tabstop with a tab, if possible */
+#ifdef FEAT_VARTABS
+       tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts, 
curbuf->b_p_vts_ary);
+#else
        tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+#endif
        if (todo >= tab_pad && !curbuf->b_p_et)
        {
            todo -= tab_pad;
            ++ind_len;
+#ifdef FEAT_VARTABS
+           ind_col += tab_pad;
+#endif
            if (p != NULL)
                *p++ = TAB;
        }
 
        /* Add tabs required for indent */
-       while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et)
-       {
-           todo -= (int)curbuf->b_p_ts;
-           ++ind_len;
-           if (p != NULL)
-               *p++ = TAB;
+       if (!curbuf->b_p_et)
+       {
+#ifdef FEAT_VARTABS
+           for (;;)
+           {
+               tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts, 
curbuf->b_p_vts_ary);
+               if (todo < tab_pad)
+                   break;
+               todo -= tab_pad;
+               ++ind_len;
+               ind_col += tab_pad;
+               if (p != NULL)
+                   *p++ = TAB;
+           }
+#else
+           while (todo >= (int)curbuf->b_p_ts)
+           {
+               todo -= (int)curbuf->b_p_ts;
+               ++ind_len;
+               if (p != NULL)
+                   *p++ = TAB;
+           }
+#endif
        }
 
        /* Count/add spaces required for indent */
@@ -741,7 +880,12 @@ open_line(dir, flags, second_line_indent)
        /*
         * count white space on current line
         */
+#ifdef FEAT_VARTABS
+       newindent = get_indent_str_vtab(saved_line, curbuf->b_p_ts,
+                                                   curbuf->b_p_vts_ary);
+#else
        newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, FALSE);
+#endif
        if (newindent == 0 && !(flags & OPENLINE_COM_LIST))
            newindent = second_line_indent; /* for ^^D command in insert mode */
 
@@ -1264,7 +1408,12 @@ open_line(dir, flags, second_line_indent)
                                        || do_si
 #endif
                                                           )
+#ifdef FEAT_VARTABS
+                       newindent = get_indent_str_vtab(leader, curbuf->b_p_ts,
+                                                               
curbuf->b_p_vts_ary);
+#else
                        newindent = get_indent_str(leader, (int)curbuf->b_p_ts, 
FALSE);
+#endif
 
                    /* Add the indent offset */
                    if (newindent + off < 0)
diff --git a/src/ops.c b/src/ops.c
--- a/src/ops.c
+++ b/src/ops.c
@@ -378,6 +378,9 @@ shift_block(oap, amount)
     char_u             *newp, *oldp;
     int                        oldcol = curwin->w_cursor.col;
     int                        p_sw = (int)get_sw_value(curbuf);
+#ifdef FEAT_VARTABS
+    int                        *p_vts = curbuf->b_p_vts_ary;
+#endif
     int                        p_ts = (int)curbuf->b_p_ts;
     struct block_def   bd;
     int                        incr;
@@ -428,12 +431,19 @@ shift_block(oap, amount)
        }
        /* OK, now total=all the VWS reqd, and textstart points at the 1st
         * non-ws char in the block. */
+#ifdef FEAT_VARTABS
+       if (!curbuf->b_p_et)
+           tabstop_fromto(ws_vcol, ws_vcol + total, p_ts, p_vts, &i, &j);
+       else
+           j = total;
+#else
        if (!curbuf->b_p_et)
            i = ((ws_vcol % p_ts) + total) / p_ts; /* number of tabs */
        if (i)
            j = ((ws_vcol % p_ts) + total) % p_ts; /* number of spp */
        else
            j = total;
+#endif
        /* if we're splitting a TAB, allow for it */
        bd.textcol -= bd.pre_whitesp_c - (bd.startspaces != 0);
        len = (int)STRLEN(bd.textstart) + 1;
@@ -3558,10 +3568,18 @@ do_put(regname, dir, count, flags)
        {
            /* Don't need to insert spaces when "p" on the last position of a
             * tab or "P" on the first position. */
+#ifdef FEAT_VARTABS
+           int viscol = getviscol();
+           if (dir == FORWARD
+                   ? tabstop_padding(viscol, curbuf->b_p_ts, 
curbuf->b_p_vts_ary) != 1
+                                               : curwin->w_cursor.coladd > 0)
+               coladvance_force(viscol);
+#else
            if (dir == FORWARD
                    ? (int)curwin->w_cursor.coladd < curbuf->b_p_ts - 1
                                                : curwin->w_cursor.coladd > 0)
                coladvance_force(getviscol());
+#endif
            else
                curwin->w_cursor.coladd = 0;
        }
diff --git a/src/option.c b/src/option.c
--- a/src/option.c
+++ b/src/option.c
@@ -180,6 +180,10 @@
 # define PV_UDF                OPT_BUF(BV_UDF)
 #endif
 #define PV_WM          OPT_BUF(BV_WM)
+#ifdef FEAT_VARTABS
+#define PV_VSTS                OPT_BUF(BV_VSTS)
+#define PV_VTS         OPT_BUF(BV_VTS)
+#endif
 
 /*
  * Definition of the PV_ values for window-local options.
@@ -374,6 +378,10 @@ static int p_tx;
 static int     p_udf;
 #endif
 static long    p_wm;
+#ifdef FEAT_VARTABS
+static char_u  *p_vsts;
+static char_u  *p_vts;
+#endif
 #ifdef FEAT_KEYMAP
 static char_u  *p_keymap;
 #endif
@@ -389,6 +397,9 @@ static long p_tw_nopaste;
 static long    p_wm_nopaste;
 static long    p_sts_nopaste;
 static int     p_ai_nopaste;
+#ifdef FEAT_VARTABS
+static char_u  *p_vsts_nopaste;
+#endif
 
 struct vimoption
 {
@@ -2722,6 +2733,14 @@ static struct vimoption
     {"updatetime",  "ut",   P_NUM|P_VI_DEF,
                            (char_u *)&p_ut, PV_NONE,
                            {(char_u *)4000L, (char_u *)0L} SCRIPTID_INIT},
+#ifdef FEAT_VARTABS
+    {"varsofttabstop", "vsts",  P_STRING|P_VI_DEF|P_VIM|P_COMMA,
+                           (char_u *)&p_vsts, PV_VSTS,
+                           {(char_u *)"", (char_u *)0L}},
+    {"vartabstop",  "vts",  P_STRING|P_VI_DEF|P_VIM|P_RBUF|P_COMMA,
+                           (char_u *)&p_vts, PV_VTS,
+                           {(char_u *)"", (char_u *)0L} SCRIPTID_INIT},
+#endif
     {"verbose",            "vbs",  P_NUM|P_VI_DEF,
                            (char_u *)&p_verbose, PV_NONE,
                            {(char_u *)0L, (char_u *)0L} SCRIPTID_INIT},
@@ -5298,6 +5317,10 @@ didset_options()
 #ifdef FEAT_LINEBREAK
     briopt_check(curwin);
 #endif
+#ifdef FEAT_VARTABS
+    tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_ary);
+    tabstop_set(curbuf->b_p_vts,  &curbuf->b_p_vts_ary);
+#endif
 }
 
 /*
@@ -5414,6 +5437,10 @@ check_buf_options(buf)
     check_string_option(&buf->b_p_lw);
 #endif
     check_string_option(&buf->b_p_bkc);
+#ifdef FEAT_VARTABS
+    check_string_option(&buf->b_p_vsts);
+    check_string_option(&buf->b_p_vts);
+#endif
 }
 
 /*
@@ -7047,6 +7074,88 @@ did_set_string_option(opt_idx, varp, new_value_alloced, 
oldval, errbuf,
     }
 #endif
 
+#ifdef FEAT_VARTABS
+    /* 'varsofttabstop' */
+    else if (varp == &(curbuf->b_p_vsts))
+    {
+       char_u *cp;
+
+       if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1]))
+       {
+           if (curbuf->b_p_vsts_ary)
+           {
+               vim_free(curbuf->b_p_vsts_ary);
+               curbuf->b_p_vsts_ary = 0;
+           }
+       }
+       else
+       {
+           for (cp = *varp; *cp; ++cp)
+           {
+               if (vim_isdigit(*cp))
+                   continue;
+               if (*cp == ',' && cp > *varp && *(cp-1) != ',')
+                   continue;
+               errmsg = e_invarg;
+               break;
+           }
+           if (errmsg == NULL)
+           {
+               int *oldarray = curbuf->b_p_vsts_ary;
+               if (tabstop_set(*varp, &(curbuf->b_p_vsts_ary)))
+               {
+                   if (oldarray)
+                       vim_free(oldarray);
+               }
+               else
+                   errmsg = e_invarg;
+           }
+       }
+    }
+
+    /* 'vartabstop' */
+    else if (varp == &(curbuf->b_p_vts))
+    {
+       char_u *cp;
+
+       if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1]))
+       {
+           if (curbuf->b_p_vts_ary)
+           {
+               vim_free(curbuf->b_p_vts_ary);
+               curbuf->b_p_vts_ary = 0;
+           }
+       }
+       else
+       {
+           for (cp = *varp; *cp; ++cp)
+           {
+               if (vim_isdigit(*cp))
+                   continue;
+               if (*cp == ',' && cp > *varp && *(cp-1) != ',')
+                   continue;
+               errmsg = e_invarg;
+               break;
+           }
+           if (errmsg == NULL)
+           {
+               int *oldarray = curbuf->b_p_vts_ary;
+               if (tabstop_set(*varp, &(curbuf->b_p_vts_ary)))
+               {
+                   if (oldarray)
+                       vim_free(oldarray);
+#ifdef FEAT_FOLDING
+                   if (foldmethodIsIndent(curwin))
+                       foldUpdateAll(curwin);
+#endif /* FEAT_FOLDING */
+               }
+               else
+                   errmsg = e_invarg;
+           }
+       }
+    }
+#endif
+
     /* Options that are a list of flags. */
     else
     {
@@ -8250,7 +8359,14 @@ set_num_option(opt_idx, varp, value, errbuf, errbuflen, 
opt_flags)
     if (curbuf->b_p_sw < 0)
     {
        errmsg = e_positive;
+#ifdef FEAT_VARTABS
+       /* Use the first 'vartabstop' value, or 'tabstop' if vts isn't in use. 
*/
+       curbuf->b_p_sw = tabstop_count(curbuf->b_p_vts_ary) > 0
+                      ? tabstop_first(curbuf->b_p_vts_ary)
+                      : curbuf->b_p_ts;
+#else
        curbuf->b_p_sw = curbuf->b_p_ts;
+#endif
     }
 
     /*
@@ -10228,6 +10344,10 @@ get_varp(p)
 #ifdef FEAT_KEYMAP
        case PV_KMAP:   return (char_u *)&(curbuf->b_p_keymap);
 #endif
+#ifdef FEAT_VARTABS
+       case PV_VSTS:   return (char_u *)&(curbuf->b_p_vsts);
+       case PV_VTS:    return (char_u *)&(curbuf->b_p_vts);
+#endif
        default:        EMSG(_("E356: get_varp ERROR"));
     }
     /* always return a valid pointer to avoid a crash! */
@@ -10540,6 +10660,15 @@ buf_copy_options(buf, flags)
 #endif
            buf->b_p_sts = p_sts;
            buf->b_p_sts_nopaste = p_sts_nopaste;
+#ifdef FEAT_VARTABS
+           buf->b_p_vsts = vim_strsave(p_vsts);
+           if (p_vsts && p_vsts != empty_option)
+               tabstop_set(p_vsts, &buf->b_p_vsts_ary);
+           else
+               buf->b_p_vsts_ary = 0;
+           buf->b_p_vsts_nopaste = p_vsts_nopaste
+                                ? vim_strsave(p_vsts_nopaste) : NULL;
+#endif
 #ifndef SHORT_FNAME
            buf->b_p_sn = p_sn;
 #endif
@@ -10655,12 +10784,27 @@ buf_copy_options(buf, flags)
             * or to a help buffer.
             */
            if (dont_do_help)
+           {
                buf->b_p_isk = save_p_isk;
+#ifdef FEAT_VARTABS
+               if (p_vts && p_vts != empty_option && !buf->b_p_vts_ary)
+                   tabstop_set(p_vts, &buf->b_p_vts_ary);
+               else
+                   buf->b_p_vts_ary = 0;
+#endif
+           }
            else
            {
                buf->b_p_isk = vim_strsave(p_isk);
                did_isk = TRUE;
                buf->b_p_ts = p_ts;
+#ifdef FEAT_VARTABS
+               buf->b_p_vts = vim_strsave(p_vts);
+               if (p_vts && p_vts != empty_option && !buf->b_p_vts_ary)
+                   tabstop_set(p_vts, &buf->b_p_vts_ary);
+               else
+                   buf->b_p_vts_ary = 0;
+#endif
                buf->b_help = FALSE;
 #ifdef FEAT_QUICKFIX
                if (buf->b_p_bt[0] == 'h')
@@ -11489,6 +11633,12 @@ paste_option_changed()
                buf->b_p_wm_nopaste = buf->b_p_wm;
                buf->b_p_sts_nopaste = buf->b_p_sts;
                buf->b_p_ai_nopaste = buf->b_p_ai;
+#ifdef FEAT_VARTABS
+               if (buf->b_p_vsts_nopaste)
+                   vim_free(buf->b_p_vsts_nopaste);
+               buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != 
empty_option
+                                    ? vim_strsave(buf->b_p_vsts) : NULL;
+#endif
            }
 
            /* save global options */
@@ -11505,6 +11655,11 @@ paste_option_changed()
            p_wm_nopaste = p_wm;
            p_sts_nopaste = p_sts;
            p_ai_nopaste = p_ai;
+#ifdef FEAT_VARTABS
+           if (p_vsts_nopaste)
+               vim_free(p_vsts_nopaste);
+           p_vsts_nopaste = p_vsts && p_vsts != empty_option ? 
vim_strsave(p_vsts) : NULL;
+#endif
        }
 
        /*
@@ -11518,6 +11673,14 @@ paste_option_changed()
            buf->b_p_wm = 0;        /* wrapmargin is 0 */
            buf->b_p_sts = 0;       /* softtabstop is 0 */
            buf->b_p_ai = 0;        /* no auto-indent */
+#ifdef FEAT_VARTABS
+           if (buf->b_p_vsts)
+               free_string_option(buf->b_p_vsts);
+           buf->b_p_vsts = empty_option;
+           if (buf->b_p_vsts_ary)
+               vim_free(buf->b_p_vsts_ary);
+           buf->b_p_vsts_ary = 0;
+#endif
        }
 
        /* set global options */
@@ -11538,6 +11701,11 @@ paste_option_changed()
        p_wm = 0;
        p_sts = 0;
        p_ai = 0;
+#ifdef FEAT_VARTABS
+       if (p_vsts)
+           free_string_option(p_vsts);
+       p_vsts = empty_option;
+#endif
     }
 
     /*
@@ -11552,6 +11720,18 @@ paste_option_changed()
            buf->b_p_wm = buf->b_p_wm_nopaste;
            buf->b_p_sts = buf->b_p_sts_nopaste;
            buf->b_p_ai = buf->b_p_ai_nopaste;
+#ifdef FEAT_VARTABS
+           if (buf->b_p_vsts)
+               free_string_option(buf->b_p_vsts);
+           buf->b_p_vsts = buf->b_p_vsts_nopaste
+                        ? vim_strsave(buf->b_p_vsts_nopaste) : empty_option;
+           if (buf->b_p_vsts_ary)
+               vim_free(buf->b_p_vsts_ary);
+           if (buf->b_p_vsts && buf->b_p_vsts != empty_option)
+               tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_ary);
+           else
+               buf->b_p_vsts_ary = 0;
+#endif
        }
 
        /* restore global options */
@@ -11572,6 +11752,11 @@ paste_option_changed()
        p_wm = p_wm_nopaste;
        p_sts = p_sts_nopaste;
        p_ai = p_ai_nopaste;
+#ifdef FEAT_VARTABS
+       if (p_vsts)
+           free_string_option(p_vsts);
+       p_vsts = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) : empty_option;
+#endif
     }
 
     old_p_paste = p_paste;
@@ -11914,6 +12099,301 @@ check_ff_value(p)
     return check_opt_strings(p, p_ff_values, FALSE);
 }
 
+#ifdef FEAT_VARTABS
+
+/*
+ * Set the integer values corresponding to the string setting of 'vartabstop'.
+ */
+    int
+tabstop_set(var, array)
+    char_u     *var;
+    int                **array;
+{
+    int valcount = 1;
+    int t;
+    char_u *cp;
+
+    if ((!var[0] || (var[0] == '0' && !var[1])))
+    {
+       *array = (int *)0;
+       return TRUE;
+    }
+
+    for (cp = var; *cp; ++cp)
+    {
+       if (cp == var || *(cp - 1) == ',')
+           if (atoi((char *)cp) <= 0)
+               return FALSE;
+
+       if (VIM_ISDIGIT(*cp))
+           continue;
+       if (*cp == ',' && cp > var && *(cp - 1) != ',')
+       {
+           ++valcount;
+           continue;
+       }
+       return FALSE;
+    }
+
+    *array = (int *) alloc((unsigned) ((valcount + 1) * sizeof(int)));
+    (*array)[0] = valcount;
+
+    t = 1;
+    for (cp = var; *cp;)
+    {
+       (*array)[t++] = atoi((char *)cp);
+       while (*cp && *cp != ',')
+           ++cp;
+       if (*cp)
+           ++cp;
+    }
+
+    return TRUE;
+}
+
+/*
+ * Calculate the number of screen spaces a tab will occupy.
+ * If vts is set then the tab widths are taken from that array,
+ * otherwise the value of ts is used.
+ */
+    int
+tabstop_padding(col, ts, vts)
+    colnr_T    col;
+    int                ts;
+    int                *vts;
+{
+    int                tabcount;
+    colnr_T    tabcol = 0;
+    int                t;
+    int                padding = 0;
+
+    if (ts == 0)
+       ts = 8;
+    if (vts == 0 || vts[0] == 0)
+       return ts - (col % ts);
+
+    tabcount = vts[0];
+
+    for (t = 1; t <= tabcount; ++t)
+    {
+       tabcol += vts[t];
+       if (tabcol > col)
+       {
+           padding = (int)(tabcol - col);
+           break;
+       }
+    }
+    if (t > tabcount)
+       padding = vts[tabcount] - (int)((col - tabcol) % vts[tabcount]);
+
+    return padding;
+}
+
+/*
+ * Find the size of the tab that covers a particular column.
+ */
+    int
+tabstop_at(col, ts, vts)
+    colnr_T    col;
+    int                ts;
+    int                *vts;
+{
+    int                tabcount;
+    colnr_T    tabcol = 0;
+    int                t;
+    int                tab_size = 0;
+
+    if (vts == 0 || vts[0] == 0)
+       return ts;
+
+    tabcount = vts[0];
+    for (t = 1; t <= tabcount; ++t)
+    {
+       tabcol += vts[t];
+       if (tabcol > col)
+       {
+           tab_size = vts[t];
+           break;
+       }
+    }
+    if (t > tabcount)
+       tab_size = vts[tabcount];
+
+    return tab_size;
+}
+
+/*
+ * Find the column on which a tab starts.
+ */
+    colnr_T
+tabstop_start(col, ts, vts)
+    colnr_T    col;
+    int                ts;
+    int                *vts;
+{
+    int                tabcount;
+    colnr_T    tabcol = 0;
+    int                t;
+
+    if (vts == 0 || vts[0] == 0)
+       return (col / ts) * ts;
+
+    tabcount = vts[0];
+    for (t = 1; t <= tabcount; ++t)
+    {
+       tabcol += vts[t];
+       if (tabcol > col)
+           return tabcol - vts[t];
+    }
+
+    int excess = tabcol % vts[tabcount];
+    return excess + ((col - excess) / vts[tabcount]) * vts[tabcount];
+}
+
+/*
+ * Find the number of tabs and spaces necessary to get from one column
+ * to another.
+ */
+    void
+tabstop_fromto(start_col, end_col, ts, vts, ntabs, nspcs)
+    colnr_T    start_col;
+    colnr_T    end_col;
+    int                ts;
+    int                *vts;
+    int                *ntabs;
+    int                *nspcs;
+{
+    int                spaces = end_col - start_col;
+    colnr_T    tabcol = 0;
+    int                padding = 0;
+    int                tabcount;
+    int                t;
+
+    if (vts == 0 || vts[0] == 0)
+    {
+       int tabs = 0;
+       int initspc = ts - (start_col % ts);
+       if (spaces >= initspc)
+       {
+           spaces -= initspc;
+           tabs++;
+       }
+       tabs += spaces / ts;
+       spaces -= (spaces / ts) * ts;
+
+       *ntabs = tabs;
+       *nspcs = spaces;
+       return;
+    }
+
+    /* Find the padding needed to reach the next tabstop. */
+    tabcount = vts[0];
+    for (t = 1; t <= tabcount; ++t)
+    {
+       tabcol += vts[t];
+       if (tabcol > start_col)
+       {
+           padding = (int)(tabcol - start_col);
+           break;
+       }
+    }
+    if (t > tabcount)
+       padding = vts[tabcount] - (int)((start_col - tabcol) % vts[tabcount]);
+
+    /* If the space needed is less than the padding no tabs can be used. */
+    if (spaces < padding)
+    {
+       *ntabs = 0;
+       *nspcs = spaces;
+       return;
+    }
+
+    *ntabs = 1;
+    spaces -= padding;
+
+    /* At least one tab has been used. See if any more will fit. */
+    while (spaces != 0 && ++t <= tabcount)
+    {
+       padding = vts[t];
+       if (spaces < padding)
+       {
+           *nspcs = spaces;
+           return;
+       }
+       ++*ntabs;
+       spaces -= padding;
+    }
+
+    *ntabs += spaces / vts[tabcount];
+    *nspcs =  spaces % vts[tabcount];
+}
+
+/*
+ * See if two tabstop arrays contain the same values.
+ */
+    int
+tabstop_eq(ts1, ts2)
+    int                *ts1;
+    int                *ts2;
+{
+    int                t;
+
+    if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0))
+       return FALSE;
+    if (ts1 == ts2)
+       return TRUE;
+    if (ts1[0] != ts2[0])
+       return FALSE;
+
+    for (t = 1; t <= ts1[0]; ++t)
+       if (ts1[t] != ts2[t])
+           return FALSE;
+
+    return TRUE;
+}
+
+/*
+ * Copy a tabstop array, allocating space for the new array.
+ */
+    int *
+tabstop_copy(oldts)
+    int                *oldts;
+{
+    int                *newts;
+    int                t;
+
+    if (oldts == 0)
+       return 0;
+
+    newts = (int *) alloc((unsigned) ((oldts[0] + 1) * sizeof(int)));
+    for (t = 0; t <= oldts[0]; ++t)
+       newts[t] = oldts[t];
+
+    return newts;
+}
+
+/*
+ * Return a count of the number of tabstops.
+ */
+    int
+tabstop_count(ts)
+    int                *ts;
+{
+    return ts ? ts[0] : 0;
+}
+
+/*
+ * Return the first tabstop, or 8 if there are no tabstops defined.
+ */
+    int
+tabstop_first(ts)
+    int                *ts;
+{
+    return ts ? ts[1] : 8;
+}
+
+#endif
+
 /*
  * Return the effective shiftwidth value for current buffer, using the
  * 'tabstop' value when 'shiftwidth' is zero.
diff --git a/src/option.h b/src/option.h
--- a/src/option.h
+++ b/src/option.h
@@ -1037,6 +1037,10 @@ enum
     , BV_UDF
     , BV_UL
     , BV_WM
+#ifdef FEAT_VARTABS
+    , BV_VSTS
+    , BV_VTS
+#endif
     , BV_COUNT     /* must be the last one */
 };
 
diff --git a/src/proto/option.pro b/src/proto/option.pro
--- a/src/proto/option.pro
+++ b/src/proto/option.pro
@@ -59,6 +59,17 @@ int can_bs __ARGS((int what));
 void save_file_ff __ARGS((buf_T *buf));
 int file_ff_differs __ARGS((buf_T *buf, int ignore_empty));
 int check_ff_value __ARGS((char_u *p));
+#ifdef FEAT_VARTABS
+int tabstop_set __ARGS((char_u *var, int **array));
+int tabstop_padding __ARGS((colnr_T col, int ts, int *vts));
+colnr_T tabstop_start __ARGS((colnr_T col, int ts, int *vts));
+int tabstop_at __ARGS((colnr_T col, int ts, int *vts));
+void tabstop_fromto __ARGS((colnr_T start_col, colnr_T end_col, int ts, int 
*vts, int *ntabs, int *nspcs));
+int tabstop_eq __ARGS((int *ts1, int *ts2));
+int *tabstop_copy __ARGS((int *oldts));
+int tabstop_count __ARGS((int *ts));
+int tabstop_first __ARGS((int *ts));
+#endif
 long get_sw_value __ARGS((buf_T *buf));
 long get_sts_value __ARGS((void));
 void find_mps_values __ARGS((int *initc, int *findc, int *backwards, int 
switchit));
diff --git a/src/screen.c b/src/screen.c
--- a/src/screen.c
+++ b/src/screen.c
@@ -4509,6 +4509,13 @@ win_line(wp, lnum, startrow, endrow, nochange)
                    /* tab amount depends on current column */
                    tab_len = (int)wp->w_buffer->b_p_ts
                                        - vcol % (int)wp->w_buffer->b_p_ts - 1;
+#ifdef FEAT_VARTABS
+                   tab_len = tabstop_padding(vcol, wp->w_buffer->b_p_ts,
+                                                   wp->w_buffer->b_p_vts_ary) 
- 1;
+#else
+                   tab_len = (int)wp->w_buffer->b_p_ts
+                                       - vcol % (int)wp->w_buffer->b_p_ts - 1;
+#endif
 #ifdef FEAT_LINEBREAK
                    if (!wp->w_p_lbr || !wp->w_p_list)
 #endif
diff --git a/src/structs.h b/src/structs.h
--- a/src/structs.h
+++ b/src/structs.h
@@ -1643,6 +1643,13 @@ struct file_buffer
     long       b_p_wm;         /* 'wrapmargin' */
     long       b_p_wm_nobin;   /* b_p_wm saved for binary mode */
     long       b_p_wm_nopaste; /* b_p_wm saved for paste mode */
+#ifdef FEAT_VARTABS
+    char_u     *b_p_vsts;      /* 'varsofttabstop' */
+    int                *b_p_vsts_ary;  /* 'varsofttabstop' in internal format 
*/
+    char_u     *b_p_vsts_nopaste; /* b_p_vsts saved for paste mode */
+    char_u     *b_p_vts;       /* 'vartabstop' */
+    int                *b_p_vts_ary;   /* 'vartabstop' in internal format */
+#endif
 #ifdef FEAT_KEYMAP
     char_u     *b_p_keymap;    /* 'keymap' */
 #endif
diff --git a/src/testdir/Make_amiga.mak b/src/testdir/Make_amiga.mak
--- a/src/testdir/Make_amiga.mak
+++ b/src/testdir/Make_amiga.mak
@@ -47,7 +47,8 @@ SCRIPTS = test1.out test3.out test4.out test5.out test6.out \
                test_options.out \
                test_qf_title.out \
                test_signs.out \
-               test_utf8.out
+               test_utf8.out \
+               test_vartabs.out
 
 .SUFFIXES: .in .out
 
@@ -182,3 +183,4 @@ test_options.out: test_options.in
 test_qf_title.out: test_qf_title.in
 test_signs.out: test_signs.in
 test_utf8.out: test_utf8.in
+test_vartabs.out: test_vartabs.in
diff --git a/src/testdir/Make_dos.mak b/src/testdir/Make_dos.mak
--- a/src/testdir/Make_dos.mak
+++ b/src/testdir/Make_dos.mak
@@ -46,7 +46,8 @@ SCRIPTS =     test3.out test4.out test5.out test6.out 
test7.out \
                test_options.out \
                test_qf_title.out \
                test_signs.out \
-               test_utf8.out
+               test_utf8.out \
+               test_vartabs.out
 
 SCRIPTS32 =    test50.out test70.out
 
diff --git a/src/testdir/Make_ming.mak b/src/testdir/Make_ming.mak
--- a/src/testdir/Make_ming.mak
+++ b/src/testdir/Make_ming.mak
@@ -66,7 +66,8 @@ SCRIPTS =     test3.out test4.out test5.out test6.out 
test7.out \
                test_options.out \
                test_qf_title.out \
                test_signs.out \
-               test_utf8.out
+               test_utf8.out \
+               test_vartabs.out
 
 SCRIPTS32 =    test50.out test70.out
 
diff --git a/src/testdir/Make_os2.mak b/src/testdir/Make_os2.mak
--- a/src/testdir/Make_os2.mak
+++ b/src/testdir/Make_os2.mak
@@ -48,7 +48,8 @@ SCRIPTS = test1.out test3.out test4.out test5.out test6.out \
                test_options.out \
                test_qf_title.out \
                test_signs.out \
-               test_utf8.out
+               test_utf8.out \
+               test_vartabs.out
 
 .SUFFIXES: .in .out
 
diff --git a/src/testdir/Make_vms.mms b/src/testdir/Make_vms.mms
--- a/src/testdir/Make_vms.mms
+++ b/src/testdir/Make_vms.mms
@@ -107,7 +107,8 @@ SCRIPT = test1.out  test2.out  test3.out  test4.out  
test5.out  \
         test_options.out \
         test_qf_title.out \
         test_signs.out \
-        test_utf8.out
+        test_utf8.out \
+        test_vartabs.out
 
 # Known problems:
 # test17: ?
diff --git a/src/testdir/Makefile b/src/testdir/Makefile
--- a/src/testdir/Makefile
+++ b/src/testdir/Makefile
@@ -44,7 +44,8 @@ SCRIPTS = test1.out test2.out test3.out test4.out test5.out 
test6.out \
                test_options.out \
                test_qf_title.out \
                test_signs.out \
-               test_utf8.out
+               test_utf8.out \
+               test_vartabs.out
 
 SCRIPTS_GUI = test16.out
 
diff --git a/src/testdir/test_vartabs.in b/src/testdir/test_vartabs.in
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_vartabs.in
@@ -0,0 +1,86 @@
+Test for variable tabstops
+
+STARTTEST
+:so small.vim
+:if !has("vartabs") | e! test.ok | w! test.out | qa! | endif
+:%d
+:" [1,2] Test normal operation of tabstops and softtabstops.
+:set ts=4
+aa     a       a       a       a:retab 8
+:.w! >>test.out
+:%d
+:set ts=8 sts=6
+ab     b       b       b       b:.w! >>test.out
+:%d
+:" [3,4] Test variable tabstops.
+:set sts=0 vts=4,8,4,8
+ac     c       c       c       c       c:retab 8
+:.w! >>test.out
+:%d
+:set et vts=4,8,4,8
+ad     d       d       d       d       d:.w! >>test.out
+:%d
+:" [5] Changing ts should have no effect if vts is in use.
+:set ts=6
+ae     e       e       e       e       e:.w! >>test.out
+:%d
+:" [6] Clearing vts should revert to using ts.
+:set vts=
+af     f       f       f       f       f:.w! >>test.out
+:%d
+:" [7] Test variable softtabstops.
+:set noet ts=8 vsts=12,2,6
+ag     g       g       g       g       g:.w! >>test.out
+:%d
+:" [8] Variable tabstops and softtabstops combined.
+:set vsts=6,12,8 vts=4,6,8
+ah     h       h       h       h:.w! >>test.out
+:%d
+:" [9] Retab with a single value, not using vts.
+:set ts=8 sts=0 vts= vsts=
+ai     i       i       i       i:retab 4
+o='9: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.-1,.w! >>test.out
+:%d
+:" [10] Retab with a single value, using vts.
+:set ts=8 sts=0 vts=6 vsts=
+aj     j       j       j       j:retab 4
+o='10: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.-1,.w! >>test.out
+:%d
+:" [11] Retab with multiple values, not using vts.
+:set ts=6 sts=0 vts= vsts=
+ak     k       k       k       k       k:retab 4,8
+o='11: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.-1,.w! >>test.out
+:%d
+:" [12] Retab with multiple values, using vts.
+:set ts=8 sts=0 vts=6 vsts=
+al     l       l       l       l       l:retab 4,8
+o='12: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.-1,.w! >>test.out
+:%d
+:" [13] Check that global and local values are set.
+:set ts=4 vts=6 sts=8 vsts=10
+am     m       m       m:.w! >>test.out
+o='13a: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.w! >>test.out
+:new
+an     n       n       n:.w! >>test.out
+o='13b: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.w! >>test.out
+:q!
+:%d
+:" [14] Check that local values only are set.
+:setlocal ts=5 vts=7 sts=9 vsts=11
+ao     o       o       o:.w! >>test.out
+o='14a: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.w! >>test.out
+:new
+ap     p       p       p:.w! >>test.out
+o='14b: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.w! >>test.out
+:q!
+:%d
+:" [15] Check that global values only are set.
+:setglobal ts=6 vts=8 sts=10 vsts=12
+aq     q       q       q:.w! >>test.out
+o='15a: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.w! >>test.out
+:new
+ar     r       r       r:.w! >>test.out
+o='15b: ts='.&ts.' vts='.&vts.' sts='.&sts.' vsts='.&vsts
:.w! >>test.out
+:qa!
+ENDTEST
+dummy text
diff --git a/src/testdir/test_vartabs.ok b/src/testdir/test_vartabs.ok
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_vartabs.ok
@@ -0,0 +1,28 @@
+a   a  a   a   a
+b     b            b     b     b
+c   c      c   c       c       c
+d   d       d   d       d       d
+e   e       e   e       e       e
+f     f     f     f     f     f
+g          g g     g     g     g
+h        h             h       h       h
+i              i               i               i               i
+9: ts=4 vts= sts=0 vsts=
+j        j             j         j             j
+10: ts=8 vts=4 sts=0 vsts=
+k        k     k     k     k     k
+11: ts=6 vts=4,8 sts=0 vsts=
+l        l     l     l     l     l
+12: ts=8 vts=4,8 sts=0 vsts=
+m          m             m             m
+13a: ts=4 vts=6 sts=8 vsts=10
+n          n             n             n
+13b: ts=4 vts=6 sts=8 vsts=10
+o          o            o           o
+14a: ts=5 vts=7 sts=9 vsts=11
+p          p             p             p
+14b: ts=4 vts=6 sts=8 vsts=10
+q          q            q           q
+15a: ts=5 vts=7 sts=9 vsts=11
+r          r           r           r
+15b: ts=6 vts=8 sts=10 vsts=12
diff --git a/src/version.c b/src/version.c
--- a/src/version.c
+++ b/src/version.c
@@ -639,6 +639,11 @@ static char *(features[]) =
 #else
        "-user_commands",
 #endif
+#ifdef FEAT_VARTABS
+       "+vartabs",
+#else
+       "-vartabs",
+#endif
 #ifdef FEAT_VERTSPLIT
        "+vertsplit",
 #else
@@ -1731,6 +1736,10 @@ static int included_patches[] =
 static char *(extra_patches[]) =
 {   /* Add your patch description below this line */
 /**/
+#ifdef FEAT_VARTABS
+    "variable tabstops",
+#endif
+/**/
     NULL
 };
 
diff --git a/src/workshop.c b/src/workshop.c
--- a/src/workshop.c
+++ b/src/workshop.c
@@ -50,7 +50,11 @@ static void   load_window(char *, int lnum);
 static void     warp_to_pc(int);
 #ifdef FEAT_BEVAL
 void            workshop_beval_cb(BalloonEval *, int);
+# ifdef FEAT_VARTABS
+static int      computeIndex(int, char_u *, int, int *);
+# else
 static int      computeIndex(int, char_u *, int);
+# endif
 #endif
 static char    *fixAccelText(char *);
 static void     addMenu(char *, char *, char *);
@@ -1537,7 +1541,11 @@ workshop_beval_cb(
             * a column number. Compute the index from col. Also set
             * line to 0 because thats what dbx expects.
             */
+#ifdef FEAT_VARTABS
+           idx = computeIndex(col, text, beval->ts, beval->vts);
+#else
            idx = computeIndex(col, text, beval->ts);
+#endif
            if (idx > 0)
            {
                lnum = 0;
@@ -1572,7 +1580,11 @@ workshop_beval_cb(
 computeIndex(
        int              wantedCol,
        char_u          *line,
-       int              ts)
+       int              ts
+#ifdef FEAT_VARTABS
+       int             *vts
+#else
+       )
 {
     int                 col = 0;
     int                 idx = 0;
@@ -1580,7 +1592,11 @@ computeIndex(
     while (line[idx])
     {
        if (line[idx] == '\t')
+#ifdef FEAT_VARTABS
+           col += tabstop_padding(col, ts, vts);
+#else
            col += ts - (col % ts);
+#endif
        else
            col++;
        idx++;

Attachment: pgpnzyvx0khRW.pgp
Description: PGP signature

Raspunde prin e-mail lui