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