-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuffer.c
More file actions
4300 lines (3820 loc) · 132 KB
/
buffer.c
File metadata and controls
4300 lines (3820 loc) · 132 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Text buffers - These store the contents of a file.
// NOTE: All text editing should be done through the two functions
// buffer_insert_text_at_pos and buffer_delete_chars_at_pos
#include "ted-internal.h"
#include <sys/stat.h>
#if __unix__
#include <fcntl.h>
#include <unistd.h>
#elif _WIN32
#include <io.h>
#endif
/// Minimum capacity of a line (in code points).
static const u32 LINE_MININUM_CAPACITY = 4;
/// A single line in a buffer
typedef struct Line Line;
/// A single undoable edit to a buffer
typedef struct BufferEdit BufferEdit;
struct Line {
SyntaxState syntax;
u32 len;
char32_t *str;
};
// This refers to replacing prev_len characters (found in prev_text) at pos with new_len characters
struct BufferEdit {
bool chain; // should this + the next edit be treated as one?
bool block_chaining; // stop this edit from being chained to the next one.
BufferPos pos;
u32 new_len;
u32 prev_len;
char32_t *prev_text;
double time; // time at start of edit (i.e. the time just before the edit), in seconds since epoch
};
struct TextBuffer {
/// NULL if this buffer is untitled or doesn't correspond to a file (e.g. line buffers)
char *path;
/// we keep a back-pointer to the ted instance so we don't have to pass it in to every buffer function
Ted *ted;
/// number of characters scrolled in the x direction (multiply by space width to get pixels)
double scroll_x;
/// number of characters scrolled in the y direction
double scroll_y;
/// last write time to \ref path
double last_write_time;
/// the language the buffer has been manually set to, or \ref LANG_NONE if it hasn't been set to anything
i64 manual_language;
/// position of cursor
BufferPos cursor_pos;
/// if \ref selection is true, the text between \ref selection_pos and \ref cursor_pos is selected.
BufferPos selection_pos;
/// "previous" position of cursor, for \ref CMD_PREVIOUS_POSITION
BufferPos prev_cursor_pos;
/// "line buffers" are buffers which can only have one line of text (used for inputs)
bool is_line_buffer;
/// is anything selected?
bool selection;
/// set to false to disable undo events
bool store_undo_events;
/// will the next undo event be chained with the ones after?
bool will_chain_edits;
/// will the next undo event be chained with the previous one?
bool chaining_edits;
/// view-only mode
bool view_only;
/// (line buffers only) set to true when submitted. you have to reset it to false.
bool line_buffer_submitted;
/// If set to true, buffer will be scrolled to the cursor position next frame.
/// This is to fix the problem that \ref x1, \ref y1, \ref x2, \ref y2 are not updated until the buffer is rendered.
bool center_cursor_next_frame;
/// whether to indent with spaces
///
/// this is either set according to the user's settings or according to the autodetected indentation
bool indent_with_spaces;
/// true if user has manually specified indentation
bool manual_indentation;
/// set to true in ted.c when buffer has been modified according to inotify
bool inotify_modified;
/// tab size
///
/// this is either set according to the user's settings or according to the autodetected indentation
uint8_t tab_width;
/// x coordinate of left side of buffer
float x1;
/// y coordinate of top side of buffer
float y1;
/// x coordinate of right side of buffer
float x2;
/// y coordinate of bottom side of buffer
float y2;
/// number of lines in buffer
u32 nlines;
/// capacity of \ref lines
u32 lines_capacity;
/// if false, need to recompute settings.
bool settings_computed;
Settings settings;
/// which LSP this document is open in
LSPID lsp_opened_in;
/// determining which LSP to use for a buffer takes some work,
/// so we don't want to do it every single frame.
/// this keeps track of the last time we actually checked what the correct LSP is.
double last_lsp_check;
/// where in the undo history was the last write? used by \ref buffer_unsaved_changes
u32 undo_history_write_pos;
/// which lines are on screen? updated when \ref buffer_render is called.
u32 first_line_on_screen, last_line_on_screen;
/// to cache syntax highlighting properly, it is important to keep track of the
/// first and last line modified since last frame.
u32 frame_earliest_line_modified;
/// see \ref frame_earliest_line_modified.
u32 frame_latest_line_modified;
Diagnostic *diagnostics;
/// lines
Line *lines;
/// last error
char error[256];
/// dynamic array of undo history
BufferEdit *undo_history;
/// dynamic array of redo history
BufferEdit *redo_history;
#if HAS_INOTIFY
int inotify_watch;
#endif
};
// this is a macro so we get -Wformat warnings
#define buffer_error(buffer, ...) \
snprintf(buffer->error, sizeof buffer->error - 1, __VA_ARGS__)
bool buffer_has_error(TextBuffer *buffer) {
return buffer->error[0] != '\0';
}
// returns the buffer's last error
const char *buffer_get_error(TextBuffer *buffer) {
return buffer ? buffer->error : "";
}
void buffer_clear_error(TextBuffer *buffer) {
*buffer->error = '\0';
}
// set error to indicate that we're out of memory
static void buffer_out_of_mem(TextBuffer *buffer) {
ted_error(buffer->ted, "Out of memory.");
}
static void buffer_edit_free(BufferEdit *edit) {
free(edit->prev_text);
}
static void buffer_clear_redo_history(TextBuffer *buffer) {
arr_foreach_ptr(buffer->redo_history, BufferEdit, edit) {
buffer_edit_free(edit);
}
arr_clear(buffer->redo_history);
// if the write pos is in the redo history,
if (buffer->undo_history_write_pos > arr_len(buffer->undo_history))
buffer->undo_history_write_pos = U32_MAX; // get rid of it
}
static void buffer_clear_undo_history(TextBuffer *buffer) {
arr_foreach_ptr(buffer->undo_history, BufferEdit, edit) {
buffer_edit_free(edit);
}
arr_clear(buffer->undo_history);
buffer->undo_history_write_pos = U32_MAX;
}
void buffer_clear_undo_redo(TextBuffer *buffer) {
buffer_clear_undo_history(buffer);
buffer_clear_redo_history(buffer);
}
bool buffer_empty(TextBuffer *buffer) {
return buffer->nlines == 1 && buffer->lines[0].len == 0;
}
bool buffer_is_named_file(TextBuffer *buffer) {
return buffer->path != NULL;
}
bool buffer_is_line_buffer(TextBuffer *buffer) {
return buffer->is_line_buffer;
}
bool line_buffer_is_submitted(TextBuffer *buffer) {
return buffer->is_line_buffer && buffer->line_buffer_submitted;
}
void line_buffer_clear_submitted(TextBuffer *buffer) {
buffer->line_buffer_submitted = false;
}
bool buffer_is_view_only(TextBuffer *buffer) {
return buffer->view_only;
}
void buffer_set_view_only(TextBuffer *buffer, bool view_only) {
buffer->view_only = view_only;
}
double buffer_get_scroll_columns(TextBuffer *buffer) {
return buffer->scroll_x;
}
double buffer_get_scroll_lines(TextBuffer *buffer) {
return buffer->scroll_y;
}
void buffer_scroll_to(TextBuffer *buffer, double cols, double lines) {
buffer->scroll_x = cols;
buffer->scroll_y = lines;
}
double buffer_last_write_time(TextBuffer *buffer) {
return buffer->last_write_time;
}
void buffer_ignore_changes_on_disk(TextBuffer *buffer) {
buffer->last_write_time = timespec_to_seconds(time_last_modified(buffer->path));
buffer->inotify_modified = false;
// no matter what, buffer_unsaved_changes should return true
buffer->undo_history_write_pos = U32_MAX;
}
BufferPos buffer_cursor_pos(TextBuffer *buffer) {
return buffer->cursor_pos;
}
bool buffer_has_selection(TextBuffer *buffer) {
return buffer->selection;
}
bool buffer_selection_pos(TextBuffer *buffer, BufferPos *pos) {
if (buffer->selection) {
if (pos) *pos = buffer->selection_pos;
return true;
} else {
if (pos) *pos = (BufferPos){0};
return false;
}
}
u32 buffer_first_line_on_screen(TextBuffer *buffer) {
return buffer->first_line_on_screen;
}
u32 buffer_last_line_on_screen(TextBuffer *buffer) {
return buffer->last_line_on_screen;
}
void buffer_set_undo_enabled(TextBuffer *buffer, bool enabled) {
buffer->store_undo_events = enabled;
if (!enabled)
buffer_clear_undo_redo(buffer);
}
Rect buffer_rect(TextBuffer *buffer) {
return rect4(buffer->x1, buffer->y1, buffer->x2, buffer->y2);
}
const char *buffer_get_path(TextBuffer *buffer) {
return buffer->path;
}
void buffer_display_filename(TextBuffer *buffer, char *filename, size_t filename_size) {
if (!buffer->path) {
str_cpy(filename, filename_size, "Untitled");
return;
}
// this stuff here is to disambiguate between files, so if you have
// two files open called
// /foo/bar/x/a.c
// and /abc/def/x/a.c
// their display names will be "bar/x/a.c" and "def/x/a.c"
int suffix_needed = 0;
Ted *ted = buffer->ted;
int buffer_path_len = (int)strlen(buffer->path);
arr_foreach_ptr(ted->buffers, const TextBufferPtr, p_other) {
TextBuffer *other = *p_other;
if (!other->path) continue;
if (streq(other->path, buffer->path)) continue;
int other_path_len = (int)strlen(other->path);
if (str_has_suffix(buffer->path, other->path)) {
// special case
suffix_needed = other_path_len + 1;
continue;
}
// find longest common suffix of buffer->path, other->path
for (int i = 1; i <= buffer_path_len && i <= other_path_len; ++i) {
if (i > suffix_needed)
suffix_needed = i;
if (buffer->path[buffer_path_len - i] != other->path[other_path_len - i]) {
break;
}
}
}
// go to last path separator
while (suffix_needed < buffer_path_len &&
buffer->path[buffer_path_len - suffix_needed] != PATH_SEPARATOR) {
++suffix_needed;
}
// don't actually include the path separator
if (suffix_needed > 0)
--suffix_needed;
assert(suffix_needed > 0 && suffix_needed <= buffer_path_len);
str_cpy(filename, filename_size, &buffer->path[buffer_path_len - suffix_needed]);
}
// add this edit to the undo history
static void buffer_append_edit(TextBuffer *buffer, BufferEdit const *edit) {
// whenever an edit is made, clear the redo history
buffer_clear_redo_history(buffer);
arr_add(buffer->undo_history, *edit);
if (!buffer->undo_history) buffer_out_of_mem(buffer);
}
// add this edit to the redo history
static void buffer_append_redo(TextBuffer *buffer, BufferEdit const *edit) {
arr_add(buffer->redo_history, *edit);
if (!buffer->redo_history) buffer_out_of_mem(buffer);
}
static void *buffer_malloc(TextBuffer *buffer, size_t size) {
void *ret = malloc(size);
if (!ret) buffer_out_of_mem(buffer);
return ret;
}
static void *buffer_calloc(TextBuffer *buffer, size_t n, size_t size) {
void *ret = calloc(n, size);
if (!ret) buffer_out_of_mem(buffer);
return ret;
}
static void *buffer_realloc(TextBuffer *buffer, void *p, size_t new_size) {
void *ret = realloc(p, new_size);
if (!ret) buffer_out_of_mem(buffer);
return ret;
}
static char *buffer_strdup(TextBuffer *buffer, const char *src) {
char *dup = str_dup(src);
if (!dup) buffer_out_of_mem(buffer);
return dup;
}
static void buffer_set_up(Ted *ted, TextBuffer *buffer) {
buffer->store_undo_events = true;
buffer->ted = ted;
// will be overwritten by buffer_new_file, buffer_load_file for file buffers
const Settings *settings = buffer_settings(buffer);
buffer->indent_with_spaces = settings->indent_with_spaces;
buffer->tab_width = settings->tab_width;
#if HAS_INOTIFY
buffer->inotify_watch = -1;
#endif
}
static void line_buffer_set_up(Ted *ted, TextBuffer *buffer) {
buffer_set_up(ted, buffer);
buffer->is_line_buffer = true;
if ((buffer->lines = buffer_calloc(buffer, 1, sizeof *buffer->lines))) {
buffer->nlines = 1;
buffer->lines_capacity = 1;
}
}
TextBuffer *buffer_new(Ted *ted) {
TextBuffer *buffer = ted_calloc(ted, 1, sizeof *buffer);
if (!buffer) return NULL;
buffer_set_up(ted, buffer);
return buffer;
}
TextBuffer *line_buffer_new(Ted *ted) {
TextBuffer *buffer = ted_calloc(ted, 1, sizeof *buffer);
if (!buffer) return NULL;
line_buffer_set_up(ted, buffer);
return buffer;
}
void buffer_pos_validate(TextBuffer *buffer, BufferPos *p) {
if (p->line >= buffer->nlines)
p->line = buffer->nlines - 1;
u32 line_len = buffer->lines[p->line].len;
if (p->index > line_len)
p->index = line_len;
}
// validate the cursor and selection positions
static void buffer_validate_cursor(TextBuffer *buffer) {
buffer_pos_validate(buffer, &buffer->cursor_pos);
if (buffer->selection)
buffer_pos_validate(buffer, &buffer->selection_pos);
}
// ensure *line points to a line in buffer.
static void buffer_validate_line(TextBuffer *buffer, u32 *line) {
if (*line >= buffer->nlines)
*line = buffer->nlines - 1;
}
bool buffer_pos_valid(TextBuffer *buffer, BufferPos p) {
return p.line < buffer->nlines && p.index <= buffer->lines[p.line].len;
}
// are there any unsaved changes?
bool buffer_unsaved_changes(TextBuffer *buffer) {
if (!buffer->path && buffer_empty(buffer))
return false; // don't worry about empty untitled buffers
return arr_len(buffer->undo_history) != buffer->undo_history_write_pos;
}
char32_t buffer_char_at_pos(TextBuffer *buffer, BufferPos pos) {
if (!buffer_pos_valid(buffer, pos))
return 0;
Line *line = &buffer->lines[pos.line];
if (pos.index >= line->len)
return 0;
return line->str[pos.index];
}
char32_t buffer_char_before_pos(TextBuffer *buffer, BufferPos pos) {
if (!buffer_pos_valid(buffer, pos))
return 0;
if (pos.index == 0) return 0;
return buffer->lines[pos.line].str[pos.index - 1];
}
char32_t buffer_char_before_cursor(TextBuffer *buffer) {
return buffer_char_before_pos(buffer, buffer->cursor_pos);
}
char32_t buffer_char_at_cursor(TextBuffer *buffer) {
return buffer_char_at_pos(buffer, buffer->cursor_pos);
}
BufferPos buffer_pos_start_of_file(TextBuffer *buffer) {
(void)buffer;
return (BufferPos){.line = 0, .index = 0};
}
BufferPos buffer_pos_end_of_file(TextBuffer *buffer) {
return (BufferPos){.line = buffer->nlines - 1, .index = buffer->lines[buffer->nlines-1].len};
}
Font *buffer_font(TextBuffer *buffer) {
return buffer->ted->font;
}
// what programming language is this?
Language buffer_language(TextBuffer *buffer) {
if (!buffer->path)
return LANG_TEXT;
if (buffer->manual_language != LANG_NONE)
return (Language)buffer->manual_language;
const Settings *settings = ted_default_settings(buffer->ted); // important we don't use buffer_settings here since that would cause infinite recursion!
const char *filename = path_filename(buffer->path);
int match_score = 0;
Language match = LANG_TEXT;
arr_foreach_ptr(settings->language_extensions, LanguageExtension, ext) {
if (str_has_suffix(filename, ext->extension)) {
int score = (int)strlen(ext->extension);
if (score > match_score) {
// found a better match!
match_score = score;
match = ext->language;
}
}
}
return match;
}
// set path = NULL to default to buffer->path
static void buffer_send_lsp_did_close(TextBuffer *buffer, LSP *lsp, const char *path) {
if (path && !path_is_absolute(path)) {
assert(0);
return;
}
LSPRequest did_close = {.type = LSP_REQUEST_DID_CLOSE};
did_close.data.close = (LSPRequestDidClose){
.document = lsp_document_id(lsp, path ? path : buffer->path)
};
lsp_send_request(lsp, &did_close);
buffer->lsp_opened_in = 0;
}
static void buffer_send_lsp_did_open(TextBuffer *buffer, LSP *lsp) {
size_t buffer_contents_len = buffer_contents_utf8(buffer, NULL);
LSPRequest request = {.type = LSP_REQUEST_DID_OPEN};
LSPRequestDidOpen *open = &request.data.open;
char *contents = lsp_message_alloc_string(&request.base, buffer_contents_len, &open->file_contents);
buffer_contents_utf8(buffer, contents);
open->document = lsp_document_id(lsp, buffer->path);
open->language = buffer_language(buffer);
lsp_send_request(lsp, &request);
buffer->lsp_opened_in = lsp_get_id(lsp);
}
LSP *buffer_lsp(TextBuffer *buffer) {
if (!buffer)
return NULL;
if (!buffer_is_named_file(buffer))
return NULL;
if (buffer->view_only)
return NULL; // we don't really want to start up an LSP in /usr/include
if (buffer->ted->frame_time - buffer->last_lsp_check < 1.0) {
return ted_get_lsp_by_id(buffer->ted, buffer->lsp_opened_in);
}
LSP *true_lsp = ted_get_lsp(buffer->ted, buffer_settings(buffer), buffer->path);
LSP *curr_lsp = ted_get_lsp_by_id(buffer->ted, buffer->lsp_opened_in);
if (true_lsp != curr_lsp) {
if (curr_lsp)
buffer_send_lsp_did_close(buffer, curr_lsp, NULL);
if (true_lsp)
buffer_send_lsp_did_open(buffer, true_lsp);
}
buffer->last_lsp_check = buffer->ted->frame_time;
return true_lsp;
}
Settings *buffer_settings(TextBuffer *buffer) {
Ted *ted = buffer->ted;
if (!buffer->settings_computed) {
ted_compute_settings(ted, buffer->path, buffer_language(buffer), &buffer->settings);
buffer->settings_computed = true;
}
return &buffer->settings;
}
void buffer_recompute_settings(TextBuffer *buffer) {
buffer->settings_computed = false;
}
u8 buffer_tab_width(TextBuffer *buffer) {
return buffer->tab_width;
}
bool buffer_indent_with_spaces(TextBuffer *buffer) {
return buffer->indent_with_spaces;
}
u32 buffer_line_count(TextBuffer *buffer) {
return buffer->nlines;
}
String32 buffer_get_line(TextBuffer *buffer, u32 line_number) {
if (line_number >= buffer->nlines) {
return str32(NULL, 0);
}
Line *line = &buffer->lines[line_number];
return (String32) {
.str = line->str, .len = line->len
};
}
char *buffer_get_line_utf8(TextBuffer *buffer, u32 line_number) {
return str32_to_utf8_cstr(buffer_get_line(buffer, line_number));
}
// Returns a simple checksum of the buffer.
// This is only used for testing, and shouldn't be relied on.
static u64 buffer_checksum(TextBuffer *buffer) {
u64 sum = 0x40fdd49b58ee4b15; // some random prime number
for (Line *line = buffer->lines, *end = line + buffer->nlines; line != end; ++line) {
for (char32_t *p = line->str, *p_end = p + line->len; p != p_end; ++p) {
sum += *p;
sum *= 0xf033ae1b58e6562f; // another random prime number
sum += 0x6fcc63c3d38a2bb9; // another random prime number
}
}
return sum;
}
size_t buffer_get_text_at_pos(TextBuffer *buffer, BufferPos pos, char32_t *text, size_t nchars) {
if (!buffer_pos_valid(buffer, pos)) {
return 0; // invalid position. no chars for you!
}
char32_t *p = text;
size_t chars_left = nchars;
Line *line = &buffer->lines[pos.line], *end = buffer->lines + buffer->nlines;
u32 index = pos.index;
while (chars_left) {
u32 chars_from_this_line = line->len - index;
if (chars_left <= chars_from_this_line) {
if (p) memcpy(p, line->str + index, chars_left * sizeof *p);
chars_left = 0;
} else {
if (p) {
memcpy(p, line->str + index, chars_from_this_line * sizeof *p);
p += chars_from_this_line;
*p++ = '\n';
}
chars_left -= chars_from_this_line+1;
}
index = 0;
++line;
if (chars_left && line == end) {
// reached end of file before getting full text
break;
}
}
return nchars - chars_left;
}
String32 buffer_get_str32_text_at_pos(TextBuffer *buffer, BufferPos pos, size_t nchars) {
String32 s32 = {0};
size_t len = buffer_get_text_at_pos(buffer, pos, NULL, nchars);
if (len) {
char32_t *str = buffer_calloc(buffer, len, sizeof *str);
if (str) {
buffer_get_text_at_pos(buffer, pos, str, nchars);
s32.str = str;
s32.len = len;
}
}
return s32;
}
char *buffer_get_utf8_text_at_pos(TextBuffer *buffer, BufferPos pos, size_t nchars) {
String32 s32 = buffer_get_str32_text_at_pos(buffer, pos, nchars);
char *ret = str32_to_utf8_cstr(s32);
if (!ret) buffer_out_of_mem(buffer);
str32_free(&s32);
return ret;
}
size_t buffer_contents_utf8(TextBuffer *buffer, char *out) {
char *p = out, x[4];
size_t size = 0;
for (Line *line = buffer->lines, *end = line + buffer->nlines; line != end; ++line) {
char32_t *str = line->str;
for (u32 i = 0, len = line->len; i < len; ++i) {
size_t bytes = unicode_utf32_to_utf8(p ? p : x, str[i]);
if (p) p += bytes;
size += bytes;
}
if (line != end - 1) {
// newline
if (p) *p++ = '\n';
size += 1;
}
}
if (p) *p = '\0';
size += 1;
return size;
}
char *buffer_contents_utf8_alloc(TextBuffer *buffer) {
size_t size = buffer_contents_utf8(buffer, NULL);
char *s = calloc(1, size);
buffer_contents_utf8(buffer, s);
return s;
}
static BufferPos buffer_pos_advance(TextBuffer *buffer, BufferPos pos, size_t nchars) {
buffer_pos_validate(buffer, &pos);
size_t chars_left = nchars;
Line *line = &buffer->lines[pos.line], *end = buffer->lines + buffer->nlines;
u32 index = pos.index;
while (line != end) {
u32 chars_from_this_line = line->len - index;
if (chars_left <= chars_from_this_line) {
index += (u32)chars_left;
pos.index = index;
pos.line = (u32)(line - buffer->lines);
return pos;
}
chars_left -= chars_from_this_line+1; // +1 for newline
index = 0;
++line;
}
return buffer_pos_end_of_file(buffer);
}
i64 buffer_pos_diff(TextBuffer *buffer, BufferPos p1, BufferPos p2) {
assert(buffer_pos_valid(buffer, p1));
assert(buffer_pos_valid(buffer, p2));
if (p1.line == p2.line) {
// p1 and p2 are in the same line
return (i64)p2.index - p1.index;
}
i64 factor = 1;
if (p1.line > p2.line) {
// switch positions so p2 has the later line
BufferPos tmp = p1;
p1 = p2;
p2 = tmp;
factor = -1;
}
assert(p2.line > p1.line);
i64 chars_at_end_of_p1_line = buffer->lines[p1.line].len - p1.index + 1; // + 1 for newline
i64 chars_at_start_of_p2_line = p2.index;
i64 chars_in_lines_in_between = 0;
// now we need to add up the lengths of the lines between p1 and p2
for (Line *line = buffer->lines + (p1.line + 1), *end = buffer->lines + p2.line; line != end; ++line) {
chars_in_lines_in_between += line->len + 1; // +1 for newline
}
i64 total = chars_at_end_of_p1_line + chars_in_lines_in_between + chars_at_start_of_p2_line;
return total * factor;
}
int buffer_pos_cmp(BufferPos p1, BufferPos p2) {
if (p1.line < p2.line) {
return -1;
} else if (p1.line > p2.line) {
return +1;
} else {
if (p1.index < p2.index) {
return -1;
} else if (p1.index > p2.index) {
return +1;
} else {
return 0;
}
}
}
bool buffer_pos_eq(BufferPos p1, BufferPos p2) {
return p1.line == p2.line && p1.index == p2.index;
}
static BufferPos buffer_pos_min(BufferPos p1, BufferPos p2) {
return buffer_pos_cmp(p1, p2) < 0 ? p1 : p2;
}
static BufferPos buffer_pos_max(BufferPos p1, BufferPos p2) {
return buffer_pos_cmp(p1, p2) > 0 ? p1 : p2;
}
static void buffer_pos_print(BufferPos p) {
printf("[%" PRIu32 ":%" PRIu32 "]", p.line, p.index);
}
// for debugging
#if !NDEBUG
static void buffer_pos_check_valid(TextBuffer *buffer, BufferPos p) {
assert(p.line < buffer->nlines);
assert(p.index <= buffer->lines[p.line].len);
}
static bool buffer_line_valid(Line *line) {
if (line->len && !line->str)
return false;
return true;
}
void buffer_check_valid(TextBuffer *buffer) {
assert(buffer->nlines);
buffer_pos_check_valid(buffer, buffer->cursor_pos);
if (buffer->selection) {
buffer_pos_check_valid(buffer, buffer->selection_pos);
// you shouldn't be able to select nothing
assert(!buffer_pos_eq(buffer->cursor_pos, buffer->selection_pos));
}
for (u32 i = 0; i < buffer->nlines; ++i) {
Line *line = &buffer->lines[i];
assert(buffer_line_valid(line));
}
}
#else
static void buffer_pos_check_valid(TextBuffer *buffer, BufferPos p) {
(void)buffer; (void)p;
}
void buffer_check_valid(TextBuffer *buffer) {
(void)buffer;
}
#endif
static Status buffer_edit_create(TextBuffer *buffer, BufferEdit *edit, BufferPos start, u32 prev_len, u32 new_len) {
edit->time = buffer->ted->frame_time;
if (prev_len == 0)
edit->prev_text = NULL; // if there's no previous text, don't allocate anything
else
edit->prev_text = calloc(1, prev_len * sizeof *edit->prev_text);
if (prev_len == 0 || edit->prev_text) {
edit->pos = start;
edit->prev_len = prev_len;
edit->new_len = new_len;
if (prev_len) {
size_t chars_gotten = buffer_get_text_at_pos(buffer, start, edit->prev_text, prev_len);
edit->prev_len = (u32)chars_gotten; // update the previous length, in case it went past the end of the file
}
return true;
} else {
return false;
}
}
static void buffer_edit_print(BufferEdit *edit) {
buffer_pos_print(edit->pos);
printf(" (%" PRIu32 " chars): ", edit->prev_len);
for (size_t i = 0; i < edit->prev_len; ++i) {
char32_t c = edit->prev_text[i];
if (c == '\n')
printf("\\n");
else
printf("%lc", (wint_t)c);
}
printf(" => %" PRIu32 " chars.\n", edit->new_len);
}
void buffer_print_undo_history(TextBuffer *buffer) {
printf("-----------------\n");
arr_foreach_ptr(buffer->undo_history, BufferEdit, e)
buffer_edit_print(e);
}
// add this edit to the undo history
// call this before actually changing buffer
static void buffer_edit(TextBuffer *buffer, BufferPos start, u32 prev_len, u32 new_len) {
BufferEdit edit = {0};
if (buffer_edit_create(buffer, &edit, start, prev_len, new_len)) {
edit.chain = buffer->chaining_edits;
if (buffer->will_chain_edits) buffer->chaining_edits = true;
buffer_append_edit(buffer, &edit);
}
}
// change the capacity of edit->prev_text
static Status buffer_edit_resize_prev_text(TextBuffer *buffer, BufferEdit *edit, u32 new_capacity) {
assert(edit->prev_len <= new_capacity);
if (new_capacity == 0) {
free(edit->prev_text);
edit->prev_text = NULL;
} else {
char32_t *new_text = buffer_realloc(buffer, edit->prev_text, new_capacity * sizeof *new_text);
if (new_text) {
edit->prev_text = new_text;
} else {
return false;
}
}
return true;
}
// does this edit actually make a difference to the buffer?
static bool buffer_edit_does_anything(TextBuffer *buffer, BufferEdit *edit) {
if (edit->prev_len == edit->new_len) {
// @TODO(optimization): compare directly to the buffer contents,
// rather than extracting them temporarily into new_text.
char32_t *new_text = buffer_calloc(buffer, edit->new_len, sizeof *new_text);
if (new_text) {
size_t len = buffer_get_text_at_pos(buffer, edit->pos, new_text, edit->new_len);
assert(len == edit->new_len);
int cmp = memcmp(edit->prev_text, new_text, len * sizeof *new_text);
free(new_text);
return cmp != 0;
} else {
return false;
}
} else {
return true;
}
}
// has enough time passed since the last edit that we should create a new one?
//
// is_deletion should be set to true if the edit involves any deletion.
static bool buffer_edit_split(TextBuffer *buffer, bool is_deletion) {
BufferEdit *last_edit = arr_lastp(buffer->undo_history);
if (!last_edit || last_edit->block_chaining) return true;
if (buffer->will_chain_edits) return true;
if (buffer->chaining_edits) return false;
double curr_time = buffer->ted->frame_time;
double undo_time_cutoff = buffer_settings(buffer)->undo_save_time; // only keep around edits for this long (in seconds).
return last_edit->time <= buffer->last_write_time // last edit happened before buffer write (we need to split this so that undo_history_write_pos works)
|| curr_time - last_edit->time > undo_time_cutoff
|| (curr_time != last_edit->time && (// if the last edit didn't take place on the same frame,
(last_edit->prev_len && !is_deletion) || // last edit deleted text but this edit inserts text
(last_edit->new_len && is_deletion) // last edit inserted text and this one deletes text
));
}
// removes the last edit in the undo history if it doesn't do anything
static void buffer_remove_last_edit_if_empty(TextBuffer *buffer) {
if (buffer->store_undo_events) {
BufferEdit *last_edit = arr_lastp(buffer->undo_history);
if (last_edit && !buffer_edit_does_anything(buffer, last_edit)) {
buffer_edit_free(last_edit);
arr_remove_last(buffer->undo_history);
}
}
}
u32 buffer_line_len(TextBuffer *buffer, u32 line_number) {
if (line_number >= buffer->nlines)
return 0;
return buffer->lines[line_number].len;
}
// returns true if allocation was succesful
static Status buffer_line_set_len(TextBuffer *buffer, Line *line, u32 new_len) {
if (new_len >= LINE_MININUM_CAPACITY) {
u32 curr_capacity = (u32)1 << (32 - util_count_leading_zeroes32(line->len));
assert(curr_capacity > line->len);
if (new_len >= curr_capacity) {
u8 leading_zeroes = util_count_leading_zeroes32(new_len);
if (leading_zeroes == 0) {
// this line is too big
return false;
} else {
u32 new_capacity = (u32)1 << (32 - leading_zeroes);
assert(new_capacity > new_len);
char32_t *new_str = buffer_realloc(buffer, line->str, new_capacity * sizeof *line->str);
if (!new_str) {
// allocation failed ):
return false;
}
// allocation successful
line->str = new_str;
}
}
} else if (!line->str) {
// start by allocating LINE_MININUM_CAPACITY code points
line->str = buffer_malloc(buffer, LINE_MININUM_CAPACITY * sizeof *line->str);
if (!line->str) {
// ):
return false;
}
}
line->len = new_len;
assert(line->str);
return true;
}
// grow capacity of lines array
// returns true if successful
static Status buffer_lines_set_min_capacity(TextBuffer *buffer, Line **lines, u32 *lines_capacity, u32 minimum_capacity) {
while (minimum_capacity >= *lines_capacity) {
// allocate more lines
u32 new_capacity = *lines_capacity * 2;
Line *new_lines = buffer_realloc(buffer, *lines, new_capacity * sizeof(Line));
if (new_lines) {
*lines = new_lines;
// zero new lines
memset(new_lines + *lines_capacity, 0, (new_capacity - *lines_capacity) * sizeof(Line));
*lines_capacity = new_capacity;
} else {
return false;
}
}
return true;
}
static void buffer_line_append_char(TextBuffer *buffer, Line *line, char32_t c) {
if (c == '\r') return;
if (buffer_line_set_len(buffer, line, line->len + 1))
line->str[line->len-1] = c;
}
static void buffer_line_free(Line *line) {
free(line->str);
}
static void diagnostic_free(Diagnostic *diagnostic) {
free(diagnostic->message);
free(diagnostic->url);
free(diagnostic->raw);
memset(diagnostic, 0, sizeof *diagnostic);