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