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) {
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_HEADER, HEADER_COLOR, BG_COLOR);
153 init_pair(CP_BOLD, BOLD_COLOR, BG_COLOR);
154 init_pair(CP_TITLE, TITLE_COLOR, BG_COLOR);
155 init_pair(CP_CODE, CODEFG_COLOR, CODEBG_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);
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) {
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 wattron(window, COLOR_PAIR(CP_CODE));
420 for(i = getcurx(window) - x; i < max_cols; i++)
421 wprintw(window, "%s", " ");
428 // IS_UNORDERED_LIST_3
429 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) {
430 offset = next_nonblank(line->text, 0);
432 int pos = 0, len, cnt;
433 len = sizeof(prompt) - pos;
434 cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? list_open1 : " ");
435 pos += (cnt > len - 1 ? len - 1 : cnt);
436 len = sizeof(prompt) - pos;
437 cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)? list_open2 : " ");
438 pos += (cnt > len - 1 ? len - 1 : cnt);
439 len = sizeof(prompt) - pos;
441 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
442 snprintf(&prompt[pos], len, "%s", line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_3)? list_open3 : " ");
444 snprintf(&prompt[pos], len, "%s", list_head3);
451 if(!CHECK_BIT(line->bits, IS_CODE))
452 inline_display(window, &line->text->value[offset], colors);
454 // IS_UNORDERED_LIST_2
455 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) {
456 offset = next_nonblank(line->text, 0);
458 int pos = 0, len, cnt;
459 len = sizeof(prompt) - pos;
460 cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? list_open1 : " ");
461 pos += (cnt > len - 1 ? len - 1 : cnt);
462 len = sizeof(prompt) - pos;
464 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
465 snprintf(&prompt[pos], len, "%s", line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_2)? list_open2 : " ");
467 snprintf(&prompt[pos], len, "%s", list_head2);
474 if(!CHECK_BIT(line->bits, IS_CODE))
475 inline_display(window, &line->text->value[offset], colors);
477 // IS_UNORDERED_LIST_1
478 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) {
479 offset = next_nonblank(line->text, 0);
482 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) {
483 strcpy(&prompt[0], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_1)? list_open1 : " ");
485 strcpy(&prompt[0], list_head1);
492 if(!CHECK_BIT(line->bits, IS_CODE))
493 inline_display(window, &line->text->value[offset], colors);
497 if(CHECK_BIT(line->bits, IS_CODE)) {
499 if (!CHECK_BIT(line->bits, IS_TILDE_CODE) &&
500 !CHECK_BIT(line->bits, IS_GFM_CODE)) {
501 // set static offset for code
502 offset = CODE_INDENT;
505 // color for code block
507 wattron(window, COLOR_PAIR(CP_CODE));
510 waddwstr(window, &line->text->value[offset]);
513 if(!CHECK_BIT(line->bits, IS_UNORDERED_LIST_1) &&
514 !CHECK_BIT(line->bits, IS_UNORDERED_LIST_2) &&
515 !CHECK_BIT(line->bits, IS_UNORDERED_LIST_3) &&
516 !CHECK_BIT(line->bits, IS_CODE)) {
519 if(CHECK_BIT(line->bits, IS_QUOTE)) {
520 while(line->text->value[offset] == '>') {
521 // print a code block
523 wattron(window, COLOR_PAIR(CP_CODE));
524 wprintw(window, "%s", " ");
525 wattron(window, COLOR_PAIR(CP_FG));
526 wprintw(window, "%s", " ");
528 wprintw(window, "%s", ">");
531 // find next quote or break
533 if(line->text->value[offset] == ' ')
534 offset = next_word(line->text, offset);
537 inline_display(window, &line->text->value[offset], colors);
541 if(CHECK_BIT(line->bits, IS_CENTER)) {
542 if(line->length < max_cols) {
543 wmove(window, y, x + ((max_cols - line->length) / 2));
548 if(CHECK_BIT(line->bits, IS_H1) || CHECK_BIT(line->bits, IS_H2)) {
550 // set headline color
552 wattron(window, COLOR_PAIR(CP_HEADER));
554 // enable underline for H1
555 if(CHECK_BIT(line->bits, IS_H1))
556 wattron(window, A_UNDERLINE);
559 while(line->text->value[offset] == '#')
560 offset = next_word(line->text, offset);
563 waddwstr(window, &line->text->value[offset]);
565 wattroff(window, A_UNDERLINE);
567 // no line-wide markdown
570 inline_display(window, &line->text->value[offset], colors);
575 // fill rest off line with spaces
576 // we only need this if the color is inverted (e.g. code-blocks)
577 if(CHECK_BIT(line->bits, IS_CODE))
578 for(i = getcurx(window) - x; i < max_cols; i++)
579 wprintw(window, "%s", " ");
581 // reset to default color
583 wattron(window, COLOR_PAIR(CP_FG));
584 wattroff(window, A_UNDERLINE);
587 void inline_display(WINDOW *window, const wchar_t *c, const int colors) {
588 const static wchar_t *special = L"\\*_`!["; // list of interpreted chars
589 const wchar_t *i = c; // iterator
590 const wchar_t *start_link_name, *start_url;
591 int length_link_name, url_num;
592 cstack_t *stack = cstack_init();
595 // for each char in line
598 // if char is in special char list
599 if(wcschr(special, *i)) {
601 // closing special char (or second backslash)
602 // only if not followed by :alnum:
603 if((stack->top)(stack, *i) &&
604 (!iswalnum(i[1]) || *(i + 1) == L'\0' || *i == L'\\')) {
607 // print escaped backslash
609 waddnwstr(window, i, 1);
614 wattron(window, COLOR_PAIR(CP_FG));
618 wattroff(window, A_UNDERLINE);
620 // disable inline code
623 wattron(window, COLOR_PAIR(CP_FG));
627 // remove top special char from stack
630 // treat special as regular char
631 } else if((stack->top)(stack, L'\\')) {
632 waddnwstr(window, i, 1);
634 // remove backslash from stack
637 // opening special char
640 // emphasis or code span can start after new-line or space only
641 // and of cause after another emphasis markup
642 //TODO this condition looks ugly
644 iswspace(*(i - 1)) ||
645 ((iswspace(*(i - 1)) || *(i - 1) == L'*' || *(i - 1) == L'_') &&
646 ((i - 1) == c || iswspace(*(i - 2)))) ||
649 // url in pandoc style
650 if ((*i == L'[' && wcschr(i, L']')) ||
651 (*i == L'!' && *(i + 1) == L'[' && wcschr(i, L']'))) {
655 if (wcschr(i, L']')[1] == L'(' && wcschr(i, L')')) {
658 // turn higlighting and underlining on
660 wattron(window, COLOR_PAIR(CP_HEADER));
661 wattron(window, A_UNDERLINE);
665 // print the content of the label
666 // the label is printed as is
668 waddnwstr(window, i, 1);
670 } while (*i != L']');
672 length_link_name = i - 1 - start_link_name;
679 while (*i != L')') i++;
681 url_num = url_add(start_link_name, length_link_name, start_url, i - start_url, 0, 0);
683 wprintw(window, " [%d]", url_num);
685 // turn highlighting and underlining off
686 wattroff(window, A_UNDERLINE);
687 wattron(window, COLOR_PAIR(CP_FG));
690 wprintw(window, "[");
697 wattron(window, COLOR_PAIR(CP_BOLD));
701 wattron(window, A_UNDERLINE);
703 // enable inline code
705 wattron(window, COLOR_PAIR(CP_CODE));
707 // do nothing for backslashes
710 // push special char to stack
711 (stack->push)(stack, *i);
714 waddnwstr(window, i, 1);
719 // remove backslash from stack
720 if((stack->top)(stack, L'\\'))
723 // print regular char
724 waddnwstr(window, i, 1);
728 // pop stack until empty to prevent formated trailing spaces
729 while(!(stack->empty)(stack)) {
730 switch((stack->pop)(stack)) {
734 wattron(window, COLOR_PAIR(CP_FG));
738 wattroff(window, A_UNDERLINE);
740 // disable inline code
743 wattron(window, COLOR_PAIR(CP_FG));
745 // do nothing for backslashes
749 (stack->delete)(stack);
752 int int_length (int val) {
761 int get_slide_number(char init) {
762 int retval = init - '0';
764 // block for tenths of a second when using getch, ERR if no input
765 halfdelay(GOTO_SLIDE_DELAY);
766 while((c = getch()) != ERR) {
767 if (c < '0' || c > '9') {
771 retval = (retval * 10) + (c - '0');
773 nocbreak(); // cancel half delay mode
774 cbreak(); // go back to cbreak
778 bool evaluate_binding(const int bindings[], char c) {
781 while((binding = bindings[ind]) != 0) {
782 if (c == binding) return true;