ncplane_puttext() work for longer lines (#880)

* The zoo demo made manifest that we had some serious problems handling sequences of longer lines in ncplane_puttext(). This remedies most of the problems, though it's not yet perfect. #871
* Guard notcurses* for NULL in log*() #878 #879
* Fix memory leak in ncdirect_dump_plane()
pull/885/head
Nick Black 4 years ago committed by GitHub
parent 4de70913bf
commit c3508d524b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1531,7 +1531,9 @@ ncplane_printf_stainable(struct ncplane* n, const char* format, ...){
// to '*bytes' if it is not NULL. Cleared columns are included in the return
// value, but *not* included in the number of bytes written. Leaves the cursor
// at the end of output. A partial write will be accomplished as far as it can;
// determine whether the write completed by inspecting '*bytes'.
// 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.
API int ncplane_puttext(struct ncplane* n, int y, ncalign_e align,
const char* text, size_t* bytes);

@ -265,7 +265,6 @@ typedef struct read_marshal {
static void*
reader_thread(void* vmarsh){
// FIXME use ncplane_puttext() to handle word breaking; this is ugly
const char text[] =
"Notcurses provides several widgets to quickly build vivid TUIs.\n\n"
"This NCReader widget facilitates free-form text entry complete with readline-style bindings. "
@ -291,9 +290,9 @@ reader_thread(void* vmarsh){
timespec_div(&demodelay, (y - targrow) / 3, &rowdelay);
// we usually won't be done rendering the text before reaching our target row
size_t textpos = 0;
const int TOWRITEMAX = 4; // FIXME throw in some jitter!
int ret;
while(y > targrow){
const int MAXTOWRITE = 8;
while(textpos < textlen || y > targrow){
pthread_mutex_lock(lock);
if( (ret = demo_render(nc)) ){
pthread_mutex_unlock(lock);
@ -303,34 +302,22 @@ reader_thread(void* vmarsh){
return THREAD_RETURN_POSITIVE;
}
}
ncplane_move_yx(rplane, --y, x);
size_t towrite = textlen - textpos;
if(towrite > TOWRITEMAX){
towrite = TOWRITEMAX;
if(y > targrow){
--y;
}
if(towrite){
ncplane_putnstr(rplane, towrite, text + textpos);
textpos += towrite;
ncplane_move_yx(rplane, y, x);
size_t towrite = strcspn(text + textpos, " \t\n") + 1;
if(towrite > MAXTOWRITE){
towrite = MAXTOWRITE;
}
pthread_mutex_unlock(lock);
clock_nanosleep(CLOCK_MONOTONIC, 0, &rowdelay, NULL);
}
while(textpos < textlen){
pthread_mutex_lock(lock);
if( (ret = demo_render(nc)) ){
pthread_mutex_unlock(lock);
if(ret < 0){
if(towrite){
char* duped = strndup(text + textpos, towrite);
size_t bytes;
if(ncplane_puttext(rplane, -1, NCALIGN_LEFT, duped, &bytes) < 0 || bytes != strlen(duped)){
free(duped);
return THREAD_RETURN_NEGATIVE;
}else if(ret > 0){
return THREAD_RETURN_POSITIVE;
}
}
size_t towrite = textlen - textpos;
if(towrite > TOWRITEMAX){
towrite = TOWRITEMAX;
}
if(towrite){
ncplane_putnstr(rplane, towrite, text + textpos);
free(duped);
textpos += towrite;
}
pthread_mutex_unlock(lock);

@ -1417,7 +1417,7 @@ int ncplane_putegc_yx(ncplane* n, int y, int x, const char* gclust, int* sbytes)
bool wide = cols > 1;
if(x == -1 && y == -1 && n->x + wide >= n->lenx){
if(!n->scrolling){
logerror(n->nc, "No room to output [%s]\n", gclust);
logerror(n->nc, "No room to output [%s] %d/%d\n", gclust, n->y, n->x);
return -1;
}
scroll_down(n);
@ -1713,8 +1713,66 @@ overlong_word(const char* text, int dimx){
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;
@ -1725,8 +1783,19 @@ int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t
const int dimx = ncplane_dim_x(n);
const int dimy = ncplane_dim_y(n);
const char* linestart = text;
int x = 0; // number of columns consumed for this line
// 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
@ -1747,8 +1816,8 @@ int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t
}
return -1;
}
//fprintf(stderr, "have possible wordbreak %lc\n", w);
if(iswordbreak(w)){
//fprintf(stderr, "wordbreak [%lc] at %d\n", w, x);
if(x == 0){
text += consumed;
linestart = text;
@ -1769,7 +1838,7 @@ int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t
x += width;
text += consumed;
}
//fprintf(stderr, "OUT! %s %zu %d\n", linestart, text - linestart, x);
//fprintf(stderr, "%d/%d OUT! %s %zu %d\n", n->y, n->x, 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.
@ -1796,7 +1865,7 @@ int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t
//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 = ncplane_align(n, align, 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){
@ -1826,6 +1895,7 @@ int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t
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){

@ -284,6 +284,80 @@ TEST_CASE("TextLayout") {
ncplane_destroy(sp);
}
SUBCASE("LayoutLongLines") {
// straight from the zoo demo, which didn't work at first
const int READER_COLS = 71; // equal to longest line
const int READER_ROWS = 4;
const char text[] =
"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.";
auto sp = ncplane_new(nc_, READER_ROWS, READER_COLS, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
ncplane_home(sp);
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
CHECK(bytes == strlen(text));
CHECK(0 == notcurses_render(nc_));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
// FIXME check line
free(line);
ncplane_destroy(sp);
}
SUBCASE("LayoutZooText") {
// straight from the zoo demo, which didn't work at first
const int READER_COLS = 64;
const int READER_ROWS = 8;
const char text[] =
"Notcurses provides several widgets to quickly build vivid TUIs.\n\n"
"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.\n\n"
"Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others.";
auto sp = ncplane_new(nc_, READER_ROWS, READER_COLS, 0, 0, nullptr);
REQUIRE(sp);
ncplane_set_scrolling(sp, true);
size_t bytes;
ncplane_home(sp);
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
CHECK(bytes == strlen(text));
CHECK(0 == notcurses_render(nc_));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
// FIXME check line
free(line);
ncplane_destroy(sp);
}
SUBCASE("LayoutZooTextNoScroll") {
// straight from the zoo demo, which didn't work at first
const int READER_COLS = 64;
const int READER_ROWS = 15;
const char text[] =
"Notcurses provides several widgets to quickly build vivid TUIs.\n\n"
"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.\n\n"
"Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others.";
auto sp = ncplane_new(nc_, READER_ROWS, READER_COLS, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
ncplane_home(sp);
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
CHECK(bytes == strlen(text));
CHECK(0 == notcurses_render(nc_));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
// FIXME check line
free(line);
ncplane_destroy(sp);
}
CHECK(0 == notcurses_stop(nc_));
}

Loading…
Cancel
Save