-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathbuffer.c
1707 lines (1304 loc) · 49.2 KB
/
buffer.c
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
/* Buffer handling functions, including allocation, deallocation, and I/O.
Copyright (C) 1993-1998 Sebastiano Vigna
Copyright (C) 1999-2025 Todd M. Lewis and Sebastiano Vigna
This file is part of ne, the nice editor.
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>. */
#include "ne.h"
#include "support.h"
#include <sys/mman.h>
/* The standard pool allocation dimension. */
#define STD_POOL_SIZE (16 * 1024)
/* The standard line descriptor pool allocation dimension (in lines). */
#define STD_LINE_DESC_POOL_SIZE (512)
/* The starting size when reading a non-seekable file. */
#define START_SIZE (8 * 1024)
/* The number of lines by which we increment the first line descriptor
pool dimension, with respect to the number of lines of the given file. */
#define STANDARD_LINE_INCREMENT (256)
/* The size of the space array. Batch printing of spaces happens in blocks of
this size. */
#define MAX_STACK_SPACES (256)
/* The length of the block used in order to optimize saves. */
#define SAVE_BLOCK_LEN (16 * 1024 - 1)
/* The length of a half of the circular buffer used for memory mapping. */
#define CIRC_BUFFER_SIZE (8 * 1024)
/* The number of line descriptors in the buffer used for memory mapping. */
#define LD_BUFFER_COUNT (256)
/* Detects (heuristically) the encoding of a buffer. */
encoding_type detect_buffer_encoding(const buffer * const b) {
line_desc *ld = (line_desc *)b->line_desc_list.head, *next;
encoding_type encoding = ENC_ASCII, e;
while(next = (line_desc *)ld->ld_node.next) {
e = detect_encoding(ld->line, ld->line_len);
if (e != ENC_ASCII) {
if (encoding == ENC_ASCII) encoding = e;
if (e == ENC_8_BIT) encoding = ENC_8_BIT;
}
ld = next;
}
return encoding;
}
/* These functions allocate and deallocate character pools. The size of the
pool is the number of characters, and it is forced to be at least
STD_POOL_SIZE. force is passed to alloc_or_mmap(). */
char_pool *alloc_char_pool(int64_t size, const int fd_or_zero, int force) {
if (size < STD_POOL_SIZE && ! fd_or_zero) size = STD_POOL_SIZE;
char_pool * const cp = calloc(1, sizeof(char_pool));
if (cp) {
if (cp->pool = alloc_or_mmap(size, fd_or_zero, &force)) {
cp->mapped = force;
cp->size = size;
return cp;
}
free(cp);
}
return NULL;
}
char_pool *alloc_char_pool_from_memory(char * const pool, const int64_t size) {
char_pool * const cp = calloc(1, sizeof(char_pool));
if (cp) {
cp->pool = pool;
cp->size = size;
return cp;
}
return NULL;
}
void free_char_pool(char_pool * const cp) {
if (cp == NULL) return;
if (cp->mapped) munmap(cp->pool, cp->size);
else free(cp->pool);
free(cp);
}
/* Given a pointer in a character pool and a buffer, this function returns the
respective pool. It can return NULL if the pointer wasn't in any pool, but
this condition denotes a severe malfunctioning. */
char_pool *get_char_pool(buffer * const b, char * const p) {
for(char_pool *cp = (char_pool *)b->char_pool_list.head; cp->cp_node.next;) {
assert_char_pool(cp);
if (p >= cp->pool && p < cp->pool + cp->size) return cp;
cp = (char_pool *)cp->cp_node.next;
}
assert(false);
return NULL;
}
/* These functions allocate and deallocate line-descriptor pools. The size of
the pool is the number of lines, and is forced to be at least
STD_LINE_DESC_POOL_SIZE. force is passed to alloc_or_mmap(). */
line_desc_pool *alloc_line_desc_pool(int64_t pool_size, int force) {
if (pool_size < STD_LINE_DESC_POOL_SIZE) pool_size = STD_LINE_DESC_POOL_SIZE;
line_desc_pool * const ldp = calloc(1, sizeof(line_desc_pool));
if (ldp) {
if (ldp->pool = alloc_or_mmap(pool_size * (do_syntax ? sizeof(line_desc) : sizeof(no_syntax_line_desc)), 0, &force)) {
ldp->mapped = force;
ldp->size = pool_size;
new_list(&ldp->free_list);
for(int64_t i = 0; i < pool_size; i++)
if (do_syntax) add_tail(&ldp->free_list, &((line_desc *)ldp->pool)[i].ld_node);
else add_tail(&ldp->free_list, &((no_syntax_line_desc *)ldp->pool)[i].ld_node);
return ldp;
}
free(ldp);
}
return NULL;
}
/* This function creates a line-descriptor pool using a given region of memory.
which must be able to hold pool_size element of the right type (depending on
do_syntax). All items in the pool are considered to be allocated. */
line_desc_pool *alloc_line_desc_pool_from_memory(void *pool, int64_t pool_size) {
line_desc_pool * const ldp = calloc(1, sizeof(line_desc_pool));
if (ldp) {
new_list(&ldp->free_list);
ldp->pool = pool;
ldp->size = ldp->allocated_items = pool_size;
return ldp;
}
return NULL;
}
void free_line_desc_pool(line_desc_pool * const ldp) {
if (ldp == NULL) return;
assert_line_desc_pool(ldp);
if (ldp->mapped) munmap(ldp->pool, ldp->size * (do_syntax ? sizeof(line_desc) : sizeof(no_syntax_line_desc)));
else free(ldp->pool);
free(ldp);
}
/* These functions allocate and deallocate a buffer. Note that on allocation
we have to initialize the list pointers, and on dellocation we have to free
all the lists. Moreover, on allocation a buffer pointer can be passed so
that the new buffer can inherit various user flags. */
buffer *alloc_buffer(const buffer * const cur_b) {
buffer *b;
if (b = calloc(1, sizeof(buffer))) {
new_list(&b->line_desc_pool_list);
new_list(&b->line_desc_list);
new_list(&b->char_pool_list);
b->act = ++buffer_actuations;
b->cur_macro = alloc_char_stream(0);
b->bpaste_support = bracketed_paste ? 1 : 0;
b->opt.tab_size = 8;
b->opt.insert =
b->opt.tabs =
b->opt.shift_tabs =
b->opt.automatch =
b->opt.do_undo =
b->opt.auto_prefs = 1;
b->opt.utf8auto = io_utf8;
b->attr_len = -1;
if (cur_b) {
b->opt.cur_clip = cur_b->opt.cur_clip;
b->opt.tab_size = cur_b->opt.tab_size;
b->opt.tabs = cur_b->opt.tabs;
b->opt.del_tabs = cur_b->opt.del_tabs;
b->opt.shift_tabs = cur_b->opt.shift_tabs;
b->opt.automatch = cur_b->opt.automatch;
b->opt.right_margin = cur_b->opt.right_margin;
b->opt.free_form = cur_b->opt.free_form;
b->opt.hex_code = cur_b->opt.hex_code;
b->opt.word_wrap = cur_b->opt.word_wrap;
b->opt.auto_indent = cur_b->opt.auto_indent;
b->opt.preserve_cr = cur_b->opt.preserve_cr;
b->opt.do_undo = cur_b->opt.do_undo;
b->opt.auto_prefs = cur_b->opt.auto_prefs;
b->opt.no_file_req = cur_b->opt.no_file_req;
b->opt.case_search = cur_b->opt.case_search;
b->opt.binary = cur_b->opt.binary;
b->opt.utf8auto = cur_b->opt.utf8auto;
b->opt.visual_bell = cur_b->opt.visual_bell;
b->bpaste_support = cur_b->bpaste_support;
if (b->bpaste_support == 2) {
b->bpaste_macro_before = str_dup(cur_b->bpaste_macro_before);
b->bpaste_macro_after = str_dup(cur_b->bpaste_macro_after);
}
}
/* This leaves out only opt.read_only and opt.search_back, which are
implicitly set to 0 by the calloc(). */
return b;
}
return NULL;
}
/* This function is useful when resetting a buffer, but not really
destroying it. Since it modifies some lists, it cannot be interrupted
from a signal. Note that the search, replace and command_line strings are
not cleared. */
void free_buffer_contents(buffer * const b) {
if (!b) return;
block_signals();
free_list(&b->line_desc_pool_list, free_line_desc_pool);
free_list(&b->char_pool_list, free_char_pool);
new_list(&b->line_desc_list);
b->cur_line_desc = b->top_line_desc = NULL;
b->allocated_chars = b->free_chars = 0;
b->num_lines = 0;
b->is_CRLF = false;
b->encoding = ENC_ASCII;
b->bookmark_mask = 0;
b->mtime = 0;
free_char_stream(b->last_deleted);
b->last_deleted = NULL;
free(b->filename);
b->filename = NULL;
reset_undo_buffer(&b->undo);
b->is_modified = b->marking = b->x_wanted = 0;
release_signals();
}
/* Removes all data in a buffer, but leaves in the current macro, the search,
replace and command_line strings, and an empty line. */
void clear_buffer(buffer * const b) {
if (!b) return;
block_signals();
free_buffer_contents(b);
line_desc * const ld = alloc_line_desc(b);
add_head(&b->line_desc_list, &ld->ld_node);
if (do_syntax) {
ld->highlight_state.state = 0;
ld->highlight_state.stack = NULL;
ld->highlight_state.saved_s[0] = 0;
}
b->num_lines = 1;
reset_position_to_sof(b);
assert_buffer(b);
release_signals();
}
/* Frees all the data associated to a buffer. */
void free_buffer(buffer * const b) {
if (b == NULL) return;
assert_buffer(b);
free_buffer_contents(b);
free_char_stream(b->cur_macro);
free(b->find_string);
free(b->replace_string);
free(b->command_line);
free(b->bpaste_macro_before);
free(b->bpaste_macro_after);
if (b->attr_buf) free(b->attr_buf);
free(b);
}
/* Computes how many characters have been "lost" in a buffer, that is, how many
free characters lie inside the first and last used characters of the
character pools. This characters can only be allocated by
alloc_chars_around(). */
int64_t calc_lost_chars(const buffer * const b) {
int64_t n = 0;
for(char_pool *cp = (char_pool *)b->char_pool_list.head; cp->cp_node.next; cp = (char_pool *)cp->cp_node.next)
n += cp->size - (cp->last_used - cp->first_used + 1);
return b->free_chars - n;
}
/* Returns the nth buffer in the global buffer list, or NULL if less than n
buffers are available. */
buffer *get_nth_buffer(int n) {
for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next)
if (!n--) return b;
return NULL;
}
/* Returns the first buffer with a name matching *p.
The buffers' names and *p are converted to their fully qualified form
for the comparisons.
This is a departure from earlier behavior, which only considered the
file_part() of either name. */
buffer *get_buffer_named(const char *p) {
char *bname=NULL, *pname=NULL, *cwd=NULL;
buffer *b;
int rc = 1;
if (!p) return NULL;
cwd = ne_getcwd(CUR_DIR_MAX_SIZE);
if (!cwd) return NULL;
if ((pname = absolute_file_path(p, cwd))) {
for(b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next) {
if (b->filename && (bname = absolute_file_path(b->filename, cwd))) {
if (!(rc = strcmp(bname, pname))) break;
}
free(bname);
bname = NULL;
}
}
free(pname);
free(bname);
free(cwd);
if (!rc) return b;
return NULL;
}
/* Returns true if the given pointer references an extant buffer. */
bool is_buffer(const buffer * const maybe_buf) {
for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next)
if (maybe_buf == b) return true;
return false;
}
/* Returns true if the given buffer is empty. */
bool is_buffer_empty(const buffer * const b) {
if (b) {
if (b->line_desc_list.head->next == NULL ||
b->line_desc_list.head->next->next == NULL && ((line_desc *)b->line_desc_list.head)->line_len == 0) return true;
}
return false;
}
/* Returns true if any of the buffers has been modified since the last save. */
int modified_buffers(void) {
for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next)
if (b->is_modified) return true;
return false;
}
/* Saves all buffers which have been modified since the last save. Returns an
error if a save is unsuccessful, a file on-disk was modified since last
loaded or saved, or if a buffer has no name. */
int save_all_modified_buffers(void) {
int rc = 0;
for(buffer *b = (buffer *)buffers.head; b->b_node.next; b = (buffer *)b->b_node.next)
if (b->is_modified) {
if (buffer_file_modified(b, NULL)) rc = ERROR;
else if (save_buffer_to_file(b, NULL)) rc = ERROR;
}
return rc;
}
/* Now we have the much more sophisticated allocation functions which create
small elements such as lines and line descriptors. All the operations
are in the context of a given buffer. Most of these functions are
protected internally against being interrupted by signals, since
auto_save could die miserably because of the inconsistent state of a
list. */
/* Allocates a line descriptor from the pools available in the given buffer. A
new pool is allocated and linked if necessary. New line descriptors are
created with an invalid syntax state, so they will always force an update. */
line_desc *alloc_line_desc(buffer * const b) {
block_signals();
line_desc_pool *ldp;
for(ldp = (line_desc_pool *)b->line_desc_pool_list.head; ldp->ldp_node.next; ldp = (line_desc_pool *)ldp->ldp_node.next) {
assert_line_desc_pool(ldp);
if (ldp->free_list.head->next) {
line_desc * const ld = (line_desc *)ldp->free_list.head;
rem(&ld->ld_node);
if (!ldp->free_list.head->next) {
rem(&ldp->ldp_node);
add_tail(&b->line_desc_pool_list, &ldp->ldp_node);
}
ldp->allocated_items++;
ld->line = NULL;
ld->line_len = 0;
if (do_syntax) ld->highlight_state.state = -1;
release_signals();
return ld;
}
}
/* No chances, all pools are full. Let's allocate a new one,
using the standard pool size, and let's put it at the start
of the list, so that it is always scanned first. */
if (ldp = alloc_line_desc_pool(0, -1)) {
add_head(&b->line_desc_pool_list, &ldp->ldp_node);
line_desc * const ld = (line_desc *)ldp->free_list.head;
rem(&ld->ld_node);
ldp->allocated_items = 1;
if (do_syntax) ld->highlight_state.state = -1;
release_signals();
return ld;
}
release_signals();
return NULL;
}
/* Frees a line descriptor, (and the line descriptor pool containing it, should
it become empty). */
void free_line_desc(buffer * const b, line_desc * const ld) {
/* We scan the pool list in order to find where the given
line descriptor lives. */
line_desc_pool *ldp;
for(ldp = (line_desc_pool *)b->line_desc_pool_list.head; ldp->ldp_node.next; ldp = (line_desc_pool *)ldp->ldp_node.next) {
assert_line_desc_pool(ldp);
if (do_syntax && ld >= (line_desc *)ldp->pool && ld < (line_desc *)ldp->pool + ldp->size
|| !do_syntax && (no_syntax_line_desc *)ld >= (no_syntax_line_desc *)ldp->pool
&& (no_syntax_line_desc *)ld < (no_syntax_line_desc *)ldp->pool + ldp->size) break;
}
assert(ldp->ldp_node.next != NULL);
block_signals();
add_head(&ldp->free_list, &ld->ld_node);
if (--ldp->allocated_items == 0) {
rem(&ldp->ldp_node);
free_line_desc_pool(ldp);
}
release_signals();
}
/* Allocates len characters from the character pools of the
given buffer. If necessary, a new pool is allocated. */
char *alloc_chars(buffer * const b, const int64_t len) {
if (!len || !b) return NULL;
assert_buffer(b);
block_signals();
char_pool *cp;
for(cp = (char_pool *)b->char_pool_list.head; cp->cp_node.next; cp = (char_pool *)cp->cp_node.next) {
assert_char_pool(cp);
/* We try to allocate before the first used character,
or after the last used character. If we succeed with a
pool which is not the head of the list, we move it to
the head in order to optimize the next try. */
if (cp->first_used >= len) {
cp->first_used -= len;
b->free_chars -= len;
if (cp != (char_pool *)b->char_pool_list.head) {
rem(&cp->cp_node);
add_head(&b->char_pool_list, &cp->cp_node);
}
release_signals();
return cp->pool + cp->first_used;
}
else if (cp->size - cp->last_used > len) {
cp->last_used += len;
b->free_chars -= len;
if (cp != (char_pool *)b->char_pool_list.head) {
rem(&cp->cp_node);
add_head(&b->char_pool_list, &cp->cp_node);
}
release_signals();
return cp->pool + cp->last_used - len + 1;
}
}
/* If no free space has been found, we allocate a new pool which is guaranteed
to contain at least len characters. The pool is added to the head of the list. */
if (cp = alloc_char_pool(len, 0, -1)) {
add_head(&b->char_pool_list, &cp->cp_node);
cp->last_used = len - 1;
b->allocated_chars += cp->size;
b->free_chars += cp->size - len;
release_signals();
return cp->pool;
}
release_signals();
return NULL;
}
/* This function is very important, since it embeds all the philosophy behind
ne's character pool management. It performs an allocation *locally*, that
is, it tries to see if there are enough free characters around the line
pointed to by a line descriptor by looking at non-nullness of surrounding
characters (if a character is set to 0, it is free). First the characters
after the line are checked, then the characters before (this can be reversed
via the check_first_before flag). The number of characters available *after*
the line is returned, or ERROR if the allocation failed. The caller can
recover the characters available before the line since he knows the length
of the allocation. Note that it is *only* through this function that the
"lost" characters can be allocated, but being editing a local activity, this
is what happens usually. */
int64_t alloc_chars_around(buffer * const b, line_desc * const ld, const int64_t n, const bool check_first_before) {
assert(ld->line != NULL);
char_pool *cp = get_char_pool(b, ld->line);
assert_char_pool(cp);
block_signals();
char *before = ld->line - 1;
char *after = ld->line + ld->line_len;
if (check_first_before) {
while(before >= cp->pool && !*before && (ld->line - 1) - before < n)
before--;
while(after < cp->pool + cp->size && !*after && (after - (ld->line + ld->line_len)) + ((ld->line - 1) - before)<n)
after++;
}
else {
while(after < cp->pool + cp->size && !*after && after - (ld->line + ld->line_len)<n)
after++;
while(before >= cp->pool && !*before && (after - (ld->line + ld->line_len)) + ((ld->line - 1) - before)<n)
before--;
}
assert(((ld->line - 1) - before) + (after - (ld->line + ld->line_len)) <= n);
assert(((ld->line - 1) - before) + (after - (ld->line + ld->line_len)) >= 0);
if (((ld->line - 1) - before) + (after - (ld->line + ld->line_len)) == n) {
if (cp->pool + cp->first_used == ld->line) cp->first_used = (before + 1) - cp->pool;
if (cp->pool + cp->last_used == ld->line + ld->line_len - 1) cp->last_used = (after - 1) - cp->pool;
b->free_chars -= n;
release_signals();
return after - (ld->line + ld->line_len);
}
release_signals();
return ERROR;
}
/* Frees a block of len characters pointed to by p. If the char pool containing
the block becomes completely free, it is removed from the list. */
void free_chars(buffer *const b, char *const p, const int64_t len) {
if (!b || !p || !len) return;
char_pool *cp = get_char_pool(b, p);
assert_char_pool(cp);
assert(*p);
assert(p[len - 1]);
block_signals();
memset(p, 0, len);
b->free_chars += len;
if (p == &cp->pool[cp->first_used]) while(cp->first_used <= cp->last_used && !cp->pool[cp->first_used]) cp->first_used++;
if (p + len - 1 == &cp->pool[cp->last_used]) while(!cp->pool[cp->last_used] && cp->first_used <= cp->last_used) cp->last_used--;
if (cp->last_used < cp->first_used) {
rem(&cp->cp_node);
b->allocated_chars -= cp->size;
b->free_chars -= cp->size;
free_char_pool(cp);
release_signals();
return;
}
assert_char_pool(cp);
release_signals();
}
/* The following functions represent the only legal way of modifying a
buffer. They are all based on insert_stream and delete_stream (except for
the I/O functions). A stream is a sequence of NULL-terminated strings. The
semantics associated is that each string is a separate line terminated by a
line feed, *except for the last one*. Thus, a NULL-terminated string is a
line with no linefeed. All the functions accept a position specified via a
line descriptor and a position (which is the offset to be applied to the
line pointer of the line descriptor). Also the line number is usually
supplied, since it is necessary for recording the operation in the undo
buffer. */
/* Inserts a line at the current position. The effect is obtained by inserting
a stream containing one NULL. */
int insert_one_line(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos) {
return insert_stream(b, ld, line, pos, "", 1);
}
/* Deletes a whole line, putting it in the temporary line buffer used by the
UndelLine command. */
int delete_one_line(buffer * const b, line_desc * const ld, const int64_t line) {
assert_line_desc(ld, b->encoding);
assert_buffer(b);
block_signals();
if (ld->line_len && (b->last_deleted = reset_stream(b->last_deleted))) add_to_stream(b->last_deleted, ld->line, ld->line_len);
/* We delete a line by delete_stream()ing its length plus one. However, if
we are on the last line of text, there is no terminating line feed. */
const int error = delete_stream(b, ld, line, 0, ld->line_len + (ld->ld_node.next->next ? 1 : 0));
release_signals();
return error;
}
/* Undeletes the last deleted line, using the last_deleted stream. */
int undelete_line(buffer * const b) {
line_desc * const ld = b->cur_line_desc;
if (!b->last_deleted) return ERROR;
start_undo_chain(b);
if (b->cur_pos > ld->line_len)
insert_spaces(b, ld, b->cur_line, ld->line_len, b->win_x + b->cur_x - calc_width(ld, ld->line_len, b->opt.tab_size, b->encoding));
insert_one_line(b, ld, b->cur_line, b->cur_pos);
insert_stream(b, ld, b->cur_line, b->cur_pos, b->last_deleted->stream, b->last_deleted->len);
end_undo_chain(b);
return OK;
}
/* Deletes a line up to its end. */
void delete_to_eol(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos) {
if (!ld || pos >= ld->line_len) return;
delete_stream(b, ld, line, pos, ld->line_len - pos);
}
/* Inserts a stream in a line at a given position. The position has to be
smaller or equal to the line length. Since the stream can contain many
lines, this function can be used for manipulating all insertions. It also
records the inverse operation in the undo buffer if b->opt.do_undo is
true. */
int insert_stream(buffer * const b, line_desc * ld, int64_t line, int64_t pos, const char * const stream, const int64_t stream_len) {
assert(pos >= 0);
assert(stream_len >= 0);
if (!b || !ld || !stream || stream_len < 1 || pos > ld->line_len) return ERROR;
assert_line_desc(ld, b->encoding);
assert_buffer(b);
block_signals();
if (b->opt.do_undo && !(b->undoing || b->redoing)) {
const int error = add_undo_step(b, line, pos, -stream_len);
if (error) {
release_signals();
return error;
}
}
const char *s = stream;
while(s - stream < stream_len) {
int64_t const len = strnlen_ne(s, stream_len - (s - stream));
if (len) {
/* First case; there is no character allocated on this line. We
have to freshly allocate the line. */
if (!ld->line) {
if (ld->line = alloc_chars(b, len)) {
memcpy(ld->line, s, len);
ld->line_len = len;
}
else {
release_signals();
return OUT_OF_MEMORY_DISK_FULL;
}
}
/* Second case. There are not enough characters around ld->line. Note
that the value of the check_first_before parameter depends on
the position at which the insertion will be done, and it is chosen
in such a way to minimize the number of characters to move. */
else {
const int64_t result = alloc_chars_around(b, ld, len, pos < ld->line_len / 2);
if (result < 0) {
char * const p = alloc_chars(b, ld->line_len + len);
if (p) {
memcpy(p, ld->line, pos);
memcpy(&p[pos], s, len);
memcpy(&p[pos + len], ld->line + pos, ld->line_len - pos);
free_chars(b, ld->line, ld->line_len);
ld->line = p;
ld->line_len += len;
}
else {
release_signals();
return OUT_OF_MEMORY_DISK_FULL;
}
}
else { /* Third case. There are enough free characters around ld->line. */
if (len - result) memmove(ld->line - (len - result), ld->line, pos);
if (result) memmove(ld->line + pos + result, ld->line + pos, ld->line_len - pos);
memcpy(ld->line - (len - result) + pos, s, len);
ld->line -= (len - result);
ld->line_len += len;
}
}
b->is_modified = 1;
/* We just inserted len chars at (line,pos); adjust bookmarks and mark accordingly. */
if (b->marking && b->block_start_line == line && b->block_start_pos > pos) b->block_start_pos += len;
for (int i = 0, mask = b->bookmark_mask; mask; i++, mask >>= 1)
if ((mask & 1) && b->bookmark[i].line == line && b->bookmark[i].pos > pos) b->bookmark[i].pos += len;
}
/* If the string we have inserted has a NULL at the end, we create a new
line under the current one and set ld to point to it. */
if (len + (s - stream) < stream_len) {
line_desc *new_ld;
if (new_ld = alloc_line_desc(b)) {
add(&new_ld->ld_node, &ld->ld_node);
b->num_lines++;
if (pos + len < ld->line_len) {
new_ld->line_len = ld->line_len - pos - len;
new_ld->line = &ld->line[pos + len];
ld->line_len = pos + len;
if (pos + len == 0) ld->line = NULL;
}
b->is_modified = 1;
ld = new_ld;
/* We just inserted a line break at (line,pos);
adjust the buffer bookmarks and mark accordingly. */
if (b->marking) {
if (b->block_start_line == line && b->block_start_pos > pos) {
b->block_start_pos -= pos + len;
b->block_start_line++;
}
else if (b->block_start_line > line) b->block_start_line++;
}
for (int i = 0, mask=b->bookmark_mask; mask; i++, mask >>= 1) {
if (mask & 1) {
if (b->bookmark[i].line == line && b->bookmark[i].pos > pos) {
b->bookmark[i].pos -= pos + len;
b->bookmark[i].line++;
}
else if (b->bookmark[i].line > line) b->bookmark[i].line++;
}
}
pos = 0;
line++;
}
else {
release_signals();
return OUT_OF_MEMORY_DISK_FULL;
}
}
s += len + 1;
}
release_signals();
return OK;
}
/* Inserts a single ISO 10646 character (it creates, if necessary, a suitable
temporary stream). The character must be compatible with the current buffer
encoding. */
int insert_one_char(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos, const int c) {
static char t[8];
assert(b->encoding == ENC_8_BIT || b->encoding == ENC_UTF8 || c <= 127);
assert(b->encoding == ENC_UTF8 || c <= 255);
assert(c != 0);
if (b->encoding == ENC_UTF8) t[utf8str(c, t)] = 0;
else t[0] = c, t[1] = 0;
return insert_stream(b, ld, line, pos, t, strlen(t));
}
/* Inserts a number of spaces. */
int insert_spaces(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos, int64_t n) {
static char spaces[MAX_STACK_SPACES];
int result = OK, i;
if (!spaces[0]) memset(spaces, ' ', sizeof spaces);
while(result == OK && n > 0) {
i = min(n, MAX_STACK_SPACES);
result = insert_stream(b, ld, line, pos, spaces, i);
n -= i;
}
assert(result != OK || n == 0);
return result;
}
/* Deletes a stream of len bytes, that is, deletes len bytes from the given
position, counting line feeds as a byte. The operation is recorded in the
undo buffer. */
int delete_stream(buffer * const b, line_desc * const ld, const int64_t line, const int64_t pos, int64_t len) {
assert_buffer(b);
assert_line_desc(ld, b->encoding);
/* If we are in no man's land, we return. */
if (!b || !ld || !len || pos > ld->line_len || pos == ld->line_len && !ld->ld_node.next->next) return ERROR;
block_signals();
if (b->opt.do_undo && !(b->undoing || b->redoing)) {
const int error = add_undo_step(b, line, pos, len);
if (error) {
release_signals();
return error;
}
}
while(len) {
/* First case: we are just on the end of a line. We join the current
line with the following one (if it's there of course). If, however,
the current line is empty, we rather remove it. The only difference
is in the resulting syntax state. */
if (pos == ld->line_len) {
line_desc *next_ld = (line_desc *)ld->ld_node.next;
/* There's nothing more to do--we are at the end of the file. */
if (next_ld->ld_node.next == NULL) break;
/* We're about to join line+1 to line; adjust mark and bookmarks accordingly. */
if (b->marking) {
if (b->block_start_line == line+1) {
b->block_start_line--;
b->block_start_pos += ld->line_len;
}
else if (b->block_start_line > line) b->block_start_line--;
}
for (int i = 0, mask = b->bookmark_mask; mask; i++, mask >>= 1) {
if (mask & 1) {
if (b->bookmark[i].line == line+1) {
b->bookmark[i].line--;
b->bookmark[i].pos += ld->line_len;
}
else if (b->bookmark[i].line > line) b->bookmark[i].line--;
}
}
/* If one of the lines is empty, or their contents are adjacent,
we either do nothing or simply set a pointer. */
if (!ld->line || !next_ld->line || ld->line + ld->line_len == next_ld->line) {