Repository: dankamongmen/notcurses (language: python)

Issue:
add functionality to allow planes to grow with output
While working on `ncman`, it's become apparent that it would be useful to allow a plane to grow with output. My first thoughts are that a plane with scrolling enabled would grow down, and one without would grow to the right.

The question is how this ought be exposed. My first idea was a new function, `ncplane_growtext()` or whatever, that engages this behavior (and otherwise works like `ncplane_puttext()`). That would work, certainly. But ought such functionality be more general? Ought a plane be placed into "growing mode", and all output ought result in growth? There would then be no new function of this kind; you would just set the property, and now all output functions use it. This is clearly more general, but it's also worryingly broad. Should blitting an image to a growling plane grow it? etc. There would likely be a lot of places needing changing to accommodate this -- all the regular output functions go through the `ncplane_put()` chokepoint, but things like boxes, etc. If a plane was set to grow, and we tried to mergedown a larger plane, should the smaller plane grow?

I think I'm just going to do this as `ncplane_growtext()`, but I'm interested in hearing opinions.
add functionality to allow planes to grow with output
While working on `ncman`, it's become apparent that it would be useful to allow a plane to grow with output. My first thoughts are that a plane with scrolling enabled would grow down, and one without would grow to the right.

The question is how this ought be exposed. My first idea was a new function, `ncplane_growtext()` or whatever, that engages this behavior (and otherwise works like `ncplane_puttext()`). That would work, certainly. But ought such functionality be more general? Ought a plane be placed into "growing mode", and all output ought result in growth? There would then be no new function of this kind; you would just set the property, and now all output functions use it. This is clearly more general, but it's also worryingly broad. Should blitting an image to a growling plane grow it? etc. There would likely be a lot of places needing changing to accommodate this -- all the regular output functions go through the `ncplane_put()` chokepoint, but things like boxes, etc. If a plane was set to grow, and we tried to mergedown a larger plane, should the smaller plane grow?

I think I'm just going to do this as `ncplane_growtext()`, but I'm interested in hearing opinions.
add functionality to allow planes to grow with output
While working on `ncman`, it's become apparent that it would be useful to allow a plane to grow with output. My first thoughts are that a plane with scrolling enabled would grow down, and one without would grow to the right.

The question is how this ought be exposed. My first idea was a new function, `ncplane_growtext()` or whatever, that engages this behavior (and otherwise works like `ncplane_puttext()`). That would work, certainly. But ought such functionality be more general? Ought a plane be placed into "growing mode", and all output ought result in growth? There would then be no new function of this kind; you would just set the property, and now all output functions use it. This is clearly more general, but it's also worryingly broad. Should blitting an image to a growling plane grow it? etc. There would likely be a lot of places needing changing to accommodate this -- all the regular output functions go through the `ncplane_put()` chokepoint, but things like boxes, etc. If a plane was set to grow, and we tried to mergedown a larger plane, should the smaller plane grow?

I think I'm just going to do this as `ncplane_growtext()`, but I'm interested in hearing opinions.
use libz when libdeflate is not available
Gentoo doesn't have libdeflate readily available, and ugh we need a way to use zlib when libdeflate cannot be used. The new `ncman` tool requires a means to inflate, so we've got to provide zlib in libdeflate's absence.
add functionality to allow planes to grow with output
While working on `ncman`, it's become apparent that it would be useful to allow a plane to grow with output. My first thoughts are that a plane with scrolling enabled would grow down, and one without would grow to the right.

The question is how this ought be exposed. My first idea was a new function, `ncplane_growtext()` or whatever, that engages this behavior (and otherwise works like `ncplane_puttext()`). That would work, certainly. But ought such functionality be more general? Ought a plane be placed into "growing mode", and all output ought result in growth? There would then be no new function of this kind; you would just set the property, and now all output functions use it. This is clearly more general, but it's also worryingly broad. Should blitting an image to a growling plane grow it? etc. There would likely be a lot of places needing changing to accommodate this -- all the regular output functions go through the `ncplane_put()` chokepoint, but things like boxes, etc. If a plane was set to grow, and we tried to mergedown a larger plane, should the smaller plane grow?

I think I'm just going to do this as `ncplane_growtext()`, but I'm interested in hearing opinions.


Discussion / hints:

Fix patch:
diff --git a/INSTALL.md b/INSTALL.md
index 3b02c9b92f..c53dab8515 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -16,13 +16,13 @@ There are no submodules. Dependencies are fairly minimal.
 
 Install build dependencies:
 
-`apt-get install build-essential cmake doctest-dev zlib1g-dev libavformat-dev libavutil-dev libdeflate-dev libgpm-dev libncurses-dev libqrcodegen-dev libswscale-dev libunistring-dev pandoc pkg-config`
+`apt-get install build-essential cmake doctest-dev libavformat-dev libavutil-dev libdeflate-dev libgpm-dev libncurses-dev libqrcodegen-dev libswscale-dev libunistring-dev pandoc pkg-config`
 
 If you only intend to build core Notcurses (without multimedia support), you
 can omit `libavformat-dev`, `libavutil-dev`, and `libswscale-dev` from this
-list. If you do not want to deflate Kitty graphics, you can omit
-'libdeflate-dev'. If you don't want to generate QR codes, you can omit
-'libqrcodegen-dev'.
+list. `zlib1g-dev` can be substituted for `libdeflate-dev`; build with
+`-DUSE_DEFLATE=off` in this case. If you don't want to generate QR codes, you can
+omit 'libqrcodegen-dev'.
 
 If you want to build the Python wrappers, you'll also need:
 
@@ -32,12 +32,13 @@ If you want to build the Python wrappers, you'll also need:
 
 Install build dependencies:
 
-`dnf install cmake doctest-devel zlib-devel ncurses-devel gpm-devel libqrcodegen-devel libunistring-devel OpenImageIO-devel pandoc`
+`dnf install cmake doctest-devel libdeflate-devel ncurses-devel gpm-devel libqrcodegen-devel libunistring-devel OpenImageIO-devel pandoc`
 
 If you only intend to build core Notcurses (without multimedia support), you
 can omit `OpenImageIO-devel`. If you're building outside Fedora Core (e.g. with
 RPM Fusion), you might want to use FFmpeg rather than OpenImageIO. If you don't
-want to generate QR codes, you can omit 'libqrcodegen-devel'.
+want to generate QR codes, you can omit 'libqrcodegen-devel'. `zlib-devel` can
+substitute for `libdeflate-devel`; build with `-DUSE_DEFLATE=off` in this case.
 
 ### FreeBSD / DragonFly BSD
 
@@ -47,8 +48,9 @@ Install build dependencies:
 
 If you only intend to build core Notcurses (without multimedia support), you
 can omit `multimedia/ffmpeg`. If you do not want to deflate Kitty graphics,
-you can omit 'archivers/libdeflate'. If you don't want to generate QR codes,
-you can omit 'graphics/qr-code-generator'.
+you can omit 'archivers/libdeflate'; build with `-DUSE_DEFLATE=off` in this
+case. If you don't want to generate QR codes, you can omit
+'graphics/qr-code-generator'.
 
 ### Microsoft Windows
 
@@ -62,9 +64,12 @@ Note that on Windows, OpenImageIO is (at the moment) recommended over FFmpeg.
 
 If you only intend to build core Notcurses (without multimedia support), you
 can omit `mingw-w64-ucrt-x86_64-openimageio`. If you do not want to deflate Kitty
-graphics, you can omit 'mingw-w64-ucrt-x86_64-libdeflate'.
+graphics, you can omit 'mingw-w64-ucrt-x86_64-libdeflate'; build with
+`-DUSE_DEFLATE=off` in this case.
 
 You'll want to add `-DUSE_DOCTEST=off -DUSE_PANDOC=off` to your `cmake` invocation.
+`notcurses-tester` does not currently work on Windows, and you probably don't want
+to build the UNIX-style documentation.
 
 ## Building
 
diff --git a/NEWS.md b/NEWS.md
index aa7991733a..fa83926f07 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,14 @@
 This document attempts to list user-visible changes and any major internal
 rearrangements of Notcurses.
 
+* 3.0.1 (not yet released)
+  * Added the `NCPLANE_OPTION_VSCROLL` flag. Creating an `ncplane` with this
+    flag is equivalent to immediately calling `ncplane_set_scrolling(true)`.
+  * Added the `NCPLANE_OPTION_AUTOGROW` flag and the `ncplane_set_autogrow()`
+    and `ncplane_autogrow_p()` functions. When autogrow is enabled, the plane
+    is automatically enlarged to accommodate output at its right (no scrolling)
+    or bottom (scrolling enabled) boundaries.
+
 * 3.0.0 (2021-12-01) **"In the A"**
   * Made the ABI/API changes that have been planned/collected during 2.x
     development. This primarily involved removing deprecated functions,
diff --git a/USAGE.md b/USAGE.md
index 19c9c8c4d5..9f14ce2036 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -787,9 +787,9 @@ When an `ncplane` is no longer needed, free it with
 
 ```c
 // Horizontal alignment relative to the parent plane. Use ncalign_e for 'x'.
-#define NCPLANE_OPTION_HORALIGNED 0x0001ull
+#define NCPLANE_OPTION_HORALIGNED   0x0001ull
 // Vertical alignment relative to the parent plane. Use ncalign_e for 'y'.
-#define NCPLANE_OPTION_VERALIGNED 0x0002ull
+#define NCPLANE_OPTION_VERALIGNED   0x0002ull
 // Maximize relative to the parent plane, modulo the provided margins. The
 // margins are best-effort; the plane will always be at least 1 column by
 // 1 row. If the margins can be effected, the plane will be sized to all
@@ -800,7 +800,15 @@ When an `ncplane` is no longer needed, free it with
 // If this plane is bound to a scrolling plane, it ought *not* scroll along
 // with the parent (it will still move with the parent, maintaining its
 // relative position, if the parent is moved to a new location).
-#define NCPLANE_OPTION_FIXED      0x0008ull
+#define NCPLANE_OPTION_FIXED        0x0008ull
+// Enable automatic growth of the plane to accommodate output. Creating a
+// plane with this flag is equivalent to immediately calling
+// ncplane_set_autogrow(p, true) following plane creation.
+#define NCPLANE_OPTION_AUTOGROW     0x0010ull
+// Enable vertical scrolling of the plane to accommodate output. Creating a
+// plane with this flag is equivalent to immediately calling
+// ncplane_set_scrolling(p, true) following plane creation.
+#define NCPLANE_OPTION_VSCROLL      0x0020ull
 
 typedef struct ncplane_options {
   int y;            // vertical placement relative to parent plane
@@ -938,7 +946,10 @@ scrolling is enabled).
 // All planes are created with scrolling disabled. Scrolling can be dynamically
 // controlled with ncplane_set_scrolling(). Returns true if scrolling was
 // previously enabled, or false if it was disabled.
-bool ncplane_set_scrolling(struct ncplane* n, bool scrollp);
+bool ncplane_set_scrolling(struct ncplane* n, unsigned scrollp);
+
+// Returns true iff the plane is scrolling.
+bool ncplane_scrolling_p(const struct ncplane* n);
 
 // Effect |r| scroll events on the plane |n|. Returns an error if |n| is not
 // a scrolling plane, and otherwise returns the number of lines scrolled.
@@ -1052,6 +1063,23 @@ void ncplane_translate(const struct ncplane* src, const struct ncplane* dst,
 bool ncplane_translate_abs(const struct ncplane* n, int* restrict y, int* restrict x);
 ```
 
+Normally, when text reaches the end of a plane (the horizontal end unless
+scrolling is enabled, and the vertical end otherwise), more cannot be written.
+If a plane is autogrowing, it will be enlarged to accommodate the extra text.
+If it is scrolling, it will be enlarged down; it will otherwise be enlarged
+right. Note that attempting to explicitly move the cursor outside the plane is
+still an error. The growth occurs when text is written, *not* when the cursor
+is moved.
+
+```c
+// By default, planes are created with autogrow disabled. Autogrow can be
+// dynamically controlled with ncplane_set_autogrow(). Returns true if
+// autogrow was previously enabled, or false if it was disabled.
+API bool ncplane_set_autogrow(struct ncplane* n, unsigned growp);
+
+API bool ncplane_autogrow_p(const struct ncplane* n);
+```
+
 If a given cell's glyph is zero, or its foreground channel is fully transparent,
 it is considered to have no foreground. A _default_ cell can be chosen for the
 `ncplane`, to be consulted in this case. If the base cell's glyph is likewise
@@ -1456,6 +1484,14 @@ of [Unicode Annex #14](http://www.unicode.org/reports/tr14/tr14-34.html).
 // determine whether the write completed by inspecting '*bytes'.
 int ncplane_puttext(struct ncplane* n, int y, ncalign_e align,
                     const char* text, size_t* bytes);
+
+// Like ncplane_puttext(), we're going for an orderly presentation of (possibly
+// bulk) text. Unlike ncplane_puttext(), we're going to grow the plane as
+// necessary to present it. If the plane is scrolling, we'll grow the bottom
+// out; we'll otherwise grow out to the right. Either way, no actual scrolling
+// will occur.
+int ncplane_growtext(struct ncplane* n, int y, ncalign_e align,
+                     const char* text, size_t* bytes);
 ```
 
 Lines and boxes can be drawn, interpolating their colors between their two
diff --git a/doc/man/man3/notcurses_output.3.md b/doc/man/man3/notcurses_output.3.md
index 42a6169474..26abf43b75 100644
--- a/doc/man/man3/notcurses_output.3.md
+++ b/doc/man/man3/notcurses_output.3.md
@@ -76,6 +76,8 @@ notcurses_output - output to ncplanes
 
 **int ncplane_puttext(struct ncplane* ***n***, int ***y***, ncalign_e ***align***, const char* ***text***, size_t* ***bytes***);**
 
+**int ncplane_growtext(struct ncplane* ***n***, int ***y***, ncalign_e ***align***, const char* ***text***, size_t* ***bytes***);**
+
 # DESCRIPTION
 
 These functions write EGCs (Extended Grapheme Clusters) to the specified
@@ -91,6 +93,7 @@ These functions write EGCs (Extended Grapheme Clusters) to the specified
 * **ncplane_vprintf()**: formatted output using **va_list**
 * **ncplane_printf()**: formatted output using variadic arguments
 * **ncplane_puttext()**: multi-line, line-broken, aligned text
+* **ncplane_growtext()**: **ncplane_growtext()** with an autogrowing plane
 
 All of these use the **ncplane**'s active styling, save **notcurses_putc()**,
 which uses the **nccell**'s styling. Functions accepting a single EGC expect a series
diff --git a/doc/man/man3/notcurses_plane.3.md b/doc/man/man3/notcurses_plane.3.md
index 5026e0c0be..f1a7876ce8 100644
--- a/doc/man/man3/notcurses_plane.3.md
+++ b/doc/man/man3/notcurses_plane.3.md
@@ -15,6 +15,8 @@ notcurses_plane - operations on ncplanes
 #define NCPLANE_OPTION_VERALIGNED   0x0002ull
 #define NCPLANE_OPTION_MARGINALIZED 0x0004ull
 #define NCPLANE_OPTION_FIXED        0x0008ull
+#define NCPLANE_OPTION_AUTOGROW     0x0010ull
+#define NCPLANE_OPTION_VSCROLL      0x0020ull
 
 typedef struct ncplane_options {
   int y;            // vertical placement relative to parent plane
@@ -207,10 +209,14 @@ typedef struct ncplane_options {
 
 **int ncplane_erase_region(struct ncplane* ***n***, int ***ystart***, int ***xstart***, int ***ylen***, int ***xlen***);**
 
-**bool ncplane_set_scrolling(struct ncplane* ***n***, bool ***scrollp***);**
+**bool ncplane_set_scrolling(struct ncplane* ***n***, unsigned ***scrollp***);**
 
 **bool ncplane_scrolling_p(const struct ncplane* ***n***);**
 
+**bool ncplane_set_autogrow(struct ncplane* ***n***, unsigned ***growp***);**
+
+**bool ncplane_autogrow_p(const struct ncplane* ***n***);**
+
 **int ncplane_scrollup(struct ncplane* ***n***, int ***r***);**
 
 **int ncplane_scrollup_child(struct ncplane* ***n***, const struct ncplane* ***child***);**
@@ -436,10 +442,30 @@ other rows are moved up, the last row is cleared, and output begins at the
 beginning of the last row. This does not take place until output is generated
 (i.e. it is possible to fill a plane when scrolling is enabled).
 
+Creating a plane with the **NCPLANE_OPTION_VSCROLL** flag is equivalent to
+immediately calling **ncplane_set_scrolling** on that plane with an argument
+of **true**.
+
 By default, planes bound to a scrolling plane will scroll along with it, if
 they intersect the plane. This can be disabled by creating them with the
 **NCPLANE_OPTION_FIXED** flag.
 
+## Autogrow
+
+Normally, once output reaches the right boundary of a plane, it is impossible
+to place more output unless the cursor is first moved. If scrolling is
+enabled, the cursor will automatically move down and to the left in this case,
+but upon reaching the bottom right corner of the plane, it is impossible to
+place more output without a scrolling event. If autogrow is in play, the plane
+will automatically be enlarged to accommodate output. If scrolling is disabled,
+growth takes place to the right; it otherwise takes place at the bottom. The
+plane only grows in one dimension. Autogrow cannot be enabled for the standard
+plane.
+
+Creating a plane with the **NCPLANE_OPTION_AUTOGROW** flag is equivalent to
+immediately calling **ncplane_set_autogrow** on that plane with an argument
+of **true**.
+
 ## Bitmaps
 
 **ncplane_pixel_geom** retrieves pixel geometry details. **pxy** and **pxx**
diff --git a/include/notcurses/ncseqs.h b/include/notcurses/ncseqs.h
index c7f4bfa22c..1bb5027d72 100644
--- a/include/notcurses/ncseqs.h
+++ b/include/notcurses/ncseqs.h
@@ -76,6 +76,13 @@ extern "C" {
 #define NCSEGDIGITS L"\U0001FBF0\U0001FBF1\U0001FBF2\U0001FBF3\U0001FBF4"\
                      "\U0001FBF5\U0001FBF6\U0001FBF7\U0001FBF8\U0001FBF9"
 
+#define NCSUITSBLACK L"\u2660\u2663\u2665\u2666" // ♠♣♥♦
+#define NCSUITSWHITE L"\u2661\u2662\u2664\u2667" // ♡♢♤♧
+#define NCCHESSBLACK L"\u265f\u265c\u265e\u265d\u265b\u265a" // ♟♜♞♝♛♚
+#define NCCHESSWHITE L"\u265f\u265c\u265e\u265d\u265b\u265a" // ♙♖♘♗♕♔
+#define NCDICE       L"\u2680\u2681\u2682\u2683\u2684\u2685" // ⚀⚁⚂⚃⚄⚅
+#define NCMUSICSYM   L"\u2669\u266A\u266B\u266C\u266D\u266E\u266F" // ♩♪♫♬♭♮♯
+
 // argh
 #define NCBOXLIGHT  "┌┐└┘─│"
 #define NCBOXHEAVY  "┏┓┗┛━┃"
diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h
index 3d145b7f63..cf475a856e 100644
--- a/include/notcurses/notcurses.h
+++ b/include/notcurses/notcurses.h
@@ -1265,9 +1265,9 @@ API char* notcurses_at_yx(struct notcurses* nc, unsigned yoff, unsigned xoff,
   __attribute__ ((nonnull (1)));
 
 // Horizontal alignment relative to the parent plane. Use ncalign_e for 'x'.
-#define NCPLANE_OPTION_HORALIGNED 0x0001ull
+#define NCPLANE_OPTION_HORALIGNED   0x0001ull
 // Vertical alignment relative to the parent plane. Use ncalign_e for 'y'.
-#define NCPLANE_OPTION_VERALIGNED 0x0002ull
+#define NCPLANE_OPTION_VERALIGNED   0x0002ull
 // Maximize relative to the parent plane, modulo the provided margins. The
 // margins are best-effort; the plane will always be at least 1 column by
 // 1 row. If the margins can be effected, the plane will be sized to all
@@ -1278,7 +1278,15 @@ API char* notcurses_at_yx(struct notcurses* nc, unsigned yoff, unsigned xoff,
 // If this plane is bound to a scrolling plane, it ought *not* scroll along
 // with the parent (it will still move with the parent, maintaining its
 // relative position, if the parent is moved to a new location).
-#define NCPLANE_OPTION_FIXED      0x0008ull
+#define NCPLANE_OPTION_FIXED        0x0008ull
+// Enable automatic growth of the plane to accommodate output. Creating a
+// plane with this flag is equivalent to immediately calling
+// ncplane_set_autogrow(p, true) following plane creation.
+#define NCPLANE_OPTION_AUTOGROW     0x0010ull
+// Enable vertical scrolling of the plane to accommodate output. Creating a
+// plane with this flag is equivalent to immediately calling
+// ncplane_set_scrolling(p, true) following plane creation.
+#define NCPLANE_OPTION_VSCROLL      0x0020ull
 
 typedef struct ncplane_options {
   int y;            // vertical placement relative to parent plane
@@ -1380,12 +1388,21 @@ API bool ncplane_translate_abs(const struct ncplane* n, int* RESTRICT y, int* RE
 // All planes are created with scrolling disabled. Scrolling can be dynamically
 // controlled with ncplane_set_scrolling(). Returns true if scrolling was
 // previously enabled, or false if it was disabled.
-API bool ncplane_set_scrolling(struct ncplane* n, bool scrollp)
+API bool ncplane_set_scrolling(struct ncplane* n, unsigned scrollp)
   __attribute__ ((nonnull (1)));
 
 API bool ncplane_scrolling_p(const struct ncplane* n)
   __attribute__ ((nonnull (1)));
 
+// By default, planes are created with autogrow disabled. Autogrow can be
+// dynamically controlled with ncplane_set_autogrow(). Returns true if
+// autogrow was previously enabled, or false if it was disabled.
+API bool ncplane_set_autogrow(struct ncplane* n, unsigned growp)
+  __attribute__ ((nonnull (1)));
+
+API bool ncplane_autogrow_p(const struct ncplane* n)
+  __attribute__ ((nonnull (1)));
+
 // Palette API. Some terminals only support 256 colors, but allow the full
 // palette to be specified with arbitrary RGB colors. In all cases, it's more
 // performant to use indexed colors, since it's much less data to write to the
diff --git a/src/info/main.c b/src/info/main.c
index 97210d0af3..eb2836fc4e 100644
--- a/src/info/main.c
+++ b/src/info/main.c
@@ -213,7 +213,7 @@ triviz(struct ncplane* n, const wchar_t* w1, const wchar_t* w2, const wchar_t* w
        const wchar_t* wa, const wchar_t* wb, const wchar_t* wc,
        const wchar_t* wd, const wchar_t* we, const wchar_t* wf,
        const wchar_t* w10, const wchar_t* w11, const wchar_t* w12,
-       const wchar_t* w13, const wchar_t* w14){
+       const wchar_t* w13, const wchar_t* w14, const wchar_t* w15){
   wvizn(n, w1, 2);
   ncplane_putchar(n, ' ');
   wvizn(n, w2, 2);
@@ -232,22 +232,20 @@ triviz(struct ncplane* n, const wchar_t* w1, const wchar_t* w2, const wchar_t* w
   wvizn(n, w9, 2);
   wvizn(n, wa, 1);
   ncplane_putchar(n, ' ');
-  ncplane_putchar(n, ' ');
   wvizn(n, wb, 2);
   wvizn(n, wc, 1);
   ncplane_putchar(n, ' ');
   wvizn(n, wd, 2);
   wvizn(n, we, 1);
   ncplane_putchar(n, ' ');
-  ncplane_putchar(n, ' ');
   wvizn(n, wf, 2);
   wvizn(n, w10, 1);
   ncplane_putchar(n, ' ');
   wvizn(n, w11, 2);
   wvizn(n, w12, 1);
-  ncplane_putchar(n, ' ');
-  wviz(n, w13);
+  wvizn(n, w13, 3); // chess
   wviz(n, w14);
+  wviz(n, w15);
 }
 
 static void
@@ -288,14 +286,14 @@ unicodedumper(struct ncplane* n, const char* indent){
            NCCIRCULARARCSW, NCWHITETRIANGLESW, NCSHADETRIANGLESW, NCBLACKTRIANGLESW,
            NCBOXLIGHTW, &NCBOXLIGHTW[4], NCBOXHEAVYW, &NCBOXHEAVYW[4], NCBOXROUNDW,
            &NCBOXROUNDW[4], NCBOXDOUBLEW, &NCBOXDOUBLEW[4], NCBOXOUTERW, &NCBOXOUTERW[4],
-           L"⩘▵△▹▷▿▽◃◁", NCARROWW);
+           NCCHESSBLACK, L"⩘▵△▹▷▿▽◃◁", NCARROWW);
     vertviz(n, L'⎪', NCEIGHTHSR[2], NCEIGHTHSL[2], L'⎪', L"├─╨╫╨─┤┇⎜⎟");
     ncplane_printf(n, "%s╱╽╲ ", indent);
     triviz(n, &NCWHITESQUARESW[2], &NCWHITECIRCLESW[2], &NCDIAGONALSW[2], &NCDIAGONALSW[6],
            &NCCIRCULARARCSW[2], &NCWHITETRIANGLESW[2], &NCSHADETRIANGLESW[2], &NCBLACKTRIANGLESW[2],
            &NCBOXLIGHTW[2], &NCBOXLIGHTW[5], &NCBOXHEAVYW[2], &NCBOXHEAVYW[5], &NCBOXROUNDW[2],
            &NCBOXROUNDW[5], &NCBOXDOUBLEW[2], &NCBOXDOUBLEW[5], &NCBOXOUTERW[2], &NCBOXOUTERW[5],
-           L"⩗▴⏶⯅▲▸⏵⯈▶", L"▾⏷⯆▼◂⏴⯇◀");
+           &NCCHESSBLACK[3], L"⩗▴⏶⯅▲▸⏵⯈▶", L"▾⏷⯆▼◂⏴⯇◀");
     vertviz(n, L'⎪', NCEIGHTHSR[3], NCEIGHTHSL[3], L'⎪', L"╞═╤╬╤═╡┋⎜⎟");
     braille_viz(n, L'⎡', NCBRAILLEEGCS, L'⎤', indent, L"⎨⎬", NCEIGHTHSR[4], NCEIGHTHSL[4],
                 L"╞╕╘╬╛╒╡┊⎜⎟");
diff --git a/src/lib/internal.h b/src/lib/internal.h
index ee148a2a04..e82e295c43 100644
--- a/src/lib/internal.h
+++ b/src/lib/internal.h
@@ -112,6 +112,7 @@ typedef struct ncplane {
   int margin_b, margin_r;// bottom and right margins, stored for resize
   bool scrolling;        // is scrolling enabled? always disabled by default
   bool fixedbound;       // are we fixed relative to the parent's scrolling?
+  bool autogrow;         // do we grow to accommodate output?
 
   // we need to track any widget to which we are bound, so that (1) we don't
   // end up bound to two widgets and (2) we can clean them up on shutdown
diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c
index 204aa23f8c..53315bbeac 100644
--- a/src/lib/notcurses.c
+++ b/src/lib/notcurses.c
@@ -511,8 +511,9 @@ ncplane* ncplane_new_internal(notcurses* nc, ncplane* n,
   if(p == NULL){
     return NULL;
   }
-  p->scrolling = false;
+  p->scrolling = nopts->flags & NCPLANE_OPTION_VSCROLL;
   p->fixedbound = nopts->flags & NCPLANE_OPTION_FIXED;
+  p->autogrow = nopts->flags & NCPLANE_OPTION_AUTOGROW;
   p->widget = NULL;
   p->wdestruct = NULL;
   if(nopts->flags & NCPLANE_OPTION_MARGINALIZED){
@@ -1639,6 +1640,10 @@ void scroll_down(ncplane* n){
 //fprintf(stderr, "pre-scroll: %d/%d %d/%d log: %d scrolling: %u\n", n->y, n->x, n->leny, n->lenx, n->logrow, n->scrolling);
   n->x = 0;
   if(n->y == n->leny - 1){
+    if(n->autogrow){
+      ncplane_resize_simple(n, n->leny + 1, n->lenx);
+      return;
+    }
     if(n == notcurses_stdplane(ncplane_notcurses(n))){
       ncplane_pile(n)->scrolls++;
     }
@@ -1733,31 +1738,34 @@ ncplane_put(ncplane* n, int y, int x, const char* egc, int cols,
   // line. if scrolling is enabled, move to the next line if so. if x or y are
   // specified, we must always try to print at exactly that location, and
   // there's no need to check the present location in that dimension.
+  bool linesend = false;
   if(x < 0){
     // we checked x for all negatives, but only -1 is valid (our else clause is
     // predicated on a non-negative x).
     if(x == -1){
       if(n->x + cols - 1 >= n->lenx){
-        if(!n->scrolling){
-          logerror("target x %d [%.*s] > length %d\n", n->x, bytes, egc, n->lenx);
-          return -1;
-        }
-        scroll_down(n);
+        linesend = true;
       }
     }
   }else{
     if((unsigned)x + cols - 1 >= n->lenx){
-      logerror("no room for %d cols [%.*s] at length %d\n", cols, bytes, egc, n->lenx);
+      linesend = true;
+    }
+  }
+  if(linesend){
+    if(n->scrolling){
+      scroll_down(n);
+    }else if(n->autogrow){
+      ncplane_resize_simple(n, n->leny, n->lenx + cols);
+    }else{
+      logerror("target x %d [%.*s] > length %d\n", n->x, bytes, egc, n->lenx);
       return -1;
     }
   }
-  // explicit targets outside the plane will be rejected here.
+  // targets outside the plane will be rejected here.
   if(ncplane_cursor_move_yx(n, y, x)){
     return -1;
   }
-  // FIXME scroll_down() always performs a virtual scroll of the plane, right?
-  // we don't need that unless we're at the bottom! is this right?!? FIXME
-  // FIXME note also the scroll_down() above--very fishy! FIXME
   if(*egc == '\n'){
     scroll_down(n);
   }
@@ -2822,7 +2830,7 @@ ncplane* ncplane_reparent_family(ncplane* n, ncplane* newparent){
   return n;
 }
 
-bool ncplane_set_scrolling(ncplane* n, bool scrollp){
+bool ncplane_set_scrolling(ncplane* n, unsigned scrollp){
   bool old = n->scrolling;
   n->scrolling = scrollp;
   return old;
@@ -2832,6 +2840,20 @@ bool ncplane_scrolling_p(const ncplane* n){
   return n->scrolling;
 }
 
+bool ncplane_set_autogrow(ncplane* n, unsigned growp){
+  if(n == notcurses_stdplane_const(ncplane_notcurses_const(n))){
+    logerror("can't set the standard plane autogrow\n");
+    return false;
+  }
+  bool old = n->autogrow;
+  n->autogrow = growp;
+  return old;
+}
+
+bool ncplane_autogrow_p(const ncplane* n){
+  return n->autogrow;
+}
+
 // extract an integer, which must be non-negative, and followed by either a
 // comma or a NUL terminator.
 static int
diff --git a/src/man/main.c b/src/man/main.c
index a20d26f4c9..949b461bac 100644
--- a/src/man/main.c
+++ b/src/man/main.c
@@ -683,6 +683,7 @@ render_troff(struct notcurses* nc, const unsigned char* map, size_t mlen,
     .cols = dimx,
     .userptr = dom,
     .resizecb = resize_pman,
+    .flags = NCPLANE_OPTION_AUTOGROW | NCPLANE_OPTION_VSCROLL,
   };
   struct ncplane* pman = ncplane_create(stdn, &popts);
   if(pman == NULL){


Test patch:
diff --git a/src/tests/autogrow.cpp b/src/tests/autogrow.cpp
new file mode 100644
index 0000000000..cb14683cf4
--- /dev/null
+++ b/src/tests/autogrow.cpp
@@ -0,0 +1,51 @@
+#include "main.h"
+
+TEST_CASE("Autogrow") {
+  auto nc_ = testing_notcurses();
+  if(!nc_){
+    return;
+  }
+  struct ncplane* n_ = notcurses_stdplane(nc_);
+  REQUIRE(n_);
+
+  // verify that the standard plane has scrolling disabled initially, and that
+  // we cannot enable it at runtime.
+  SUBCASE("AutogrowDisabledStdplane") {
+    CHECK(!ncplane_set_autogrow(n_, true));  // disabled at start?
+    CHECK(!ncplane_set_autogrow(n_, false)); // attempt to enable failed?
+  }
+
+  // by default, a new plane ought not have autogrow enabled--but we ought be
+  // able to enable(+disable) it, unlike the standard plane.
+  SUBCASE("AutogrowDisabledNewPlane") {
+    struct ncplane_options nopts{};
+    nopts.rows = 10;
+    nopts.cols = 10;
+    auto np = ncplane_create(n_, &nopts);
+    REQUIRE(np);
+    CHECK(!ncplane_set_autogrow(np, true));  // ought be false by default
+    CHECK(ncplane_set_autogrow(np, false));  // did we set it true?
+    CHECK(!ncplane_set_autogrow(np, false)); // did we set it false?
+    CHECK(0 == notcurses_render(nc_));
+    CHECK(0 == ncplane_destroy(np));
+  }
+
+  // with the NCPLANE_OPTION_AUTOGROW flag, the plane ought have autogrow
+  // enabled upon creation. we ought be able to disable it.
+  SUBCASE("AutogrowDisabledNewPlane") {
+    struct ncplane_options nopts{};
+    nopts.rows = 10;
+    nopts.cols = 10;
+    nopts.flags = NCPLANE_OPTION_AUTOGROW;
+    auto np = ncplane_create(n_, &nopts);
+    REQUIRE(np);
+    CHECK(ncplane_set_autogrow(np, false));  // ought be true at creation
+    CHECK(!ncplane_set_autogrow(np, true));  // did we set it false?
+    CHECK(ncplane_set_autogrow(np, false));  // did we set it true?
+    CHECK(0 == notcurses_render(nc_));
+    CHECK(0 == ncplane_destroy(np));
+  }
+
+  CHECK(0 == notcurses_stop(nc_));
+
+}