X-Git-Url: https://git.danieliu.xyz/?p=smdp.git;a=blobdiff_plain;f=src%2Fviewer.c;h=97e62df7f9114dc354502df634c5f9a0ae66fcd3;hp=ee6fc905df3ae364c0e7d59052dfa0a2c0e9fe97;hb=51b2a847fc5798c5b0f56ae93f6e1355038df840;hpb=a1d883fc5b7acbf45fdc80a7806b4d4c21fdcd1f diff --git a/src/viewer.c b/src/viewer.c index ee6fc90..97e62df 100644 --- a/src/viewer.c +++ b/src/viewer.c @@ -21,11 +21,11 @@ * */ -#include // setlocale -#include -#include // strchr -#include - +#include // isalnum +#include // wcschr +#include // iswalnum +#include // strcpy +#include // usleep #include "viewer.h" // color ramp for fading from black to color @@ -60,11 +60,12 @@ static short red_ramp_invert[24] = { 15, 231, 231, 224, 224, 225, 206, 207, 201, 200, 199, 199, 198, 198, 197, 197, 196, 196}; -int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { +int ncurses_display(deck_t *deck, int notrans, int nofade, int invert, int reload, int noreload) { int c = 0; // char int i = 0; // iterate int l = 0; // line number + int lc = 0; // line count int sc = 1; // slide count int colors = 0; // amount of colors supported int fade = 0; // disable color fading by default @@ -72,6 +73,7 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { int max_lines = 0; // max lines per slide int max_cols = 0; // max columns per line int offset; // text offset + int stop = 0; // passed stop bits per slide // header line 1 is displayed at the top int bar_top = (deck->headers > 0) ? 1 : 0; @@ -82,41 +84,79 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { slide_t *slide = deck->slide; line_t *line; + // init ncurses + initscr(); + while(slide) { - // set max_lines if line count exceeded - max_lines = (slide->lines > max_lines) ? slide->lines : max_lines; + lc = 0; line = slide->line; - while(line) { - // set max_cols if length exceeded - max_cols = (line->length > max_cols) ? line->length : max_cols; + + while(line && line->text) { + + if (line->text->value) + lc += url_count_inline(line->text->value); + + if (line->text->value) + line->length -= url_len_inline(line->text->value); + + if(line->length > COLS) { + i = line->length; + offset = 0; + while(i > COLS) { + + i = prev_blank(line->text, offset + COLS) - offset; + + // single word is > COLS + if(!i) { + // calculate min_width + i = next_blank(line->text, offset + COLS) - offset; + + // disable ncurses + endwin(); + + // print error + fwprintf(stderr, L"Error: Terminal width (%i columns) too small. Need at least %i columns.\n", COLS, i); + fwprintf(stderr, L"You may need to shorten some lines by inserting line breaks.\n"); + + // no reload + return 0; + } + + // set max_cols + max_cols = MAX(i, max_cols); + + // iterate to next line + offset = prev_blank(line->text, offset + COLS); + i = line->length - offset; + lc++; + } + // set max_cols one last time + max_cols = MAX(i, max_cols); + } else { + // set max_cols + max_cols = MAX(line->length, max_cols); + } + lc++; line = line->next; } - slide = slide->next; - } - // set locale to display UTF-8 correctly in ncurses - setlocale(LC_CTYPE, ""); + max_lines = MAX(lc, max_lines); - // init ncurses - initscr(); + slide = slide->next; + } - if((max_cols > COLS) || - (max_lines + bar_top + bar_bottom + 2 > LINES)) { + // not enough lines + if(max_lines + bar_top + bar_bottom > LINES) { // disable ncurses endwin(); // print error - fprintf(stderr, "Error: Terminal size %ix%i too small. Need at least %ix%i.\n", - COLS, LINES, max_cols, max_lines + bar_top + bar_bottom + 2); + fwprintf(stderr, L"Error: Terminal height (%i lines) too small. Need at least %i lines.\n", LINES, max_lines + bar_top + bar_bottom); + fwprintf(stderr, L"You may need to add additional horizontal rules (---) to split your file in shorter slides.\n"); - // print hint to solve it - if(max_lines + bar_top + bar_bottom + 2 > LINES) - fprintf(stderr, "You may need to add additional horizontal rules ('***') to split your file in shorter slides.\n"); - if(max_cols > COLS) - fprintf(stderr, "Automatic line wrapping is not supported yet. You may need to shorten some lines by inserting line breaks.\n"); - - return(1); + // no reload + return 0; } // disable cursor @@ -161,7 +201,8 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { init_pair(CP_YELLOW, 208, trans); // enable color fading - if(!nofade) fade = 1; + if(!nofade) + fade = true; // 8 color mode } else { @@ -199,7 +240,21 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { wbkgd(content, COLOR_PAIR(CP_WHITE)); slide = deck->slide; + + // find slide to reload + while(reload > 1 && reload <= deck->slides) { + slide = slide->next; + sc++; + reload--; + } + + // reset reload indicator + reload = 0; + while(slide) { + + url_init(); + // clear windows werase(content); werase(stdscr); @@ -212,9 +267,9 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { line = deck->header; offset = next_blank(line->text, 0) + 1; // add text to header - mvwprintw(stdscr, - 0, (COLS - line->length + offset) / 2, - "%s", &line->text->text[offset]); + mvwaddwstr(stdscr, + 0, (COLS - line->length + offset) / 2, + &line->text->value[offset]); } // setup footer @@ -222,26 +277,49 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { line = deck->header->next; offset = next_blank(line->text, 0) + 1; // add text to left footer - mvwprintw(stdscr, - LINES - 1, 3, - "%s", &line->text->text[offset]); + mvwaddwstr(stdscr, + LINES - 1, 3, + &line->text->value[offset]); } + // add slide number to right footer mvwprintw(stdscr, LINES - 1, COLS - int_length(deck->slides) - int_length(sc) - 6, "%d / %d", sc, deck->slides); // make header + fooder visible + wrefresh(content); wrefresh(stdscr); line = slide->line; - l = 0; + l = stop = 0; // print lines while(line) { add_line(content, l, (COLS - max_cols) / 2, line, max_cols, colors); + + // raise stop counter if we pass a line having a stop bit + if(CHECK_BIT(line->bits, IS_STOP)) + stop++; + + l += (line->length / COLS) + 1; line = line->next; - l++; + + // only stop here if we didn't stop here recently + if(stop > slide->stop) + break; + } + + // print pandoc URL references + // only if we already printed all lines of the current slide + if(!line) { + int i, ymax; + getmaxyx( content, ymax, i ); + for (i = 0; i < url_get_amount(); i++) { + mvwprintw(content, ymax - url_get_amount() - 1 + i, 3, + "[%d] ", i); + waddwstr(content, url_get_target(i)); + } } // make content visible @@ -252,7 +330,8 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { fade_in(content, trans, colors, invert); // re-enable fading after any undefined key press - if(COLORS == 256 && !nofade) fade = 1; + if(COLORS == 256 && !nofade) + fade = true; // wait for user input c = getch(); @@ -261,48 +340,67 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { i = 0; switch(c) { - // show previous slide + // show previous slide or stop bit case KEY_UP: case KEY_LEFT: + case KEY_PPAGE: case 8: // BACKSPACE (ascii) case 127: // BACKSPACE (xterm) case 263: // BACKSPACE (getty) case 'h': case 'k': - if(slide->prev) { - slide = slide->prev; - sc--; + if(stop > 1 || (stop == 1 && !line)) { + // show current slide again + // but stop one stop bit earlier + slide->stop--; } else { - fade = 0; + if(slide->prev) { + // show previous slide + slide = slide->prev; + sc--; + } else { + // do nothing + fade = false; + } } break; - // show next slide + // show next slide or stop bit case KEY_DOWN: case KEY_RIGHT: + case KEY_NPAGE: case '\n': // ENTER case ' ': // SPACE case 'j': case 'l': - if(slide->next) { - slide = slide->next; - sc++; + if(stop && line) { + // show current slide again + // but stop one stop bit later (or at end of slide) + slide->stop++; } else { - fade = 0; + if(slide->next) { + // show next slide + slide = slide->next; + sc++; + } else { + // do nothing + fade = false; + } } break; // show slide n - case '9': i++; - case '8': i++; - case '7': i++; - case '6': i++; - case '5': i++; - case '4': i++; - case '3': i++; - case '2': i++; - case '1': i++; - if(i <= deck->slides) { + case '9': + case '8': + case '7': + case '6': + case '5': + case '4': + case '3': + case '2': + case '1': + i = get_slide_number(c); + if(i > 0 && i <= deck->slides) { while(sc != i) { // search forward if(sc < i) { @@ -320,17 +418,19 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { } } else { // disable fading if slide n doesn't exist - fade = 0; + fade = false; } break; // show first slide + case 'g': case KEY_HOME: slide = deck->slide; sc = 1; break; // show last slide + case 'G': case KEY_END: for(i = sc; i <= deck->slides; i++) { if(slide->next) { @@ -340,87 +440,169 @@ int ncurses_display(deck_t *deck, int notrans, int nofade, int invert) { } break; + // reload + case 'r': + if(noreload == 0) { + // reload slide N + reload = sc; + slide = NULL; + } else { + // disable fading if reload is not possible + fade = false; + } + break; + // quit case 'q': // do not fade out on exit - fade = 0; - slide = (void*)0; + fade = false; + // do not reload + reload = 0; + slide = NULL; break; default: // disable fading on undefined key press - fade = 0; + fade = false; break; } // fade out if(fade) fade_out(content, trans, colors, invert); + + url_purge(); } + // disable ncurses endwin(); - return(0); + // free ncurses memory + delwin(content); + if(reload == 0) + delwin(stdscr); + + // return reload indicator (0 means no reload) + return reload; } void add_line(WINDOW *window, int y, int x, line_t *line, int max_cols, int colors) { - int i = 0; // increment - char *c; // char pointer for iteration - char *special = "\\*_`"; // list of interpreted chars - cstack_t *stack = cstack_init(); - - if(line->text->text) { - int offset = 0; // text offset - - // IS_UNORDERED_LIST_3 - if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) { - offset = next_nonblank(line->text, 0); - char format_s[15]; - strcpy(&format_s[0], CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? " | " : " "); - strcpy(&format_s[4], CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)? " | " : " "); - strcpy(&format_s[8], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_3)? " +- %s" : " `- %s"); - mvwprintw(window, - y, x, - format_s, - &line->text->text[offset + 2]); - - // IS_UNORDERED_LIST_2 - } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) { - offset = next_nonblank(line->text, 0); - char format_s[11]; - strcpy(&format_s[0], CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? " | " : " "); - strcpy(&format_s[4], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_2)? " +- %s" : " `- %s"); - mvwprintw(window, - y, x, - format_s, - &line->text->text[offset + 2]); - - // IS_UNORDERED_LIST_1 - } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) { - offset = next_nonblank(line->text, 0); - char format_s[7]; - strcpy(&format_s[0], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_1)? " +- %s" : " `- %s"); - mvwprintw(window, - y, x, - format_s, - &line->text->text[offset + 2]); - - // IS_CODE - } else if(CHECK_BIT(line->bits, IS_CODE)) { - - // set static offset for code - offset = CODE_INDENT; - - // reverse color for code blocks - if(colors) - wattron(window, COLOR_PAIR(CP_BLACK)); - - // print whole lines - mvwprintw(window, - y, x, - "%s", &line->text->text[offset]); + if(!line->text->value) { + return; + } + + int i; // increment + int offset = 0; // text offset + + // move the cursor in position + wmove(window, y, x); + + // IS_UNORDERED_LIST_3 + if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) { + offset = next_nonblank(line->text, 0); + char prompt[13]; + strcpy(&prompt[0], CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? " | " : " "); + strcpy(&prompt[4], CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)? " | " : " "); + + if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { + strcpy(&prompt[8], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_3)? " | " : " "); } else { + strcpy(&prompt[8], " +- "); + offset += 2; + } + + wprintw(window, + "%s", prompt); + + if(!CHECK_BIT(line->bits, IS_CODE)) + inline_display(window, &line->text->value[offset], colors); + + // IS_UNORDERED_LIST_2 + } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) { + offset = next_nonblank(line->text, 0); + char prompt[9]; + strcpy(&prompt[0], CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? " | " : " "); + + if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { + strcpy(&prompt[4], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_2)? " | " : " "); + } else { + strcpy(&prompt[4], " +- "); + offset += 2; + } + + wprintw(window, + "%s", prompt); + + if(!CHECK_BIT(line->bits, IS_CODE)) + inline_display(window, &line->text->value[offset], colors); + + // IS_UNORDERED_LIST_1 + } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) { + offset = next_nonblank(line->text, 0); + char prompt[5]; + + if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { + strcpy(&prompt[0], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_1)? " | " : " "); + } else { + strcpy(&prompt[0], " +- "); + offset += 2; + } + + wprintw(window, + "%s", prompt); + + if(!CHECK_BIT(line->bits, IS_CODE)) + inline_display(window, &line->text->value[offset], colors); + } + + // IS_CODE + if(CHECK_BIT(line->bits, IS_CODE)) { + + // set static offset for code + offset = CODE_INDENT; + + // reverse color for code blocks + if(colors) + wattron(window, COLOR_PAIR(CP_BLACK)); + + // print whole lines + waddwstr(window, &line->text->value[offset]); + } + + if(!CHECK_BIT(line->bits, IS_UNORDERED_LIST_1) && + !CHECK_BIT(line->bits, IS_UNORDERED_LIST_2) && + !CHECK_BIT(line->bits, IS_UNORDERED_LIST_3) && + !CHECK_BIT(line->bits, IS_CODE)) { + + // IS_QUOTE + if(CHECK_BIT(line->bits, IS_QUOTE)) { + while(line->text->value[offset] == '>') { + // print a reverse color block + if(colors) { + wattron(window, COLOR_PAIR(CP_BLACK)); + wprintw(window, "%s", " "); + wattron(window, COLOR_PAIR(CP_WHITE)); + wprintw(window, "%s", " "); + } else { + wprintw(window, "%s", ">"); + } + + // find next quote or break + offset++; + if(line->text->value[offset] == ' ') + offset = next_word(line->text, offset); + } + + inline_display(window, &line->text->value[offset], colors); + } else { + + // IS_CENTER + if(CHECK_BIT(line->bits, IS_CENTER)) { + if(line->length < max_cols) { + wmove(window, y, x + ((max_cols - line->length) / 2)); + } + } // IS_H1 || IS_H2 if(CHECK_BIT(line->bits, IS_H1) || CHECK_BIT(line->bits, IS_H2)) { @@ -434,148 +616,196 @@ void add_line(WINDOW *window, int y, int x, line_t *line, int max_cols, int colo wattron(window, A_UNDERLINE); // skip hashes - while(line->text->text[offset] == '#') + while(line->text->value[offset] == '#') offset = next_word(line->text, offset); // print whole lines - mvwprintw(window, - y, x, - "%s", &line->text->text[offset]); + waddwstr(window, &line->text->value[offset]); wattroff(window, A_UNDERLINE); + // no line-wide markdown } else { - // move the cursor in position - wmove(window, y, x); - - // IS_QUOTE - if(CHECK_BIT(line->bits, IS_QUOTE)) { - while(line->text->text[offset] == '>') { - // print a reverse color block - if(colors) { - wattron(window, COLOR_PAIR(CP_BLACK)); - wprintw(window, "%s", " "); - wattron(window, COLOR_PAIR(CP_WHITE)); - wprintw(window, "%s", " "); - } else { - wprintw(window, "%s", ">"); - } - // find next quote or break - offset++; - if(line->text->text[offset] == ' ') - offset = next_word(line->text, offset); - } + inline_display(window, &line->text->value[offset], colors); + } + } + } + + // fill rest off line with spaces + // we only need this if the color is inverted (e.g. code-blocks), + // to ensure the background fades too + if(CHECK_BIT(line->bits, IS_CODE)) + for(i = getcurx(window) - x; i < max_cols; i++) + wprintw(window, "%s", " "); + + // reset to default color + if(colors) + wattron(window, COLOR_PAIR(CP_WHITE)); + wattroff(window, A_UNDERLINE); +} + +void inline_display(WINDOW *window, const wchar_t *c, const int colors) { + const static wchar_t *special = L"\\*_`!["; // list of interpreted chars + const wchar_t *i = c; // iterator + const wchar_t *start_link_name, *start_url; + int length_link_name, url_num; + cstack_t *stack = cstack_init(); + + + // for each char in line + for(; *i; i++) { + + // if char is in special char list + if(wcschr(special, *i)) { + + // closing special char (or second backslash) + // only if not followed by :alnum: + if((stack->top)(stack, *i) && + (!iswalnum(i[1]) || *(i + 1) == L'\0' || *i == L'\\')) { + + switch(*i) { + // print escaped backslash + case L'\\': + waddnwstr(window, i, 1); + break; + // disable highlight + case L'*': + if(colors) + wattron(window, COLOR_PAIR(CP_WHITE)); + break; + // disable underline + case L'_': + wattroff(window, A_UNDERLINE); + break; + // disable inline code + case L'`': + if(colors) + wattron(window, COLOR_PAIR(CP_WHITE)); + break; } - // for each char in line - c = &line->text->text[offset]; - while(*c) { - - // if char is in special char list - if(strchr(special, *c)) { - - // closing special char (or second backslash) - if((stack->top)(stack, *c)) { - - switch(*c) { - // print escaped backslash - case '\\': - wprintw(window, "%c", *c); - break; - // disable highlight - case '*': - if(colors) - wattron(window, COLOR_PAIR(CP_WHITE)); - break; - // disable underline - case '_': - wattroff(window, A_UNDERLINE); - break; - // disable inline code - case '`': - if(colors) - wattron(window, COLOR_PAIR(CP_WHITE)); - break; - } + // remove top special char from stack + (stack->pop)(stack); - // remove top special char from stack - (stack->pop)(stack); + // treat special as regular char + } else if((stack->top)(stack, L'\\')) { + waddnwstr(window, i, 1); - // treat special as regular char - } else if((stack->top)(stack, '\\')) { - wprintw(window, "%c", *c); + // remove backslash from stack + (stack->pop)(stack); - // remove backslash from stack - (stack->pop)(stack); + // opening special char + } else { - // opening special char - } else { - switch(*c) { - // enable highlight - case '*': - if(colors) - wattron(window, COLOR_PAIR(CP_RED)); - break; - // enable underline - case '_': - wattron(window, A_UNDERLINE); - break; - // enable inline code - case '`': - if(colors) - wattron(window, COLOR_PAIR(CP_BLACK)); - break; - // do nothing for backslashes - } + // emphasis or code span can start after new-line or space only + // and of cause after another emphasis markup + //TODO this condition looks ugly + if(i == c || + iswspace(*(i - 1)) || + ((iswspace(*(i - 1)) || *(i - 1) == L'*' || *(i - 1) == L'_') && + ((i - 1) == c || iswspace(*(i - 2)))) || + *i == L'\\') { - // push special char to stack - (stack->push)(stack, *c); - } + // url in pandoc style + if ((*i == L'[' && wcschr(i, L']')) || + (*i == L'!' && *(i + 1) == L'[' && wcschr(i, L']'))) { - } else { - // remove backslash from stack - if((stack->top)(stack, '\\')) - (stack->pop)(stack); + if (*i == L'!') i++; - // print regular char - wprintw(window, "%c", *c); - } + if (wcschr(i, L']')[1] == L'(') { + i++; - c++; - } + // turn higlighting and underlining on + if (colors) + wattron(window, COLOR_PAIR(CP_BLUE)); + wattron(window, A_UNDERLINE); + + start_link_name = i; + + // print the content of the label + // the label is printed as is + do { + waddnwstr(window, i, 1); + i++; + } while (*i != L']'); + + length_link_name = i - 1 - start_link_name; + + i++; + i++; + + start_url = i; + + while (*i != L')') i++; + + url_num = url_add(start_link_name, length_link_name, start_url, i - start_url, 0, 0); - // pop stack until empty to prevent formated trailing spaces - while(!(stack->empty)(stack)) { - switch((stack->pop)(stack)) { - // disable highlight - case '*': + wprintw(window, " [%d]", url_num); + + // turn highlighting and underlining off + wattroff(window, A_UNDERLINE); + wattron(window, COLOR_PAIR(CP_WHITE)); + + } else { + wprintw(window, "["); + } + + } else switch(*i) { + // enable highlight + case L'*': if(colors) - wattron(window, COLOR_PAIR(CP_WHITE)); + wattron(window, COLOR_PAIR(CP_RED)); break; - // disable underline - case '_': - wattroff(window, A_UNDERLINE); + // enable underline + case L'_': + wattron(window, A_UNDERLINE); break; - // disable inline code - case '`': + // enable inline code + case L'`': if(colors) - wattron(window, COLOR_PAIR(CP_WHITE)); + wattron(window, COLOR_PAIR(CP_BLACK)); break; // do nothing for backslashes } + + // push special char to stack + (stack->push)(stack, *i); + + } else { + waddnwstr(window, i, 1); } } - } - // fill rest off line with spaces - for(i = getcurx(window) - x; i < max_cols; i++) - wprintw(window, "%s", " "); + } else { + // remove backslash from stack + if((stack->top)(stack, L'\\')) + (stack->pop)(stack); - // reset to default color - if(colors) - wattron(window, COLOR_PAIR(CP_WHITE)); - wattroff(window, A_UNDERLINE); + // print regular char + waddnwstr(window, i, 1); + } + } + + // pop stack until empty to prevent formated trailing spaces + while(!(stack->empty)(stack)) { + switch((stack->pop)(stack)) { + // disable highlight + case L'*': + if(colors) + wattron(window, COLOR_PAIR(CP_WHITE)); + break; + // disable underline + case L'_': + wattroff(window, A_UNDERLINE); + break; + // disable inline code + case L'`': + if(colors) + wattron(window, COLOR_PAIR(CP_WHITE)); + break; + // do nothing for backslashes + } } (stack->delete)(stack); @@ -643,3 +873,20 @@ int int_length (int val) { } return l; } + +int get_slide_number(char init) { + int retval = init - '0'; + char c; + // block for tenths of a second when using getch, ERR if no input + halfdelay(GOTO_SLIDE_DELAY); + while((c = getch()) != ERR) { + if (c < '0' || c > '9') { + retval = -1; + break; + } + retval = (retval * 10) + (c - '0'); + } + nocbreak(); // cancel half delay mode + cbreak(); // go back to cbreak + return retval; +}