ncplane_at_cursor (#76), CCCflag, nonblocking stdin (#78) (#84)

* put stdin into nonblocking mode, retry on short write to stdout #78
* wrap getc_blocking() around a poll #78
* get CCCflag from terminfo. stop clearing the screen in render/startup
* implement ncplane_at_cursor() #76
* ncplane_at_cursor() unit test for simples #76
* PlaneAtCursorComplex unit test #76
* PlaneAtCursorInsane() unit test #76
* nplane_at_cursor: return number of bytes, not just 0/-1
* uniblock-demo: add a bunch of pages from Unicode 12
* demo: make -d delay multiplier a float
* egcpool: check offset against poolsize in check_validity()
* notcurses_init(): set smkx/rmkx to NULL with pass_through_esc
* PlaneAtCursorAttrs unit test #76
* add ncplane_styles() accessor
pull/85/head
Nick Black 5 years ago committed by GitHub
parent 0cb1c24622
commit a7d50b557d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -48,7 +48,7 @@ target_compile_options(notcurses
)
target_compile_definitions(notcurses
PUBLIC
_DEFAULT_SOURCE _XOPEN_SOURCE=700
_DEFAULT_SOURCE _XOPEN_SOURCE=700 _GNU_SOURCE
)
file(GLOB DEMOSRCS CONFIGURE_DEPENDS src/demo/*.c)

@ -216,9 +216,9 @@ API int ncplane_move_below(struct ncplane* RESTRICT n, struct ncplane* RESTRICT
// Splice ncplane 'n' out of the z-buffer, and reinsert it above 'above'.
API int ncplane_move_above(struct ncplane* RESTRICT n, struct ncplane* RESTRICT above);
// Retrieve the topmost cell at the cursor location on the specified plane,
// returning it in 'c'. This copy is safe to use until the ncplane is destroyed.
API void ncplane_at_cursor(const struct ncplane* n, cell* c);
// Retrieve the cell at the cursor location on the specified plane, returning
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
API int ncplane_at_cursor(struct ncplane* n, cell* c);
// Manipulate the opaque user pointer associated with this plane.
// ncplane_set_userptr() returns the previous userptr after replacing
@ -321,6 +321,9 @@ API void ncplane_styles_on(struct ncplane* n, unsigned stylebits);
// Remove the specified styles from the ncplane's existing spec.
API void ncplane_styles_off(struct ncplane* n, unsigned stylebits);
// Return the current styling for this ncplane.
API unsigned ncplane_styles(const struct ncplane* n);
// Fine details about terminal
// Returns a 16-bit bitmask in the LSBs of supported curses-style attributes
@ -377,7 +380,7 @@ cell_styles_set(cell* c, unsigned stylebits){
// Get the style bits, shifted over into the LSBs.
static inline unsigned
cell_get_style(const cell* c){
cell_styles(const cell* c){
return (c->attrword & CELL_STYLE_MASK) >> 16u;
}
@ -531,6 +534,10 @@ cell_egc_idx(const cell* c){
return c->gcluster - 0x80;
}
// return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer
// is invalidated by any further operation on the plane 'n', so...watch out!
API const char* cell_extended_gcluster(const struct ncplane* n, const cell* c);
// load up six cells with the EGCs necessary to draw a box. returns 0 on
// success, -1 on error. on error, any cells this function might
// have loaded before the error are cell_release()d. There must be at least
@ -592,6 +599,142 @@ API struct AVFrame* ncvisual_decode(struct ncvisual* nc);
// appropriate size.
API int ncvisual_render(struct ncvisual* ncv, int ystop, int xstop);
// A panelreel is an notcurses region devoted to displaying zero or more
// line-oriented, contained panels between which the user may navigate. If at
// least one panel exists, there is an active panel. As much of the active
// panel as is possible is always displayed. If there is space left over, other
// panels are included in the display. Panels can come and go at any time, and
// can grow or shrink at any time.
//
// This structure is amenable to line- and page-based navigation via keystrokes,
// scrolling gestures, trackballs, scrollwheels, touchpads, and verbal commands.
enum bordermaskbits {
BORDERMASK_TOP = 0x1,
BORDERMASK_RIGHT = 0x2,
BORDERMASK_BOTTOM = 0x4,
BORDERMASK_LEFT = 0x8,
};
typedef struct panelreel_options {
// require this many rows and columns (including borders). otherwise, a
// message will be displayed stating that a larger terminal is necessary, and
// input will be queued. if 0, no minimum will be enforced. may not be
// negative. note that panelreel_create() does not return error if given a
// WINDOW smaller than these minima; it instead patiently waits for the
// screen to get bigger.
int min_supported_cols;
int min_supported_rows;
// use no more than this many rows and columns (including borders). may not be
// less than the corresponding minimum. 0 means no maximum.
int max_supported_cols;
int max_supported_rows;
// desired offsets within the surrounding WINDOW (top right bottom left) upon
// creation / resize. a panelreel_move() operation updates these.
int toff, roff, boff, loff;
// is scrolling infinite (can one move down or up forever, or is an end
// reached?). if true, 'circular' specifies how to handle the special case of
// an incompletely-filled reel.
bool infinitescroll;
// is navigation circular (does moving down from the last panel move to the
// first, and vice versa)? only meaningful when infinitescroll is true. if
// infinitescroll is false, this must be false.
bool circular;
// outcurses can draw a border around the panelreel, and also around the
// component tablets. inhibit borders by setting all valid bits in the masks.
// partially inhibit borders by setting individual bits in the masks. the
// appropriate attr and pair values will be used to style the borders.
// focused and non-focused tablets can have different styles. you can instead
// draw your own borders, or forgo borders entirely.
unsigned bordermask; // bitfield; 1s will not be drawn (see bordermaskbits)
uint32_t borderattr; // attributes used for panelreel border, no color!
int borderpair; // extended color pair for panelreel border
unsigned tabletmask; // bitfield; same as bordermask but for tablet borders
uint32_t tabletattr; // attributes used for tablet borders, no color!
int tabletpair; // extended color pair for tablet borders
uint32_t focusedattr;// attributes used for focused tablet borders, no color!
int focusedpair; // extended color pair for focused tablet borders
} panelreel_options;
struct tablet;
struct panelreel;
// Create a panelreel according to the provided specifications. Returns NULL on
// failure. w must be a valid WINDOW*, to which offsets are relative. Note that
// there might not be enough room for the specified offsets, in which case the
// panelreel will be clipped on the bottom and right. A minimum number of rows
// and columns can be enforced via popts. efd, if non-negative, is an eventfd
// that ought be written to whenever panelreel_touch() updates a tablet (this
// is useful in the case of nonblocking input).
struct panelreel* panelreel_create(struct ncplane* nc,
const panelreel_options* popts,
int efd);
// Tablet draw callback, provided a ncplane the first column that may be used,
// the first row that may be used, the first column that may not be used, the
// first row that may not be used, and a bool indicating whether output ought
// be clipped at the top (true) or bottom (false). Rows and columns are
// zero-indexed, and both are relative to the panel.
//
// Regarding clipping: it is possible that the tablet is only partially
// displayed on the screen. If so, it is either partially present on the top of
// the screen, or partially present at the bottom. In the former case, the top
// is clipped (cliptop will be true), and output ought start from the end. In
// the latter case, cliptop is false, and output ought start from the beginning.
//
// Returns the number of lines of output, which ought be less than or equal to
// maxy - begy, and non-negative (negative values might be used in the future).
typedef int (*tabletcb)(struct ncplane* p, int begx, int begy, int maxx,
int maxy, bool cliptop);
// Add a new tablet to the provided panelreel, having the callback object
// opaque. Neither, either, or both of after and before may be specified. If
// neither is specified, the new tablet can be added anywhere on the reel. If
// one or the other is specified, the tablet will be added before or after the
// specified tablet. If both are specifid, the tablet will be added to the
// resulting location, assuming it is valid (after->next == before->prev); if
// it is not valid, or there is any other error, NULL will be returned.
struct tablet* panelreel_add(struct panelreel* pr, struct tablet* after,
struct tablet *before, tabletcb cb, void* opaque);
// Return the number of tablets.
int panelreel_tabletcount(const struct panelreel* pr);
// Indicate that the specified tablet has been updated in a way that would
// change its display. This will trigger some non-negative number of callbacks
// (though not in the caller's context).
int panelreel_touch(struct panelreel* pr, struct tablet* t);
// Delete the tablet specified by t from the panelreel specified by pr. Returns
// -1 if the tablet cannot be found.
int panelreel_del(struct panelreel* pr, struct tablet* t);
// Delete the active tablet. Returns -1 if there are no tablets.
int panelreel_del_focused(struct panelreel* pr);
// Move to the specified location within the containing WINDOW.
int panelreel_move(struct panelreel* pr, int x, int y);
// Redraw the panelreel in its entirety, for instance after
// clearing the screen due to external corruption, or a SIGWINCH.
int panelreel_redraw(struct panelreel* pr);
// Return the focused tablet, if any tablets are present. This is not a copy;
// be careful to use it only for the duration of a critical section.
struct tablet* panelreel_focused(struct panelreel* pr);
// Change focus to the next tablet, if one exists
struct tablet* panelreel_next(struct panelreel* pr);
// Change focus to the previous tablet, if one exists
struct tablet* panelreel_prev(struct panelreel* pr);
// Destroy a panelreel allocated with panelreel_create(). Does not destroy the
// underlying WINDOW. Returns non-zero on failure.
int panelreel_destroy(struct panelreel* pr);
#undef API
#ifdef __cplusplus

@ -8,6 +8,8 @@
#include <notcurses.h>
#include "demo.h"
static const unsigned long GIG = 1000000000;
struct timespec demodelay = {
.tv_sec = 1,
.tv_nsec = 0,
@ -19,7 +21,7 @@ usage(const char* exe, int status){
fprintf(out, "usage: %s [ -h ] [ -k ] [ -d ns ] [ -f renderfile ] demospec\n", exe);
fprintf(out, " -h: this message\n");
fprintf(out, " -k: keep screen; do not switch to alternate\n");
fprintf(out, " -d: delay in nanoseconds between demos\n");
fprintf(out, " -d: delay multiplier (float)\n");
fprintf(out, " -f: render to file in addition to stdout\n");
fprintf(out, "all demos are run if no specification is provided\n");
fprintf(out, " i: run intro\n");
@ -173,13 +175,15 @@ handle_opts(int argc, char** argv, notcurses_options* opts){
}
break;
case 'd':{
char* eptr;
unsigned long ns = strtoul(optarg, &eptr, 0);
if(*eptr){
float f;
if(sscanf(optarg, "%f", &f) != 1){
fprintf(stderr, "Couldn't get a float from %s\n", optarg);
usage(*argv, EXIT_FAILURE);
}
demodelay.tv_sec = ns / 1000000000;
demodelay.tv_nsec = ns % 1000000000;
uint64_t ns = f * GIG;
printf("F: %f NS: %lu\n", f, ns);
demodelay.tv_sec = ns / GIG;
demodelay.tv_nsec = ns % GIG;
break;
}default:
usage(*argv, EXIT_FAILURE);

@ -45,9 +45,39 @@ int unicodeblocks_demo(struct notcurses* nc){
{ .name = "Supplemental Mathematical Operators", .start = 0x2a00, },
{ .name = "Glagolitic, Georgian Supplement, Tifinagh", .start = 0x2c00, },
{ .name = "Supplemental Punctuation, CJK Radicals", .start = 0x2e00, },
{ .name = "CJK Symbols and Punctuation", .start = 0x3000, },
{ .name = "Enclosed CJK Letters and Months", .start = 0x3200, },
{ .name = "CJK Unified Ideographs Extension A", .start = 0x3400, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3600, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3800, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3a00, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3c00, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x3e00, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4000, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4200, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4400, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4600, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4800, },
{ .name = "CJK Unified Ideographs Extension A (cont.)", .start = 0x4a00, },
{ .name = "CJK Unified Ideographs Extension A, Yijang Hexagram", .start = 0x4c00, },
{ .name = "CJK Unified Ideographs", .start = 0x4e00, },
{ .name = "Yi Syllables", .start = 0xa000, },
{ .name = "Yi Syllables", .start = 0xa200, },
{ .name = "Yi Syllables, Yi Radicals, Lisu, Vai", .start = 0xa400, },
{ .name = "Vai, Cyrillic Extended-B, Bamum, Tone Letters, Latin Extended-D", .start = 0xa600, },
{ .name = "Halfwidth and Fullwidth Forms", .start = 0xff00, },
{ .name = "Linear B Syllabary, Linear B Ideograms, Aegean Numbers, Phaistos Disc", .start = 0x10000, },
{ .name = "Lycian, Carian, Coptic Epact Numbers, Old Italic, Gothic, Old Permic", .start = 0x10200, },
{ .name = "Cuneiform", .start = 0x12000, },
{ .name = "Cuneiform (cont.)", .start = 0x12200, },
{ .name = "Byzantine Musical Symbols, Musical Symbols", .start = 0x1d000, },
{ .name = "Ancient Greek Musical Notation, Mayan Numerals, Tai Xuan Jing, Counting Rods", .start = 0x1d200, },
{ .name = "Mathematical Alphanumeric Symbols", .start = 0x1d400, },
{ .name = "Mathematical Alphanumeric Symbols (cont.)", .start = 0x1d600, },
{ .name = "Sutton SignWriting", .start = 0x1d800, },
{ .name = "Glagolitic Supplement, Nyiakeng Puachue Hmong", .start = 0x1e000, },
{ .name = "Ottoman Siyaq Numbers", .start = 0x1ed00, },
{ .name = "Arabic Mathematical Alphabetic Symbols", .start = 0x1ee00, },
{ .name = "Mahjong Tiles, Domino Tiles, Playing Cards", .start = 0x1f000, },
{ .name = "Enclosed Ideographic Supplement, Miscellaneous Symbols", .start = 0x1f200, },
{ .name = "Miscellaneous Symbols and Pictographs (cont.)", .start = 0x1f400, },
@ -72,7 +102,6 @@ int unicodeblocks_demo(struct notcurses* nc){
nstotal /= 10;
subdelay.tv_sec = nstotal / 1000000000;
subdelay.tv_nsec = nstotal % 1000000000;
fprintf(stderr, "SUBDELAY: %lu %lu\n", subdelay.tv_sec, subdelay.tv_nsec);
for(sindex = 0 ; sindex < sizeof(blocks) / sizeof(*blocks) ; ++sindex){
uint32_t blockstart = blocks[sindex].start;
const char* description = blocks[sindex].name;
@ -105,7 +134,6 @@ fprintf(stderr, "SUBDELAY: %lu %lu\n", subdelay.tv_sec, subdelay.tv_nsec);
memset(&ps, 0, sizeof(ps));
wchar_t w = blockstart + chunk * CHUNKSIZE + z;
char utf8arr[MB_CUR_MAX + 1];
// FIXME we can print wide ones here, just add an extra line
if(wcwidth(w) >= 1 && iswprint(w)){
int bwc = wcrtomb(utf8arr, w, &ps);
if(bwc < 0){
@ -122,8 +150,6 @@ fprintf(stderr, "SUBDELAY: %lu %lu\n", subdelay.tv_sec, subdelay.tv_nsec);
if(cell_load(n, &c, utf8arr) < 0){ // FIXME check full len was eaten?
return -1;;
}
/*ncplane_fg_rgb8(n, 0xad + z, 0xd8, 0xe6);
ncplane_bg_rgb8(n, 0x20, 0x20, 0x20);*/
cell_set_fg(&c, 0xad + z * 2, 0xd8, 0xe6 - z * 2);
cell_set_bg(&c, 8 * chunk, 8 * chunk + z, 8 * chunk);
if(ncplane_putc(n, &c) < 0){

@ -179,6 +179,10 @@ egcpool_stash(egcpool* pool, const char* egc, size_t ulen){
// Run a consistency check on the offset; ensure it's a valid, non-empty EGC.
static inline bool
egcpool_check_validity(const egcpool* pool, int offset){
if(offset >= pool->poolsize){
fprintf(stderr, "Offset (%d) greater than size (%d)\n", offset, pool->poolsize);
return false;
}
const char* egc = pool->pool + offset;
if(*egc == '\0'){
fprintf(stderr, "Bad offset (%d): empty\n", offset);

@ -1,6 +1,7 @@
#include <ncurses.h> // needed for some definitions, see terminfo(3ncurses)
#include <time.h>
#include <term.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
@ -8,6 +9,7 @@
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/poll.h>
#include <stdatomic.h>
#include <sys/ioctl.h>
#include <libavutil/version.h>
@ -94,6 +96,7 @@ typedef struct notcurses {
struct termios tpreserved; // terminal state upon entry
bool RGBflag; // terminfo-reported "RGB" flag for 24bpc directcolor
bool CCCflag; // terminfo-reported "CCC" flag for palette set capability
ncplane* top; // the contents of our topmost plane (initially entire screen)
ncplane* stdscr;// aliases some plane from the z-buffer, covers screen
FILE* renderfp; // debugging FILE* to which renderings are written
@ -217,6 +220,26 @@ const void* ncplane_userptr_const(const ncplane* n){
return n->userptr;
}
// is the cursor in an invalid position? it never should be, but it's probably
// better to make sure (it's cheap) than to read from/write to random crap.
static bool
cursor_invalid_p(const ncplane* n){
if(n->y >= n->leny || n->x >= n->lenx){
return true;
}
if(n->y < 0 || n->x < 0){
return true;
}
return false;
}
int ncplane_at_cursor(ncplane* n, cell* c){
if(cursor_invalid_p(n)){
return -1;
}
return cell_duplicate(n, c, &n->fb[fbcellidx(n, n->y, n->x)]);
}
void ncplane_dim_yx(const ncplane* n, int* rows, int* cols){
if(rows){
*rows = n->leny;
@ -472,6 +495,8 @@ int ncplane_resize(ncplane* n, int keepy, int keepx, int keepleny,
return 0;
}
// find the pointer on the z-index referencing the specified plane. writing to
// this pointer will remove the plane (and everything below it) from the stack.
static ncplane**
find_above_ncplane(ncplane* n){
notcurses* nc = n->nc;
@ -507,6 +532,7 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
char* longname_term = longname();
fprintf(stderr, "Term: %s\n", longname_term ? longname_term : "?");
nc->RGBflag = tigetflag("RGB") == 1;
nc->CCCflag = tigetflag("ccc") == 1;
if((nc->colors = tigetnum("colors")) <= 0){
fprintf(stderr, "This terminal doesn't appear to support colors\n");
nc->colors = 1;
@ -578,6 +604,8 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
if(!opts->pass_through_esc){
term_verify_seq(&nc->smkx, "smkx");
term_verify_seq(&nc->rmkx, "rmkx");
}else{
nc->smkx = nc->rmkx = NULL;
}
// Neither of these is supported on e.g. the "linux" virtual console.
if(!opts->inhibit_alternate_screen){
@ -590,6 +618,19 @@ interrogate_terminfo(notcurses* nc, const notcurses_options* opts){
return 0;
}
static int
make_nonblocking(FILE* fp){
int fd = fileno(fp);
if(fd < 0){
return -1;
}
int flags = fcntl(fd, F_GETFL, 0);
if(flags < 0){
return -1;
}
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
notcurses* notcurses_init(const notcurses_options* opts){
struct termios modtermios;
notcurses* ret = malloc(sizeof(*ret));
@ -599,6 +640,10 @@ notcurses* notcurses_init(const notcurses_options* opts){
ret->ttyfp = opts->outfp;
ret->renderfp = opts->renderfp;
ret->ttyinfp = stdin; // FIXME
if(make_nonblocking(ret->ttyinfp)){
free(ret);
return NULL;
}
if((ret->ttyfd = fileno(ret->ttyfp)) < 0){
fprintf(stderr, "No file descriptor was available in opts->outfp\n");
free(ret);
@ -644,7 +689,7 @@ notcurses* notcurses_init(const notcurses_options* opts){
free_plane(ret->top);
goto err;
}
term_emit(ret->clear, ret->ttyfp, false);
// term_emit(ret->clear, ret->ttyfp, false);
fprintf(ret->ttyfp, "\n"
" notcurses %s by nick black\n"
" terminfo from %s\n"
@ -806,6 +851,10 @@ extended_gcluster(const ncplane* n, const cell* c){
return n->pool.pool + idx;
}
const char* cell_extended_gcluster(const struct ncplane* n, const cell* c){
return extended_gcluster(n, c);
}
// write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the
// number of columns occupied by this EGC (only an approximation; it's actually
// a property of the font being used).
@ -835,18 +884,12 @@ term_putc(FILE* out, const ncplane* n, const cell* c){
static void
advance_cursor(ncplane* n, int cols){
if(n->y >= n->leny){
if(n->x >= n->lenx){
return; // stuck!
}
if(cursor_invalid_p(n)){
return; // stuck!
}
if((n->x += cols) >= n->lenx){
if(n->y >= n->leny){
n->x = n->lenx;
}else{
n->x -= n->lenx;
++n->y;
}
++n->y;
n->x -= n->lenx;
}
}
@ -882,7 +925,7 @@ term_setstyles(const notcurses* nc, FILE* out, uint32_t* curattr, const cell* c)
if(cell_inherits_style(c)){
return 0; // change nothing
}
uint32_t cellattr = cell_get_style(c);
uint32_t cellattr = cell_styles(c);
if(cellattr == *curattr){
return 0; // happy agreement, change nothing
}
@ -973,6 +1016,36 @@ int ncplane_move_bottom(ncplane* n){
return 0;
}
static int
blocking_write(int fd, const char* buf, size_t buflen){
size_t written = 0;
do{
ssize_t w = write(fd, buf + written, buflen - written);
if(w < 0){
if(errno != EAGAIN && errno != EWOULDBLOCK){
return -1;
}
}else{
written += w;
}
}while(written < buflen);
return 0;
}
// determine the best palette for the current frame, and write the necessary
// escape sequences to 'out'.
static int
prep_optimized_palette(notcurses* nc, FILE* out){
if(nc->RGBflag){
return 0; // DirectColor, no need to write palette
}
if(!nc->CCCflag){
return 0; // can't change palette
}
// FIXME
return 0;
}
// FIXME this needs to keep an invalidation bitmap, rather than blitting the
// world every time
int notcurses_render(notcurses* nc){
@ -986,8 +1059,11 @@ int notcurses_render(notcurses* nc){
if(out == NULL){
return -1;
}
prep_optimized_palette(nc, out); // FIXME do what on failure?
uint32_t curattr = 0; // current attributes set (does not include colors)
term_emit(nc->clear, out, false);
// no need to write a clearscreen, since we update everything that's been
// changed. just move the physical cursor to the upper left corner.
term_emit(tiparm(nc->cup, 0, 0), out, false);
unsigned lastr, lastg, lastb;
unsigned lastbr, lastbg, lastbb;
// we can elide a color escape iff the color has not changed between the two
@ -1060,8 +1136,8 @@ int notcurses_render(notcurses* nc){
}
ret |= fclose(out);
fflush(nc->ttyfp);
ssize_t w = write(nc->ttyfd, buf, buflen);
if(w < 0 || (size_t)w != buflen){
fprintf(nc->ttyfp, "%s", buf);
if(blocking_write(nc->ttyfd, buf, buflen)){
ret = -1;
}
/*fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu\n", defaultelisions, defaultemissions,
@ -1119,7 +1195,7 @@ int cell_duplicate(ncplane* n, cell* targ, const cell* c){
}
int ncplane_putc(ncplane* n, const cell* c){
if(n->y >= n->leny || n->x >= n->lenx){
if(cursor_invalid_p(n)){
return -1;
}
cell* targ = &n->fb[fbcellidx(n, n->y, n->x)];
@ -1230,6 +1306,10 @@ void ncplane_styles_set(ncplane* n, unsigned stylebits){
((stylebits & 0xffff) << 16u);
}
unsigned ncplane_styles(const ncplane* n){
return (n->attrword & CELL_STYLE_MASK) >> 16u;
}
int ncplane_printf(ncplane* n, const char* format, ...){
int ret;
va_list va;
@ -1365,6 +1445,7 @@ handle_getc(const notcurses* nc __attribute__ ((unused)), cell* c, int kpress,
if(kpress == 0x04){ // ctrl-d
return -1;
}
// FIXME look for keypad
if(kpress < 0x80){
c->gcluster = kpress;
}else{
@ -1387,18 +1468,31 @@ int notcurses_getc(const notcurses* nc, cell* c, ncspecial_key* special){
return handle_getc(nc, c, r, special);
}
// we set our infd to non-blocking on entry, so to do a blocking call (without
// burning cpu) we'll need to set up a poll().
int notcurses_getc_blocking(const notcurses* nc, cell* c, ncspecial_key* special){
int r = getc(nc->ttyinfp);
if(r < 0){
if(errno == EINTR){
if(resize_seen){
resize_seen = 0;
c->gcluster = 0;
*special = NCKEY_RESIZE;
return 1;
struct pollfd pfd = {
.fd = fileno(nc->ttyinfp),
.events = POLLIN | POLLRDHUP,
.revents = 0,
};
int pret;
while((pret = poll(&pfd, 1, -1)) >= 0){
if(pret == 0){
continue;
}
int r = getc(nc->ttyinfp);
if(r < 0){
if(errno == EINTR){
if(resize_seen){
resize_seen = 0;
c->gcluster = 0;
*special = NCKEY_RESIZE;
return 1;
}
}
return handle_getc(nc, c, r, special);
}
return r;
}
return handle_getc(nc, c, r, special);
return -1;
}

@ -1,3 +1,4 @@
#include <cstdlib>
#include <notcurses.h>
#include "main.h"
@ -394,3 +395,148 @@ TEST_F(NcplaneTest, GrowPlane) {
// FIXME check dims, pos
ASSERT_EQ(0, ncplane_destroy(newp));
}
// we ought be able to see what we're about to render, or have just rendered, or
// in any case whatever's in the virtual framebuffer for a plane
TEST_F(NcplaneTest, PlaneAtCursorSimples){
const char STR1[] = "Jackdaws love my big sphinx of quartz";
const char STR2[] = "Cwm fjord bank glyphs vext quiz";
const char STR3[] = "Pack my box with five dozen liquor jugs";
ncplane_styles_set(n_, 0);
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
ASSERT_LT(0, ncplane_putstr(n_, STR1));
int dimy, dimx;
ncplane_dim_yx(n_, &dimy, &dimx);
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 1, dimx - strlen(STR2)));
ASSERT_LT(0, ncplane_putstr(n_, STR2));
int y, x;
ncplane_cursor_yx(n_, &y, &x);
ASSERT_EQ(2, y);
ASSERT_EQ(0, x);
ASSERT_LT(0, ncplane_putstr(n_, STR3));
cell testcell = CELL_TRIVIAL_INITIALIZER;
ASSERT_EQ(0, ncplane_at_cursor(n_, &testcell)); // want nothing at the cursor
EXPECT_EQ(0, testcell.gcluster);
EXPECT_EQ(0, testcell.attrword);
EXPECT_EQ(0, testcell.channels);
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell)); // want first char of STR1
EXPECT_EQ(STR1[0], testcell.gcluster);
EXPECT_EQ(0, testcell.attrword);
EXPECT_EQ(0, testcell.channels);
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 1, dimx - 1));
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell)); // want last char of STR2
EXPECT_EQ(STR2[strlen(STR2) - 1], testcell.gcluster);
EXPECT_EQ(0, testcell.attrword);
EXPECT_EQ(0, testcell.channels);
// FIXME maybe check all cells?
EXPECT_EQ(0, notcurses_render(nc_));
}
// ensure we read back what's expected for latinesque complex characters
TEST_F(NcplaneTest, PlaneAtCursorComplex){
const char STR1[] = "Σιβυλλα τι θελεις; respondebat illa:";
const char STR2[] = "αποθανειν θελω";
const char STR3[] = "Война и мир"; // just thrown in to complicate things
ncplane_styles_set(n_, 0);
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
ASSERT_LT(0, ncplane_putstr(n_, STR1));
int dimy, dimx;
ncplane_dim_yx(n_, &dimy, &dimx);
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 1, dimx - mbstowcs(NULL, STR2, 0)));
ASSERT_LT(0, ncplane_putstr(n_, STR2));
int y, x;
ncplane_cursor_yx(n_, &y, &x);
ASSERT_EQ(2, y);
ASSERT_EQ(0, x);
ASSERT_LT(0, ncplane_putstr(n_, STR3));
cell testcell = CELL_TRIVIAL_INITIALIZER;
ncplane_at_cursor(n_, &testcell); // should be nothing at the cursor
EXPECT_EQ(0, testcell.gcluster);
EXPECT_EQ(0, testcell.attrword);
EXPECT_EQ(0, testcell.channels);
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 0, 0));
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell)); // want first char of STR1
EXPECT_STREQ("Σ", cell_extended_gcluster(n_, &testcell));
EXPECT_EQ(0, testcell.attrword);
EXPECT_EQ(0, testcell.channels);
ASSERT_EQ(0, ncplane_cursor_move_yx(n_, 1, dimx - mbstowcs(NULL, STR2, 0)));
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell)); // want first char of STR2
EXPECT_STREQ("α", cell_extended_gcluster(n_, &testcell));
EXPECT_EQ(0, testcell.attrword);
EXPECT_EQ(0, testcell.channels);
// FIXME maybe check all cells?
EXPECT_EQ(0, notcurses_render(nc_));
}
// half-width, double-width, huge-yet-narrow, all that crap
TEST_F(NcplaneTest, PlaneAtCursorInsane){
const char EGC0[] = "\uffe0"; // fullwidth cent sign ¢
const char EGC1[] = "\u00c5"; // neutral A with ring above Å
const char EGC2[] = "\u20a9"; // half-width won ₩
const char EGC3[] = "\u212b"; // ambiguous angstrom Å
const char EGC4[] = "\ufdfd"; // neutral yet huge bismillah ﷽
std::array<cell, 5> tcells;
for(auto i = 0u ; i < tcells.size() ; ++i){
cell_init(&tcells[i]);
}
ASSERT_LT(1, cell_load(n_, &tcells[0], EGC0));
ASSERT_LT(1, cell_load(n_, &tcells[1], EGC1));
ASSERT_LT(1, cell_load(n_, &tcells[2], EGC2));
ASSERT_LT(1, cell_load(n_, &tcells[3], EGC3));
ASSERT_LT(1, cell_load(n_, &tcells[4], EGC4));
for(auto i = 0u ; i < tcells.size() ; ++i){
ASSERT_LT(1, ncplane_putc(n_, &tcells[i]));
}
EXPECT_EQ(0, notcurses_render(nc_));
int x = 0;
for(auto i = 0u ; i < tcells.size() ; ++i){
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, 0, x));
cell testcell = CELL_TRIVIAL_INITIALIZER;
ASSERT_LT(0, ncplane_at_cursor(n_, &testcell));
EXPECT_STREQ(cell_extended_gcluster(n_, &tcells[i]),
cell_extended_gcluster(n_, &testcell));
EXPECT_EQ(0, testcell.attrword);
wchar_t w;
ASSERT_LT(0, mbtowc(&w, cell_extended_gcluster(n_, &tcells[i]), MB_CUR_MAX));
if(wcwidth(w) == 2){
EXPECT_EQ(CELL_WIDEASIAN_MASK, testcell.channels);
++x;
}else{
EXPECT_EQ(0, testcell.channels);
}
++x;
}
}
// test that we read back correct attrs/colors despite changing defaults
TEST_F(NcplaneTest, PlaneAtCursorAttrs){
const char STR1[] = "this has been a world destroyer production";
const char STR2[] = "not to mention dank";
const char STR3[] = "da chronic lives";
ncplane_styles_set(n_, CELL_STYLE_BOLD);
ASSERT_LT(0, ncplane_putstr(n_, STR1));
int y, x;
ncplane_cursor_yx(n_, &y, &x);
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y + 1, x - strlen(STR2)));
ncplane_styles_on(n_, CELL_STYLE_ITALIC);
ASSERT_LT(0, ncplane_putstr(n_, STR2));
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y + 2, x - strlen(STR3)));
ncplane_styles_off(n_, CELL_STYLE_BOLD);
ASSERT_LT(0, ncplane_putstr(n_, STR3));
ncplane_styles_off(n_, CELL_STYLE_ITALIC);
EXPECT_EQ(0, notcurses_render(nc_));
int newx;
ncplane_cursor_yx(n_, &y, &newx);
EXPECT_EQ(newx, x);
cell testcell = CELL_TRIVIAL_INITIALIZER;
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y - 2, x - 1));
ASSERT_EQ(1, ncplane_at_cursor(n_, &testcell));
EXPECT_EQ(testcell.gcluster, STR1[strlen(STR1) - 1]);
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y - 1, x - 1));
ASSERT_EQ(1, ncplane_at_cursor(n_, &testcell));
EXPECT_EQ(testcell.gcluster, STR2[strlen(STR2) - 1]);
EXPECT_EQ(0, ncplane_cursor_move_yx(n_, y, x - 1));
ASSERT_EQ(1, ncplane_at_cursor(n_, &testcell));
EXPECT_EQ(testcell.gcluster, STR3[strlen(STR3) - 1]);
}

Loading…
Cancel
Save