I've been working on an implementation of the non-uniform tabstops
that were discussed about a month ago, and I have a patch ready for
trying out. I've done some testing on Linux and Windows XP; the parts
I'm able to test seem to be OK and on Linux it survives "make test"
cleanly, but I need people to try it out on other systems and test
the parts I can't reach.

With this patch the set tabstop, set softtabstop and retab commands
take a comma-separated list of tab widths (as in ":set ts=4,20,8" or
":retab 20,4"). Note that these are _widths_, not positions. The final
value is repeated as required. If only one value is used the behaviour
should be the same as with an unpatched Vim.

This change has implications for the ballooneval feature and the
Visual Workshop code (workshop.c), but I haven't been able to test
it because I don't have the necessary environment. The changes relate
to how the number of characters from the start of the line is mapped
on to the screen position, so I'd be grateful if someone who uses the
ballooneval feature can check that this mapping happens correctly
and that it doesn't dump cores all over the place.

The internal changes are as follows:--

The existing p_ts and p_sts global option variables and the buffer
variables b_p_ts and b_p_sts have changed from long to char_u*; the
corresponding options are now strings. Each of the buffer variables
is shadowed by an int[] array b_p_ts_ary and b_p_sts_ary. The zeroth
element of this array is the number of tabstops, and the remaining
elements are the tabstop values. Thus after "set ts=8,20,5" the values
in b_p_ts_ary are:

    [0]  3
    [1]  8
    [2]  20
    [3]  5

This internal implementation is handled by a set of functions at the
bottom of option.c. The only detail of the implementation that has
leaked out to the rest of Vim is the int* type of the variables.

This patch is against 7.1.135.

-- 
Matthew Winn

--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_dev" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

*** orig_src/buffer.c   2007-10-04 18:43:08.000000000 +0100
--- src/buffer.c        2007-10-05 14:45:22.000000000 +0100
***************
*** 1751,1756 ****
--- 1751,1767 ----
      clear_string_option(&buf->b_p_fo);
      clear_string_option(&buf->b_p_flp);
      clear_string_option(&buf->b_p_isk);
+     clear_string_option(&buf->b_p_sts);
+     if (buf->b_p_sts_nopaste)
+       vim_free(buf->b_p_sts_nopaste);
+     buf->b_p_sts_nopaste = 0;
+     if (buf->b_p_sts_ary)
+       vim_free(buf->b_p_sts_ary);
+     buf->b_p_sts_ary = 0;
+     clear_string_option(&buf->b_p_ts);
+     if (buf->b_p_ts_ary)
+       vim_free(buf->b_p_ts_ary);
+     buf->b_p_ts_ary = 0;
  #ifdef FEAT_KEYMAP
      clear_string_option(&buf->b_p_keymap);
      ga_clear(&buf->b_kmap_ga);
*** orig_src/charset.c  2007-10-04 18:43:08.000000000 +0100
--- src/charset.c       2007-10-04 20:20:07.000000000 +0100
***************
*** 799,807 ****
  #define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \
      if (*(p) == TAB && (!(wp)->w_p_list || lcs_tab1)) \
      { \
!       int ts; \
!       ts = (buf)->b_p_ts; \
!       return (int)(ts - (col % ts)); \
      } \
      else \
        return ptr2cells(p);
--- 799,805 ----
  #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_ary); \
      } \
      else \
        return ptr2cells(p);
***************
*** 1039,1044 ****
--- 1037,1043 ----
      int               size;
      colnr_T   col2;
      colnr_T   colmax;
+     colnr_T   orig_col = col;
      int               added;
  # ifdef FEAT_MBYTE
      int               mb_added = 0;
***************
*** 1147,1153 ****
        {
            added = vim_strsize(p_sbr);
            if (tab_corr)
!               size += (added / wp->w_buffer->b_p_ts) * wp->w_buffer->b_p_ts;
            else
                size += added;
            if (col != 0)
--- 1146,1155 ----
        {
            added = vim_strsize(p_sbr);
            if (tab_corr)
!           {
!               int ts = tabstop_at(orig_col, wp->w_buffer->b_p_ts_ary);
!               size += (added / ts) * ts;
!           }
            else
                size += added;
            if (col != 0)
***************
*** 1176,1185 ****
      int               n;
  
      if (*s == TAB && (!wp->w_p_list || lcs_tab1))
!     {
!       n = wp->w_buffer->b_p_ts;
!       return (int)(n - (col % n));
!     }
      n = ptr2cells(s);
      /* Add one cell for a double-width character in the last column of the
       * window, displayed with a ">". */
--- 1178,1184 ----
      int               n;
  
      if (*s == TAB && (!wp->w_p_list || lcs_tab1))
!       return tabstop_padding(col, wp->w_buffer->b_p_ts_ary);
      n = ptr2cells(s);
      /* Add one cell for a double-width character in the last column of the
       * window, displayed with a ">". */
***************
*** 1239,1245 ****
      char_u    *posptr;        /* points to char at pos->col */
      int               incr;
      int               head;
!     int               ts = wp->w_buffer->b_p_ts;
      int               c;
  
      vcol = 0;
--- 1238,1244 ----
      char_u    *posptr;        /* points to char at pos->col */
      int               incr;
      int               head;
!     int               *ts = wp->w_buffer->b_p_ts_ary;
      int               c;
  
      vcol = 0;
***************
*** 1274,1280 ****
            }
            /* A tab gets expanded, depending on the current column */
            if (c == TAB)
!               incr = ts - (vcol % ts);
            else
            {
  #ifdef FEAT_MBYTE
--- 1273,1279 ----
            }
            /* A tab gets expanded, depending on the current column */
            if (c == TAB)
!               incr = tabstop_padding(vcol, ts);
            else
            {
  #ifdef FEAT_MBYTE
*** orig_src/edit.c     2007-10-04 18:43:09.000000000 +0100
--- src/edit.c  2007-10-04 21:53:41.000000000 +0100
***************
*** 650,656 ****
            mincol = curwin->w_wcol;
            validate_cursor_col();
  
!           if ((int)curwin->w_wcol < (int)mincol - curbuf->b_p_ts
                    && curwin->w_wrow == W_WINROW(curwin)
                                                 + curwin->w_height - 1 - p_so
                    && (curwin->w_cursor.lnum != curwin->w_topline
--- 650,656 ----
            mincol = curwin->w_wcol;
            validate_cursor_col();
  
!           if ((int)curwin->w_wcol < (int)mincol - 
tabstop_at(get_nolist_virtcol(), curbuf->b_p_ts_ary)
                    && curwin->w_wrow == W_WINROW(curwin)
                                                 + curwin->w_height - 1 - p_so
                    && (curwin->w_cursor.lnum != curwin->w_topline
***************
*** 8369,8375 ****
         */
        if (       mode == BACKSPACE_CHAR
                && ((p_sta && in_indent)
!                   || (curbuf->b_p_sts != 0
                        && (*(ml_get_cursor() - 1) == TAB
                            || (*(ml_get_cursor() - 1) == ' '
                                && (!*inserted_space_p
--- 8369,8375 ----
         */
        if (       mode == BACKSPACE_CHAR
                && ((p_sta && in_indent)
!                   || (tabstop_count(curbuf->b_p_sts_ary) != 0
                        && (*(ml_get_cursor() - 1) == TAB
                            || (*(ml_get_cursor() - 1) == ' '
                                && (!*inserted_space_p
***************
*** 8383,8392 ****
  #endif
  
            *inserted_space_p = FALSE;
-           if (p_sta && in_indent)
-               ts = curbuf->b_p_sw;
-           else
-               ts = curbuf->b_p_sts;
            /* 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. */
--- 8383,8388 ----
***************
*** 8394,8400 ****
            dec_cursor();
            getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol);
            inc_cursor();
!           want_vcol = (want_vcol / ts) * ts;
  
            /* delete characters until we are at or before want_vcol */
            while (vcol > want_vcol
--- 8390,8399 ----
            dec_cursor();
            getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol);
            inc_cursor();
!           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_ary);
  
            /* delete characters until we are at or before want_vcol */
            while (vcol > want_vcol
***************
*** 9049,9056 ****
       * When nothing special, insert TAB like a normal character
       */
      if (!curbuf->b_p_et
!           && !(p_sta && ind && curbuf->b_p_ts != curbuf->b_p_sw)
!           && curbuf->b_p_sts == 0)
        return TRUE;
  
      if (stop_arrow() == FAIL)
--- 9048,9057 ----
       * When nothing special, insert TAB like a normal character
       */
      if (!curbuf->b_p_et
!           && !(p_sta && ind
!                && !(tabstop_count(curbuf->b_p_ts_ary) == 1
!                     && tabstop_first(curbuf->b_p_ts_ary) == curbuf->b_p_sw))
!           && tabstop_count(curbuf->b_p_sts_ary) == 0)
        return TRUE;
  
      if (stop_arrow() == FAIL)
***************
*** 9065,9076 ****
      AppendToRedobuff((char_u *)"\t");
  
      if (p_sta && ind)         /* insert tab in indent, use 'shiftwidth' */
        temp = (int)curbuf->b_p_sw;
!     else if (curbuf->b_p_sts > 0) /* use 'softtabstop' when set */
!       temp = (int)curbuf->b_p_sts;
      else                      /* otherwise use 'tabstop' */
!       temp = (int)curbuf->b_p_ts;
!     temp -= get_nolist_virtcol() % temp;
  
      /*
       * Insert the first space with ins_char().        It will delete one char 
in
--- 9066,9079 ----
      AppendToRedobuff((char_u *)"\t");
  
      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_sts_ary) > 0) /* use 'softtabstop' 
when set */
!       temp = tabstop_padding(get_nolist_virtcol(), curbuf->b_p_sts_ary);
      else                      /* otherwise use 'tabstop' */
!       temp = tabstop_padding(get_nolist_virtcol(), curbuf->b_p_ts_ary);
  
      /*
       * Insert the first space with ins_char().        It will delete one char 
in
***************
*** 9095,9101 ****
      /*
       * When 'expandtab' not set: Replace spaces by TABs where possible.
       */
!     if (!curbuf->b_p_et && (curbuf->b_p_sts || (p_sta && ind)))
      {
        char_u          *ptr;
  #ifdef FEAT_VREPLACE
--- 9098,9104 ----
      /*
       * When 'expandtab' not set: Replace spaces by TABs where possible.
       */
!     if (!curbuf->b_p_et && (tabstop_count(curbuf->b_p_sts_ary) > 0 || (p_sta 
&& ind)))
      {
        char_u          *ptr;
  #ifdef FEAT_VREPLACE
*** orig_src/ex_cmds.c  2007-10-04 18:43:09.000000000 +0100
--- src/ex_cmds.c       2007-10-05 14:45:45.000000000 +0100
***************
*** 572,598 ****
      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 */
!     int               new_ts;
      int               save_list;
      linenr_T  first_line = 0;         /* first changed line */
      linenr_T  last_line = 0;          /* last changed line */
  
      save_list = curwin->w_p_list;
      curwin->w_p_list = 0;         /* don't want list mode here */
  
!     new_ts = getdigits(&(eap->arg));
!     if (new_ts < 0)
      {
!       EMSG(_(e_positive));
        return;
      }
      if (new_ts == 0)
!       new_ts = curbuf->b_p_ts;
      for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
      {
        ptr = ml_get(lnum);
--- 572,610 ----
      long      vcol;
      long      start_col = 0;          /* For start of white-space string */
      long      start_vcol = 0;         /* For start of white-space string */
      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 */
!     int               *new_ts = 0;
      int               save_list;
      linenr_T  first_line = 0;         /* first changed line */
      linenr_T  last_line = 0;          /* last changed line */
+     char_u    *new_ts_str;            /* string value of tab argument */
  
      save_list = curwin->w_p_list;
      curwin->w_p_list = 0;         /* don't want list mode here */
  
!     new_ts_str = eap->arg;
!     if (vim_isdigit(*(eap->arg)) && !tabstop_set(eap->arg, &new_ts, FALSE))
      {
!       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_ts_ary;
!       new_ts_str = NULL;
!     }
!     else
!       new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str);
! 
      for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
      {
        ptr = ml_get(lnum);
***************
*** 625,638 ****
                    num_tabs = 0;
                    if (!curbuf->b_p_et)
                    {
!                       temp = new_ts - (start_vcol % new_ts);
!                       if (num_spaces >= temp)
!                       {
!                           num_spaces -= temp;
!                           num_tabs++;
!                       }
!                       num_tabs += num_spaces / new_ts;
!                       num_spaces -= (num_spaces / new_ts) * new_ts;
                    }
                    if (curbuf->b_p_et || got_tab ||
                                        (num_spaces + num_tabs < len))
--- 637,646 ----
                    num_tabs = 0;
                    if (!curbuf->b_p_et)
                    {
!                       int t, s;
!                       tabstop_fromto(start_vcol, vcol, new_ts, &t, &s);
!                       num_tabs = t;
!                       num_spaces = s;
                    }
                    if (curbuf->b_p_et || got_tab ||
                                        (num_spaces + num_tabs < len))
***************
*** 690,703 ****
      if (got_int)
        EMSG(_(e_interr));
  
!     if (curbuf->b_p_ts != new_ts)
        redraw_curbuf_later(NOT_VALID);
      if (first_line != 0)
        changed_lines(first_line, 0, last_line + 1, 0L);
  
      curwin->w_p_list = save_list;     /* restore 'list' */
  
!     curbuf->b_p_ts = new_ts;
      coladvance(curwin->w_curswant);
  
      u_clearline();
--- 698,720 ----
      if (got_int)
        EMSG(_(e_interr));
  
!     if (!tabstop_eq(curbuf->b_p_ts_ary, new_ts))
        redraw_curbuf_later(NOT_VALID);
      if (first_line != 0)
        changed_lines(first_line, 0, last_line + 1, 0L);
  
      curwin->w_p_list = save_list;     /* restore 'list' */
  
!     if (new_ts_str != NULL)           /* set the new tabstop */
!     {
!       int* old_ts_ary = curbuf->b_p_ts_ary;
!       set_string_option_direct((char_u *)"ts", -1, new_ts_str,
!                                               OPT_FREE|OPT_LOCAL, 0);
!       vim_free(new_ts_str);
!       curbuf->b_p_ts_ary = new_ts;
!       vim_free(old_ts_ary);
!     }
! 
      coladvance(curwin->w_curswant);
  
      u_clearline();
***************
*** 3448,3454 ****
            (void)buf_init_chartab(curbuf, FALSE);
        }
  
!       curbuf->b_p_ts = 8;             /* 'tabstop' is 8 */
        curwin->w_p_list = FALSE;       /* no list mode */
  
        curbuf->b_p_ma = FALSE;         /* not modifiable */
--- 3465,3473 ----
            (void)buf_init_chartab(curbuf, FALSE);
        }
  
!       set_string_option_direct((char_u *)"ts", -1, "8",       /* 'tabstop' is 
8 */
!                                                      OPT_FREE|OPT_LOCAL, 0);
!       tabstop_set("8", &(curbuf->b_p_ts_ary), FALSE); /* 'tabstop' is 8 */
        curwin->w_p_list = FALSE;       /* no list mode */
  
        curbuf->b_p_ma = FALSE;         /* not modifiable */
*** orig_src/gui_beval.c        2007-10-04 18:43:09.000000000 +0100
--- src/gui_beval.c     2007-10-04 18:44:03.000000000 +0100
***************
*** 221,226 ****
--- 221,227 ----
        beval->msg = mesg;
        beval->msgCB = mesgCB;
        beval->clientData = clientData;
+       beval->ts = 0;
  
        /*
         * Set up event handler which will keep its eyes on the pointer,
***************
*** 264,269 ****
--- 265,272 ----
  # else
      XtDestroyWidget(beval->balloonShell);
  # endif
+     if (beval->ts)
+       vim_free(beval->ts);
      vim_free(beval);
  }
  #endif
***************
*** 401,407 ****
                *lnump = lnum;
                *textp = lbuf;
                *colp = col;
!               beval->ts = wp->w_buffer->b_p_ts;
                return OK;
            }
        }
--- 404,412 ----
                *lnump = lnum;
                *textp = lbuf;
                *colp = col;
!               if (beval->ts)
!                   vim_free(beval->ts);
!               beval->ts = tabstop_copy(wp->w_buffer->b_p_ts_ary);
                return OK;
            }
        }
*** orig_src/gui_beval.h        2007-10-04 18:43:09.000000000 +0100
--- src/gui_beval.h     2007-10-04 18:44:03.000000000 +0100
***************
*** 58,64 ****
      BeState           showState;      /* tells us whats currently going on */
  # endif
  #endif
!     int                       ts;             /* tabstop setting for this 
buffer */
      char_u            *msg;
      void              (*msgCB)__ARGS((struct BalloonEvalStruct *, int));
      void              *clientData;    /* For callback */
--- 58,64 ----
      BeState           showState;      /* tells us whats currently going on */
  # endif
  #endif
!     int                       *ts;            /* tabstop setting for this 
buffer */
      char_u            *msg;
      void              (*msgCB)__ARGS((struct BalloonEvalStruct *, int));
      void              *clientData;    /* For callback */
*** orig_src/gui_w32.c  2007-10-04 18:43:12.000000000 +0100
--- src/gui_w32.c       2007-10-04 18:44:03.000000000 +0100
***************
*** 4697,4702 ****
--- 4697,4703 ----
        beval->msg = mesg;
        beval->msgCB = mesgCB;
        beval->clientData = clientData;
+       beval->ts = 0;
  
        InitCommonControls();
        cur_beval = beval;
***************
*** 4757,4762 ****
--- 4758,4765 ----
  gui_mch_destroy_beval_area(beval)
      BalloonEval       *beval;
  {
+     if (beval->ts)
+       vim_free(beval->ts);
      vim_free(beval);
  }
  #endif /* FEAT_BEVAL */
*** orig_src/hardcopy.c 2007-10-04 18:43:09.000000000 +0100
--- src/hardcopy.c      2007-10-04 18:44:03.000000000 +0100
***************
*** 901,907 ****
        if (line[col] == TAB || tab_spaces != 0)
        {
            if (tab_spaces == 0)
!               tab_spaces = (int)(curbuf->b_p_ts - (print_pos % 
curbuf->b_p_ts));
  
            while (tab_spaces > 0)
            {
--- 901,907 ----
        if (line[col] == TAB || tab_spaces != 0)
        {
            if (tab_spaces == 0)
!               tab_spaces = tabstop_padding(print_pos, curbuf->b_p_ts_ary);
  
            while (tab_spaces > 0)
            {
*** orig_src/message.c  2007-10-04 18:43:09.000000000 +0100
--- src/message.c       2007-10-04 18:44:03.000000000 +0100
***************
*** 1608,1614 ****
            if (c == TAB && (!list || lcs_tab1))
            {
                /* tab amount depends on current column */
!               n_extra = curbuf->b_p_ts - col % curbuf->b_p_ts - 1;
                if (!list)
                {
                    c = ' ';
--- 1608,1614 ----
            if (c == TAB && (!list || lcs_tab1))
            {
                /* tab amount depends on current column */
!               n_extra = tabstop_padding(col, curbuf->b_p_ts_ary) - 1;
                if (!list)
                {
                    c = ' ';
*** orig_src/misc1.c    2007-10-04 18:43:09.000000000 +0100
--- src/misc1.c 2007-10-04 18:44:03.000000000 +0100
***************
*** 28,34 ****
      int
  get_indent()
  {
!     return get_indent_str(ml_get_curline(), (int)curbuf->b_p_ts);
  }
  
  /*
--- 28,34 ----
      int
  get_indent()
  {
!     return get_indent_str_vtab(ml_get_curline(), curbuf->b_p_ts_ary);
  }
  
  /*
***************
*** 38,44 ****
  get_indent_lnum(lnum)
      linenr_T  lnum;
  {
!     return get_indent_str(ml_get(lnum), (int)curbuf->b_p_ts);
  }
  
  #if defined(FEAT_FOLDING) || defined(PROTO)
--- 38,44 ----
  get_indent_lnum(lnum)
      linenr_T  lnum;
  {
!     return get_indent_str_vtab(ml_get(lnum), curbuf->b_p_ts_ary);
  }
  
  #if defined(FEAT_FOLDING) || defined(PROTO)
***************
*** 51,57 ****
      buf_T     *buf;
      linenr_T  lnum;
  {
!     return get_indent_str(ml_get_buf(buf, lnum, FALSE), (int)buf->b_p_ts);
  }
  #endif
  
--- 51,57 ----
      buf_T     *buf;
      linenr_T  lnum;
  {
!     return get_indent_str_vtab(ml_get_buf(buf, lnum, FALSE), buf->b_p_ts_ary);
  }
  #endif
  
***************
*** 79,84 ****
--- 79,107 ----
  }
  
  /*
+  * count the size (in window cells) of the indent in line "ptr", using
+  * variable tabstops
+  */
+     int
+ get_indent_str_vtab(ptr, ts)
+     char_u    *ptr;
+     colnr_T   *ts;
+ {
+     int               count = 0;
+ 
+     for ( ; *ptr; ++ptr)
+     {
+       if (*ptr == TAB)    /* count a tab for what it is worth */
+           count += tabstop_padding(count, ts);
+       else if (*ptr == ' ')
+           ++count;            /* count a space for one */
+       else
+           break;
+     }
+     return count;
+ }
+ 
+ /*
   * Set the indent of the current line.
   * Leaves the cursor on the first non-blank in the line.
   * Caller must take care of undo.
***************
*** 102,107 ****
--- 125,131 ----
      int               line_len;
      int               doit = FALSE;
      int               ind_done = 0;       /* measured in spaces */
+     int               ind_col = 0;
      int               tab_pad;
      int               retval = FALSE;
      int               orig_char_len = -1; /* number of initial whitespace 
chars when
***************
*** 134,141 ****
            {
                if (*p == TAB)
                {
!                   tab_pad = (int)curbuf->b_p_ts
!                                          - (ind_done % (int)curbuf->b_p_ts);
                    /* stop if this tab will overshoot the target */
                    if (todo < tab_pad)
                        break;
--- 158,164 ----
            {
                if (*p == TAB)
                {
!                   tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts_ary);
                    /* stop if this tab will overshoot the target */
                    if (todo < tab_pad)
                        break;
***************
*** 152,183 ****
                ++p;
            }
  
            /* 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 */
!           tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
            if (todo >= tab_pad && orig_char_len == -1)
            {
                doit = TRUE;
                todo -= tab_pad;
                ++ind_len;
                /* ind_done += tab_pad; */
            }
        }
  
        /* count tabs required for indent */
!       while (todo >= (int)curbuf->b_p_ts)
        {
            if (*p != TAB)
                doit = TRUE;
            else
                ++p;
!           todo -= (int)curbuf->b_p_ts;
            ++ind_len;
!           /* ind_done += (int)curbuf->b_p_ts; */
        }
      }
      /* count spaces required for indent */
--- 175,213 ----
                ++p;
            }
  
+           /* These diverge from this point. */
+           ind_col = ind_done;
+ 
            /* 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 */
!           tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts_ary);
            if (todo >= tab_pad && orig_char_len == -1)
            {
                doit = TRUE;
                todo -= tab_pad;
                ++ind_len;
                /* ind_done += tab_pad; */
+               ind_col += tab_pad;
            }
        }
  
        /* count tabs required for indent */
!       for (;;)
        {
+           tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts_ary);
+           if (todo < tab_pad)
+               break;
            if (*p != TAB)
                doit = TRUE;
            else
                ++p;
!           todo -= tab_pad;
            ++ind_len;
!           ind_col += tab_pad;
        }
      }
      /* count spaces required for indent */
***************
*** 252,259 ****
            {
                if (*p == TAB)
                {
!                   tab_pad = (int)curbuf->b_p_ts
!                                          - (ind_done % (int)curbuf->b_p_ts);
                    /* stop if this tab will overshoot the target */
                    if (todo < tab_pad)
                        break;
--- 282,288 ----
            {
                if (*p == TAB)
                {
!                   tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts_ary);
                    /* stop if this tab will overshoot the target */
                    if (todo < tab_pad)
                        break;
***************
*** 269,288 ****
            }
  
            /* Fill to next tabstop with a tab, if possible */
!           tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
            if (todo >= tab_pad)
            {
                *s++ = TAB;
                todo -= tab_pad;
            }
  
            p = skipwhite(p);
        }
  
!       while (todo >= (int)curbuf->b_p_ts)
        {
            *s++ = TAB;
!           todo -= (int)curbuf->b_p_ts;
        }
      }
      while (todo > 0)
--- 298,322 ----
            }
  
            /* Fill to next tabstop with a tab, if possible */
!           tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts_ary);
            if (todo >= tab_pad)
            {
                *s++ = TAB;
                todo -= tab_pad;
+               ind_done += tab_pad;
            }
  
            p = skipwhite(p);
        }
  
!       for (;;)
        {
+           tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts_ary);
+           if (todo < tab_pad)
+               break;
            *s++ = TAB;
!           todo -= tab_pad;
!           ind_done += tab_pad;
        }
      }
      while (todo > 0)
***************
*** 329,334 ****
--- 363,369 ----
      int               line_len = 0;
      int               tab_pad;
      int               ind_done;
+     int               ind_col;
      int               round;
  
      /* Round 1: compute the number of characters needed for the indent
***************
*** 338,343 ****
--- 373,379 ----
        todo = size;
        ind_len = 0;
        ind_done = 0;
+       ind_col = 0;
        s = src;
  
        /* Count/copy the usable portion of the source line */
***************
*** 345,362 ****
        {
            if (*s == TAB)
            {
-               tab_pad = (int)curbuf->b_p_ts
-                                          - (ind_done % (int)curbuf->b_p_ts);
                /* Stop if this tab will overshoot the target */
                if (todo < tab_pad)
                    break;
                todo -= tab_pad;
                ind_done += tab_pad;
            }
            else
            {
                --todo;
                ++ind_done;
            }
            ++ind_len;
            if (p != NULL)
--- 381,399 ----
        {
            if (*s == TAB)
            {
                /* Stop if this tab will overshoot the target */
+               tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts_ary);
                if (todo < tab_pad)
                    break;
                todo -= tab_pad;
                ind_done += tab_pad;
+               ind_col += tab_pad;
            }
            else
            {
                --todo;
                ++ind_done;
+               ++ind_col;
            }
            ++ind_len;
            if (p != NULL)
***************
*** 365,384 ****
        }
  
        /* Fill to next tabstop with a tab, if possible */
!       tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
        if (todo >= tab_pad)
        {
            todo -= tab_pad;
            ++ind_len;
            if (p != NULL)
                *p++ = TAB;
        }
  
        /* Add tabs required for indent */
!       while (todo >= (int)curbuf->b_p_ts)
        {
!           todo -= (int)curbuf->b_p_ts;
            ++ind_len;
            if (p != NULL)
                *p++ = TAB;
        }
--- 402,426 ----
        }
  
        /* Fill to next tabstop with a tab, if possible */
!       tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts_ary);
        if (todo >= tab_pad)
        {
            todo -= tab_pad;
            ++ind_len;
+           ind_col += tab_pad;
            if (p != NULL)
                *p++ = TAB;
        }
  
        /* Add tabs required for indent */
!       for (;;)
        {
!           tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts_ary);
!           if (todo < tab_pad)
!               break;
!           todo -= tab_pad;
            ++ind_len;
+           ind_col += tab_pad;
            if (p != NULL)
                *p++ = TAB;
        }
***************
*** 644,650 ****
        /*
         * count white space on current line
         */
!       newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts);
        if (newindent == 0)
            newindent = old_indent;     /* for ^^D command in insert mode */
  
--- 686,692 ----
        /*
         * count white space on current line
         */
!       newindent = get_indent_str_vtab(saved_line, curbuf->b_p_ts_ary);
        if (newindent == 0)
            newindent = old_indent;     /* for ^^D command in insert mode */
  
***************
*** 1165,1171 ****
                                        || do_si
  #endif
                                                           )
!                       newindent = get_indent_str(leader, (int)curbuf->b_p_ts);
  
                    /* Add the indent offset */
                    if (newindent + off < 0)
--- 1207,1213 ----
                                        || do_si
  #endif
                                                           )
!                       newindent = get_indent_str_vtab(leader, 
curbuf->b_p_ts_ary);
  
                    /* Add the indent offset */
                    if (newindent + off < 0)
*** orig_src/ops.c      2007-10-04 18:43:09.000000000 +0100
--- src/ops.c   2007-10-04 18:44:03.000000000 +0100
***************
*** 385,391 ****
      char_u            *newp, *oldp, *midp, *ptr;
      int                       oldcol = curwin->w_cursor.col;
      int                       p_sw = (int)curbuf->b_p_sw;
!     int                       p_ts = (int)curbuf->b_p_ts;
      struct block_def  bd;
      int                       internal = 0;
      int                       incr;
--- 385,391 ----
      char_u            *newp, *oldp, *midp, *ptr;
      int                       oldcol = curwin->w_cursor.col;
      int                       p_sw = (int)curbuf->b_p_sw;
!     int                       *p_ts = curbuf->b_p_ts_ary;
      struct block_def  bd;
      int                       internal = 0;
      int                       incr;
***************
*** 435,443 ****
        /* OK, now total=all the VWS reqd, and textstart points at the 1st
         * non-ws char in the block. */
        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;
        /* if we're splitting a TAB, allow for it */
--- 435,441 ----
        /* OK, now total=all the VWS reqd, and textstart points at the 1st
         * non-ws char in the block. */
        if (!curbuf->b_p_et)
!           tabstop_fromto(ws_vcol, ws_vcol + total, p_ts, &i, &j);
        else
            j = total;
        /* if we're splitting a TAB, allow for it */
***************
*** 455,518 ****
      }
      else /* left */
      {
!       vcol = oap->start_vcol;
!       /* walk vcol past ws to be removed */
!       for (midp = oldp + bd.textcol;
!             vcol < (oap->start_vcol + total) && vim_iswhite(*midp); )
        {
!           incr = lbr_chartabsize_adv(&midp, (colnr_T)vcol);
!           vcol += incr;
        }
!       /* internal is the block-internal ws replacing a split TAB */
!       if (vcol > (oap->start_vcol + total))
        {
!           /* we have to split the TAB *(midp-1) */
!           internal = vcol - (oap->start_vcol + total);
!       }
!       /* if 'expandtab' is not set, use TABs */
  
!       split = bd.startspaces + internal;
!       if (split > 0)
!       {
            if (!curbuf->b_p_et)
            {
                for (ptr = oldp, col = 0; ptr < oldp+bd.textcol; )
                    col += lbr_chartabsize_adv(&ptr, (colnr_T)col);
  
!               /* col+1 now equals the start col of the first char of the
!                * block (may be < oap.start_vcol if we're splitting a TAB) */
!               i = ((col % p_ts) + split) / p_ts; /* number of tabs */
            }
-           if (i)
-               j = ((col % p_ts) + split) % p_ts; /* number of spp */
-           else
-               j = split;
-       }
  
!       newp = alloc_check(bd.textcol + i + j + (unsigned)STRLEN(midp) + 1);
!       if (newp == NULL)
!           return;
!       vim_memset(newp, NUL, (size_t)(bd.textcol + i + j + STRLEN(midp) + 1));
  
!       /* copy first part we want to keep */
!       mch_memmove(newp, oldp, (size_t)bd.textcol);
!       /* Now copy any TABS and spp to ensure correct alignment! */
!       while (vim_iswhite(*midp))
!       {
!           if (*midp == TAB)
!               i++;
!           else /*space */
!               j++;
!           midp++;
!       }
!       /* We might have an extra TAB worth of spp now! */
!       if (j / p_ts && !curbuf->b_p_et)
!       {
!           i++;
!           j -= p_ts;
        }
-       copy_chars(newp + bd.textcol, (size_t)i, TAB);
-       copy_spaces(newp + bd.textcol + i, (size_t)j);
  
        /* the end */
        mch_memmove(newp + STRLEN(newp), midp, (size_t)STRLEN(midp) + 1);
--- 453,591 ----
      }
      else /* left */
      {
!       /* If there's only one tabstop then use the old method, partly
!        * for simplicity and partly to guarantee that a single ts value
!        * will result in exactly the same behaviour as before.
!        */
!       if (tabstop_count(p_ts) == 1)
        {
!           int ts = tabstop_first(p_ts);
! 
!           vcol = oap->start_vcol;
!           /* walk vcol past ws to be removed */
!           for (midp = oldp + bd.textcol;
!                 vcol < (oap->start_vcol + total) && vim_iswhite(*midp); )
!           {
!               incr = lbr_chartabsize_adv(&midp, (colnr_T)vcol);
!               vcol += incr;
!           }
!           /* internal is the block-internal ws replacing a split TAB */
!           if (vcol > (oap->start_vcol + total))
!           {
!               /* we have to split the TAB *(midp-1) */
!               internal = vcol - (oap->start_vcol + total);
!           }
!           /* if 'expandtab' is not set, use TABs */
! 
!           split = bd.startspaces + internal;
!           if (split > 0)
!           {
!               if (!curbuf->b_p_et)
!               {
!                   for (ptr = oldp, col = 0; ptr < oldp+bd.textcol; )
!                       col += lbr_chartabsize_adv(&ptr, (colnr_T)col);
! 
!                   /* col+1 now equals the start col of the first char of the
!                    * block (may be < oap.start_vcol if we're splitting a TAB) 
*/
!                   i = ((col % ts) + split) / ts; /* number of tabs */
!               }
!               if (i)
!                   j = ((col % ts) + split) % ts; /* number of spp */
!               else
!                   j = split;
!           }
! 
!           newp = alloc_check(bd.textcol + i + j + (unsigned)STRLEN(midp) + 1);
!           if (newp == NULL)
!               return;
!           vim_memset(newp, NUL, (size_t)(bd.textcol + i + j + STRLEN(midp) + 
1));
! 
!           /* copy first part we want to keep */
!           mch_memmove(newp, oldp, (size_t)bd.textcol);
!           /* Now copy any TABS and spp to ensure correct alignment! */
!           while (vim_iswhite(*midp))
!           {
!               if (*midp == TAB)
!                   i++;
!               else /*space */
!                   j++;
!               midp++;
!           }
!           /* We might have an extra TAB worth of spp now! */
!           if (j / ts && !curbuf->b_p_et)
!           {
!               i++;
!               j -= ts;
!           }
! 
!           copy_chars(newp + bd.textcol, (size_t)i, TAB);
!           copy_spaces(newp + bd.textcol + i, (size_t)j);
        }
!       else
        {
!           /* With multiple tabstops a completely different method is needed.
!            * Tabs count for different widths in different parts of the line,
!            * so instead of trying to preserve tabs copied from the old line
!            * the total width of all whitespace is calculated, the shift is
!            * removed, and then width is converted back to tabs and spaces.
!            */
!           int  totalspace = 0;
!           int  tcol;          /* used for tracking tab columns */
  
!           vcol = oap->start_vcol;
!           /* walk vcol past ws to be removed */
!           for (midp = oldp + bd.textcol;
!                 vcol < (oap->start_vcol + total) && vim_iswhite(*midp); )
!           {
!               incr = lbr_chartabsize_adv(&midp, (colnr_T)vcol);
!               vcol += incr;
!           }
!           /* internal is the block-internal ws replacing a split TAB */
!           if (vcol > (oap->start_vcol + total))
!           {
!               /* we have to split the TAB *(midp-1) */
!               internal = vcol - (oap->start_vcol + total);
!           }
! 
!           totalspace = bd.startspaces + internal;
!           /* Add the remaining ws that will be retained in the shift. */
!           tcol = vcol;
!           while (vim_iswhite(*midp))
!           {
!               if (*midp == TAB)
!               {
!                   int pad = tabstop_padding(tcol, p_ts);
!                   totalspace += pad;
!                   tcol += pad;
!               }
!               else /*space */
!               {
!                   totalspace++;
!                   tcol++;
!               }
!               midp++;
!           }
! 
!           /* Convert the ws width into tabs and spaces. */
            if (!curbuf->b_p_et)
            {
                for (ptr = oldp, col = 0; ptr < oldp+bd.textcol; )
                    col += lbr_chartabsize_adv(&ptr, (colnr_T)col);
  
!               tabstop_fromto(col, col + totalspace, p_ts, &i, &j);
            }
  
!           newp = alloc_check(bd.textcol + i + j + (unsigned)STRLEN(midp) + 1);
!           if (newp == NULL)
!               return;
!           vim_memset(newp, NUL, (size_t)(bd.textcol + i + j + STRLEN(midp) + 
1));
  
!           /* copy first part we want to keep */
!           mch_memmove(newp, oldp, (size_t)bd.textcol);
! 
!           copy_chars(newp + bd.textcol, (size_t)i, TAB);
!           copy_spaces(newp + bd.textcol + i, (size_t)j);
        }
  
        /* the end */
        mch_memmove(newp + STRLEN(newp), midp, (size_t)STRLEN(midp) + 1);
***************
*** 3367,3376 ****
        {
            /* Don't need to insert spaces when "p" on the last position of a
             * tab or "P" on the first position. */
            if (dir == FORWARD
!                   ? (int)curwin->w_cursor.coladd < curbuf->b_p_ts - 1
                                                : curwin->w_cursor.coladd > 0)
!               coladvance_force(getviscol());
            else
                curwin->w_cursor.coladd = 0;
        }
--- 3440,3450 ----
        {
            /* Don't need to insert spaces when "p" on the last position of a
             * tab or "P" on the first position. */
+           int viscol = getviscol();
            if (dir == FORWARD
!                   ? tabstop_padding(viscol, curbuf->b_p_ts_ary) != 1
                                                : curwin->w_cursor.coladd > 0)
!               coladvance_force(viscol);
            else
                curwin->w_cursor.coladd = 0;
        }
*** orig_src/option.c   2007-10-04 18:43:09.000000000 +0100
--- src/option.c        2007-10-05 14:47:31.000000000 +0100
***************
*** 338,344 ****
  #ifndef SHORT_FNAME
  static int    p_sn;
  #endif
! static long   p_sts;
  #if defined(FEAT_SEARCHPATH)
  static char_u *p_sua;
  #endif
--- 338,344 ----
  #ifndef SHORT_FNAME
  static int    p_sn;
  #endif
! static char_u *p_sts;
  #if defined(FEAT_SEARCHPATH)
  static char_u *p_sua;
  #endif
***************
*** 353,359 ****
  static char_u *p_spf;
  static char_u *p_spl;
  #endif
! static long   p_ts;
  static long   p_tw;
  static int    p_tx;
  static long   p_wm;
--- 353,359 ----
  static char_u *p_spf;
  static char_u *p_spl;
  #endif
! static char_u *p_ts;
  static long   p_tw;
  static int    p_tx;
  static long   p_wm;
***************
*** 370,376 ****
  /* Saved values for when 'paste' is set */
  static long   p_tw_nopaste;
  static long   p_wm_nopaste;
! static long   p_sts_nopaste;
  static int    p_ai_nopaste;
  
  struct vimoption
--- 370,376 ----
  /* Saved values for when 'paste' is set */
  static long   p_tw_nopaste;
  static long   p_wm_nopaste;
! static char_u *p_sts_nopaste;
  static int    p_ai_nopaste;
  
  struct vimoption
***************
*** 2266,2274 ****
      {"smarttab",    "sta",  P_BOOL|P_VI_DEF|P_VIM,
                            (char_u *)&p_sta, PV_NONE,
                            {(char_u *)FALSE, (char_u *)0L}},
!     {"softtabstop", "sts",  P_NUM|P_VI_DEF|P_VIM,
                            (char_u *)&p_sts, PV_STS,
!                           {(char_u *)0L, (char_u *)0L}},
      {"sourceany",   NULL,   P_BOOL|P_VI_DEF,
                            (char_u *)NULL, PV_NONE,
                            {(char_u *)FALSE, (char_u *)0L}},
--- 2266,2274 ----
      {"smarttab",    "sta",  P_BOOL|P_VI_DEF|P_VIM,
                            (char_u *)&p_sta, PV_NONE,
                            {(char_u *)FALSE, (char_u *)0L}},
!     {"softtabstop", "sts",  P_STRING|P_VI_DEF|P_VIM,
                            (char_u *)&p_sts, PV_STS,
!                           {(char_u *)"", (char_u *)0L}},
      {"sourceany",   NULL,   P_BOOL|P_VI_DEF,
                            (char_u *)NULL, PV_NONE,
                            {(char_u *)FALSE, (char_u *)0L}},
***************
*** 2393,2401 ****
                            (char_u *)NULL, PV_NONE,
  #endif
                            {(char_u *)10L, (char_u *)0L}},
!     {"tabstop",           "ts",   P_NUM|P_VI_DEF|P_RBUF,
                            (char_u *)&p_ts, PV_TS,
!                           {(char_u *)8L, (char_u *)0L}},
      {"tagbsearch",  "tbs",   P_BOOL|P_VI_DEF,
                            (char_u *)&p_tbs, PV_NONE,
  #ifdef VMS    /* binary searching doesn't appear to work on VMS */
--- 2393,2401 ----
                            (char_u *)NULL, PV_NONE,
  #endif
                            {(char_u *)10L, (char_u *)0L}},
!     {"tabstop",           "ts",   P_STRING|P_VI_DEF|P_RBUF,
                            (char_u *)&p_ts, PV_TS,
!                           {(char_u *)"8", (char_u *)0L}},
      {"tagbsearch",  "tbs",   P_BOOL|P_VI_DEF,
                            (char_u *)&p_tbs, PV_NONE,
  #ifdef VMS    /* binary searching doesn't appear to work on VMS */
***************
*** 5050,5055 ****
--- 5050,5057 ----
      /* set cedit_key */
      (void)check_cedit();
  #endif
+     tabstop_set(curbuf->b_p_ts, &curbuf->b_p_ts_ary, FALSE);
+     tabstop_set(curbuf->b_p_sts, &curbuf->b_p_sts_ary, TRUE);
  }
  
  /*
***************
*** 5161,5166 ****
--- 5163,5170 ----
      check_string_option(&buf->b_p_dict);
      check_string_option(&buf->b_p_tsr);
  #endif
+     check_string_option(&buf->b_p_sts);
+     check_string_option(&buf->b_p_ts);
  }
  
  /*
***************
*** 6584,6589 ****
--- 6588,6650 ----
      }
  #endif
  
+     /* 'softtabstop' */
+     else if (varp == &(curbuf->b_p_sts))
+     {
+       char_u *cp;
+ 
+       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_sts_ary;
+           if (tabstop_set(*varp, &(curbuf->b_p_sts_ary), TRUE))
+           {
+               if (oldarray)
+                   vim_free(oldarray);
+           }
+           else
+               errmsg = e_invarg;
+       }
+     }
+ 
+     /* 'tabstop' */
+     else if (varp == &(curbuf->b_p_ts))
+     {
+       char_u *cp;
+       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_ts_ary;
+           if (tabstop_set(*varp, &(curbuf->b_p_ts_ary), FALSE))
+           {
+               if (oldarray)
+                   vim_free(oldarray);
+ #ifdef FEAT_FOLDING
+               if (foldmethodIsIndent(curwin))
+                   foldUpdateAll(curwin);
+ #endif /* FEAT_FOLDING */
+           }
+           else
+               errmsg = e_invarg;
+       }
+     }
+ 
      /* Options that are a list of flags. */
      else
      {
***************
*** 7598,7604 ****
      if (curbuf->b_p_sw <= 0)
      {
        errmsg = e_positive;
!       curbuf->b_p_sw = curbuf->b_p_ts;
      }
  
      /*
--- 7659,7668 ----
      if (curbuf->b_p_sw <= 0)
      {
        errmsg = e_positive;
!       /* Use the tabstop value if there's only one of them, or 8 if ts
!        * has multiple values. */
!       curbuf->b_p_sw = tabstop_count(curbuf->b_p_ts_ary) == 1
!                      ? tabstop_first(curbuf->b_p_ts_ary) : 8;
      }
  
      /*
***************
*** 7748,7755 ****
        }
      }
  
!     /* 'shiftwidth' or 'tabstop' */
!     else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts)
      {
        if (foldmethodIsIndent(curwin))
            foldUpdateAll(curwin);
--- 7812,7819 ----
        }
      }
  
!     /* 'shiftwidth' */
!     else if (pp == &curbuf->b_p_sw)
      {
        if (foldmethodIsIndent(curwin))
            foldUpdateAll(curwin);
***************
*** 7942,7957 ****
            p_window = Rows - 1;
      }
  
-     if (curbuf->b_p_sts < 0)
-     {
-       errmsg = e_positive;
-       curbuf->b_p_sts = 0;
-     }
-     if (curbuf->b_p_ts <= 0)
-     {
-       errmsg = e_positive;
-       curbuf->b_p_ts = 8;
-     }
      if (curbuf->b_p_tw < 0)
      {
        errmsg = e_positive;
--- 8006,8011 ----
***************
*** 9430,9437 ****
            buf->b_p_cfu = vim_strsave(p_cfu);
            buf->b_p_ofu = vim_strsave(p_ofu);
  #endif
!           buf->b_p_sts = p_sts;
!           buf->b_p_sts_nopaste = p_sts_nopaste;
  #ifndef SHORT_FNAME
            buf->b_p_sn = p_sn;
  #endif
--- 9484,9496 ----
            buf->b_p_cfu = vim_strsave(p_cfu);
            buf->b_p_ofu = vim_strsave(p_ofu);
  #endif
!           buf->b_p_sts = vim_strsave(p_sts);
!           if (p_sts && p_sts != empty_option)
!               tabstop_set(p_sts, &buf->b_p_sts_ary, TRUE);
!           else
!               buf->b_p_sts_ary = 0;
!           buf->b_p_sts_nopaste = p_sts_nopaste
!                                ? vim_strsave(p_sts_nopaste) : NULL;
  #ifndef SHORT_FNAME
            buf->b_p_sn = p_sn;
  #endif
***************
*** 9538,9549 ****
             * or to a help buffer.
             */
            if (dont_do_help)
                buf->b_p_isk = save_p_isk;
            else
            {
                buf->b_p_isk = vim_strsave(p_isk);
                did_isk = TRUE;
!               buf->b_p_ts = p_ts;
                buf->b_help = FALSE;
  #ifdef FEAT_QUICKFIX
                if (buf->b_p_bt[0] == 'h')
--- 9597,9613 ----
             * or to a help buffer.
             */
            if (dont_do_help)
+           {
                buf->b_p_isk = save_p_isk;
+               if (p_ts && p_ts != empty_option && !buf->b_p_ts_ary)
+                   tabstop_set(p_ts, &buf->b_p_ts_ary, FALSE);
+           }
            else
            {
                buf->b_p_isk = vim_strsave(p_isk);
                did_isk = TRUE;
!               buf->b_p_ts = vim_strsave(p_ts);
!               tabstop_set(p_ts, &buf->b_p_ts_ary, FALSE);
                buf->b_help = FALSE;
  #ifdef FEAT_QUICKFIX
                if (buf->b_p_bt[0] == 'h')
***************
*** 10270,10276 ****
            {
                buf->b_p_tw_nopaste = buf->b_p_tw;
                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;
            }
  
--- 10334,10343 ----
            {
                buf->b_p_tw_nopaste = buf->b_p_tw;
                buf->b_p_wm_nopaste = buf->b_p_wm;
!               if (buf->b_p_sts_nopaste)
!                   vim_free(buf->b_p_sts_nopaste);
!               buf->b_p_sts_nopaste = buf->b_p_sts && buf->b_p_sts != 
empty_option
!                                    ? vim_strsave(buf->b_p_sts) : NULL;
                buf->b_p_ai_nopaste = buf->b_p_ai;
            }
  
***************
*** 10286,10292 ****
            /* save global values for local buffer options */
            p_tw_nopaste = p_tw;
            p_wm_nopaste = p_wm;
!           p_sts_nopaste = p_sts;
            p_ai_nopaste = p_ai;
        }
  
--- 10353,10361 ----
            /* save global values for local buffer options */
            p_tw_nopaste = p_tw;
            p_wm_nopaste = p_wm;
!           if (p_sts_nopaste)
!               vim_free(p_sts_nopaste);
!           p_sts_nopaste = p_sts && p_sts != empty_option ? vim_strsave(p_sts) 
: NULL;
            p_ai_nopaste = p_ai;
        }
  
***************
*** 10299,10305 ****
        {
            buf->b_p_tw = 0;        /* textwidth is 0 */
            buf->b_p_wm = 0;        /* wrapmargin is 0 */
!           buf->b_p_sts = 0;       /* softtabstop is 0 */
            buf->b_p_ai = 0;        /* no auto-indent */
        }
  
--- 10368,10379 ----
        {
            buf->b_p_tw = 0;        /* textwidth is 0 */
            buf->b_p_wm = 0;        /* wrapmargin is 0 */
!           if (buf->b_p_sts)       /* softtabstop is 0 */
!               free_string_option(buf->b_p_sts);
!           buf->b_p_sts = empty_option;
!           if (buf->b_p_sts_ary)
!               vim_free(buf->b_p_sts_ary);
!           buf->b_p_sts_ary = 0;
            buf->b_p_ai = 0;        /* no auto-indent */
        }
  
***************
*** 10319,10325 ****
        /* set global values for local buffer options */
        p_tw = 0;
        p_wm = 0;
!       p_sts = 0;
        p_ai = 0;
      }
  
--- 10393,10401 ----
        /* set global values for local buffer options */
        p_tw = 0;
        p_wm = 0;
!       if (p_sts)
!           free_string_option(p_sts);
!       p_sts = empty_option;
        p_ai = 0;
      }
  
***************
*** 10333,10339 ****
        {
            buf->b_p_tw = buf->b_p_tw_nopaste;
            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;
        }
  
--- 10409,10424 ----
        {
            buf->b_p_tw = buf->b_p_tw_nopaste;
            buf->b_p_wm = buf->b_p_wm_nopaste;
!           if (buf->b_p_sts)
!               free_string_option(buf->b_p_sts);
!           buf->b_p_sts = buf->b_p_sts_nopaste
!                        ? vim_strsave(buf->b_p_sts_nopaste) : empty_option;
!           if (buf->b_p_sts_ary)
!               vim_free(buf->b_p_sts_ary);
!           if (buf->b_p_sts && buf->b_p_sts != empty_option)
!               tabstop_set(buf->b_p_sts, &buf->b_p_sts_ary, TRUE);
!           else
!               buf->b_p_sts_ary = 0;
            buf->b_p_ai = buf->b_p_ai_nopaste;
        }
  
***************
*** 10353,10359 ****
        /* set global values for local buffer options */
        p_tw = p_tw_nopaste;
        p_wm = p_wm_nopaste;
!       p_sts = p_sts_nopaste;
        p_ai = p_ai_nopaste;
      }
  
--- 10438,10446 ----
        /* set global values for local buffer options */
        p_tw = p_tw_nopaste;
        p_wm = p_wm_nopaste;
!       if (p_sts)
!           free_string_option(p_sts);
!       p_sts = p_sts_nopaste ? vim_strsave(p_sts_nopaste) : empty_option;
        p_ai = p_ai_nopaste;
      }
  
***************
*** 10679,10681 ****
--- 10766,11033 ----
  {
      return check_opt_strings(p, p_ff_values, FALSE);
  }
+ 
+ /*
+  * Set the integer values corresponding to the string setting of 'tabstop'.
+  *
+  * If zero_ok is true then a value of "0" is allowed as a way of unsetting
+  * the option. (This is used for 'softtabstop'.)
+  */
+     int
+ tabstop_set(var, array, zero_ok)
+     char_u    *var;
+     int               **array;
+     int               zero_ok;
+ {
+     int valcount = 1;
+     int t;
+     char_u *cp;
+ 
+     if (zero_ok && (!var[0] || (var[0] == '0' && !var[1])))
+     {
+       *array = (int *)0;
+       return TRUE;
+     }
+ 
+     for (cp = var; *cp; ++cp)
+     {
+       if (cp == var || *(cp - 1) == ',')
+           if (atoi(cp) <= 0)
+               return FALSE;
+ 
+       if (VIM_ISDIGIT(*cp))
+           continue;
+       if (*cp == ',' && cp > var && *(cp - 1) != ',')
+       {
+           ++valcount;
+           continue;
+       }
+       return FALSE;
+     }
+ 
+     *array = (colnr_T *) alloc((unsigned) ((valcount + 1) * sizeof(colnr_T)));
+     (*array)[0] = valcount;
+ 
+     t = 1;
+     for (cp = var; *cp;)
+     {
+       (*array)[t++] = (colnr_T) atoi(cp);
+       while (*cp && *cp != ',')
+           ++cp;
+       if (*cp)
+           ++cp;
+     }
+ 
+     return TRUE;
+ }
+ 
+ /*
+  * Calculate the number of screen spaces a tab will occupy.
+  */
+     int
+ tabstop_padding(col, ts)
+     colnr_T   col;
+     int               *ts;
+ {
+     int               tabcount = ts[0];
+     colnr_T   tabcol = 0;
+     int               t;
+     int               padding = 0;
+ 
+     for (t = 1; t <= tabcount; ++t)
+     {
+       tabcol += ts[t];
+       if (tabcol > col)
+       {
+           padding = (int)(tabcol - col);
+           break;
+       }
+     }
+     if (t > tabcount)
+       padding = ts[tabcount] - (int)((col - tabcol) % ts[tabcount]);
+ 
+     return padding;
+ }
+ 
+ /*
+  * Find the size of the tab that covers a particular column.
+  */
+     int
+ tabstop_at(col, ts)
+     colnr_T   col;
+     int               *ts;
+ {
+     int               tabcount = ts[0];
+     colnr_T   tabcol = 0;
+     int               t;
+     int               tab_size = 0;
+ 
+     for (t = 1; t <= tabcount; ++t)
+     {
+       tabcol += ts[t];
+       if (tabcol > col)
+       {
+           tab_size = ts[t];
+           break;
+       }
+     }
+     if (t > tabcount)
+       tab_size = ts[tabcount];
+ 
+     return tab_size;
+ }
+ 
+ /*
+  * Find the column on which a tab starts.
+  */
+     colnr_T
+ tabstop_start(col, ts)
+     colnr_T   col;
+     int               *ts;
+ {
+     int               tabcount = ts[0];
+     colnr_T   tabcol = 0;
+     int               t;
+ 
+     for (t = 1; t <= tabcount; ++t)
+     {
+       tabcol += ts[t];
+       if (tabcol > col)
+           return tabcol - ts[t];
+     }
+ 
+     int excess = tabcol % ts[tabcount];
+     return excess + ((col - excess) / ts[tabcount]) * ts[tabcount];
+ }
+ 
+ /*
+  * Find the number of tabs and spaces necessary to get from one column
+  * to another.
+  */
+     void
+ tabstop_fromto(start_col, end_col, ts, ntabs, nspcs)
+     colnr_T   start_col;
+     colnr_T   end_col;
+     int               *ts;
+     int               *ntabs;
+     int               *nspcs;
+ {
+     int               spaces = end_col - start_col;
+     colnr_T   tabcol = 0;
+     int               padding = 0;
+     int               tabcount;
+     int               t;
+ 
+     if (ts == 0 || ts[0] == 0)
+     {
+       /* "shouldn't happen" */
+       *ntabs = 0;
+       *nspcs = spaces;
+       return;
+     }
+ 
+     /* Find the padding needed to reach the next tabstop. */
+     tabcount = ts[0];
+     for (t = 1; t <= tabcount; ++t)
+     {
+       tabcol += ts[t];
+       if (tabcol > start_col)
+       {
+           padding = (int)(tabcol - start_col);
+           break;
+       }
+     }
+     if (t > tabcount)
+       padding = ts[tabcount] - (int)((start_col - tabcol) % ts[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 = ts[t];
+       if (spaces < padding)
+       {
+           *nspcs = spaces;
+           return;
+       }
+       ++*ntabs;
+       spaces -= padding;
+     }
+ 
+     *ntabs += spaces / ts[tabcount];
+     *nspcs =  spaces % ts[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;
+ }
*** orig_src/screen.c   2007-10-04 18:43:09.000000000 +0100
--- src/screen.c        2007-10-04 18:44:04.000000000 +0100
***************
*** 4053,4060 ****
                if (c == TAB && (!wp->w_p_list || lcs_tab1))
                {
                    /* tab amount depends on current column */
!                   n_extra = (int)wp->w_buffer->b_p_ts
!                                  - vcol % (int)wp->w_buffer->b_p_ts - 1;
  #ifdef FEAT_MBYTE
                    mb_utf8 = FALSE;    /* don't draw as UTF-8 */
  #endif
--- 4053,4059 ----
                if (c == TAB && (!wp->w_p_list || lcs_tab1))
                {
                    /* tab amount depends on current column */
!                   n_extra = tabstop_padding(vcol, wp->w_buffer->b_p_ts_ary) - 
1;
  #ifdef FEAT_MBYTE
                    mb_utf8 = FALSE;    /* don't draw as UTF-8 */
  #endif
*** orig_src/structs.h  2007-10-04 18:43:09.000000000 +0100
--- src/structs.h       2007-10-04 18:44:04.000000000 +0100
***************
*** 1409,1416 ****
  #ifdef FEAT_SMARTINDENT
      int               b_p_si;         /* 'smartindent' */
  #endif
!     long      b_p_sts;        /* 'softtabstop' */
!     long      b_p_sts_nopaste; /* b_p_sts saved for paste mode */
  #ifdef FEAT_SEARCHPATH
      char_u    *b_p_sua;       /* 'suffixesadd' */
  #endif
--- 1409,1417 ----
  #ifdef FEAT_SMARTINDENT
      int               b_p_si;         /* 'smartindent' */
  #endif
!     char_u    *b_p_sts;       /* 'softtabstop' */
!     int               *b_p_sts_ary;   /* 'softtabstop' in internal format */
!     char_u    *b_p_sts_nopaste; /* b_p_sts saved for paste mode */
  #ifdef FEAT_SEARCHPATH
      char_u    *b_p_sua;       /* 'suffixesadd' */
  #endif
***************
*** 1425,1431 ****
      char_u    *b_p_spf;       /* 'spellfile' */
      char_u    *b_p_spl;       /* 'spelllang' */
  #endif
!     long      b_p_ts;         /* 'tabstop' */
      int               b_p_tx;         /* 'textmode' */
      long      b_p_tw;         /* 'textwidth' */
      long      b_p_tw_nobin;   /* b_p_tw saved for binary mode */
--- 1426,1433 ----
      char_u    *b_p_spf;       /* 'spellfile' */
      char_u    *b_p_spl;       /* 'spelllang' */
  #endif
!     char_u    *b_p_ts;        /* 'tabstop' */
!     int               *b_p_ts_ary;    /* 'tabstop' in internal format */
      int               b_p_tx;         /* 'textmode' */
      long      b_p_tw;         /* 'textwidth' */
      long      b_p_tw_nobin;   /* b_p_tw saved for binary mode */
*** orig_src/workshop.c 2007-10-04 18:43:12.000000000 +0100
--- src/workshop.c      2007-10-04 18:44:04.000000000 +0100
***************
*** 62,68 ****
  static char   *fixAccelText(char *);
  static void    addMenu(char *, char *, char *);
  static char   *lookupVerb(char *, int);
! static int     computeIndex(int, char_u *, int);
  static void    coloncmd(char *, Boolean);
  
  extern Widget  vimShell;
--- 62,68 ----
  static char   *fixAccelText(char *);
  static void    addMenu(char *, char *, char *);
  static char   *lookupVerb(char *, int);
! static int     computeIndex(int, char_u *, int *);
  static void    coloncmd(char *, Boolean);
  
  extern Widget  vimShell;
***************
*** 1634,1640 ****
  computeIndex(
        int              wantedCol,
        char_u          *line,
!       int              ts)
  {
      int                col = 0;
      int                idx = 0;
--- 1634,1640 ----
  computeIndex(
        int              wantedCol,
        char_u          *line,
!       int             *ts)
  {
      int                col = 0;
      int                idx = 0;
***************
*** 1642,1648 ****
      while (line[idx])
      {
        if (line[idx] == '\t')
!           col += ts - (col % ts);
        else
            col++;
        idx++;
--- 1642,1648 ----
      while (line[idx])
      {
        if (line[idx] == '\t')
!           col += tabstop_padding(col, ts);
        else
            col++;
        idx++;
*** orig_src/proto/misc1.pro    2007-10-04 18:43:11.000000000 +0100
--- src/proto/misc1.pro 2007-10-04 18:44:04.000000000 +0100
***************
*** 3,8 ****
--- 3,9 ----
  int get_indent_lnum __ARGS((linenr_T lnum));
  int get_indent_buf __ARGS((buf_T *buf, linenr_T lnum));
  int get_indent_str __ARGS((char_u *ptr, int ts));
+ int get_indent_str_vtab __ARGS((char_u *ptr, colnr_T *ts));
  int set_indent __ARGS((int size, int flags));
  int get_number_indent __ARGS((linenr_T lnum));
  int open_line __ARGS((int dir, int flags, int old_indent));
*** orig_src/proto/option.pro   2007-10-04 18:43:11.000000000 +0100
--- src/proto/option.pro        2007-10-04 20:05:45.000000000 +0100
***************
*** 53,56 ****
--- 53,65 ----
  void save_file_ff __ARGS((buf_T *buf));
  int file_ff_differs __ARGS((buf_T *buf));
  int check_ff_value __ARGS((char_u *p));
+ int tabstop_set __ARGS((char_u *var, int **array, int zero_ok));
+ int tabstop_padding __ARGS((colnr_T col, int *ts));
+ colnr_T tabstop_start __ARGS((colnr_T col, int *ts));
+ int tabstop_at __ARGS((colnr_T col, int *ts));
+ void tabstop_fromto __ARGS((colnr_T start_col, colnr_T end_col, int *ts, 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));
  /* vim: set ft=c : */

Raspunde prin e-mail lui