2 * Functions necessary to display a deck of slides in different color modes
4 * Copyright (C) 2018 Michael Goehler
6 * This file is part of mdp.
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include <ctype.h> // isalnum
24 #include <wchar.h> // wcschr
25 #include <wctype.h> // iswalnum
26 #include <string.h> // strcpy
27 #include <unistd.h> // usleep
28 #include <stdlib.h> // getenv
32 int ncurses_display(deck_t *deck, int reload, int noreload, int slidenum, int nocodebg) {
36 int l = 0; // line number
37 int lc = 0; // line count
38 int sc = 1; // slide count
39 int colors = 0; // amount of colors supported
40 int max_lines = 0; // max lines per slide
41 int max_lines_slide = -1; // the slide that has the most lines
42 int max_cols = 0; // max columns per line
43 int offset; // text offset
44 int stop = 0; // passed stop bits per slide
46 // header line 1 is displayed at the top
47 int bar_top = (deck->headers > 0) ? 1 : 0;
48 // header line 2 is displayed at the bottom
49 // anyway we display the slide number at the bottom
50 int bar_bottom = (slidenum || deck->headers > 1)? 1 : 0;
52 slide_t *slide = deck->slide;
62 while(line && line->text) {
64 if (line->text->value) {
65 lc += url_count_inline(line->text->value);
66 line->length -= url_len_inline(line->text->value);
69 if(line->length > COLS) {
74 i = prev_blank(line->text, offset + COLS) - offset;
76 // single word is > COLS
78 // calculate min_width
79 i = next_blank(line->text, offset + COLS) - offset;
85 fwprintf(stderr, L"Error: Terminal width (%i columns) too small. Need at least %i columns.\n", COLS, i);
86 fwprintf(stderr, L"You may need to shorten some lines by inserting line breaks.\n");
93 max_cols = MAX(i, max_cols);
95 // iterate to next line
96 offset = prev_blank(line->text, offset + COLS);
97 i = line->length - offset;
100 // set max_cols one last time
101 max_cols = MAX(i, max_cols);
104 max_cols = MAX(line->length, max_cols);
110 max_lines = MAX(lc, max_lines);
111 if (lc == max_lines) {
112 max_lines_slide = sc;
115 slide->lines_consumed = lc;
121 if(max_lines + bar_top + bar_bottom > LINES) {
127 fwprintf(stderr, L"Error: Terminal height (%i lines) too small. Need at least %i lines for slide #%i.\n", LINES, max_lines + bar_top + bar_bottom, max_lines_slide);
128 fwprintf(stderr, L"You may need to add additional horizontal rules (---) to split your file in shorter slides.\n");
137 // disable output of keyboard typing
140 // make getch() process one char at a time
147 if(has_colors() == TRUE) {
149 use_default_colors();
151 init_pair(CP_FG, FG_COLOR, BG_COLOR);
152 init_pair(CP_BG, BG_COLOR, FG_COLOR);
153 init_pair(CP_HEADER, HEADER_COLOR, BG_COLOR);
154 init_pair(CP_BOLD, BOLD_COLOR, BG_COLOR);
155 init_pair(CP_TITLE, TITLE_COLOR, BG_COLOR);
160 // set background color for main window
162 wbkgd(stdscr, COLOR_PAIR(CP_FG));
164 // setup content window
165 WINDOW *content = newwin(LINES - bar_top - bar_bottom, COLS, 0 + bar_top, 0);
167 // set background color of content window
169 wbkgd(content, COLOR_PAIR(CP_FG));
173 // find slide to reload
175 while(reload > 1 && reload <= deck->slides) {
181 // reset reload indicator
192 // always resize window in case terminal geometry has changed
193 wresize(content, LINES - bar_top - bar_bottom, COLS);
195 // set main window text color
197 wattron(stdscr, COLOR_PAIR(CP_TITLE));
202 offset = next_blank(line->text, 0) + 1;
203 // add text to header
205 0, (COLS - line->length + offset) / 2,
206 &line->text->value[offset]);
210 if(deck->headers > 1) {
211 line = deck->header->next;
212 offset = next_blank(line->text, 0) + 1;
214 case 0: // add text to center footer
216 LINES - 1, (COLS - line->length + offset) / 2,
217 &line->text->value[offset]);
220 case 2: // add text to left footer
223 &line->text->value[offset]);
228 // add slide number to right footer
230 case 1: // show slide number only
232 LINES - 1, COLS - int_length(sc) - 3,
235 case 2: // show current slide & number of slides
237 LINES - 1, COLS - int_length(deck->slides) - int_length(sc) - 6,
238 "%d / %d", sc, deck->slides);
242 // copy changed lines in main window to virtual screen
243 wnoutrefresh(stdscr);
250 add_line(content, l + ((LINES - slide->lines_consumed - bar_top - bar_bottom) / 2),
251 (COLS - max_cols) / 2, line, max_cols, colors, nocodebg);
253 // raise stop counter if we pass a line having a stop bit
254 if(CHECK_BIT(line->bits, IS_STOP))
257 l += (line->length / COLS) + 1;
260 // only stop here if we didn't stop here recently
261 if(stop > slide->stop)
265 // print pandoc URL references
266 // only if we already printed all lines of the current slide (or output is stopped)
268 stop > slide->stop) {
270 getmaxyx( content, ymax, i );
271 for (i = 0; i < url_get_amount(); i++) {
272 mvwprintw(content, ymax - url_get_amount() - 1 + i, 3,
274 waddwstr(content, url_get_target(i));
278 // copy changed lines in content window to virtual screen
279 wnoutrefresh(content);
281 // compare virtual screen to physical screen and does the actual updates
284 // wait for user input
287 // evaluate user input
290 if (evaluate_binding(prev_slide_binding, c)) {
291 // show previous slide or stop bit
292 if(stop > 1 || (stop == 1 && !line)) {
293 // show current slide again
294 // but stop one stop bit earlier
298 // show previous slide
301 //stop on first bullet point always
306 } else if (evaluate_binding(next_slide_binding, c)) {
307 // show next slide or stop bit
309 // show current slide again
310 // but stop one stop bit later (or at end of slide)
319 } else if (isdigit(c) && c != '0') {
321 i = get_slide_number(c);
322 if(i > 0 && i <= deck->slides) {
339 } else if (evaluate_binding(first_slide_binding, c)) {
343 } else if (evaluate_binding(last_slide_binding, c)) {
345 for(i = sc; i <= deck->slides; i++) {
351 } else if (evaluate_binding(reload_binding, c)) {
358 } else if (evaluate_binding(quit_binding, c)) {
371 // free ncurses memory
376 // return reload indicator (0 means no reload)
380 void setup_list_strings(void)
384 /* utf8 can require 6 bytes */
385 if ((str = getenv("MDP_LIST_OPEN")) != NULL && strlen(str) <= 4*6) {
386 list_open1 = list_open2 = list_open3 = str;
388 if ((str = getenv("MDP_LIST_OPEN1")) != NULL && strlen(str) <= 4*6)
390 if ((str = getenv("MDP_LIST_OPEN2")) != NULL && strlen(str) <= 4*6)
392 if ((str = getenv("MDP_LIST_OPEN3")) != NULL && strlen(str) <= 4*6)
395 if ((str = getenv("MDP_LIST_HEAD")) != NULL && strlen(str) <= 4*6) {
396 list_head1 = list_head2 = list_head3 = str;
398 if ((str = getenv("MDP_LIST_HEAD1")) != NULL && strlen(str) <= 4*6)
400 if ((str = getenv("MDP_LIST_HEAD2")) != NULL && strlen(str) <= 4*6)
402 if ((str = getenv("MDP_LIST_HEAD3")) != NULL && strlen(str) <= 4*6)
407 void add_line(WINDOW *window, int y, int x, line_t *line, int max_cols, int colors, int nocodebg) {
410 int offset = 0; // text offset
412 // move the cursor in position
415 if(!line->text->value) {
417 // fill rest off line with spaces if we are in a code block
418 if(CHECK_BIT(line->bits, IS_CODE) && colors) {
419 if(colors && !nocodebg)
420 wattron(window, COLOR_PAIR(CP_BG));
421 for(i = getcurx(window) - x; i < max_cols; i++)
422 wprintw(window, "%s", " ");
429 // IS_UNORDERED_LIST_3
430 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) {
431 offset = next_nonblank(line->text, 0);
433 int pos = 0, len, cnt;
434 len = sizeof(prompt) - pos;
435 cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? list_open1 : " ");
436 pos += (cnt > len - 1 ? len - 1 : cnt);
437 len = sizeof(prompt) - pos;
438 cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)? list_open2 : " ");
439 pos += (cnt > len - 1 ? len - 1 : cnt);
440 len = sizeof(prompt) - pos;
442 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
443 snprintf(&prompt[pos], len, "%s", line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_3)? list_open3 : " ");
445 snprintf(&prompt[pos], len, "%s", list_head3);
452 if(!CHECK_BIT(line->bits, IS_CODE))
453 inline_display(window, &line->text->value[offset], colors, nocodebg);
455 // IS_UNORDERED_LIST_2
456 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) {
457 offset = next_nonblank(line->text, 0);
459 int pos = 0, len, cnt;
460 len = sizeof(prompt) - pos;
461 cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? list_open1 : " ");
462 pos += (cnt > len - 1 ? len - 1 : cnt);
463 len = sizeof(prompt) - pos;
465 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
466 snprintf(&prompt[pos], len, "%s", line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_2)? list_open2 : " ");
468 snprintf(&prompt[pos], len, "%s", list_head2);
475 if(!CHECK_BIT(line->bits, IS_CODE))
476 inline_display(window, &line->text->value[offset], colors, nocodebg);
478 // IS_UNORDERED_LIST_1
479 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) {
480 offset = next_nonblank(line->text, 0);
483 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
484 strcpy(&prompt[0], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_1)? list_open1 : " ");
486 strcpy(&prompt[0], list_head1);
493 if(!CHECK_BIT(line->bits, IS_CODE))
494 inline_display(window, &line->text->value[offset], colors, nocodebg);
498 if(CHECK_BIT(line->bits, IS_CODE)) {
500 if (!CHECK_BIT(line->bits, IS_TILDE_CODE) &&
501 !CHECK_BIT(line->bits, IS_GFM_CODE)) {
502 // set static offset for code
503 offset = CODE_INDENT;
506 // reverse color for code blocks
507 if(colors && !nocodebg)
508 wattron(window, COLOR_PAIR(CP_BG));
511 waddwstr(window, &line->text->value[offset]);
514 if(!CHECK_BIT(line->bits, IS_UNORDERED_LIST_1) &&
515 !CHECK_BIT(line->bits, IS_UNORDERED_LIST_2) &&
516 !CHECK_BIT(line->bits, IS_UNORDERED_LIST_3) &&
517 !CHECK_BIT(line->bits, IS_CODE)) {
520 if(CHECK_BIT(line->bits, IS_QUOTE)) {
521 while(line->text->value[offset] == '>') {
522 // print a reverse color block
524 wattron(window, COLOR_PAIR(CP_BG));
525 wprintw(window, "%s", " ");
526 wattron(window, COLOR_PAIR(CP_FG));
527 wprintw(window, "%s", " ");
529 wprintw(window, "%s", ">");
532 // find next quote or break
534 if(line->text->value[offset] == ' ')
535 offset = next_word(line->text, offset);
538 inline_display(window, &line->text->value[offset], colors, nocodebg);
542 if(CHECK_BIT(line->bits, IS_CENTER)) {
543 if(line->length < max_cols) {
544 wmove(window, y, x + ((max_cols - line->length) / 2));
549 if(CHECK_BIT(line->bits, IS_H1) || CHECK_BIT(line->bits, IS_H2)) {
551 // set headline color
553 wattron(window, COLOR_PAIR(CP_HEADER));
555 // enable underline for H1
556 if(CHECK_BIT(line->bits, IS_H1))
557 wattron(window, A_UNDERLINE);
560 while(line->text->value[offset] == '#')
561 offset = next_word(line->text, offset);
564 waddwstr(window, &line->text->value[offset]);
566 wattroff(window, A_UNDERLINE);
568 // no line-wide markdown
571 inline_display(window, &line->text->value[offset], colors, nocodebg);
576 // reset to default color
578 wattron(window, COLOR_PAIR(CP_FG));
579 wattroff(window, A_UNDERLINE);
582 void inline_display(WINDOW *window, const wchar_t *c, const int colors, int nocodebg) {
583 const static wchar_t *special = L"\\*_`!["; // list of interpreted chars
584 const wchar_t *i = c; // iterator
585 const wchar_t *start_link_name, *start_url;
586 int length_link_name, url_num;
587 cstack_t *stack = cstack_init();
590 // for each char in line
593 // if char is in special char list
594 if(wcschr(special, *i)) {
596 // closing special char (or second backslash)
597 // only if not followed by :alnum:
598 if((stack->top)(stack, *i) &&
599 (!iswalnum(i[1]) || *(i + 1) == L'\0' || *i == L'\\')) {
602 // print escaped backslash
604 waddnwstr(window, i, 1);
609 wattron(window, COLOR_PAIR(CP_FG));
613 wattroff(window, A_UNDERLINE);
615 // disable inline code
618 wattron(window, COLOR_PAIR(CP_FG));
622 // remove top special char from stack
625 // treat special as regular char
626 } else if((stack->top)(stack, L'\\')) {
627 waddnwstr(window, i, 1);
629 // remove backslash from stack
632 // opening special char
635 // emphasis or code span can start after new-line or space only
636 // and of cause after another emphasis markup
637 //TODO this condition looks ugly
639 iswspace(*(i - 1)) ||
640 ((iswspace(*(i - 1)) || *(i - 1) == L'*' || *(i - 1) == L'_') &&
641 ((i - 1) == c || iswspace(*(i - 2)))) ||
644 // url in pandoc style
645 if ((*i == L'[' && wcschr(i, L']')) ||
646 (*i == L'!' && *(i + 1) == L'[' && wcschr(i, L']'))) {
650 if (wcschr(i, L']')[1] == L'(' && wcschr(i, L')')) {
653 // turn higlighting and underlining on
655 wattron(window, COLOR_PAIR(CP_HEADER));
656 wattron(window, A_UNDERLINE);
660 // print the content of the label
661 // the label is printed as is
663 waddnwstr(window, i, 1);
665 } while (*i != L']');
667 length_link_name = i - 1 - start_link_name;
674 while (*i != L')') i++;
676 url_num = url_add(start_link_name, length_link_name, start_url, i - start_url, 0, 0);
678 wprintw(window, " [%d]", url_num);
680 // turn highlighting and underlining off
681 wattroff(window, A_UNDERLINE);
682 wattron(window, COLOR_PAIR(CP_FG));
685 wprintw(window, "[");
692 wattron(window, COLOR_PAIR(CP_BOLD));
696 wattron(window, A_UNDERLINE);
698 // enable inline code
700 if(colors && !nocodebg)
701 wattron(window, COLOR_PAIR(CP_BG));
703 // do nothing for backslashes
706 // push special char to stack
707 (stack->push)(stack, *i);
710 waddnwstr(window, i, 1);
715 // remove backslash from stack
716 if((stack->top)(stack, L'\\'))
719 // print regular char
720 waddnwstr(window, i, 1);
724 // pop stack until empty to prevent formated trailing spaces
725 while(!(stack->empty)(stack)) {
726 switch((stack->pop)(stack)) {
730 wattron(window, COLOR_PAIR(CP_FG));
734 wattroff(window, A_UNDERLINE);
736 // disable inline code
739 wattron(window, COLOR_PAIR(CP_FG));
741 // do nothing for backslashes
745 (stack->delete)(stack);
748 int int_length (int val) {
757 int get_slide_number(char init) {
758 int retval = init - '0';
760 // block for tenths of a second when using getch, ERR if no input
761 halfdelay(GOTO_SLIDE_DELAY);
762 while((c = getch()) != ERR) {
763 if (c < '0' || c > '9') {
767 retval = (retval * 10) + (c - '0');
769 nocbreak(); // cancel half delay mode
770 cbreak(); // go back to cbreak
774 bool evaluate_binding(const int bindings[], char c) {
777 while((binding = bindings[ind]) != 0) {
778 if (c == binding) return true;