1 /* Copyright (C) 2020  C. McEnroe <june@causal.agency>
2  *
3  * This program is free software: you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation, either version 3 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  *
16  * Additional permission under GNU GPL version 3 section 7:
17  *
18  * If you modify this Program, or any covered work, by linking or
19  * combining it with OpenSSL (or a modified version of that library),
20  * containing parts covered by the terms of the OpenSSL License and the
21  * original SSLeay license, the licensors of this Program grant you
22  * additional permission to convey the resulting work. Corresponding
23  * Source for a non-source form of such a combination shall include the
24  * source code for the parts of OpenSSL used as well as that of the
25  * covered work.
26  */
27 
28 #include <err.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sysexits.h>
34 #include <time.h>
35 #include <wchar.h>
36 #include <wctype.h>
37 
38 #include "chat.h"
39 
40 struct Lines {
41 	size_t len;
42 	struct Line lines[BufferCap];
43 };
44 _Static_assert(!(BufferCap & (BufferCap - 1)), "BufferCap is power of two");
45 
46 struct Buffer {
47 	struct Lines soft;
48 	struct Lines hard;
49 };
50 
bufferAlloc(void)51 struct Buffer *bufferAlloc(void) {
52 	struct Buffer *buffer = calloc(1, sizeof(*buffer));
53 	if (!buffer) err(EX_OSERR, "calloc");
54 	return buffer;
55 }
56 
bufferFree(struct Buffer * buffer)57 void bufferFree(struct Buffer *buffer) {
58 	for (size_t i = 0; i < BufferCap; ++i) {
59 		free(buffer->soft.lines[i].str);
60 		free(buffer->hard.lines[i].str);
61 	}
62 	free(buffer);
63 }
64 
linesLine(const struct Lines * lines,size_t i)65 static const struct Line *linesLine(const struct Lines *lines, size_t i) {
66 	const struct Line *line = &lines->lines[(lines->len + i) % BufferCap];
67 	return (line->str ? line : NULL);
68 }
69 
linesNext(struct Lines * lines)70 static struct Line *linesNext(struct Lines *lines) {
71 	struct Line *line = &lines->lines[lines->len++ % BufferCap];
72 	free(line->str);
73 	return line;
74 }
75 
bufferSoft(const struct Buffer * buffer,size_t i)76 const struct Line *bufferSoft(const struct Buffer *buffer, size_t i) {
77 	return linesLine(&buffer->soft, i);
78 }
79 
bufferHard(const struct Buffer * buffer,size_t i)80 const struct Line *bufferHard(const struct Buffer *buffer, size_t i) {
81 	return linesLine(&buffer->hard, i);
82 }
83 
84 enum { StyleCap = 10 };
styleCopy(char * ptr,char * end,struct Style style)85 static char *styleCopy(char *ptr, char *end, struct Style style) {
86 	ptr = seprintf(
87 		ptr, end, "%s%s%s%s",
88 		(style.attr & Bold ? (const char []) { B, '\0' } : ""),
89 		(style.attr & Reverse ? (const char []) { R, '\0' } : ""),
90 		(style.attr & Italic ? (const char []) { I, '\0' } : ""),
91 		(style.attr & Underline ? (const char []) { U, '\0' } : "")
92 	);
93 	if (style.fg != Default || style.bg != Default) {
94 		ptr = seprintf(ptr, end, "\3%02d,%02d", style.fg, style.bg);
95 	}
96 	return ptr;
97 }
98 
99 static const wchar_t ZWS = L'\u200B';
100 static const wchar_t ZWNJ = L'\u200C';
101 
flow(struct Lines * hard,int cols,const struct Line * soft)102 static int flow(struct Lines *hard, int cols, const struct Line *soft) {
103 	int flowed = 1;
104 
105 	struct Line *line = linesNext(hard);
106 	line->num = soft->num;
107 	line->heat = soft->heat;
108 	line->time = soft->time;
109 	line->str = strdup(soft->str);
110 	if (!line->str) err(EX_OSERR, "strdup");
111 
112 	int width = 0;
113 	int align = 0;
114 	char *wrap = NULL;
115 	struct Style style = StyleDefault;
116 	struct Style wrapStyle = StyleDefault;
117 	for (char *str = line->str; *str;) {
118 		size_t len = styleParse(&style, (const char **)&str);
119 		if (!len) continue;
120 
121 		bool tab = (*str == '\t' && !align);
122 		if (tab) *str = ' ';
123 
124 		wchar_t wc = L'\0';
125 		int n = mbtowc(&wc, str, len);
126 		if (n < 0) {
127 			n = 1;
128 			// ncurses will render these as "~A".
129 			width += (*str & '\200' ? 2 : 1);
130 		} else if (wc == ZWS || wc == ZWNJ) {
131 			// ncurses likes to render these as spaces when they should be
132 			// zero-width, so just remove them entirely.
133 			memmove(str, &str[n], strlen(&str[n]) + 1);
134 			continue;
135 		} else if (wc == L'\t') {
136 			// Assuming TABSIZE = 8.
137 			width += 8 - (width % 8);
138 		} else if (wc < L' ' || wc == L'\177') {
139 			// ncurses will render these as "^A".
140 			width += 2;
141 		} else if (wcwidth(wc) > 0) {
142 			width += wcwidth(wc);
143 		}
144 
145 		if (tab && width < cols) {
146 			align = width;
147 			wrap = NULL;
148 		}
149 		if (iswspace(wc) && !tab) {
150 			wrap = str;
151 			wrapStyle = style;
152 		}
153 		if (wc == L'-' && width <= cols) {
154 			wrap = &str[n];
155 			wrapStyle = style;
156 		}
157 
158 		if (width <= cols) {
159 			str += n;
160 			continue;
161 		} else if (!wrap) {
162 			wrap = str;
163 			wrapStyle = style;
164 		}
165 
166 		n = 0;
167 		len = strlen(wrap);
168 		for (int m; wrap[n] && (m = mbtowc(&wc, &wrap[n], len - n)); n += m) {
169 			if (m < 0) {
170 				m = 1;
171 			} else if (!iswspace(wc)) {
172 				break;
173 			}
174 		}
175 		if (!wrap[n]) {
176 			*wrap = '\0';
177 			break;
178 		}
179 
180 		flowed++;
181 		line = linesNext(hard);
182 		line->num = soft->num;
183 		line->heat = soft->heat;
184 		line->time = 0;
185 
186 		size_t cap = StyleCap + align + strlen(&wrap[n]) + 1;
187 		line->str = malloc(cap);
188 		if (!line->str) err(EX_OSERR, "malloc");
189 
190 		char *end = &line->str[cap];
191 		str = seprintf(line->str, end, "%*s", (width = align), "");
192 		str = styleCopy(str, end, wrapStyle);
193 		style = wrapStyle;
194 		seprintf(str, end, "%s", &wrap[n]);
195 
196 		*wrap = '\0';
197 		wrap = NULL;
198 	}
199 
200 	return flowed;
201 }
202 
bufferPush(struct Buffer * buffer,int cols,enum Heat thresh,enum Heat heat,time_t time,const char * str)203 int bufferPush(
204 	struct Buffer *buffer, int cols, enum Heat thresh,
205 	enum Heat heat, time_t time, const char *str
206 ) {
207 	struct Line *soft = linesNext(&buffer->soft);
208 	soft->num = buffer->soft.len;
209 	soft->heat = heat;
210 	soft->time = time;
211 	soft->str = strdup(str);
212 	if (!soft->str) err(EX_OSERR, "strdup");
213 	if (heat < thresh) return 0;
214 	return flow(&buffer->hard, cols, soft);
215 }
216 
217 int
bufferReflow(struct Buffer * buffer,int cols,enum Heat thresh,size_t tail)218 bufferReflow(struct Buffer *buffer, int cols, enum Heat thresh, size_t tail) {
219 	buffer->hard.len = 0;
220 	for (size_t i = 0; i < BufferCap; ++i) {
221 		free(buffer->hard.lines[i].str);
222 		buffer->hard.lines[i].str = NULL;
223 	}
224 	int flowed = 0;
225 	for (size_t i = 0; i < BufferCap; ++i) {
226 		const struct Line *soft = bufferSoft(buffer, i);
227 		if (!soft) continue;
228 		if (soft->heat < thresh) continue;
229 		int n = flow(&buffer->hard, cols, soft);
230 		if (i >= BufferCap - tail) flowed += n;
231 	}
232 	return flowed;
233 }
234