1 /*
2 Editor text keep buffer.
3
4 Copyright (C) 2013-2021
5 Free Software Foundation, Inc.
6
7 Written by:
8 Andrew Borodin <aborodin@vmail.ru> 2013
9
10 This file is part of the Midnight Commander.
11
12 The Midnight Commander is free software: you can redistribute it
13 and/or modify it under the terms of the GNU General Public License as
14 published by the Free Software Foundation, either version 3 of the License,
15 or (at your option) any later version.
16
17 The Midnight Commander is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26 /** \file
27 * \brief Source: editor text keep buffer.
28 * \author Andrew Borodin
29 * \date 2013
30 */
31
32 #include <config.h>
33
34 #include <ctype.h> /* isdigit() */
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/types.h>
38
39 #include "lib/global.h"
40
41 #include "lib/vfs/vfs.h"
42
43 #include "edit-impl.h"
44 #include "editbuffer.h"
45
46 /* --------------------------------------------------------------------------------------------- */
47 /*-
48 *
49 * here's a quick sketch of the layout: (don't run this through indent.)
50 *
51 * |
52 * \0\0\0\0\0m e _ f i l e . \nf i n . \n|T h i s _ i s _ s o\0\0\0\0\0\0\0\0\0
53 * ______________________________________|______________________________________
54 * |
55 * ... | b2[2] | b2[1] | b2[0] | b1[0] | b1[1] | b1[2] | ...
56 * |-> |-> |-> |-> |-> |-> |
57 * |
58 * _<------------------------->|<----------------->_
59 * curs2 | curs1
60 * ^ | ^
61 * | ^|^ |
62 * cursor ||| cursor
63 * |||
64 * file end|||file beginning
65 * |
66 * |
67 *
68 * _
69 * This_is_some_file
70 * fin.
71 *
72 *
73 * This is called a "gap buffer".
74 * See also:
75 * http://en.wikipedia.org/wiki/Gap_buffer
76 * http://stackoverflow.com/questions/4199694/data-structure-for-text-editor
77 */
78
79 /*** global variables ****************************************************************************/
80
81 /*** file scope macro definitions ****************************************************************/
82
83 /*
84 * The editor keeps data in two arrays of buffers.
85 * All buffers have the same size, which must be a power of 2.
86 */
87
88 /* Configurable: log2 of the buffer size in bytes */
89 #ifndef S_EDIT_BUF_SIZE
90 #define S_EDIT_BUF_SIZE 16
91 #endif
92
93 /* Size of the buffer */
94 #define EDIT_BUF_SIZE (((off_t) 1) << S_EDIT_BUF_SIZE)
95
96 /* Buffer mask (used to find cursor position relative to the buffer) */
97 #define M_EDIT_BUF_SIZE (EDIT_BUF_SIZE - 1)
98
99 /*** file scope type declarations ****************************************************************/
100
101 /*** file scope variables ************************************************************************/
102
103 /* --------------------------------------------------------------------------------------------- */
104 /*** file scope functions ************************************************************************/
105 /* --------------------------------------------------------------------------------------------- */
106 /**
107 * Get pointer to byte at specified index
108 *
109 * @param buf pointer to editor buffer
110 * @param byte_index byte index
111 *
112 * @return NULL if byte_index is negative or larger than file size; pointer to byte otherwise.
113 */
114 static char *
edit_buffer_get_byte_ptr(const edit_buffer_t * buf,off_t byte_index)115 edit_buffer_get_byte_ptr (const edit_buffer_t * buf, off_t byte_index)
116 {
117 void *b;
118
119 if (byte_index >= (buf->curs1 + buf->curs2) || byte_index < 0)
120 return NULL;
121
122 if (byte_index >= buf->curs1)
123 {
124 off_t p;
125
126 p = buf->curs1 + buf->curs2 - byte_index - 1;
127 b = g_ptr_array_index (buf->b2, p >> S_EDIT_BUF_SIZE);
128 return (char *) b + EDIT_BUF_SIZE - 1 - (p & M_EDIT_BUF_SIZE);
129 }
130
131 b = g_ptr_array_index (buf->b1, byte_index >> S_EDIT_BUF_SIZE);
132 return (char *) b + (byte_index & M_EDIT_BUF_SIZE);
133 }
134
135 /* --------------------------------------------------------------------------------------------- */
136 /*** public functions ****************************************************************************/
137 /* --------------------------------------------------------------------------------------------- */
138 /**
139 * Initialize editor buffers.
140 *
141 * @param buf pointer to editor buffer
142 */
143
144 void
edit_buffer_init(edit_buffer_t * buf,off_t size)145 edit_buffer_init (edit_buffer_t * buf, off_t size)
146 {
147 buf->b1 = g_ptr_array_sized_new (32);
148 buf->b2 = g_ptr_array_sized_new (32);
149
150 buf->curs1 = 0;
151 buf->curs2 = 0;
152
153 buf->size = size;
154 buf->lines = 0;
155 }
156
157 /* --------------------------------------------------------------------------------------------- */
158 /**
159 * Clean editor buffers.
160 *
161 * @param buf pointer to editor buffer
162 */
163
164 void
edit_buffer_clean(edit_buffer_t * buf)165 edit_buffer_clean (edit_buffer_t * buf)
166 {
167 if (buf->b1 != NULL)
168 {
169 g_ptr_array_foreach (buf->b1, (GFunc) g_free, NULL);
170 g_ptr_array_free (buf->b1, TRUE);
171 }
172
173 if (buf->b2 != NULL)
174 {
175 g_ptr_array_foreach (buf->b2, (GFunc) g_free, NULL);
176 g_ptr_array_free (buf->b2, TRUE);
177 }
178 }
179
180 /* --------------------------------------------------------------------------------------------- */
181 /**
182 * Get byte at specified index
183 *
184 * @param buf pointer to editor buffer
185 * @param byte_index byte index
186 *
187 * @return '\n' if byte_index is negative or larger than file size; byte at byte_index otherwise.
188 */
189
190 int
edit_buffer_get_byte(const edit_buffer_t * buf,off_t byte_index)191 edit_buffer_get_byte (const edit_buffer_t * buf, off_t byte_index)
192 {
193 char *p;
194
195 p = edit_buffer_get_byte_ptr (buf, byte_index);
196
197 return (p != NULL) ? *(unsigned char *) p : '\n';
198 }
199
200 /* --------------------------------------------------------------------------------------------- */
201
202 #ifdef HAVE_CHARSET
203 /**
204 * Get utf-8 symbol at specified index
205 *
206 * @param buf pointer to editor buffer
207 * @param byte_index byte index
208 * @param char_length length of returned symbol
209 *
210 * @return '\n' if byte_index is negative or larger than file size;
211 * 0 if utf-8 symbol at specified index is invalid;
212 * utf-8 symbol otherwise
213 */
214
215 int
edit_buffer_get_utf(const edit_buffer_t * buf,off_t byte_index,int * char_length)216 edit_buffer_get_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length)
217 {
218 gchar *str = NULL;
219 gunichar res;
220 gunichar ch;
221 gchar *next_ch = NULL;
222
223 if (byte_index >= (buf->curs1 + buf->curs2) || byte_index < 0)
224 {
225 *char_length = 0;
226 return '\n';
227 }
228
229 str = edit_buffer_get_byte_ptr (buf, byte_index);
230 if (str == NULL)
231 {
232 *char_length = 0;
233 return 0;
234 }
235
236 res = g_utf8_get_char_validated (str, -1);
237 if (res == (gunichar) (-2) || res == (gunichar) (-1))
238 {
239 /* Retry with explicit bytes to make sure it's not a buffer boundary */
240 size_t i;
241 gchar utf8_buf[UTF8_CHAR_LEN + 1];
242
243 for (i = 0; i < UTF8_CHAR_LEN; i++)
244 utf8_buf[i] = edit_buffer_get_byte (buf, byte_index + i);
245 utf8_buf[i] = '\0';
246 res = g_utf8_get_char_validated (utf8_buf, -1);
247 }
248
249 if (res == (gunichar) (-2) || res == (gunichar) (-1))
250 {
251 ch = *str;
252 *char_length = 0;
253 }
254 else
255 {
256 ch = res;
257 /* Calculate UTF-8 char length */
258 next_ch = g_utf8_next_char (str);
259 *char_length = next_ch - str;
260 }
261
262 return (int) ch;
263 }
264
265 /* --------------------------------------------------------------------------------------------- */
266 /**
267 * Get utf-8 symbol before specified index
268 *
269 * @param buf pointer to editor buffer
270 * @param byte_index byte index
271 * @param char_length length of returned symbol
272 *
273 * @return 0 if byte_index is negative or larger than file size;
274 * 1-byte value before specified index if utf-8 symbol before specified index is invalid;
275 * utf-8 symbol otherwise
276 */
277
278 int
edit_buffer_get_prev_utf(const edit_buffer_t * buf,off_t byte_index,int * char_length)279 edit_buffer_get_prev_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length)
280 {
281 size_t i;
282 gchar utf8_buf[3 * UTF8_CHAR_LEN + 1];
283 gchar *str;
284 gchar *cursor_buf_ptr;
285 gunichar res;
286
287 if (byte_index > (buf->curs1 + buf->curs2) || byte_index <= 0)
288 {
289 *char_length = 0;
290 return 0;
291 }
292
293 for (i = 0; i < (3 * UTF8_CHAR_LEN); i++)
294 utf8_buf[i] = edit_buffer_get_byte (buf, byte_index + i - (2 * UTF8_CHAR_LEN));
295 utf8_buf[i] = '\0';
296
297 cursor_buf_ptr = utf8_buf + (2 * UTF8_CHAR_LEN);
298 str = g_utf8_find_prev_char (utf8_buf, cursor_buf_ptr);
299
300 if (str == NULL || g_utf8_next_char (str) != cursor_buf_ptr)
301 {
302 *char_length = 1;
303 return *(cursor_buf_ptr - 1);
304 }
305
306 res = g_utf8_get_char_validated (str, -1);
307 if (res == (gunichar) (-2) || res == (gunichar) (-1))
308 {
309 *char_length = 1;
310 return *(cursor_buf_ptr - 1);
311 }
312
313 *char_length = cursor_buf_ptr - str;
314 return (int) res;
315 }
316 #endif /* HAVE_CHARSET */
317
318 /* --------------------------------------------------------------------------------------------- */
319 /**
320 * Count lines in editor buffer.
321 *
322 * @param buf editor buffer
323 * @param first start byte offset
324 * @param last finish byte offset
325 *
326 * @return line numbers between "first" and "last" bytes
327 */
328
329 long
edit_buffer_count_lines(const edit_buffer_t * buf,off_t first,off_t last)330 edit_buffer_count_lines (const edit_buffer_t * buf, off_t first, off_t last)
331 {
332 long lines = 0;
333
334 first = MAX (first, 0);
335 last = MIN (last, buf->size);
336
337 while (first < last)
338 if (edit_buffer_get_byte (buf, first++) == '\n')
339 lines++;
340
341 return lines;
342 }
343
344 /* --------------------------------------------------------------------------------------------- */
345 /**
346 * Get "begin-of-line" offset of line contained specified byte offset
347 *
348 * @param buf editor buffer
349 * @param current byte offset
350 *
351 * @return index of first char of line
352 */
353
354 off_t
edit_buffer_get_bol(const edit_buffer_t * buf,off_t current)355 edit_buffer_get_bol (const edit_buffer_t * buf, off_t current)
356 {
357 if (current <= 0)
358 return 0;
359
360 for (; edit_buffer_get_byte (buf, current - 1) != '\n'; current--)
361 ;
362
363 return current;
364 }
365
366 /* --------------------------------------------------------------------------------------------- */
367 /**
368 * Get "end-of-line" offset of line contained specified byte offset
369 *
370 * @param buf editor buffer
371 * @param current byte offset
372 *
373 * @return index of last char of line + 1
374 */
375
376 off_t
edit_buffer_get_eol(const edit_buffer_t * buf,off_t current)377 edit_buffer_get_eol (const edit_buffer_t * buf, off_t current)
378 {
379 if (current >= buf->size)
380 return buf->size;
381
382 for (; edit_buffer_get_byte (buf, current) != '\n'; current++)
383 ;
384
385 return current;
386 }
387
388 /* --------------------------------------------------------------------------------------------- */
389 /**
390 * Get word from specified offset.
391 *
392 * @param buf editor buffer
393 * @param current start_pos offset
394 * @param start actual start word ofset
395 * @param cut
396 *
397 * @return word as newly allocated object
398 */
399
400 GString *
edit_buffer_get_word_from_pos(const edit_buffer_t * buf,off_t start_pos,off_t * start,gsize * cut)401 edit_buffer_get_word_from_pos (const edit_buffer_t * buf, off_t start_pos, off_t * start,
402 gsize * cut)
403 {
404 off_t word_start;
405 gsize cut_len = 0;
406 GString *match_expr;
407 int c1, c2;
408
409 for (word_start = start_pos; word_start != 0; word_start--, cut_len++)
410 {
411 c1 = edit_buffer_get_byte (buf, word_start);
412 c2 = edit_buffer_get_byte (buf, word_start - 1);
413
414 if (is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n')
415 break;
416 }
417
418 match_expr = g_string_sized_new (16);
419
420 do
421 {
422 c1 = edit_buffer_get_byte (buf, word_start + match_expr->len);
423 c2 = edit_buffer_get_byte (buf, word_start + match_expr->len + 1);
424 g_string_append_c (match_expr, c1);
425 }
426 while (!(is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n'));
427
428 *start = word_start;
429 *cut = cut_len;
430
431 return match_expr;
432 }
433
434 /* --------------------------------------------------------------------------------------------- */
435 /**
436 * Find first character of current word
437 *
438 * @param buf editor buffer
439 * @param word_start position of first character of current word
440 * @param word_len length of current word
441 *
442 * @return TRUE if first character of word is found and this character is not 1) a digit and
443 * 2) a begin of file, FALSE otherwise
444 */
445
446 gboolean
edit_buffer_find_word_start(const edit_buffer_t * buf,off_t * word_start,gsize * word_len)447 edit_buffer_find_word_start (const edit_buffer_t * buf, off_t * word_start, gsize * word_len)
448 {
449 int c;
450 off_t i;
451
452 /* return if at begin of file */
453 if (buf->curs1 <= 0)
454 return FALSE;
455
456 c = edit_buffer_get_previous_byte (buf);
457 /* return if not at end or in word */
458 if (is_break_char (c))
459 return FALSE;
460
461 /* search start of word */
462 for (i = 1;; i++)
463 {
464 int last;
465
466 last = c;
467 c = edit_buffer_get_byte (buf, buf->curs1 - i - 1);
468
469 if (is_break_char (c))
470 {
471 /* return if word starts with digit */
472 if (isdigit (last))
473 return FALSE;
474
475 break;
476 }
477 }
478
479 /* success */
480 *word_start = buf->curs1 - i; /* start found */
481 *word_len = (gsize) i;
482
483 return TRUE;
484 }
485
486 /* --------------------------------------------------------------------------------------------- */
487 /**
488 * Basic low level single character buffer alterations and movements at the cursor: insert character
489 * at the cursor position and move right.
490 *
491 * @param buf pointer to editor buffer
492 * @param c character to insert
493 */
494
495 void
edit_buffer_insert(edit_buffer_t * buf,int c)496 edit_buffer_insert (edit_buffer_t * buf, int c)
497 {
498 void *b;
499 off_t i;
500
501 i = buf->curs1 & M_EDIT_BUF_SIZE;
502
503 /* add a new buffer if we've reached the end of the last one */
504 if (i == 0)
505 g_ptr_array_add (buf->b1, g_malloc0 (EDIT_BUF_SIZE));
506
507 /* perform the insertion */
508 b = g_ptr_array_index (buf->b1, buf->curs1 >> S_EDIT_BUF_SIZE);
509 *((unsigned char *) b + i) = (unsigned char) c;
510
511 /* update cursor position */
512 buf->curs1++;
513
514 /* update file length */
515 buf->size++;
516 }
517
518 /* --------------------------------------------------------------------------------------------- */
519 /**
520 * Basic low level single character buffer alterations and movements at the cursor: insert character
521 * at the cursor position and move left.
522 *
523 * @param buf pointer to editor buffer
524 * @param c character to insert
525 */
526
527 void
edit_buffer_insert_ahead(edit_buffer_t * buf,int c)528 edit_buffer_insert_ahead (edit_buffer_t * buf, int c)
529 {
530 void *b;
531 off_t i;
532
533 i = buf->curs2 & M_EDIT_BUF_SIZE;
534
535 /* add a new buffer if we've reached the end of the last one */
536 if (i == 0)
537 g_ptr_array_add (buf->b2, g_malloc0 (EDIT_BUF_SIZE));
538
539 /* perform the insertion */
540 b = g_ptr_array_index (buf->b2, buf->curs2 >> S_EDIT_BUF_SIZE);
541 *((unsigned char *) b + EDIT_BUF_SIZE - 1 - i) = (unsigned char) c;
542
543 /* update cursor position */
544 buf->curs2++;
545
546 /* update file length */
547 buf->size++;
548 }
549
550 /* --------------------------------------------------------------------------------------------- */
551 /**
552 * Basic low level single character buffer alterations and movements at the cursor: delete character
553 * at the cursor position.
554 *
555 * @param buf pointer to editor buffer
556 * @param c character to insert
557 */
558
559 int
edit_buffer_delete(edit_buffer_t * buf)560 edit_buffer_delete (edit_buffer_t * buf)
561 {
562 void *b;
563 unsigned char c;
564 off_t prev;
565 off_t i;
566
567 prev = buf->curs2 - 1;
568
569 b = g_ptr_array_index (buf->b2, prev >> S_EDIT_BUF_SIZE);
570 i = prev & M_EDIT_BUF_SIZE;
571 c = *((unsigned char *) b + EDIT_BUF_SIZE - 1 - i);
572
573 if (i == 0)
574 {
575 guint j;
576
577 j = buf->b2->len - 1;
578 b = g_ptr_array_index (buf->b2, j);
579 g_ptr_array_remove_index (buf->b2, j);
580 g_free (b);
581 }
582
583 buf->curs2 = prev;
584
585 /* update file length */
586 buf->size--;
587
588 return c;
589 }
590
591 /* --------------------------------------------------------------------------------------------- */
592 /**
593 * Basic low level single character buffer alterations and movements at the cursor: delete character
594 * before the cursor position and move left.
595 *
596 * @param buf pointer to editor buffer
597 * @param c character to insert
598 */
599
600 int
edit_buffer_backspace(edit_buffer_t * buf)601 edit_buffer_backspace (edit_buffer_t * buf)
602 {
603 void *b;
604 unsigned char c;
605 off_t prev;
606 off_t i;
607
608 prev = buf->curs1 - 1;
609
610 b = g_ptr_array_index (buf->b1, prev >> S_EDIT_BUF_SIZE);
611 i = prev & M_EDIT_BUF_SIZE;
612 c = *((unsigned char *) b + i);
613
614 if (i == 0)
615 {
616 guint j;
617
618 j = buf->b1->len - 1;
619 b = g_ptr_array_index (buf->b1, j);
620 g_ptr_array_remove_index (buf->b1, j);
621 g_free (b);
622 }
623
624 buf->curs1 = prev;
625
626 /* update file length */
627 buf->size--;
628
629 return c;
630 }
631
632 /* --------------------------------------------------------------------------------------------- */
633 /**
634 * Calculate forward offset with specified number of lines.
635 *
636 * @param buf editor buffer
637 * @param current current offset
638 * @param lines number of lines to move forward
639 * @param upto offset to count lines between current and upto.
640 *
641 * @return If lines is zero returns the count of lines from current to upto.
642 * If upto is zero returns offset of lines forward current.
643 * Else returns forward offset with specified number of lines
644 */
645
646 off_t
edit_buffer_get_forward_offset(const edit_buffer_t * buf,off_t current,long lines,off_t upto)647 edit_buffer_get_forward_offset (const edit_buffer_t * buf, off_t current, long lines, off_t upto)
648 {
649 if (upto != 0)
650 return (off_t) edit_buffer_count_lines (buf, current, upto);
651
652 lines = MAX (lines, 0);
653
654 while (lines-- != 0)
655 {
656 long next;
657
658 next = edit_buffer_get_eol (buf, current) + 1;
659 if (next > buf->size)
660 break;
661 current = next;
662 }
663
664 return current;
665 }
666
667 /* --------------------------------------------------------------------------------------------- */
668 /**
669 * Calculate backward offset with specified number of lines.
670 *
671 * @param buf editor buffer
672 * @param current current offset
673 * @param lines number of lines to move backward
674 *
675 * @return backward offset with specified number of lines.
676 */
677
678 off_t
edit_buffer_get_backward_offset(const edit_buffer_t * buf,off_t current,long lines)679 edit_buffer_get_backward_offset (const edit_buffer_t * buf, off_t current, long lines)
680 {
681 lines = MAX (lines, 0);
682 current = edit_buffer_get_bol (buf, current);
683
684 while (lines-- != 0 && current != 0)
685 current = edit_buffer_get_bol (buf, current - 1);
686
687 return current;
688 }
689
690 /* --------------------------------------------------------------------------------------------- */
691 /**
692 * Load file into editor buffer
693 *
694 * @param buf pointer to editor buffer
695 * @param fd file descriptor
696 * @param size file size
697 *
698 * @return number of read bytes
699 */
700
701 off_t
edit_buffer_read_file(edit_buffer_t * buf,int fd,off_t size,edit_buffer_read_file_status_msg_t * sm,gboolean * aborted)702 edit_buffer_read_file (edit_buffer_t * buf, int fd, off_t size,
703 edit_buffer_read_file_status_msg_t * sm, gboolean * aborted)
704 {
705 off_t ret = 0;
706 off_t i, j;
707 off_t data_size;
708 void *b;
709 status_msg_t *s = STATUS_MSG (sm);
710 unsigned short update_cnt = 0;
711
712 *aborted = FALSE;
713
714 buf->lines = 0;
715 buf->curs2 = size;
716 i = buf->curs2 >> S_EDIT_BUF_SIZE;
717
718 /* fill last part of b2 */
719 data_size = buf->curs2 & M_EDIT_BUF_SIZE;
720 if (data_size != 0)
721 {
722 b = g_malloc0 (EDIT_BUF_SIZE);
723 g_ptr_array_add (buf->b2, b);
724 b = (char *) b + EDIT_BUF_SIZE - data_size;
725 ret = mc_read (fd, b, data_size);
726
727 /* count lines */
728 for (j = 0; j < ret; j++)
729 if (*((char *) b + j) == '\n')
730 buf->lines++;
731
732 if (ret < 0 || ret != data_size)
733 return ret;
734 }
735
736 /* fulfill other parts of b2 from end to begin */
737 data_size = EDIT_BUF_SIZE;
738 for (--i; i >= 0; i--)
739 {
740 off_t sz;
741
742 b = g_malloc0 (data_size);
743 g_ptr_array_add (buf->b2, b);
744 sz = mc_read (fd, b, data_size);
745 if (sz >= 0)
746 ret += sz;
747
748 /* count lines */
749 for (j = 0; j < sz; j++)
750 if (*((char *) b + j) == '\n')
751 buf->lines++;
752
753 if (s != NULL && s->update != NULL)
754 {
755 update_cnt = (update_cnt + 1) & 0xf;
756 if (update_cnt == 0)
757 {
758 /* FIXME: overcare */
759 if (sm->buf == NULL)
760 sm->buf = buf;
761
762 sm->loaded = ret;
763 if (s->update (s) == B_CANCEL)
764 {
765 *aborted = TRUE;
766 return (-1);
767 }
768 }
769 }
770
771 if (sz != data_size)
772 break;
773 }
774
775 /* reverse buffer */
776 for (i = 0; i < (off_t) buf->b2->len / 2; i++)
777 {
778 void **b1, **b2;
779
780 b1 = &g_ptr_array_index (buf->b2, i);
781 b2 = &g_ptr_array_index (buf->b2, buf->b2->len - 1 - i);
782
783 b = *b1;
784 *b1 = *b2;
785 *b2 = b;
786
787 if (s != NULL && s->update != NULL)
788 {
789 update_cnt = (update_cnt + 1) & 0xf;
790 if (update_cnt == 0)
791 {
792 sm->loaded = ret;
793 if (s->update (s) == B_CANCEL)
794 {
795 *aborted = TRUE;
796 return (-1);
797 }
798 }
799 }
800 }
801
802 return ret;
803 }
804
805 /* --------------------------------------------------------------------------------------------- */
806 /**
807 * Write editor buffer content to file
808 *
809 * @param buf pointer to editor buffer
810 * @param fd file descriptor
811 *
812 * @return number of written bytes
813 */
814
815 off_t
edit_buffer_write_file(edit_buffer_t * buf,int fd)816 edit_buffer_write_file (edit_buffer_t * buf, int fd)
817 {
818 off_t ret = 0;
819 off_t i;
820 off_t data_size, sz;
821 void *b;
822
823 /* write all fulfilled parts of b1 from begin to end */
824 if (buf->b1->len != 0)
825 {
826 data_size = EDIT_BUF_SIZE;
827 for (i = 0; i < (off_t) buf->b1->len - 1; i++)
828 {
829 b = g_ptr_array_index (buf->b1, i);
830 sz = mc_write (fd, b, data_size);
831 if (sz >= 0)
832 ret += sz;
833 else if (i == 0)
834 ret = sz;
835 if (sz != data_size)
836 return ret;
837 }
838
839 /* write last partially filled part of b1 */
840 data_size = ((buf->curs1 - 1) & M_EDIT_BUF_SIZE) + 1;
841 b = g_ptr_array_index (buf->b1, i);
842 sz = mc_write (fd, b, data_size);
843 if (sz >= 0)
844 ret += sz;
845 if (sz != data_size)
846 return ret;
847 }
848
849 /* write b2 from end to begin, if b2 contains some data */
850 if (buf->b2->len != 0)
851 {
852 /* write last partially filled part of b2 */
853 i = buf->b2->len - 1;
854 b = g_ptr_array_index (buf->b2, i);
855 data_size = ((buf->curs2 - 1) & M_EDIT_BUF_SIZE) + 1;
856 sz = mc_write (fd, (char *) b + EDIT_BUF_SIZE - data_size, data_size);
857 if (sz >= 0)
858 ret += sz;
859
860 if (sz == data_size)
861 {
862 /* write other fulfilled parts of b2 from end to begin */
863 data_size = EDIT_BUF_SIZE;
864 while (--i >= 0)
865 {
866 b = g_ptr_array_index (buf->b2, i);
867 sz = mc_write (fd, b, data_size);
868 if (sz >= 0)
869 ret += sz;
870 if (sz != data_size)
871 break;
872 }
873 }
874 }
875
876 return ret;
877 }
878
879 /* --------------------------------------------------------------------------------------------- */
880 /**
881 * Calculate percentage of specified character offset
882 *
883 * @param buf pointer to editor buffer
884 * @param p character offset
885 *
886 * @return percentage of specified character offset
887 */
888
889 int
edit_buffer_calc_percent(const edit_buffer_t * buf,off_t offset)890 edit_buffer_calc_percent (const edit_buffer_t * buf, off_t offset)
891 {
892 int percent;
893
894 if (buf->size == 0)
895 percent = 0;
896 else if (offset >= buf->size)
897 percent = 100;
898 else if (offset > (INT_MAX / 100))
899 percent = offset / (buf->size / 100);
900 else
901 percent = offset * 100 / buf->size;
902
903 return percent;
904 }
905
906 /* --------------------------------------------------------------------------------------------- */
907