Blob


1 /*
2 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <telescope.h>
19 #include <curses.h>
20 #include <event.h>
21 #include <locale.h>
22 #include <signal.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #define TAB_CURRENT 0x1
28 #define CTRL(x) ((x)&0x1F)
30 static struct event stdioev, winchev;
32 static struct tab *current_tab(void);
33 static void dispatch_stdio(int, short, void*);
34 static void handle_resize(int, short, void*);
35 static int word_bourdaries(const char*, const char*, const char**, const char**);
36 static void wrap_text(const char*, const char*, const char*, const char*);
37 static void redraw_tab(struct tab*);
39 static struct tab *
40 current_tab(void)
41 {
42 struct tab *t;
44 TAILQ_FOREACH(t, &tabshead, tabs) {
45 if (t->flags & TAB_CURRENT)
46 return t;
47 }
49 /* unreachable */
50 abort();
51 }
53 static void
54 dispatch_stdio(int fd, short ev, void *d)
55 {
56 int c;
58 c = getch();
60 if (c == ERR)
61 return;
63 if (c == 'q') {
64 event_loopbreak();
65 return;
66 }
68 if (c == CTRL('L')) {
69 clear();
70 redraw_tab(current_tab());
71 return;
72 }
74 printw("You typed %c\n", c);
75 refresh();
76 }
78 static void
79 handle_resize(int sig, short ev, void *d)
80 {
81 endwin();
82 refresh();
83 clear();
85 redraw_tab(current_tab());
86 }
88 /*
89 * Helper function for wrap_text. Find the end of the current word
90 * and the end of the separator after the word.
91 */
92 static int
93 word_boundaries(const char *s, const char *sep, const char **endword, const char **endspc)
94 {
95 *endword = s;
96 *endword = s;
98 if (*s == '\0')
99 return 0;
101 /* find the end of the current world */
102 for (; *s != '\0'; ++s) {
103 if (strchr(sep, *s) != NULL)
104 break;
107 *endword = s;
109 /* find the end of the separator */
110 for (; *s != '\0'; ++s) {
111 if (strchr(sep, *s) == NULL)
112 break;
115 *endspc = s;
117 return 1;
120 static inline void
121 emitline(const char *prfx, size_t zero, size_t *off)
123 printw("\n%s", prfx);
124 *off = zero;
127 static inline void
128 emitstr(const char **s, size_t len, size_t *off)
130 size_t i;
132 /* printw("%*s", ...) doesn't seem to respect the precision, so... */
133 for (i = 0; i < len; ++i)
134 addch((*s)[i]);
135 *off += len;
136 *s += len;
139 /*
140 * Wrap the text, prefixing the first line with prfx1 and the
141 * following with prfx2, and breaking on characters in the breakon set.
142 * The idea is pretty simple: if there is enough space, write the next
143 * word; if we are at the start of a line and there's not enough
144 * space, hard-split it.
146 * TODO: it considers each byte one cell on the screen!
147 * TODO: assume strlen(prfx1) == strlen(prfx2)
148 */
149 static void
150 wrap_text(const char *prfx1, const char *prfx2, const char *line, const char *breakon)
152 size_t zero, off, len, split;
153 const char *endword, *endspc;
155 printw("%s", prfx1);
156 zero = strlen(prfx1);
157 off = zero;
159 while (word_boundaries(line, breakon, &endword, &endspc)) {
160 len = endword - line;
161 if (off + len < COLS) {
162 emitstr(&line, len, &off);
163 } else {
164 emitline(prfx2, zero, &off);
165 while (len >= COLS) {
166 /* hard wrap */
167 printw("%*s", COLS-1, line);
168 emitline(prfx2, zero, &off);
169 len -= COLS-1;
170 line += COLS-1;
173 if (len != 0)
174 emitstr(&line, len, &off);
177 /* print the spaces iff not at bol */
178 len = endspc - endword;
179 /* line = endspc; */
180 if (off != zero) {
181 if (off + len >= COLS)
182 emitline(prfx2, zero, &off);
183 else
184 emitstr(&line, len, &off);
187 line = endspc;
190 printw("\n");
193 static void
194 redraw_tab(struct tab *tab)
196 struct line *l;
197 const char *sep = " \t";
199 erase();
201 TAILQ_FOREACH(l, &tab->page.head, lines) {
202 switch (l->type) {
203 case LINE_TEXT:
204 wrap_text("", "", l->line, sep);
205 break;
206 case LINE_LINK:
207 wrap_text("=> ", " ", l->line, sep);
208 break;
209 case LINE_TITLE_1:
210 wrap_text("# ", " ", l->line, sep);
211 break;
212 case LINE_TITLE_2:
213 wrap_text("## ", " ", l->line, sep);
214 break;
215 case LINE_TITLE_3:
216 wrap_text("### ", " ", l->line, sep);
217 break;
218 case LINE_ITEM:
219 wrap_text("* ", " ", l->line, sep);
220 break;
221 case LINE_QUOTE:
222 wrap_text("> ", "> ", l->line, sep);
223 break;
224 case LINE_PRE_START:
225 case LINE_PRE_END:
226 printw("```\n");
227 break;
228 case LINE_PRE_CONTENT:
229 printw("%s\n", l->line);
230 break;
234 refresh();
237 int
238 ui_init(void)
240 setlocale(LC_ALL, "");
242 initscr();
243 raw();
244 noecho();
246 nonl();
247 intrflush(stdscr, FALSE);
248 keypad(stdscr, TRUE);
250 /* non-blocking input */
251 timeout(0);
253 mvprintw(0, 0, "");
255 event_set(&stdioev, 0, EV_READ | EV_PERSIST, dispatch_stdio, NULL);
256 event_add(&stdioev, NULL);
258 signal_set(&winchev, SIGWINCH, handle_resize, NULL);
259 signal_add(&winchev, NULL);
261 return 1;
264 void
265 ui_on_new_tab(struct tab *tab)
267 struct tab *t;
269 TAILQ_FOREACH(t, &tabshead, tabs) {
270 t->flags &= ~TAB_CURRENT;
273 tab->flags = TAB_CURRENT;
275 /* TODO: redraw the tab list */
278 void
279 ui_on_tab_refresh(struct tab *tab)
281 if (!(tab->flags & TAB_CURRENT))
282 return;
284 redraw_tab(tab);
287 void
288 ui_end(void)
290 endwin();