Repository: dankamongmen/notcurses (language: python)

Issue:
ncplane_puttext() isn't honoring double newlines beyond the plane
in `ncman`, we're growing a plane vertically as we write to it with `ncplane_puttext()`. before each paragraph, we emit `ncplane_puttext("\n\n")`. this works fine for the parts of the plane originally visible, but once we get into off-screen growth, they get folded down into a single newline, making the displayed page look compressed.

put a unit test around this.
ncplane_puttext() isn't honoring double newlines beyond the plane
in `ncman`, we're growing a plane vertically as we write to it with `ncplane_puttext()`. before each paragraph, we emit `ncplane_puttext("\n\n")`. this works fine for the parts of the plane originally visible, but once we get into off-screen growth, they get folded down into a single newline, making the displayed page look compressed.

put a unit test around this.
ncplane_puttext() isn't honoring double newlines beyond the plane
in `ncman`, we're growing a plane vertically as we write to it with `ncplane_puttext()`. before each paragraph, we emit `ncplane_puttext("\n\n")`. this works fine for the parts of the plane originally visible, but once we get into off-screen growth, they get folded down into a single newline, making the displayed page look compressed.

put a unit test around this.


Discussion / hints:

Fix patch:
diff --git a/doc/man/man3/notcurses.3.md b/doc/man/man3/notcurses.3.md
index 3d750e2677..fa829b0d82 100644
--- a/doc/man/man3/notcurses.3.md
+++ b/doc/man/man3/notcurses.3.md
@@ -109,20 +109,20 @@ is assumed to be 80x24 cells). Further ncplanes can be created with
 and new ncplanes are placed at the top of the z-buffer. Ncplanes can be larger,
 smaller, or the same size as the physical screen, and can be placed anywhere
 relative to it (including entirely off-screen). Ncplanes are made up of
-`nccell`s (see [NcCells][] below). Information on ncplanes is available at
+**nccell**s (see [NcCells][] below). Information on ncplanes is available at
 **notcurses_plane(3)**.
 
 ## NcCells
 
-`nccell`s make up the framebuffers backing each ncplane, one cell per
+**nccell**s make up the framebuffers backing each ncplane, one cell per
 coordinate, one extended grapheme cluster (see **unicode(7)**) per cell. An
-`nccell` consists of a gcluster (either a directly-encoded 7-bit ASCII
+**nccell** consists of a gcluster (either a directly-encoded 7-bit ASCII
 character (see **ascii(7)**), or a 25-bit index into the ncplane's egcpool), a
 set of attributes, and two channels (one for the foreground, and one for the
 background—see **notcurses_channels(3)**). Information on cells is available at
 **notcurses_cell(3)**.
 
-It is not usually necessary for users to interact directly with `nccell`s. They
+It is not usually necessary for users to interact directly with **nccell**s. They
 are typically encountered when retrieving data from ncplanes or the rendered
 scene (see e.g. **ncplane_at_yx(3)**), or to achieve peak performance when a
 particular EGC is heavily reused within a plane.
diff --git a/doc/man/man3/notcurses_channels.3.md b/doc/man/man3/notcurses_channels.3.md
index 542e746bd1..d251065c3e 100644
--- a/doc/man/man3/notcurses_channels.3.md
+++ b/doc/man/man3/notcurses_channels.3.md
@@ -102,9 +102,9 @@ foreground channel and ***bchan*** as the background channel.
 
 # RETURN VALUES
 
-Functions returning `int` return -1 on failure, or 0 on success. Failure is
-always due to invalid inputs. Functions returning `bool` are predicates, and
-return the requested value. Functions returning `unsigned` forms return the
+Functions returning **int** return -1 on failure, or 0 on success. Failure is
+always due to invalid inputs. Functions returning **bool** are predicates, and
+return the requested value. Functions returning **unsigned** forms return the
 input, modified as requested.
 
 **ncchannels_reverse** inverts the color components of the two channels,
diff --git a/doc/man/man3/notcurses_output.3.md b/doc/man/man3/notcurses_output.3.md
index 26abf43b75..fb66eb03ea 100644
--- a/doc/man/man3/notcurses_output.3.md
+++ b/doc/man/man3/notcurses_output.3.md
@@ -106,7 +106,7 @@ broken by cluster breaks, terminated by the appropriate NUL terminator.
 Control characters are rejected, except for a newline when the output plane
 is in scrolling mode. A newline outside of scrolling mode will be rejected.
 
-These functions output to the `ncplane`'s current cursor location. Aside from
+These functions output to the **ncplane**'s current cursor location. Aside from
 **ncplane_puttext()**, they *do not* move to the next line upon reaching the
 right extreme of the containing plane. If the entirety of the content cannot be
 output, they will output as much as possible.
diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c
index 9ceafadec7..069721cabd 100644
--- a/src/lib/notcurses.c
+++ b/src/lib/notcurses.c
@@ -1646,6 +1646,7 @@ void scroll_down(ncplane* n){
   if(n->y == n->leny - 1){
     if(n->autogrow){
       ncplane_resize_simple(n, n->leny + 1, n->lenx);
+      ncplane_cursor_move_yx(n, n->leny - 1, 0);
       return;
     }
     if(n == notcurses_stdplane(ncplane_notcurses(n))){
diff --git a/src/man/main.c b/src/man/main.c
index 827dde7da8..bac9dbe089 100644
--- a/src/man/main.c
+++ b/src/man/main.c
@@ -588,14 +588,11 @@ puttext(struct ncplane* p, const char* s, const char* e){
 // set up the style, and continue.
 static int
 putpara(struct ncplane* p, const char* text){
-  size_t b = 0;
-  ncplane_puttext(p, -1, NCALIGN_LEFT, "\n\n", &b);
   // cur indicates where the current text to be displayed starts.
   const char* cur = text;
   uint16_t style = 0;
   const char* posttext = NULL;
   while(*cur){
-    b = 0;
     // find the next style marker
     bool inescape = false;
     const char* textend = NULL; // one past where the text to print ends
@@ -639,6 +636,8 @@ putpara(struct ncplane* p, const char* text){
           } macros[] = {
             { "aq]", "'", },
             { "dq]", "\"", },
+            { "rg]", "®", },
+            { "rs]", "\\", },
             { NULL, NULL, }
           };
           ++curend;
@@ -679,42 +678,57 @@ putpara(struct ncplane* p, const char* text){
 }
 
 static int
-draw_domnode(struct ncplane* p, const pagedom* dom, const pagenode* n){
+draw_domnode(struct ncplane* p, const pagedom* dom, const pagenode* n,
+             unsigned* wrotetext){
   ncplane_set_fchannel(p, n->ttype->channel);
   size_t b = 0;
   switch(n->ttype->ltype){
-    case LINE_TH:
+    case LINE_TH: /*
       ncplane_set_styles(p, NCSTYLE_UNDERLINE);
       ncplane_printf_aligned(p, 0, NCALIGN_LEFT, "%s(%s)", dom->title, dom->section);
       ncplane_printf_aligned(p, 0, NCALIGN_RIGHT, "%s(%s)", dom->title, dom->section);
       ncplane_set_styles(p, NCSTYLE_NONE);
-      break;
+      */break;
     case LINE_SH: // section heading
-      ncplane_puttext(p, -1, NCALIGN_LEFT, "\n\n", &b);
-      ncplane_set_styles(p, NCSTYLE_BOLD);
-      ncplane_putstr(p, n->text);
-      ncplane_set_styles(p, NCSTYLE_NONE);
-      ncplane_cursor_move_yx(p, -1, 0);
+      if(strcmp(n->text, "NAME")){
+        ncplane_puttext(p, -1, NCALIGN_LEFT, "\n\n", &b);
+        ncplane_set_styles(p, NCSTYLE_BOLD | NCSTYLE_UNDERLINE);
+        ncplane_putstr_aligned(p, -1, NCALIGN_CENTER, n->text);
+        ncplane_set_styles(p, NCSTYLE_NONE);
+        ncplane_cursor_move_yx(p, -1, 0);
+        *wrotetext = true;
+      }
       break;
     case LINE_SS: // subsection heading
       ncplane_puttext(p, -1, NCALIGN_LEFT, "\n\n", &b);
-      ncplane_set_styles(p, NCSTYLE_ITALIC);
-      ncplane_printf(p, "    %s", n->text);
+      ncplane_set_styles(p, NCSTYLE_ITALIC | NCSTYLE_UNDERLINE);
+      ncplane_putstr_aligned(p, -1, NCALIGN_CENTER, n->text);
       ncplane_set_styles(p, NCSTYLE_NONE);
       ncplane_cursor_move_yx(p, -1, 0);
+      *wrotetext = true;
       break;
     case LINE_PP: // paragraph
     case LINE_TP: // tagged paragraph
-      if(n->text){
-        putpara(p, n->text);
+      if(*wrotetext){
+        if(n->text){
+          ncplane_puttext(p, -1, NCALIGN_LEFT, "\n\n", &b);
+          putpara(p, n->text);
+        }
+      }else{
+        ncplane_set_styles(p, NCSTYLE_BOLD | NCSTYLE_ITALIC);
+        ncplane_set_fg_rgb(p, 0xff6a00);
+        ncplane_putstr_aligned(p, -1, NCALIGN_CENTER, n->text);
+        ncplane_set_fg_default(p);
+        ncplane_set_styles(p, NCSTYLE_NONE);
       }
+      *wrotetext = true;
       break;
     default:
       fprintf(stderr, "unhandled ltype %d\n", n->ttype->ltype);
       return 0; // FIXME
   }
   for(unsigned z = 0 ; z < n->subcount ; ++z){
-    if(draw_domnode(p, dom, &n->subs[z])){
+    if(draw_domnode(p, dom, &n->subs[z], wrotetext)){
       return -1;
     }
   }
@@ -727,7 +741,8 @@ draw_domnode(struct ncplane* p, const pagedom* dom, const pagenode* n){
 static int
 draw_content(struct ncplane* p){
   const pagedom* dom = ncplane_userptr(p);
-  return draw_domnode(p, dom, dom->root);
+  unsigned wrotetext = 0; // unused by us
+  return draw_domnode(p, dom, dom->root, &wrotetext);
 }
 
 static int
@@ -770,7 +785,8 @@ render_troff(struct notcurses* nc, const unsigned char* map, size_t mlen,
   return pman;
 }
 
-static const char USAGE_TEXT[] = "(k↑/j↓) (q)uit";
+static const char USAGE_TEXT[] = "⎥b⇞k↑j↓f⇟⎢ (q)uit";
+static const char USAGE_TEXT_ASCII[] = "(bkjf) (q)uit";
 
 static int
 draw_bar(struct ncplane* bar, pagedom* dom){
@@ -784,7 +800,11 @@ draw_bar(struct ncplane* bar, pagedom* dom){
   ncplane_set_styles(bar, NCSTYLE_NONE);
   ncplane_printf(bar, ") %s", dom->version);
   ncplane_set_styles(bar, NCSTYLE_ITALIC);
-  ncplane_putstr_aligned(bar, 0, NCALIGN_RIGHT, USAGE_TEXT);
+  if(notcurses_canutf8(ncplane_notcurses(bar))){
+    ncplane_putstr_aligned(bar, 0, NCALIGN_RIGHT, USAGE_TEXT);
+  }else{
+    ncplane_putstr_aligned(bar, 0, NCALIGN_RIGHT, USAGE_TEXT_ASCII);
+  }
   return 0;
 }
 
@@ -897,12 +917,29 @@ manloop(struct notcurses* nc, const char* arg){
           ncplane_move_rel(page, 1, 0);
         }
         break;
+      // we can move down iff our last line is beyond the visible area
       case 'j': case NCKEY_DOWN:
         if(ncplane_y(page) + ncplane_dim_y(page) > ncplane_dim_y(stdn)){
           ncplane_move_rel(page, -1, 0);
         }
         break;
-      case 'q':
+      case 'b': case NCKEY_PGUP:{
+        int newy = ncplane_y(page) + (int)ncplane_dim_y(stdn);
+        if(newy > 0){
+          newy = 0;
+        }
+        ncplane_move_yx(page, newy, 0);
+        break;
+      }case 'f': case NCKEY_PGDOWN:{
+        int newy = ncplane_y(page) - (int)ncplane_dim_y(stdn);
+        if(newy + ncplane_dim_y(page) < ncplane_dim_y(stdn)){
+          if((newy = ncplane_dim_y(stdn) - ncplane_dim_y(page)) > 0){
+            newy = 0;
+          }
+        }
+        ncplane_move_yx(page, newy, 0);
+        break;
+      }case 'q':
         ret = 0;
         goto done;
     }


Test patch:
diff --git a/src/tests/main.cpp b/src/tests/main.cpp
index a32ee8de82..c690942bce 100644
--- a/src/tests/main.cpp
+++ b/src/tests/main.cpp
@@ -18,7 +18,9 @@ auto testing_notcurses() -> struct notcurses* {
   // get loglevel from command line. enabling it by default leads to
   // more confusion than useful information, so leave it off by default.
   nopts.loglevel = cliloglevel;
-  nopts.flags = NCOPTION_SUPPRESS_BANNERS | NCOPTION_NO_ALTERNATE_SCREEN;
+  nopts.flags = NCOPTION_SUPPRESS_BANNERS
+                | NCOPTION_NO_ALTERNATE_SCREEN
+                | NCOPTION_DRAIN_INPUT;
   auto nc = notcurses_init(&nopts, nullptr);
   return nc;
 }
diff --git a/src/tests/textlayout.cpp b/src/tests/textlayout.cpp
index 506fd3f5e7..79ddcca753 100644
--- a/src/tests/textlayout.cpp
+++ b/src/tests/textlayout.cpp
@@ -653,6 +653,44 @@ TEST_CASE("TextLayout") {
     CHECK(0 == ncplane_destroy(sp));
   }
 
+  // test that multiple new lines are treated as such, both on the plane
+  // originally, and in any autogrown region.
+  SUBCASE("MultipleNewlines") {
+    struct ncplane_options nopts{};
+    nopts.rows = 3;
+    nopts.cols = 10;
+    nopts.flags = NCPLANE_OPTION_VSCROLL | NCPLANE_OPTION_AUTOGROW;
+    auto nn = ncplane_create(n_, &nopts);
+    REQUIRE(nn);
+    size_t b;
+    CHECK(0 == ncplane_puttext(nn, -1, NCALIGN_LEFT, "\n\n", &b));
+    unsigned y, x;
+    ncplane_cursor_yx(nn, &y, &x);
+    CHECK(2 == y);
+    CHECK(0 == x);
+    CHECK(3 == ncplane_puttext(nn, -1, NCALIGN_LEFT, "erp", &b));
+    ncplane_cursor_yx(nn, &y, &x);
+    CHECK(2 == y);
+    CHECK(3 == x);
+    CHECK(0 == notcurses_render(nc_));
+    CHECK(0 == ncplane_puttext(nn, -1, NCALIGN_LEFT, "\n", &b));
+    ncplane_cursor_yx(nn, &y, &x);
+    CHECK(3 == y);
+    CHECK(0 == x);
+    CHECK(0 == notcurses_render(nc_));
+    CHECK(0 == ncplane_puttext(nn, -1, NCALIGN_LEFT, "\n\n", &b));
+    ncplane_cursor_yx(nn, &y, &x);
+    CHECK(5 == y);
+    CHECK(0 == x);
+    CHECK(0 == notcurses_render(nc_));
+    CHECK(3 == ncplane_puttext(nn, -1, NCALIGN_LEFT, "erp\n", &b));
+    ncplane_cursor_yx(nn, &y, &x);
+    CHECK(6 == y);
+    CHECK(0 == x);
+    CHECK(0 == notcurses_render(nc_));
+    CHECK(0 == ncplane_destroy(nn));
+  }
+
   CHECK(0 == notcurses_stop(nc_));
 
 }