You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
682 lines
19 KiB
C
682 lines
19 KiB
C
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <getopt.h>
|
|
#include <inttypes.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <notcurses/notcurses.h>
|
|
#include "structure.h"
|
|
#include "builddef.h"
|
|
#include "parse.h"
|
|
|
|
static void
|
|
usage(const char* argv0, FILE* o){
|
|
fprintf(o, "usage: %s [ -hVq ] files\n", argv0);
|
|
fprintf(o, " -h: print help and return success\n");
|
|
fprintf(o, " -V: print version and return success\n");
|
|
fprintf(o, " -q: don't wait for any input\n");
|
|
}
|
|
|
|
static int
|
|
parse_args(int argc, char** argv, unsigned* noui){
|
|
const char* argv0 = *argv;
|
|
int longindex;
|
|
int c;
|
|
struct option longopts[] = {
|
|
{ .name = "help", .has_arg = 0, .flag = NULL, .val = 'h', },
|
|
{ .name = NULL, .has_arg = 0, .flag = NULL, .val = 0, }
|
|
};
|
|
while((c = getopt_long(argc, argv, "hVq", longopts, &longindex)) != -1){
|
|
switch(c){
|
|
case 'h': usage(argv0, stdout);
|
|
exit(EXIT_SUCCESS);
|
|
break;
|
|
case 'V': fprintf(stderr, "%s version %s\n", argv[0], notcurses_version());
|
|
exit(EXIT_SUCCESS);
|
|
break;
|
|
case 'q': *noui = true;
|
|
break;
|
|
default: usage(argv0, stderr);
|
|
return -1;
|
|
break;
|
|
}
|
|
}
|
|
if(argv[optind] == NULL){
|
|
usage(argv0, stderr);
|
|
return -1;
|
|
}
|
|
return optind;
|
|
}
|
|
|
|
#ifdef USE_DEFLATE // libdeflate implementation
|
|
#include <libdeflate.h>
|
|
// assume that |buf| is |*len| bytes of deflated data, and try to inflate
|
|
// it. if successful, the inflated map will be returned. either way, the
|
|
// input map will be unmapped (we take ownership). |*len| will be updated
|
|
// if an inflated map is successfully returned.
|
|
static unsigned char*
|
|
map_gzipped_data(unsigned char* buf, size_t* len, unsigned char* ubuf, uint32_t ulen){
|
|
struct libdeflate_decompressor* inflate = libdeflate_alloc_decompressor();
|
|
if(inflate == NULL){
|
|
fprintf(stderr, "couldn't get libdeflate inflator\n");
|
|
munmap(buf, *len);
|
|
return NULL;
|
|
}
|
|
size_t outbytes;
|
|
enum libdeflate_result r;
|
|
r = libdeflate_gzip_decompress(inflate, buf, *len, ubuf, ulen, &outbytes);
|
|
munmap(buf, *len);
|
|
libdeflate_free_decompressor(inflate);
|
|
if(r != LIBDEFLATE_SUCCESS){
|
|
fprintf(stderr, "error inflating %"PRIuPTR" (%d)\n", *len, r);
|
|
return NULL;
|
|
}
|
|
*len = ulen;
|
|
return ubuf;
|
|
}
|
|
#else // libz implementation
|
|
#include <zlib.h>
|
|
static unsigned char*
|
|
map_gzipped_data(unsigned char* buf, size_t* len, unsigned char* ubuf, uint32_t ulen){
|
|
z_stream z = {0};
|
|
int r = inflateInit2(&z, 15 | 16);
|
|
if(r != Z_OK){
|
|
fprintf(stderr, "error getting zlib inflator (%d)\n", r);
|
|
munmap(buf, *len);
|
|
return NULL;
|
|
}
|
|
z.next_in = buf;
|
|
z.avail_in = *len;
|
|
z.next_out = ubuf;
|
|
z.avail_out = ulen;
|
|
r = inflate(&z, Z_FINISH);
|
|
munmap(buf, *len);
|
|
if(r != Z_STREAM_END){
|
|
fprintf(stderr, "error inflating (%d) (%s?)\n", r, z.msg);
|
|
inflateEnd(&z);
|
|
return NULL;
|
|
}
|
|
inflateEnd(&z);
|
|
munmap(buf, *len);
|
|
*len = ulen;
|
|
return ubuf;
|
|
}
|
|
#endif
|
|
|
|
static unsigned char*
|
|
map_troff_data(int fd, size_t* len){
|
|
struct stat sbuf;
|
|
if(fstat(fd, &sbuf)){
|
|
return NULL;
|
|
}
|
|
// gzip has a 10-byte mandatory header and an 8-byte mandatory footer
|
|
if(sbuf.st_size < 18){
|
|
return NULL;
|
|
}
|
|
*len = sbuf.st_size;
|
|
unsigned char* buf = mmap(NULL, *len, PROT_READ,
|
|
#ifdef MAP_POPULATE
|
|
MAP_POPULATE |
|
|
#endif
|
|
MAP_PRIVATE, fd, 0);
|
|
if(buf == MAP_FAILED){
|
|
fprintf(stderr, "error mapping %"PRIuPTR" (%s?)\n", *len, strerror(errno));
|
|
return NULL;
|
|
}
|
|
if(buf[0] == 0x1f && buf[1] == 0x8b && buf[2] == 0x08){
|
|
// the last four bytes have the uncompressed length
|
|
uint32_t ulen;
|
|
memcpy(&ulen, buf + *len - 4, 4);
|
|
long sc = sysconf(_SC_PAGESIZE);
|
|
if(sc <= 0){
|
|
fprintf(stderr, "couldn't get page size\n");
|
|
return NULL;
|
|
}
|
|
size_t pgsize = sc;
|
|
void* ubuf = mmap(NULL, (ulen + pgsize - 1) / pgsize * pgsize,
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
|
if(ubuf == MAP_FAILED){
|
|
fprintf(stderr, "error mapping %"PRIu32" (%s?)\n", ulen, strerror(errno));
|
|
munmap(buf, *len);
|
|
return NULL;
|
|
}
|
|
if(map_gzipped_data(buf, len, ubuf, ulen) == NULL){
|
|
munmap(ubuf, ulen);
|
|
return NULL;
|
|
}
|
|
return ubuf;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
// find the man page, and inflate it if deflated
|
|
static unsigned char*
|
|
get_troff_data(const char *arg, size_t* len){
|
|
// FIXME we'll want to use the mandb. for now, require a full path.
|
|
int fd = open(arg, O_RDONLY | O_CLOEXEC);
|
|
if(fd < 0){
|
|
fprintf(stderr, "error opening %s (%s?)\n", arg, strerror(errno));
|
|
return NULL;
|
|
}
|
|
unsigned char* buf = map_troff_data(fd, len);
|
|
close(fd);
|
|
return buf;
|
|
}
|
|
|
|
// invoke ncplane_puttext() on the text starting at s and ending
|
|
// (non-inclusive) at e.
|
|
static int
|
|
puttext(struct ncplane* p, const char* s, const char* e){
|
|
if(e <= s){
|
|
//fprintf(stderr, "no text to print\n");
|
|
return 0; // no text to print
|
|
}
|
|
char* dup = strndup(s, e - s);
|
|
if(dup == NULL){
|
|
return -1;
|
|
}
|
|
size_t b = 0;
|
|
int r = ncplane_puttext(p, -1, NCALIGN_LEFT, dup, &b);
|
|
free(dup);
|
|
return r;
|
|
}
|
|
|
|
// try to identify a unicode macro in |macro|, and if so, update |end|. returns
|
|
// a static buffer, so this is not threadsafe/reentrant.
|
|
static const char*
|
|
unicode_macro(const char* macro, const char** end){
|
|
unsigned long u;
|
|
u = strtoul(macro + 1, (char**)end, 16);
|
|
if(**end != ']'){
|
|
return NULL;
|
|
}
|
|
++*end;
|
|
static char fill[5];
|
|
wint_t wu = u;
|
|
snprintf(fill, sizeof(fill), "%lc", wu);
|
|
return fill;
|
|
}
|
|
|
|
// paragraphs can have formatting information inline within their text. we
|
|
// proceed until we find such an inline marker, print the text we've skipped,
|
|
// set up the style, and continue.
|
|
static int
|
|
putpara(struct ncplane* p, const char* text){
|
|
// cur indicates where the current text to be displayed starts.
|
|
const char* cur = text;
|
|
uint16_t style = 0;
|
|
const char* posttext = NULL;
|
|
while(*cur){
|
|
// find the next style marker
|
|
bool inescape = false;
|
|
const char* textend = NULL; // one past where the text to print ends
|
|
// curend is where the text + style markings end
|
|
const char* curend;
|
|
for(curend = cur ; *curend ; ++curend){
|
|
if(*curend == '\\'){
|
|
if(inescape){ // escaped backslash
|
|
inescape = false;
|
|
}else{
|
|
inescape = true;
|
|
}
|
|
}else if(inescape){
|
|
if(*curend == 'f'){ // set font
|
|
textend = curend - 1; // account for backslash
|
|
bool bracketed = false;
|
|
if(*++curend == '['){
|
|
bracketed = true;
|
|
++curend;
|
|
}
|
|
while(isalpha(*curend)){
|
|
switch(toupper(*curend)){
|
|
case 'R': style = 0; break; // roman, default
|
|
case 'I': style |= NCSTYLE_ITALIC; break;
|
|
case 'B': style |= NCSTYLE_BOLD; break;
|
|
case 'C': break; // unsure! seems to be used with .nf/.fi
|
|
default:
|
|
fprintf(stderr, "unknown font macro %s\n", curend);
|
|
return -1;
|
|
}
|
|
++curend;
|
|
}
|
|
if(bracketed){
|
|
if(*curend != ']'){
|
|
fprintf(stderr, "missing ']': %s\n", curend);
|
|
return -1;
|
|
}
|
|
++curend;
|
|
}
|
|
break;
|
|
}else if(*curend == '['){ // escaped sequence
|
|
textend = curend - 1; // account for backslash
|
|
static const struct {
|
|
const char* macro;
|
|
const char* tr;
|
|
} macros[] = {
|
|
{ "aq]", "'", },
|
|
{ "dq]", "\"", },
|
|
{ "lq]", u8"\u201c", }, // left double quote
|
|
{ "rq]", u8"\u201d", }, // right double quote
|
|
{ "em]", u8"\u2014", }, // em dash
|
|
{ "en]", u8"\u2013", }, // en dash
|
|
{ "rg]", "®", },
|
|
{ "rs]", "\\", },
|
|
{ "ti]", "~", },
|
|
{ NULL, NULL, }
|
|
};
|
|
++curend;
|
|
const char* macend = NULL;
|
|
for(typeof(&*macros) m = macros ; m->tr ; ++m){
|
|
if(strncmp(curend, m->macro, strlen(m->macro)) == 0){
|
|
posttext = m->tr;
|
|
macend = curend + strlen(m->macro);
|
|
break;
|
|
}
|
|
}
|
|
if(macend == NULL){
|
|
posttext = unicode_macro(curend, &macend);
|
|
if(posttext == NULL){
|
|
fprintf(stderr, "unknown macro %s\n", curend);
|
|
return -1;
|
|
}
|
|
}
|
|
curend = macend;
|
|
break;
|
|
}else{
|
|
cur = curend++;
|
|
break;
|
|
}
|
|
inescape = false;
|
|
}
|
|
}
|
|
if(textend == NULL){
|
|
textend = curend;
|
|
}
|
|
if(puttext(p, cur, textend) < 0){
|
|
return -1;
|
|
}
|
|
if(posttext){
|
|
if(puttext(p, posttext, posttext + strlen(posttext)) < 0){
|
|
return -1;
|
|
}
|
|
posttext = NULL;
|
|
}
|
|
cur = curend;
|
|
ncplane_set_styles(p, style);
|
|
}
|
|
ncplane_cursor_move_yx(p, -1, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
draw_domnode(struct ncplane* p, const pagedom* dom, const pagenode* n,
|
|
unsigned* wrotetext, unsigned* insubsec){
|
|
ncplane_set_fg_rgb(p, ncchannel_rgb(n->ttype->channel));
|
|
size_t b = 0;
|
|
unsigned y;
|
|
ncplane_cursor_yx(p, &y, NULL);
|
|
switch(n->ttype->ltype){
|
|
case LINE_TH:
|
|
if(docstructure_add(dom->ds, dom->title, y)){
|
|
return -1;
|
|
}
|
|
/*
|
|
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;
|
|
case LINE_SH: // section heading
|
|
if(docstructure_add(dom->ds, n->text, y)){
|
|
return -1;
|
|
}
|
|
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
|
|
if(docstructure_add(dom->ds, n->text, y)){
|
|
return -1;
|
|
}
|
|
ncplane_puttext(p, -1, NCALIGN_LEFT, "\n\n", &b);
|
|
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;
|
|
*insubsec = true;
|
|
break;
|
|
case LINE_PP: // paragraph
|
|
case LINE_TP: // tagged paragraph
|
|
case LINE_IP: // indented paragraph
|
|
if(*wrotetext){
|
|
if(n->text){
|
|
ncplane_set_fg_rgb(p, 0xe0f0ff);
|
|
if(*insubsec){
|
|
ncplane_puttext(p, -1, NCALIGN_LEFT, "\n", &b);
|
|
*insubsec = false;
|
|
}else{
|
|
ncplane_puttext(p, -1, NCALIGN_LEFT, "\n\n", &b);
|
|
}
|
|
ncplane_set_fg_alpha(p, NCALPHA_HIGHCONTRAST);
|
|
putpara(p, n->text);
|
|
}
|
|
}else{
|
|
if(n->text){
|
|
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], wrotetext, insubsec)){
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// for now, we draw the entire thing, resizing as necessary, and we'll
|
|
// scroll the entire plane. higher memory cost, longer initial latency,
|
|
// very fast moves.
|
|
static int
|
|
draw_content(struct ncplane* stdn, struct ncplane* p){
|
|
pagedom* dom = ncplane_userptr(p);
|
|
unsigned wrotetext = 0; // unused by us
|
|
unsigned insubsec = 0;
|
|
docstructure_free(dom->ds);
|
|
dom->ds = docstructure_create(stdn);
|
|
if(dom->ds == NULL){
|
|
return -1;
|
|
}
|
|
return draw_domnode(p, dom, dom->root, &wrotetext, &insubsec);
|
|
}
|
|
|
|
static int
|
|
resize_pman(struct ncplane* pman){
|
|
unsigned dimy, dimx;
|
|
ncplane_dim_yx(ncplane_parent_const(pman), &dimy, &dimx);
|
|
ncplane_resize_simple(pman, dimy - 1, dimx);
|
|
ncplane_erase(pman);
|
|
struct ncplane* stdn = notcurses_stdplane(ncplane_notcurses(pman));
|
|
int r = draw_content(stdn, pman);
|
|
ncplane_move_yx(pman, 0, 0);
|
|
return r;
|
|
}
|
|
|
|
// we create a plane sized appropriately for the troff data. all we do
|
|
// after that is move the plane up and down.
|
|
static struct ncplane*
|
|
render_troff(struct notcurses* nc, const unsigned char* map, size_t mlen,
|
|
pagedom* dom){
|
|
unsigned dimy, dimx;
|
|
struct ncplane* stdn = notcurses_stddim_yx(nc, &dimy, &dimx);
|
|
// this is just an estimate
|
|
if(troff_parse(map, mlen, dom)){
|
|
return NULL;
|
|
}
|
|
// this is just an estimate
|
|
struct ncplane_options popts = {
|
|
.rows = dimy - 2,
|
|
.cols = dimx,
|
|
.y = 1,
|
|
.userptr = dom,
|
|
.resizecb = resize_pman,
|
|
.flags = NCPLANE_OPTION_AUTOGROW | NCPLANE_OPTION_VSCROLL,
|
|
};
|
|
struct ncplane* pman = ncplane_create(stdn, &popts);
|
|
if(pman == NULL){
|
|
return NULL;
|
|
}
|
|
ncplane_set_base(pman, " ", 0, 0);
|
|
if(draw_content(stdn, pman)){
|
|
ncplane_destroy(pman);
|
|
return NULL;
|
|
}
|
|
return pman;
|
|
}
|
|
|
|
static const char USAGE_TEXT[] = "⎥h←s→l⎢⎥b⇞k↑↓j⇟f⎢ (q)uit";
|
|
static const char USAGE_TEXT_ASCII[] = "(hsl) (bkjf) (q)uit";
|
|
|
|
static int
|
|
draw_bar(struct ncplane* bar, pagedom* dom){
|
|
ncplane_cursor_move_yx(bar, 0, 0);
|
|
ncplane_set_styles(bar, NCSTYLE_BOLD);
|
|
ncplane_putstr(bar, dom_get_title(dom));
|
|
ncplane_set_styles(bar, NCSTYLE_NONE);
|
|
ncplane_putchar(bar, '(');
|
|
ncplane_set_styles(bar, NCSTYLE_BOLD);
|
|
ncplane_putstr(bar, dom->section);
|
|
ncplane_set_styles(bar, NCSTYLE_NONE);
|
|
ncplane_printf(bar, ") %s", dom->version);
|
|
ncplane_set_styles(bar, NCSTYLE_ITALIC);
|
|
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;
|
|
}
|
|
|
|
static int
|
|
resize_bar(struct ncplane* bar){
|
|
unsigned dimy, dimx;
|
|
ncplane_dim_yx(ncplane_parent_const(bar), &dimy, &dimx);
|
|
ncplane_resize_simple(bar, 1, dimx);
|
|
ncplane_erase(bar);
|
|
int r = draw_bar(bar, ncplane_userptr(bar));
|
|
ncplane_move_yx(bar, dimy - 1, 0);
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
domnode_destroy(pagenode* node){
|
|
if(node){
|
|
free(node->text);
|
|
for(unsigned z = 0 ; z < node->subcount ; ++z){
|
|
domnode_destroy(&node->subs[z]);
|
|
}
|
|
free(node->subs);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pagedom_destroy(pagedom* dom){
|
|
destroy_trofftrie(dom->trie);
|
|
domnode_destroy(dom->root);
|
|
free(dom->root);
|
|
free(dom->title);
|
|
free(dom->version);
|
|
free(dom->section);
|
|
docstructure_free(dom->ds);
|
|
}
|
|
|
|
static struct ncplane*
|
|
create_bar(struct notcurses* nc, pagedom* dom){
|
|
unsigned dimy, dimx;
|
|
struct ncplane* stdn = notcurses_stddim_yx(nc, &dimy, &dimx);
|
|
struct ncplane_options nopts = {
|
|
.y = dimy - 1,
|
|
.x = 0,
|
|
.rows = 1,
|
|
.cols = dimx,
|
|
.resizecb = resize_bar,
|
|
.userptr = dom,
|
|
};
|
|
struct ncplane* bar = ncplane_create(stdn, &nopts);
|
|
if(bar == NULL){
|
|
return NULL;
|
|
}
|
|
uint64_t barchan = NCCHANNELS_INITIALIZER(0, 0, 0, 0x26, 0x62, 0x41);
|
|
ncplane_set_fg_rgb(bar, 0xffffff);
|
|
if(ncplane_set_base(bar, " ", 0, barchan) != 1){
|
|
ncplane_destroy(bar);
|
|
return NULL;
|
|
}
|
|
if(draw_bar(bar, dom)){
|
|
ncplane_destroy(bar);
|
|
return NULL;
|
|
}
|
|
if(notcurses_render(nc)){
|
|
ncplane_destroy(bar);
|
|
return NULL;
|
|
}
|
|
return bar;
|
|
}
|
|
|
|
static int
|
|
manloop(struct notcurses* nc, const char* arg, unsigned noui){
|
|
struct ncplane* stdn = notcurses_stdplane(nc);
|
|
int ret = -1;
|
|
struct ncplane* page = NULL;
|
|
struct ncplane* bar = NULL;
|
|
pagedom dom = {0};
|
|
size_t len;
|
|
unsigned char* buf = get_troff_data(arg, &len);
|
|
if(buf == NULL){
|
|
goto done;
|
|
}
|
|
dom.trie = trofftrie();
|
|
if(dom.trie == NULL){
|
|
goto done;
|
|
}
|
|
page = render_troff(nc, buf, len, &dom);
|
|
if(page == NULL){
|
|
goto done;
|
|
}
|
|
bar = create_bar(nc, &dom);
|
|
if(bar == NULL){
|
|
goto done;
|
|
}
|
|
if(notcurses_render(nc)){
|
|
goto done;
|
|
}
|
|
if(noui){
|
|
ret = 0;
|
|
goto done;
|
|
}
|
|
uint32_t key;
|
|
do{
|
|
bool movedown = false;
|
|
int newy = ncplane_y(page);
|
|
ncinput ni;
|
|
key = notcurses_get(nc, NULL, &ni);
|
|
if(ni.evtype == NCTYPE_RELEASE){
|
|
continue;
|
|
}
|
|
switch(key){
|
|
case 'L':
|
|
if(ni.ctrl && !ni.alt){
|
|
notcurses_refresh(nc, NULL, NULL);
|
|
}
|
|
break;
|
|
case 's':
|
|
docstructure_toggle(page, bar, dom.ds);
|
|
break;
|
|
case 'h': case NCKEY_LEFT:
|
|
newy = docstructure_prev(dom.ds);
|
|
break;
|
|
case 'l': case NCKEY_RIGHT:
|
|
newy = docstructure_next(dom.ds);
|
|
movedown = true;
|
|
break;
|
|
case 'k': case NCKEY_UP:
|
|
newy = ncplane_y(page) + 1;
|
|
break;
|
|
// we can move down iff our last line is beyond the visible area
|
|
case 'j': case NCKEY_DOWN:
|
|
newy = ncplane_y(page) - 1;
|
|
movedown = true;
|
|
break;
|
|
case 'b': case NCKEY_PGUP:{
|
|
newy = ncplane_y(page) + (int)ncplane_dim_y(stdn);
|
|
break;
|
|
}case 'f': case NCKEY_PGDOWN:{
|
|
newy = ncplane_y(page) - (int)ncplane_dim_y(stdn) + 1;
|
|
movedown = true;
|
|
break;
|
|
case 'g': case NCKEY_HOME:
|
|
newy = 1;
|
|
break;
|
|
}case 'q':
|
|
ret = 0;
|
|
goto done;
|
|
}
|
|
if(newy > 1){
|
|
newy = 1;
|
|
}
|
|
if(newy + (int)ncplane_dim_y(page) < (int)ncplane_dim_y(stdn)){
|
|
newy += (int)ncplane_dim_y(stdn) - (newy + (int)ncplane_dim_y(page)) - 1;
|
|
}
|
|
if(newy != ncplane_y(page)){
|
|
ncplane_move_yx(page, newy, 0);
|
|
docstructure_move(dom.ds, newy, movedown);
|
|
if(notcurses_render(nc)){
|
|
goto done;
|
|
}
|
|
}
|
|
}while(key != (uint32_t)-1);
|
|
|
|
done:
|
|
if(page){
|
|
ncplane_destroy(page);
|
|
}
|
|
ncplane_destroy(bar);
|
|
if(buf){
|
|
munmap(buf, len);
|
|
}
|
|
pagedom_destroy(&dom);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
tfman(struct notcurses* nc, const char* arg, unsigned noui){
|
|
int r = manloop(nc, arg, noui);
|
|
return r;
|
|
}
|
|
|
|
int main(int argc, char** argv){
|
|
unsigned noui = false;
|
|
int nonopt = parse_args(argc, argv, &noui);
|
|
if(nonopt <= 0){
|
|
return EXIT_FAILURE;
|
|
}
|
|
struct notcurses_options nopts = {0};
|
|
if(noui){
|
|
nopts.flags |= NCOPTION_NO_ALTERNATE_SCREEN
|
|
| NCOPTION_NO_CLEAR_BITMAPS
|
|
| NCOPTION_PRESERVE_CURSOR
|
|
| NCOPTION_DRAIN_INPUT;
|
|
}
|
|
struct notcurses* nc = notcurses_core_init(&nopts, NULL);
|
|
if(nc == NULL){
|
|
return EXIT_FAILURE;
|
|
}
|
|
bool success = false;
|
|
for(int i = 0 ; i < argc - nonopt ; ++i){
|
|
success = false;
|
|
if(tfman(nc, argv[nonopt + i], noui)){
|
|
break;
|
|
}
|
|
success = true;
|
|
}
|
|
return notcurses_stop(nc) || !success ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
}
|