take-no-prisoners overhaul of ncplane_puttext() #829

pull/938/head
nick black 4 years ago committed by Nick Black
parent df3dc7f8e7
commit 14d6129007

@ -1267,24 +1267,28 @@ API void* ncplane_userptr(struct ncplane* n);
API void ncplane_center_abs(const struct ncplane* n, int* RESTRICT y,
int* RESTRICT x);
// Return the column at which 'c' cols ought start in order to be aligned
// according to 'align' within ncplane 'n'. Returns INT_MAX on invalid 'align'.
// Undefined behavior on negative 'c'.
// return the offset into 'availcols' at which 'cols' ought be output given the
// requirements of 'align'.
static inline int
ncplane_align(const struct ncplane* n, ncalign_e align, int c){
notcurses_align(int availcols, ncalign_e align, int cols){
if(align == NCALIGN_LEFT){
return 0;
}
int cols = ncplane_dim_x(n);
if(c > cols){
if(cols > availcols){
return 0;
}
if(align == NCALIGN_CENTER){
return (cols - c) / 2;
}else if(align == NCALIGN_RIGHT){
return cols - c;
return (availcols - cols) / 2;
}
return INT_MAX;
return availcols - cols; // NCALIGN_RIGHT
}
// Return the column at which 'c' cols ought start in order to be aligned
// according to 'align' within ncplane 'n'. Returns INT_MAX on invalid 'align'.
// Undefined behavior on negative 'c'.
static inline int
ncplane_align(const struct ncplane* n, ncalign_e align, int c){
return notcurses_align(ncplane_dim_x(n), align, c);
}
// Move the cursor to the specified position (the cursor needn't be visible).
@ -1553,6 +1557,15 @@ ncplane_printf_stainable(struct ncplane* n, const char* format, ...){
// determine whether the write completed by inspecting '*bytes'. Can output to
// multiple rows even in the absence of scrolling, but not more rows than are
// available. With scrolling enabled, arbitrary amounts of data can be emitted.
// All provided whitespace is preserved -- ncplane_puttext() followed by an
// appropriate ncplane_contents() will read back the original output.
//
// If 'y' is -1, the first row of output is taken relative to the current
// cursor: it will be left-, right-, or center-aligned in whatever remains
// of the row. On subsequent rows -- or if 'y' is not -1 -- the entire row can
// be used, and alignment works normally.
//
// A newline at any point will move the cursor to the next row.
API int ncplane_puttext(struct ncplane* n, int y, ncalign_e align,
const char* text, size_t* bytes);

@ -324,7 +324,8 @@ reader_thread(void* vmarsh){
while(textpos < textlen || y > targrow){
pthread_mutex_lock(lock);
ncplane_move_yx(rplane, y, x);
size_t towrite = strcspn(text + textpos, " \t\n") + 1;
size_t towrite = strcspn(text + textpos, " \t\n");
towrite += strspn(text + textpos + towrite, " \t\n");
if(towrite){
char* duped = strndup(text + textpos, towrite);
size_t bytes;

@ -918,6 +918,9 @@ pool_load(egcpool* pool, cell* c, const char* gcluster){
return pool_load_direct(pool, c, gcluster, bytes, cols);
}
// increment y by 1 and rotate the framebuffer up one line. x moves to 0.
void scroll_down(ncplane* n);
#ifdef __cplusplus
}
#endif

@ -0,0 +1,201 @@
#include "internal.h"
#include <unictype.h>
static bool
islinebreak(wchar_t wchar){
// UC_LINE_SEPARATOR + UC_PARAGRAPH_SEPARATOR
if(wchar == '\n'){
return true;
}
const uint32_t mask = UC_CATEGORY_MASK_Zl | UC_CATEGORY_MASK_Zp;
return uc_is_general_category_withtable(wchar, mask);
}
static bool
iswordbreak(wchar_t wchar){
const uint32_t mask = UC_CATEGORY_MASK_Z |
UC_CATEGORY_MASK_Zs;
return uc_is_general_category_withtable(wchar, mask);
}
// print the first 'bytes' bytes of 'text' to 'n', using alignment 'align'
// and requiring 'cols' columns, relative to the current cursor position.
// it is an error to call ncplane_putline() with more data than can be printed
// on the current row.
static inline int
ncplane_putline(ncplane* n, ncalign_e align, int cols, const char* text, size_t bytes){
const int avail = ncplane_dim_x(n) - n->x - 1;
const int offset = notcurses_align(avail, align, cols);
return ncplane_putnstr_yx(n, -1, n->x + offset, bytes, text);
}
static int
puttext_advance_line(ncplane* n){
//fprintf(stderr, "ADVANCING LINE FROM %d/%d\n", n->y, n->x);
if(n->scrolling){
scroll_down(n);
/*if(ncplane_putsimple_yx(n, -1, -1, '\n') < 0){
return -1;
}*/
}else{
// will fail on last line in the absence of scrolling, which is proper
if(ncplane_cursor_move_yx(n, n->y + 1, 0)){
return -1;
}
}
return 0;
}
// put up to a line of text down at the current cursor position. returns the
// number of columns consumed, or -1 on error. the number of bytes consumed is
// added to '*bytes', if 'bytes' is not NULL. any alignment is done relative to
// the current cursor position. any line-breaking character will immediately
// end the output, and move the cursor to the beginning of the next row. on an
// error, '*bytes' is not updated, and nothing is printed.
//
// an input with C columns available on the row can be one of a few things:
// * text wholly within C columns -- print it, advance x
// * text + newline within C columns -- print through newline, ++y, x = 0
// * text + wordbreak at C columns -- print through C, ++y, x = 0
// * text + text at C columns:
// * breaker (some text followed by whitespace): print through breaker, ++y, x = 0
// * no breaker (all one word, with possible leading whitespace):
// * leading whitespace? dump it, ++y, x = 0
// * C == dimx: print through C, ++y, x = 0
// * C < dimx: ++y, x = 0
static int
puttext_line(ncplane* n, ncalign_e align, const char* text, size_t* bytes){
int cursx; // current cursor location
ncplane_cursor_yx(n, NULL, &cursx);
const int dimx = ncplane_dim_x(n);
const int avail = dimx - cursx - 1;
//fprintf(stderr, "LINE %d starts at %d, len %d, avail %d\n", n->y, cursx, dimx, avail);
int bytes_leading_ws; // bytes thus far of leading whitespace
int cols_leading_ws; // cols thus far of leading whitespace
int bytes_leading_break; // bytes through last wordbreaker, 0 for no break yet
int cols_leading_break; // cols through last wordbreaker, 0 for no break yet
int cols = 0; // columns consumed thus far, cols > cols_leading_ws -> got_glyph
int b = 0; // bytes consumed thus far
bytes_leading_ws = cols_leading_ws = 0;
bytes_leading_break = cols_leading_break = 0;
while(cols <= avail){ // we can print everything we've read, if desired
mbstate_t mbstate = {};
wchar_t w;
const size_t consumed = mbrtowc(&w, text + b, MB_CUR_MAX, &mbstate);
if(consumed == (size_t)-2 || consumed == (size_t)-1){
logerror(n->nc, "Invalid UTF-8 after %d bytes\n", b);
return -1;
}
//fprintf(stderr, "converted [%s] -> %lc\n", text + b, w);
if(consumed == 0){ // text was wholly within destination row, print it
if(ncplane_putline(n, align, cols, text, b) < 0){
return -1;
}
if(bytes){
*bytes = b;
}
return cols;
}
// if w is a linebreaker, print what we have, advance, and return
if(islinebreak(w)){
//fprintf(stderr, "LINEBREAK at %d/%d\n", n->y, n->x);
if(b){
if(ncplane_putline(n, align, cols, text, b) < 0){
return -1;
}
}
if(puttext_advance_line(n)){
return -1;
}
if(bytes){
*bytes += b + consumed;
}
return cols;
}
b += consumed;
int width = wcwidth(w);
if(width < 0){
width = 0; // FIXME
}
cols += width;
if(iswordbreak(w)){
if(cols > cols_leading_ws){
bytes_leading_break = b;
cols_leading_break = cols;
}else{
bytes_leading_ws = b;
cols_leading_ws = cols;
}
}
//fprintf(stderr, "%d approved [%lc] (tbytes: %d tcols: %d)\n", n->y, w, b, cols);
}
int colsreturn = 0;
if(bytes_leading_break){
if(ncplane_putline(n, align, cols, text, bytes_leading_break) < 0){
return -1;
}
if(bytes){
*bytes += bytes_leading_break;
}
colsreturn = cols_leading_break;
}else if(bytes_leading_ws){
if(ncplane_putline(n, align, cols, text, bytes_leading_ws) < 0){
return -1;
}
if(bytes){
*bytes += bytes_leading_ws;
}
colsreturn = cols_leading_ws;
}else if(cols == dimx){
if(ncplane_putline(n, align, cols, text, b) < 0){
return -1;
}
if(bytes){
*bytes = b;
}
colsreturn = cols;
}
//fprintf(stderr, "FELL OFF line %d after %d cols %dB returning %d\n", n->y, cols, b, colsreturn);
if(puttext_advance_line(n)){
return -1;
}
return colsreturn;
}
// FIXME probably best to use u8_wordbreaks() and get all wordbreaks at once...
int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t* bytes){
if(bytes){
*bytes = 0;
}
int totalcols = 0;
// text points to the text we have *not* yet output. at each step, we see
// how much space we have available, and begin iterating from text. remember
// the most recent linebreaker that we see. when we exhaust our line, print
// through the linebreaker, and advance text.
// if we're using NCALIGN_LEFT, we'll be printing with x==-1, i.e. wherever
// the cursor is. if there's insufficient room to print anything, we need to
// try moving to the next line first. FIXME this ought actually apply to all
// alignments, which ought be taken relative to n->x. no change for
// NCALIGN_RIGHT, but NCALIGN_CENTER needs explicitly handle it...
do{
if(y != -1){
if(ncplane_cursor_move_yx(n, y, -1)){
return -1;
}
}
size_t linebytes = 0;
int cols = puttext_line(n, align, text, &linebytes);
if(cols < 0){
return -1;
}
totalcols += cols;
if(bytes){
*bytes += linebytes;
}
text += linebytes;
//fprintf(stderr, "new cursor: %d/%d consumed: %zu\n", n->y, n->x, linebytes);
y = n->y;
}while(*text);
return totalcols;
}

@ -15,7 +15,6 @@
#include <signal.h>
#include <locale.h>
#include <uniwbrk.h>
#include <unictype.h>
#include <langinfo.h>
#include <stdatomic.h>
#include <sys/ioctl.h>
@ -1291,8 +1290,7 @@ cell_obliterate(ncplane* n, cell* c){
}
// increment y by 1 and rotate the framebuffer up one line. x moves to 0.
static inline void
scroll_down(ncplane* n){
void scroll_down(ncplane* n){
n->x = 0;
if(n->y == n->leny - 1){
n->logrow = (n->logrow + 1) % n->leny;
@ -1320,6 +1318,7 @@ int ncplane_putc_yx(ncplane* n, int y, int x, const cell* c){
return -1;
}
if(c->gcluster == '\n'){
fprintf(stderr, "GOT NEWLINE AT %d/%d\n", n->y, n->x);
if(n->scrolling){
scroll_down(n);
return 0;
@ -1643,235 +1642,6 @@ int ncplane_hline_interp(ncplane* n, const cell* c, int len,
return ret;
}
static bool
islinebreak(wchar_t wchar){
// UC_LINE_SEPARATOR + UC_PARAGRAPH_SEPARATOR
if(wchar == '\n'){
return true;
}
const uint32_t mask = UC_CATEGORY_MASK_Zl | UC_CATEGORY_MASK_Zp;
return uc_is_general_category_withtable(wchar, mask);
}
static bool
iswordbreak(wchar_t wchar){
const uint32_t mask = UC_CATEGORY_MASK_Z |
UC_CATEGORY_MASK_Zs;
return uc_is_general_category_withtable(wchar, mask);
}
static bool
overlong_word(const char* text, int dimx){
size_t width = 0;
while(*text && !iswordbreak(*text)){
mbstate_t mbstate = {};
wchar_t w;
size_t consumed = mbrtowc(&w, text, MB_CUR_MAX, &mbstate);
if(consumed == (size_t)-2 || consumed == (size_t)-1){
return false;
}
text += consumed;
size_t wide = wcwidth(w);
if(wide > 0){
width += wide;
}
if(width > (size_t)dimx){
return true;
}
}
return false;
}
// Determine if we need to drop down to the next line before trying to print
// anything. We do if both (1) there is not enough room to print an entire word
// on the line, and (2) we did not start on the left-hand side. If #2 is not
// true, it's just a very long word, and we print the portion we can. Performs
// the move, if it is determined to be necessary.
static int
puttext_premove(ncplane* n, const char* text){
//fprintf(stderr, "CHECKING %d/%d %s\n", n->y, n->x, text);
if(n->x == 0){ // never move down when starting on the left hand origin
return 0;
}
const char* breaker = NULL; // where the last wordbreaker starts
if(n->x > 0 && n->x < n->lenx){
int x = n->x;
const char* beginning = text;
while(*text && x <= ncplane_dim_x(n)){
mbstate_t mbstate = {};
wchar_t w;
size_t consumed = mbrtowc(&w, text, MB_CUR_MAX, &mbstate);
if(consumed == (size_t)-2 || consumed == (size_t)-1){
logerror(n->nc, "Invalid UTF-8 after %zu bytes\n", text - beginning);
return -1;
}
if(iswordbreak(w)){
//fprintf(stderr, "wordbreak [%lc] at %d\n", w, x);
if(x == n->x){
text += consumed;
continue; // don't emit leading whitespace, or count it
}else{
return 0;
}
}
int width = wcwidth(w);
//fprintf(stderr, "have char %lc (%d) (%zu)\n", w, width, text - linestart);
if(width < 0){
width = 0;
}
if(x + width > n->lenx){
break;
}
x += width;
text += consumed;
}
}
if(*text && breaker == NULL){
fprintf(stderr, "ADVANCING DA FOKKER, JA\n");
if(n->scrolling){
scroll_down(n);
}else{
return ncplane_cursor_move_yx(n, n->y + 1, 0);
}
}
return 0;
}
// FIXME probably best to use u8_wordbreaks() and get all wordbreaks at once...
int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t* bytes){
if(bytes){
*bytes = 0;
}
int totalcols = 0;
// save the beginning for diagnostic
const char* beginning = text;
// text points to the text we have *not* yet output. at each step, we see
// how much space we have available, and begin iterating from text. remember
// the most recent linebreaker that we see. when we exhaust our line, print
// through the linebreaker, and advance text.
const int dimx = ncplane_dim_x(n);
const int dimy = ncplane_dim_y(n);
const char* linestart = text;
// if we're using NCALIGN_LEFT, we'll be printing with x==-1, i.e. wherever
// the cursor is. if there's insufficient room to print anything, we need to
// try moving to the next line first. FIXME this ought actually apply to all
// alignments, which ought be taken relative to n->x. no change for
// NCALIGN_RIGHT, but NCALIGN_CENTER needs explicitly handle it...
do{
//if(align == NCALIGN_LEFT){
if(puttext_premove(n, text)){
return -1;
}
//}
//fprintf(stderr, "**************STARTING AT %d/%d of %d/%d\n", n->y, n->x, n->leny, n->lenx);
int x = n->x; // number of columns consumed for this line
const char* breaker = NULL; // where the last wordbreaker starts
int breakercol = 0; // column of the last wordbreaker
// figure how much text to output on this line
mbstate_t mbstate = {};
int width;
wchar_t w;
// let it go all the way through to dimx. on that last hit of dimx, we
// might catch a space, in which case we want breaker updated. if it's
// not a space, it won't be printed, and we carry the word forward.
// FIXME what ought be done with multiple/leading spaces?
while(*text && x <= dimx){
//fprintf(stderr, "laying out [%s] at %d <= %d, %zu\n", linestart, x, dimx, text - linestart);
size_t consumed = mbrtowc(&w, text, MB_CUR_MAX, &mbstate);
if(consumed == (size_t)-2 || consumed == (size_t)-1){
logerror(n->nc, "Invalid UTF-8 after %zu bytes\n", text - beginning);
if(bytes){
*bytes = text - beginning;
}
return -1;
}
if(iswordbreak(w)){
//fprintf(stderr, "wordbreak [%lc] at %d\n", w, x);
if(x == 0){
text += consumed;
linestart = text;
continue; // don't emit leading whitespace, or count it
}else{
breaker = text;
breakercol = x;
}
}
width = wcwidth(w);
//fprintf(stderr, "have char %lc (%d) (%zu)\n", w, width, text - linestart);
if(width < 0){
width = 0;
}
if(x + width > dimx){
break;
}
x += width;
text += consumed;
}
fprintf(stderr, "oury: %d cursor: %d/%d(%d) OUT! %s %zu %d\n", y, n->y, n->x, n->lenx, linestart, text - linestart, x);
bool overlong = false; // ugh
// if we have no breaker, we got a single word that was longer than our
// line. print what we can and move along. if *text is nul, we're done.
if(!*text || breaker == NULL){
breaker = text;
breakercol = dimx;
}else{
// if the word on which we ended is overlong (longer than the plane is
// wide), go ahead and start printing it where it starts. otherwise, punt
// it to the next line, to avoid breaking it across lines.
if(overlong_word(breaker + 1, dimx)){
breaker = text;
breakercol = dimx;
overlong = true;
//fprintf(stderr, "NEW BREAKER: %s\n", breaker);
}
}
// if the most recent breaker was the last column, it doesn't really count
if(breakercol == dimx - 1){
//fprintf(stderr, "END OF THE LINE. breakercol: %d -> %d breakerdiff: %zu\n", breakercol, dimx, breaker - linestart);
breakercol = dimx;
++breaker; // FIXME need to advance # of bytes in the UTF8 breaker, not 1
}
fprintf(stderr, "exited at %d (%d) %zu looking at [%.*s]\n", x, dimx, breaker - linestart, (int)(breaker - linestart), linestart);
if(breaker != linestart){
totalcols += breakercol;
const int xpos = (align == NCALIGN_LEFT) ? -1 : ncplane_align(n, align, breakercol);
// blows out if we supply a y beyond leny
fprintf(stderr, "y: %d %ld %.*s\n", y, breaker - linestart, (int)(breaker - linestart), linestart);
if(ncplane_putnstr_yx(n, y, xpos, breaker - linestart, linestart) <= 0){
if(bytes){
*bytes = linestart - beginning;
}
return -1;
}
text = breaker;
}
//fprintf(stderr, "x gets %d\n", dimx == breakercol ? 0 : breakercol);
if(breaker == text || overlong){
linestart = breaker;
}else{
linestart = breaker + 1;
}
// FIXME does this print a bottom line with breakers twice?
if(y >= 0 && ++y >= dimy){
if(n->scrolling){
if(ncplane_putsimple_yx(n, -1, -1, '\n') < 0){
if(bytes){
*bytes = linestart - beginning;
}
return -1;
}
y = -1;
}
}
//fprintf(stderr, "new cursor: %d/%d\n", n->y, n->x);
//fprintf(stderr, "LOOKING AT: [%c] [%s]\n", *text, linestart);
}while(*text);
if(bytes){
*bytes = text - beginning;
}
return totalcols;
}
int ncplane_vline_interp(ncplane* n, const cell* c, int len,
uint64_t c1, uint64_t c2){
unsigned ur, ug, ub;
@ -2582,7 +2352,7 @@ int ncplane_putnstr_aligned(struct ncplane* n, int y, ncalign_e align, size_t s,
int ncplane_putnstr_yx(struct ncplane* n, int y, int x, size_t s, const char* gclusters){
int ret = 0;
fprintf(stderr, "PUT %zu at %d/%d [%.*s]\n", s, y, x, (int)s, gclusters);
//fprintf(stderr, "PUT %zu at %d/%d [%.*s]\n", s, y, x, (int)s, gclusters);
// FIXME speed up this blissfully naive solution
while((size_t)ret < s && *gclusters){
int wcs;

@ -55,7 +55,7 @@ TEST_CASE("TextLayout") {
// lay out text where a word ends on the boundary
SUBCASE("LayoutOnBoundary") {
auto sp = ncplane_new(nc_, 2, 10, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 3, 10, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "my nuclear arms";
@ -64,7 +64,7 @@ TEST_CASE("TextLayout") {
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "my nucleararms"));
CHECK(0 == strcmp(line, "my nuclear arms"));
free(line);
ncplane_destroy(sp);
}
@ -80,7 +80,7 @@ TEST_CASE("TextLayout") {
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "mygraspingarms"));
CHECK(0 == strcmp(line, "my grasping arms"));
free(line);
ncplane_destroy(sp);
}
@ -96,16 +96,14 @@ TEST_CASE("TextLayout") {
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
fprintf(stderr, "LINE: [%s]\n", line);
sleep(3);
CHECK(0 == strcmp(line, "a b c d e"));
CHECK(0 == strcmp(line, "abcde")); // FIXME should have newlines
free(line);
ncplane_destroy(sp);
}
// ensure we're honoring newlines at the start/end of rows
SUBCASE("LayoutNewlinesAtBorders") {
auto sp = ncplane_new(nc_, 4, 3, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 5, 3, 0, 0, nullptr);
REQUIRE(sp);
const char boundstr[] = "ab\ncde\nfgh";
size_t bytes;
@ -114,9 +112,7 @@ sleep(3);
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
fprintf(stderr, "LINE: [%s]\n", line);
sleep(3);
CHECK(0 == strcmp(line, "ab cde fgh"));
CHECK(0 == strcmp(line, "abcdefgh"));
free(line);
ncplane_destroy(sp);
}
@ -124,7 +120,7 @@ sleep(3);
// lay out text where a wide word crosses the boundary
SUBCASE("LayoutCrossBoundaryWide") {
if(enforce_utf8()){
auto sp = ncplane_new(nc_, 2, 6, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 2, 7, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "a 血的神";
@ -133,7 +129,7 @@ sleep(3);
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "a血的神"));
CHECK(0 == strcmp(line, "a 血的神"));
free(line);
ncplane_destroy(sp);
}
@ -151,7 +147,7 @@ sleep(3);
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "my thermonucleararms"));
CHECK(0 == strcmp(line, "my thermonuclear arms"));
free(line);
ncplane_destroy(sp);
}
@ -160,7 +156,7 @@ sleep(3);
// next line, but instead be printed where it starts
SUBCASE("LayoutTransPlanarWide") {
if(enforce_utf8()){
auto sp = ncplane_new(nc_, 2, 8, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 3, 10, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "1 我能吞下玻璃";
@ -176,7 +172,7 @@ sleep(3);
}
SUBCASE("LayoutLeadingSpaces") {
auto sp = ncplane_new(nc_, 3, 10, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 3, 18, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = " \t\n my thermonuclear arms";
@ -185,39 +181,39 @@ sleep(3);
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "my thermonucleararms"));
CHECK(0 == strcmp(line, " my thermonuclear arms"));
free(line);
ncplane_destroy(sp);
}
// create a plane of a single row, and fill it exactly with one word
// create a plane of two rows, and fill exactly one with one word
SUBCASE("LayoutFills1DPlane") {
auto sp = ncplane_new(nc_, 1, 14, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 2, 15, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "quarkgluonfart";
const char boundstr[] = "quarkgluonfart ";
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
CHECK(0 == notcurses_render(nc_));
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "quarkgluonfart"));
CHECK(0 == strcmp(line, "quarkgluonfart "));
free(line);
ncplane_destroy(sp);
}
// create a plane of a single row, and fill it exactly with words
// create a plane of two rows, and fill exactly one with words
SUBCASE("LayoutFills1DPlaneWords") {
auto sp = ncplane_new(nc_, 1, 16, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 2, 17, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "quark gluon fart";
const char boundstr[] = "quark gluon fart ";
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
CHECK(0 == notcurses_render(nc_));
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "quark gluon fart"));
CHECK(0 == strcmp(line, "quark gluon fart "));
free(line);
ncplane_destroy(sp);
}
@ -238,25 +234,25 @@ sleep(3);
ncplane_destroy(sp);
}
// create a plane of two rows, and exactly fill both
// create a plane of three rows, and exactly fill two with regular ol' words
SUBCASE("LayoutFillsPlane") {
auto sp = ncplane_new(nc_, 2, 13, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 3, 14, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "quantum balls scratchy no?!";
const char boundstr[] = "quantum balls scratchy no?! ";
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
CHECK(0 == notcurses_render(nc_));
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "quantum ballsscratchy no?!"));
CHECK(0 == strcmp(line, "quantum balls scratchy no?! "));
free(line);
ncplane_destroy(sp);
}
// create a plane of two rows, and exactly fill both, with no spaces
// create a plane of three rows, and exactly fill two, with no spaces
SUBCASE("LayoutFillsPlaneNoSpaces") {
auto sp = ncplane_new(nc_, 2, 6, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 3, 6, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "0123456789AB";
@ -270,26 +266,28 @@ sleep(3);
ncplane_destroy(sp);
}
// create a plane of two rows, and exactly fill both with wide chars
// create a plane of three rows, and exactly fill two with wide chars
SUBCASE("LayoutFillsPlaneWide") {
if(enforce_utf8()){
auto sp = ncplane_new(nc_, 2, 6, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 3, 7, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "我能吞 下玻璃";
const char boundstr[] = "我能吞 下玻璃 ";
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
CHECK(0 == notcurses_render(nc_));
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "我能吞下玻璃"));
CHECK(0 == strcmp(line, "我能吞 下玻璃 "));
free(line);
ncplane_destroy(sp);
}
}
// if we don't have scrolling enabled, puttext() with more text than will
// fit on the plane ought return error, but print what it can.
SUBCASE("LayoutLongNoScroll") {
auto sp = ncplane_new(nc_, 2, 13, 0, 0, nullptr);
auto sp = ncplane_new(nc_, 2, 14, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
const char boundstr[] = "quantum balls scratchy no?! truly! arrrrp";
@ -299,7 +297,7 @@ sleep(3);
CHECK(bytes < strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "quantum ballsscratchy no?!"));
CHECK(0 == strcmp(line, "quantum balls scratchy no?! "));
free(line);
ncplane_destroy(sp);
}
@ -315,7 +313,7 @@ sleep(3);
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "scratchy?!true! arrrrp"));
CHECK(0 == strcmp(line, "scratchy?! true! arrrrp"));
free(line);
ncplane_destroy(sp);
}
@ -338,7 +336,7 @@ sleep(3);
CHECK(0 == notcurses_render(nc_));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornareneque ac ipsum viverra, vestibulum hendrerit leo consequat. Integervelit, pharetra sed nisl quis, porttitor ornare purus. Cras acsollicitudin dolor, eget elementum dolor. Quisque lobortis sagittis."));
CHECK(0 == strcmp(line, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare neque ac ipsum viverra, vestibulum hendrerit leo consequat. Integer velit, pharetra sed nisl quis, porttitor ornare purus. Cras ac sollicitudin dolor, eget elementum dolor. Quisque lobortis sagittis."));
free(line);
ncplane_destroy(sp);
}
@ -363,7 +361,7 @@ sleep(3);
CHECK(0 == notcurses_render(nc_));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "to be selected from a list of n items. NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. A variety of plots are supported, and menus can beplaced along the top and/or bottom of any plane.Widgets can be controlled with the keyboard and/or mouse. Theyare implemented atop ncplanes, and these planes can bemanipulated like all others."));
CHECK(0 == strcmp(line, "to be selected from a list of n items. NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others."));
free(line);
ncplane_destroy(sp);
}
@ -389,7 +387,7 @@ sleep(3);
CHECK(0 == notcurses_render(nc_));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "Notcurses provides several widgets to quickly build vividTUIs.This NCReader widget facilitates free-form text entrycomplete with readline-style bindings. NCSelector allows asingleoption to be selected from a list. NCMultiselector allows 0..noptions to be selected from a list of n items. NCFdplane streamsa file descriptor, while NCSubproc spawns a subprocess andstreams its output. A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.Widgetscan be controlled with the keyboard and/or mouse. They areimplemented atop ncplanes, and these planes can be manipulatedlike all others."));
CHECK(0 == strcmp(line, "Notcurses provides several widgets to quickly build vivid TUIs.This NCReader widget facilitates free-form text entry complete with readline-style bindings. NCSelector allows a single option to be selected from a list. NCMultiselector allows 0..n options to be selected from a list of n items. NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others."));
free(line);
ncplane_destroy(sp);
}

Loading…
Cancel
Save