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, ' ')); } /*