This makes the tab width customizable per-buffer.  There's a new
function called `set-tab-width' that changes the tab width for the
current buffer or the default one for new buffers if called with a
prefix argument (or from the startup file.)

The default tab width is still 8 column if not changed by the user,
but together with the newly resurrected no-tab-mode this allows mg to
be used for a variety of programming languages and coding styles.
Want to hack on lua?  easy:

        $ cat <<EOF >> ~/.mg
        set-tab-width 3
        auto-execute *.lua no-tab-mode
        EOF

You might be tempted to say

        auto-execute *.lua set-tab-width 3   # wrong!

but it's currently a syntax error since auto-execute takes a function
without arguments.  For the time being at least.

Most of the changes are to replace the common idiom

        col |= 0x07;
        col++;

with the new helper function ntabstop().

display.c has quite some churn since I'm basically breaking an
abstraction: update() deals with the line to render but knows very
little of the state of the terminal, vtputc() and friends instead
knows nothing of the buffer but know the current physical column.  I'm
pushing the mgwin struct down into the vt*() layer so that it knows
how wide a tab is.  Previously I did it with a global variable and
while it needed less changes, it was uglier.

util.c may not be the perfect place for ntabstop().  c-mode won't be
happy with a tab width different from eight, still have to think what
to do there.  1 and 16 as min/max value for the tab width are
arbirtrary.

All in all I'm quite happy with how it works.  Comments, reviews and
tests appreciated :)


Index: basic.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/basic.c,v
retrieving revision 1.53
diff -u -p -r1.53 basic.c
--- basic.c     17 Apr 2023 09:49:04 -0000      1.53
+++ basic.c     17 Apr 2023 16:32:21 -0000
@@ -277,8 +277,7 @@ getgoal(struct line *dlp)
        for (i = 0; i < llength(dlp); i++) {
                c = lgetc(dlp, i);
                if (c == '\t') {
-                       col |= 0x07;
-                       col++;
+                       col = ntabstop(col, curbp->b_tabw);
                } else if (ISCTRL(c) != FALSE) {
                        col += 2;
                } else if (isprint(c))
Index: buffer.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/buffer.c,v
retrieving revision 1.113
diff -u -p -r1.113 buffer.c
--- buffer.c    8 Mar 2023 04:43:11 -0000       1.113
+++ buffer.c    17 Apr 2023 16:32:21 -0000
@@ -26,9 +26,42 @@ static struct buffer *bnew(const char *)
 
 static int usebufname(const char *);
 
+/* Default tab width */
+int     defb_tabw = 8;
+
 /* Flag for global working dir */
 extern int globalwd;
 
+/*
+ * Set the tab width for the current buffer, or the default for new
+ * buffers if called with a prefix argument.
+ */
+int
+settabw(int f, int n)
+{
+       char    buf[8], *bufp;
+       const char *errstr;
+
+       if (f & FFARG) {
+               if (n <= 0 || n > 16)
+                       return (FALSE);
+               defb_tabw = n;
+               return (TRUE);
+       }
+
+       if ((bufp = eread("Tab Width: ", buf, sizeof(buf),
+           EFNUL | EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       if (bufp[0] == '\0')
+               return (ABORT);
+       n = strtonum(buf, 1, 16, &errstr);
+       if (errstr)
+               return (dobeep_msgs("Tab width", errstr));
+       curbp->b_tabw = n;
+       curwp->w_rflag |= WFFRAME;
+       return (TRUE);
+}
+
 int
 togglereadonlyall(int f, int n)
 {
@@ -588,6 +621,7 @@ bnew(const char *bname)
        bp->b_lines = 1;
        bp->b_nlseq = "\n";             /* use unix default */
        bp->b_nlchr = bp->b_nlseq;
+       bp->b_tabw = defb_tabw;
        if ((bp->b_bname = strdup(bname)) == NULL) {
                dobeep();
                ewprintf("Can't get %d bytes", strlen(bname) + 1);
Index: cmode.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/cmode.c,v
retrieving revision 1.21
diff -u -p -r1.21 cmode.c
--- cmode.c     17 Apr 2023 09:49:04 -0000      1.21
+++ cmode.c     17 Apr 2023 16:32:21 -0000
@@ -245,10 +245,10 @@ getindent(const struct line *lp, int *cu
        for (lo = 0; lo < llength(lp); lo++) {
                if (!isspace(c = lgetc(lp, lo)))
                        break;
-               if (c == '\t') {
-                       nicol |= 0x07;
-               }
-               nicol++;
+               if (c == '\t')
+                       nicol = ntabstop(nicol, curbp->b_tabw);
+               else
+                       nicol++;
        }
 
        /* If last line was blank, choose 0 */
@@ -411,8 +411,7 @@ findcolpos(const struct buffer *bp, cons
        for (i = 0; i < lo; ++i) {
                c = lgetc(lp, i);
                if (c == '\t') {
-                       col |= 0x07;
-                       col++;
+                       col = ntabstop(col, curbp->b_tabw);
                } else if (ISCTRL(c) != FALSE)
                        col += 2;
                else if (isprint(c)) {
Index: def.h
===================================================================
RCS file: /cvs/src/usr.bin/mg/def.h,v
retrieving revision 1.179
diff -u -p -r1.179 def.h
--- def.h       17 Apr 2023 09:49:04 -0000      1.179
+++ def.h       17 Apr 2023 16:32:21 -0000
@@ -269,6 +269,7 @@ struct buffer {
        char             b_cwd[NFILEN]; /* working directory             */
        char            *b_nlseq;       /* Newline sequence of chars     */
        char            *b_nlchr;       /* 1st newline character         */
+       int              b_tabw;        /* Width of a tab character      */
        struct fileinfo  b_fi;          /* File attributes               */
        struct undoq     b_undo;        /* Undo actions list             */
        struct undo_rec *b_undoptr;
@@ -430,6 +431,7 @@ int          shrinkwind(int, int);
 int             delwind(int, int);
 
 /* buffer.c */
+int             settabw(int, int);
 int             togglereadonly(int, int);
 int             togglereadonlyall(int, int);
 struct buffer   *bfind(const char *, int);
@@ -542,6 +544,7 @@ int          gotoline(int, int);
 int             setlineno(int);
 
 /* util.c X */
+int             ntabstop(int, int);
 int             showcpos(int, int);
 int             getcolpos(struct mgwin *);
 int             twiddle(int, int);
Index: display.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/display.c,v
retrieving revision 1.51
diff -u -p -r1.51 display.c
--- display.c   17 Apr 2023 09:49:04 -0000      1.51
+++ display.c   17 Apr 2023 16:32:21 -0000
@@ -52,9 +52,9 @@ struct score {
 };
 
 void   vtmove(int, int);
-void   vtputc(int);
-void   vtpute(int);
-int    vtputs(const char *);
+void   vtputc(int, struct mgwin *);
+void   vtpute(int, struct mgwin *);
+int    vtputs(const char *, struct mgwin *);
 void   vteeol(void);
 void   updext(int, int);
 void   modeline(struct mgwin *, int);
@@ -308,9 +308,10 @@ vtmove(int row, int col)
  * Three guesses how we found this.
  */
 void
-vtputc(int c)
+vtputc(int c, struct mgwin *wp)
 {
        struct video    *vp;
+       int              target;
 
        c &= 0xff;
 
@@ -318,19 +319,20 @@ vtputc(int c)
        if (vtcol >= ncol)
                vp->v_text[ncol - 1] = '$';
        else if (c == '\t') {
+               target = ntabstop(vtcol, wp->w_bufp->b_tabw);
                do {
-                       vtputc(' ');
-               } while (vtcol < ncol && (vtcol & 0x07) != 0);
+                       vtputc(' ', wp);
+               } while (vtcol < ncol && vtcol < target);
        } else if (ISCTRL(c)) {
-               vtputc('^');
-               vtputc(CCHR(c));
+               vtputc('^', wp);
+               vtputc(CCHR(c), wp);
        } else if (isprint(c))
                vp->v_text[vtcol++] = c;
        else {
                char bf[5];
 
                snprintf(bf, sizeof(bf), "\\%o", c);
-               vtputs(bf);
+               vtputs(bf, wp);
        }
 }
 
@@ -340,9 +342,10 @@ vtputc(int c)
  * margin.
  */
 void
-vtpute(int c)
+vtpute(int c, struct mgwin *wp)
 {
        struct video *vp;
+       int target;
 
        c &= 0xff;
 
@@ -350,12 +353,13 @@ vtpute(int c)
        if (vtcol >= ncol)
                vp->v_text[ncol - 1] = '$';
        else if (c == '\t') {
+               target = ntabstop(vtcol + lbound, wp->w_bufp->b_tabw);
                do {
-                       vtpute(' ');
-               } while (((vtcol + lbound) & 0x07) != 0 && vtcol < ncol);
+                       vtpute(' ', wp);
+               } while (((vtcol + lbound) < target) && vtcol < ncol);
        } else if (ISCTRL(c) != FALSE) {
-               vtpute('^');
-               vtpute(CCHR(c));
+               vtpute('^', wp);
+               vtpute(CCHR(c), wp);
        } else if (isprint(c)) {
                if (vtcol >= 0)
                        vp->v_text[vtcol] = c;
@@ -365,7 +369,7 @@ vtpute(int c)
 
                snprintf(bf, sizeof(bf), "\\%o", c);
                for (cp = bf; *cp != '\0'; cp++)
-                       vtpute(*cp);
+                       vtpute(*cp, wp);
        }
 }
 
@@ -476,7 +480,7 @@ update(int modelinecolor)
                        vscreen[i]->v_flag |= (VFCHG | VFHBAD);
                        vtmove(i, 0);
                        for (j = 0; j < llength(lp); ++j)
-                               vtputc(lgetc(lp, j));
+                               vtputc(lgetc(lp, j), wp);
                        vteeol();
                } else if ((wp->w_rflag & (WFEDIT | WFFULL)) != 0) {
                        hflag = TRUE;
@@ -486,7 +490,7 @@ update(int modelinecolor)
                                vtmove(i, 0);
                                if (lp != wp->w_bufp->b_headp) {
                                        for (j = 0; j < llength(lp); ++j)
-                                               vtputc(lgetc(lp, j));
+                                               vtputc(lgetc(lp, j), wp);
                                        lp = lforw(lp);
                                }
                                vteeol();
@@ -509,8 +513,7 @@ update(int modelinecolor)
        while (i < curwp->w_doto) {
                c = lgetc(lp, i++);
                if (c == '\t') {
-                       curcol |= 0x07;
-                       curcol++;
+                       curcol = ntabstop(curcol, curwp->w_bufp->b_tabw);
                } else if (ISCTRL(c) != FALSE)
                        curcol += 2;
                else if (isprint(c))
@@ -545,7 +548,7 @@ update(int modelinecolor)
                                    (curcol < ncol - 1)) {
                                        vtmove(i, 0);
                                        for (j = 0; j < llength(lp); ++j)
-                                               vtputc(lgetc(lp, j));
+                                               vtputc(lgetc(lp, j), wp);
                                        vteeol();
                                        /* this line no longer is extended */
                                        vscreen[i]->v_flag &= ~VFEXT;
@@ -677,7 +680,7 @@ updext(int currow, int curcol)
        vtmove(currow, -lbound);                /* start scanning offscreen */
        lp = curwp->w_dotp;                     /* line to output */
        for (j = 0; j < llength(lp); ++j)       /* until the end-of-line */
-               vtpute(lgetc(lp, j));
+               vtpute(lgetc(lp, j), curwp);
        vteeol();                               /* truncate the virtual line */
        vscreen[currow]->v_text[0] = '$';       /* and put a '$' in column 1 */
 }
@@ -793,45 +796,45 @@ modeline(struct mgwin *wp, int modelinec
        vscreen[n]->v_flag |= (VFCHG | VFHBAD); /* Recompute, display.   */
        vtmove(n, 0);                           /* Seek to right line.   */
        bp = wp->w_bufp;
-       vtputc('-');
-       vtputc('-');
+       vtputc('-', wp);
+       vtputc('-', wp);
        if ((bp->b_flag & BFREADONLY) != 0) {
-               vtputc('%');
+               vtputc('%', wp);
                if ((bp->b_flag & BFCHG) != 0)
-                       vtputc('*');
+                       vtputc('*', wp);
                else
-                       vtputc('%');
+                       vtputc('%', wp);
        } else if ((bp->b_flag & BFCHG) != 0) { /* "*" if changed.       */
-               vtputc('*');
-               vtputc('*');
+               vtputc('*', wp);
+               vtputc('*', wp);
        } else {
-               vtputc('-');
-               vtputc('-');
+               vtputc('-', wp);
+               vtputc('-', wp);
        }
-       vtputc('-');
+       vtputc('-', wp);
        n = 5;
-       n += vtputs("Mg: ");
+       n += vtputs("Mg: ", wp);
        if (bp->b_bname[0] != '\0')
-               n += vtputs(&(bp->b_bname[0]));
+               n += vtputs(&(bp->b_bname[0]), wp);
        while (n < 42) {                        /* Pad out with blanks.  */
-               vtputc(' ');
+               vtputc(' ', wp);
                ++n;
        }
-       vtputc('(');
+       vtputc('(', wp);
        ++n;
        for (md = 0; ; ) {
-               n += vtputs(bp->b_modes[md]->p_name);
+               n += vtputs(bp->b_modes[md]->p_name, wp);
                if (++md > bp->b_nmodes)
                        break;
-               vtputc('-');
+               vtputc('-', wp);
                ++n;
        }
        /* XXX These should eventually move to a real mode */
        if (macrodef == TRUE)
-               n += vtputs("-def");
+               n += vtputs("-def", wp);
        if (globalwd == TRUE)
-               n += vtputs("-gwd");
-       vtputc(')');
+               n += vtputs("-gwd", wp);
+       vtputc(')', wp);
        ++n;
 
        if (linenos && colnos)
@@ -842,10 +845,10 @@ modeline(struct mgwin *wp, int modelinec
        else if (colnos)
                len = snprintf(sl, sizeof(sl), "--C%d", getcolpos(wp));
        if ((linenos || colnos) && len < sizeof(sl) && len != -1)
-               n += vtputs(sl);
+               n += vtputs(sl, wp);
 
        while (n < ncol) {                      /* Pad out.              */
-               vtputc('-');
+               vtputc('-', wp);
                ++n;
        }
 }
@@ -854,12 +857,12 @@ modeline(struct mgwin *wp, int modelinec
  * Output a string to the mode line, report how long it was.
  */
 int
-vtputs(const char *s)
+vtputs(const char *s, struct mgwin *wp)
 {
        int n = 0;
 
        while (*s != '\0') {
-               vtputc(*s++);
+               vtputc(*s++, wp);
                ++n;
        }
        return (n);
Index: funmap.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/funmap.c,v
retrieving revision 1.66
diff -u -p -r1.66 funmap.c
--- funmap.c    17 Apr 2023 09:49:04 -0000      1.66
+++ funmap.c    17 Apr 2023 16:32:21 -0000
@@ -199,6 +199,7 @@ static struct funmap functnames[] = {
        {ask_selfinsert, "self-insert-char", 1},
        {selfinsert, "self-insert-command", 1},         /* startup only */
        {sentencespace, "sentence-end-double-space", 0},
+       {settabw, "set-tab-width", 1},
 #ifdef REGEX
        {setcasefold, "set-case-fold-search", 0},
 #endif /* REGEX */
Index: match.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/match.c,v
retrieving revision 1.24
diff -u -p -r1.24 match.c
--- match.c     17 Apr 2023 15:18:25 -0000      1.24
+++ match.c     17 Apr 2023 16:32:21 -0000
@@ -136,6 +136,7 @@ displaymatch(struct line *clp, int cbo)
        int      cp;
        int      bufo;
        int      c;
+       int      col;
        int      inwindow;
        char     buf[NLINE];
 
@@ -181,9 +182,9 @@ displaymatch(struct line *clp, int cbo)
                                } else
                                        buf[bufo++] = c;
                        } else {
-                               do {
+                               col = ntabstop(bufo, curbp->b_tabw);
+                               while (bufo < col && bufo < sizeof(buf) - 1)
                                        buf[bufo++] = ' ';
-                               } while ((bufo & 7) && bufo < sizeof(buf) - 1);
                        }
                }
                buf[bufo++] = '\0';
Index: mg.1
===================================================================
RCS file: /cvs/src/usr.bin/mg/mg.1,v
retrieving revision 1.129
diff -u -p -r1.129 mg.1
--- mg.1        17 Apr 2023 09:49:04 -0000      1.129
+++ mg.1        17 Apr 2023 16:32:22 -0000
@@ -896,6 +896,9 @@ Used by auto-fill-mode.
 Sets the mark in the current window to the current dot location.
 .It set-prefix-string
 Sets the prefix string to be used by the 'prefix-region' command.
+.It set-tab-width
+Set the tab width for the current buffer, or the default for new buffers
+if called with a prefix argument or from the startup file.
 .It shell-command
 Execute external command from mini-buffer.
 .It shell-command-on-region
Index: paragraph.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/paragraph.c,v
retrieving revision 1.48
diff -u -p -r1.48 paragraph.c
--- paragraph.c 17 Apr 2023 09:49:04 -0000      1.48
+++ paragraph.c 17 Apr 2023 16:32:22 -0000
@@ -413,7 +413,7 @@ fillword(int f, int n)
                        return selfinsert(f, n);
                c = lgetc(curwp->w_dotp, i);
                if (c == '\t')
-                       col |= 0x07;
+                       col = ntabstop(col, curwp->w_bufp->b_tabw);
                else if (ISCTRL(c) != FALSE)
                        ++col;
        }
Index: util.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/util.c,v
retrieving revision 1.47
diff -u -p -r1.47 util.c
--- util.c      17 Apr 2023 09:53:08 -0000      1.47
+++ util.c      17 Apr 2023 16:32:22 -0000
@@ -19,6 +19,16 @@
 int    doindent(int);
 
 /*
+ * Compute next tab stop, with `col' being the a column number and
+ * `tabw' the tab width.
+ */
+int
+ntabstop(int col, int tabw)
+{
+       return (((col + tabw) / tabw) * tabw);
+}
+
+/*
  * Display a bunch of useful information about the current location of dot.
  * The character under the cursor (in octal), the current line, row, and
  * column, and approximate position of the cursor in the file (as a
@@ -103,8 +113,7 @@ getcolpos(struct mgwin *wp)
        for (i = 0; i < wp->w_doto; ++i) {
                c = lgetc(wp->w_dotp, i);
                if (c == '\t') {
-                       col |= 0x07;
-                       col++;
+                       col = ntabstop(col, wp->w_bufp->b_tabw);
                } else if (ISCTRL(c) != FALSE)
                        col += 2;
                else if (isprint(c)) {
@@ -377,8 +386,9 @@ lfindent(int f, int n)
                        if (c != ' ' && c != '\t')
                                break;
                        if (c == '\t')
-                               nicol |= 0x07;
-                       ++nicol;
+                               nicol = ntabstop(nicol, curwp->w_bufp->b_tabw);
+                       else
+                               ++nicol;
                }
                (void)delwhite(FFRAND, 1);
 
@@ -472,11 +482,17 @@ backdel(int f, int n)
 int
 space_to_tabstop(int f, int n)
 {
+       int     c;
+
        if (n < 0)
                return (FALSE);
        if (n == 0)
                return (TRUE);
-       return (linsert((n << 3) - (curwp->w_doto & 7), ' '));
+
+       c = curwp->w_doto;
+       while (n-- > 0)
+               c = ntabstop(c, curbp->b_tabw);
+       return (linsert(c - curwp->w_doto, ' '));
 }
 
 /*

Reply via email to