aboutsummaryrefslogtreecommitdiff
path: root/examples/dte/buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/dte/buffer.c')
-rw-r--r--examples/dte/buffer.c480
1 files changed, 480 insertions, 0 deletions
diff --git a/examples/dte/buffer.c b/examples/dte/buffer.c
new file mode 100644
index 0000000..705ec0c
--- /dev/null
+++ b/examples/dte/buffer.c
@@ -0,0 +1,480 @@
1#include <stdlib.h>
2#include <string.h>
3#include <sys/stat.h>
4#include "buffer.h"
5#include "editor.h"
6#include "file-option.h"
7#include "filetype.h"
8#include "lock.h"
9#include "syntax/state.h"
10#include "util/debug.h"
11#include "util/intern.h"
12#include "util/log.h"
13#include "util/numtostr.h"
14#include "util/path.h"
15#include "util/str-util.h"
16#include "util/time-util.h"
17#include "util/xmalloc.h"
18
19void set_display_filename(Buffer *buffer, char *name)
20{
21 free(buffer->display_filename);
22 buffer->display_filename = name;
23}
24
25/*
26 * Mark line range min...max (inclusive) "changed". These lines will be
27 * redrawn when screen is updated. This is called even when content has not
28 * been changed, but selection has or line has been deleted and all lines
29 * after the deleted line move up.
30 *
31 * Syntax highlighter has different logic. It cares about contents of the
32 * lines, not about selection or if the lines have been moved up or down.
33 */
34void buffer_mark_lines_changed(Buffer *buffer, long min, long max)
35{
36 if (min > max) {
37 long tmp = min;
38 min = max;
39 max = tmp;
40 }
41 buffer->changed_line_min = MIN(min, buffer->changed_line_min);
42 buffer->changed_line_max = MAX(max, buffer->changed_line_max);
43}
44
45const char *buffer_filename(const Buffer *buffer)
46{
47 const char *name = buffer->display_filename;
48 return name ? name : "(No name)";
49}
50
51void buffer_set_encoding(Buffer *buffer, Encoding encoding, bool utf8_bom)
52{
53 if (
54 buffer->encoding.type != encoding.type
55 || buffer->encoding.name != encoding.name
56 ) {
57 const EncodingType type = encoding.type;
58 if (type == UTF8) {
59 buffer->bom = utf8_bom;
60 } else {
61 buffer->bom = type < NR_ENCODING_TYPES && !!get_bom_for_encoding(type);
62 }
63 buffer->encoding = encoding;
64 }
65}
66
67Buffer *buffer_new(PointerArray *buffers, const GlobalOptions *gopts, const Encoding *encoding)
68{
69 static unsigned long id;
70 Buffer *buffer = xnew0(Buffer, 1);
71 list_init(&buffer->blocks);
72 buffer->cur_change = &buffer->change_head;
73 buffer->saved_change = &buffer->change_head;
74 buffer->id = ++id;
75 buffer->crlf_newlines = gopts->crlf_newlines;
76
77 if (encoding) {
78 buffer_set_encoding(buffer, *encoding, gopts->utf8_bom);
79 } else {
80 buffer->encoding.type = ENCODING_AUTODETECT;
81 }
82
83 static_assert(sizeof(*gopts) >= sizeof(CommonOptions));
84 memcpy(&buffer->options, gopts, sizeof(CommonOptions));
85 buffer->options.brace_indent = 0;
86 buffer->options.filetype = str_intern("none");
87 buffer->options.indent_regex = NULL;
88
89 ptr_array_append(buffers, buffer);
90 return buffer;
91}
92
93Buffer *open_empty_buffer(PointerArray *buffers, const GlobalOptions *gopts)
94{
95 Encoding enc = encoding_from_type(UTF8);
96 Buffer *buffer = buffer_new(buffers, gopts, &enc);
97
98 // At least one block required
99 Block *blk = block_new(1);
100 list_add_before(&blk->node, &buffer->blocks);
101
102 return buffer;
103}
104
105void free_blocks(Buffer *buffer)
106{
107 ListHead *item = buffer->blocks.next;
108 while (item != &buffer->blocks) {
109 ListHead *next = item->next;
110 Block *blk = BLOCK(item);
111 free(blk->data);
112 free(blk);
113 item = next;
114 }
115}
116
117void free_buffer(Buffer *buffer)
118{
119 if (buffer->locked) {
120 unlock_file(buffer->abs_filename);
121 }
122
123 free_changes(&buffer->change_head);
124 free(buffer->line_start_states.ptrs);
125 free(buffer->views.ptrs);
126 free(buffer->display_filename);
127 free(buffer->abs_filename);
128
129 if (buffer->stdout_buffer) {
130 return;
131 }
132
133 free_blocks(buffer);
134 free(buffer);
135}
136
137void remove_and_free_buffer(PointerArray *buffers, Buffer *buffer)
138{
139 ptr_array_remove(buffers, buffer);
140 free_buffer(buffer);
141}
142
143static bool same_file(const Buffer *buffer, const struct stat *st)
144{
145 return (st->st_dev == buffer->file.dev) && (st->st_ino == buffer->file.ino);
146}
147
148Buffer *find_buffer(const PointerArray *buffers, const char *abs_filename)
149{
150 struct stat st;
151 bool st_ok = stat(abs_filename, &st) == 0;
152 for (size_t i = 0, n = buffers->count; i < n; i++) {
153 Buffer *buffer = buffers->ptrs[i];
154 const char *f = buffer->abs_filename;
155 if ((f && streq(f, abs_filename)) || (st_ok && same_file(buffer, &st))) {
156 return buffer;
157 }
158 }
159 return NULL;
160}
161
162Buffer *find_buffer_by_id(const PointerArray *buffers, unsigned long id)
163{
164 for (size_t i = 0, n = buffers->count; i < n; i++) {
165 Buffer *buffer = buffers->ptrs[i];
166 if (buffer->id == id) {
167 return buffer;
168 }
169 }
170 return NULL;
171}
172
173bool buffer_detect_filetype(Buffer *buffer, const PointerArray *filetypes)
174{
175 StringView line = STRING_VIEW_INIT;
176 if (BLOCK(buffer->blocks.next)->size) {
177 BlockIter bi = block_iter(buffer);
178 fill_line_ref(&bi, &line);
179 } else if (!buffer->abs_filename) {
180 return false;
181 }
182
183 const char *ft = find_ft(filetypes, buffer->abs_filename, line);
184 if (ft && !streq(ft, buffer->options.filetype)) {
185 buffer->options.filetype = str_intern(ft);
186 return true;
187 }
188
189 return false;
190}
191
192void update_short_filename_cwd(Buffer *buffer, const StringView *home, const char *cwd)
193{
194 const char *abs = buffer->abs_filename;
195 if (!abs) {
196 return;
197 }
198 char *name = cwd ? short_filename_cwd(abs, cwd, home) : xstrdup(abs);
199 set_display_filename(buffer, name);
200}
201
202void update_short_filename(Buffer *buffer, const StringView *home)
203{
204 BUG_ON(!buffer->abs_filename);
205 set_display_filename(buffer, short_filename(buffer->abs_filename, home));
206}
207
208void buffer_update_syntax(EditorState *e, Buffer *buffer)
209{
210 Syntax *syn = NULL;
211 if (buffer->options.syntax) {
212 // Even "none" can have syntax
213 syn = find_syntax(&e->syntaxes, buffer->options.filetype);
214 if (!syn) {
215 syn = load_syntax_by_filetype(e, buffer->options.filetype);
216 }
217 }
218 if (syn == buffer->syn) {
219 return;
220 }
221
222 buffer->syn = syn;
223 if (syn) {
224 // Start state of first line is constant
225 PointerArray *s = &buffer->line_start_states;
226 if (!s->alloc) {
227 ptr_array_init(s, 64);
228 }
229 s->ptrs[0] = syn->start_state;
230 s->count = 1;
231 }
232
233 mark_all_lines_changed(buffer);
234}
235
236static bool allow_odd_indent(uint8_t indents_bitmask)
237{
238 static_assert(INDENT_WIDTH_MAX == 8);
239 return !!(indents_bitmask & 0x55); // 0x55 == 0b01010101
240}
241
242static int indent_len(StringView line, uint8_t indents_bitmask, bool *tab_indent)
243{
244 bool space_before_tab = false;
245 size_t spaces = 0;
246 size_t tabs = 0;
247 size_t pos = 0;
248
249 for (size_t n = line.length; pos < n; pos++) {
250 switch (line.data[pos]) {
251 case '\t':
252 tabs++;
253 if (spaces) {
254 space_before_tab = true;
255 }
256 continue;
257 case ' ':
258 spaces++;
259 continue;
260 }
261 break;
262 }
263
264 *tab_indent = false;
265 if (pos == line.length) {
266 return -1; // Whitespace only
267 }
268 if (pos == 0) {
269 return 0; // Not indented
270 }
271 if (space_before_tab) {
272 return -2; // Mixed indent
273 }
274 if (tabs) {
275 // Tabs and possible spaces after tab for alignment
276 *tab_indent = true;
277 return tabs * 8;
278 }
279 if (line.length > spaces && line.data[spaces] == '*') {
280 // '*' after indent, could be long C style comment
281 if (spaces & 1 || allow_odd_indent(indents_bitmask)) {
282 return spaces - 1;
283 }
284 }
285 return spaces;
286}
287
288UNITTEST {
289 bool tab;
290 int len = indent_len(strview_from_cstring(" 4 space"), 0, &tab);
291 BUG_ON(len != 4);
292 BUG_ON(tab);
293
294 len = indent_len(strview_from_cstring("\t\t2 tab"), 0, &tab);
295 BUG_ON(len != 16);
296 BUG_ON(!tab);
297
298 len = indent_len(strview_from_cstring("no indent"), 0, &tab);
299 BUG_ON(len != 0);
300
301 len = indent_len(strview_from_cstring(" \t mixed"), 0, &tab);
302 BUG_ON(len != -2);
303
304 len = indent_len(strview_from_cstring("\t \t "), 0, &tab);
305 BUG_ON(len != -1); // whitespace only
306
307 len = indent_len(strview_from_cstring(" * 5 space"), 0, &tab);
308 BUG_ON(len != 4);
309
310 StringView line = strview_from_cstring(" * 4 space");
311 len = indent_len(line, 0, &tab);
312 BUG_ON(len != 4);
313 len = indent_len(line, 1 << 2, &tab);
314 BUG_ON(len != 3);
315}
316
317static bool detect_indent(Buffer *buffer)
318{
319 LocalOptions *options = &buffer->options;
320 unsigned int bitset = options->detect_indent;
321 BlockIter bi = block_iter(buffer);
322 unsigned int tab_count = 0;
323 unsigned int space_count = 0;
324 int current_indent = 0;
325 int counts[INDENT_WIDTH_MAX + 1] = {0};
326 BUG_ON((bitset & ((1u << INDENT_WIDTH_MAX) - 1)) != bitset);
327
328 for (size_t i = 0, j = 1; i < 200 && j > 0; i++, j = block_iter_next_line(&bi)) {
329 StringView line;
330 fill_line_ref(&bi, &line);
331 bool tab;
332 int indent = indent_len(line, bitset, &tab);
333 switch (indent) {
334 case -2: // Ignore mixed indent because tab width might not be 8
335 case -1: // Empty line; no change in indent
336 continue;
337 case 0:
338 current_indent = 0;
339 continue;
340 }
341
342 BUG_ON(indent <= 0);
343 int change = indent - current_indent;
344 if (change >= 1 && change <= INDENT_WIDTH_MAX) {
345 counts[change]++;
346 }
347
348 if (tab) {
349 tab_count++;
350 } else {
351 space_count++;
352 }
353 current_indent = indent;
354 }
355
356 if (tab_count == 0 && space_count == 0) {
357 return false;
358 }
359
360 if (tab_count > space_count) {
361 options->emulate_tab = false;
362 options->expand_tab = false;
363 options->indent_width = options->tab_width;
364 return true;
365 }
366
367 size_t m = 0;
368 for (size_t i = 1; i < ARRAYLEN(counts); i++) {
369 unsigned int bit = 1u << (i - 1);
370 if ((bitset & bit) && counts[i] > counts[m]) {
371 m = i;
372 }
373 }
374
375 if (m == 0) {
376 return false;
377 }
378
379 options->emulate_tab = true;
380 options->expand_tab = true;
381 options->indent_width = m;
382 return true;
383}
384
385void buffer_setup(EditorState *e, Buffer *buffer)
386{
387 const char *filename = buffer->abs_filename;
388 buffer->setup = true;
389 buffer_detect_filetype(buffer, &e->filetypes);
390 set_file_options(e, buffer);
391 set_editorconfig_options(buffer);
392 buffer_update_syntax(e, buffer);
393 if (buffer->options.detect_indent && filename) {
394 detect_indent(buffer);
395 }
396 sanity_check_local_options(&buffer->options);
397}
398
399void buffer_count_blocks_and_bytes(const Buffer *buffer, uintmax_t counts[2])
400{
401 uintmax_t blocks = 0;
402 uintmax_t bytes = 0;
403 Block *blk;
404 block_for_each(blk, &buffer->blocks) {
405 blocks += 1;
406 bytes += blk->size;
407 }
408 counts[0] = blocks;
409 counts[1] = bytes;
410}
411
412// TODO: Human-readable size (MiB/GiB/etc.) for "Bytes" and FileInfo::size
413String dump_buffer(const Buffer *buffer)
414{
415 uintmax_t counts[2];
416 buffer_count_blocks_and_bytes(buffer, counts);
417 BUG_ON(counts[0] < 1);
418 BUG_ON(!buffer->setup);
419 String buf = string_new(1024);
420
421 string_sprintf (
422 &buf,
423 "%s %s\n%s %lu\n%s %s\n%s %s\n%s %ju\n%s %zu\n%s %ju\n",
424 " Name:", buffer_filename(buffer),
425 " ID:", buffer->id,
426 " Encoding:", buffer->encoding.name,
427 " Filetype:", buffer->options.filetype,
428 " Blocks:", counts[0],
429 " Lines:", buffer->nl,
430 " Bytes:", counts[1]
431 );
432
433 if (
434 buffer->stdout_buffer || buffer->temporary || buffer->readonly
435 || buffer->locked || buffer->crlf_newlines || buffer->bom
436 ) {
437 string_sprintf (
438 &buf,
439 " Flags:%s%s%s%s%s%s\n",
440 buffer->stdout_buffer ? " STDOUT" : "",
441 buffer->temporary ? " TMP" : "",
442 buffer->readonly ? " RO" : "",
443 buffer->locked ? " LOCKED" : "",
444 buffer->crlf_newlines ? " CRLF" : "",
445 buffer->bom ? " BOM" : ""
446 );
447 }
448
449 if (buffer->views.count > 1) {
450 string_sprintf(&buf, " Views: %zu\n", buffer->views.count);
451 }
452
453 if (!buffer->abs_filename) {
454 return buf;
455 }
456
457 const FileInfo *file = &buffer->file;
458 unsigned int perms = file->mode & 07777;
459 char modestr[12];
460 char timestr[64];
461 if (!timespec_to_str(&file->mtime, timestr, sizeof(timestr))) {
462 memcpy(timestr, STRN("[error]") + 1);
463 }
464
465 string_sprintf (
466 &buf,
467 "\nLast stat:\n----------\n\n"
468 "%s %s\n%s %s\n%s -%s (%04o)\n%s %jd\n%s %jd\n%s %ju\n%s %jd\n%s %ju\n",
469 " Path:", buffer->abs_filename,
470 " Modified:", timestr,
471 " Mode:", filemode_to_str(file->mode, modestr), perms,
472 " User:", (intmax_t)file->uid,
473 " Group:", (intmax_t)file->gid,
474 " Size:", (uintmax_t)file->size,
475 " Device:", (intmax_t)file->dev,
476 " Inode:", (uintmax_t)file->ino
477 );
478
479 return buf;
480}