mcedit: rename functions:
[midnight-commander.git] / src / editor / editbuffer.c
blobb3c5c06f55aabac4b0472b2ae5413760584fc576
1 /*
2 Editor text keep buffer.
4 Copyright (C) 2013-2016
5 Free Software Foundation, Inc.
7 Written by:
8 Andrew Borodin <aborodin@vmail.ru> 2013
10 This file is part of the Midnight Commander.
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.
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.
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/>.
26 /** \file
27 * \brief Source: editor text keep buffer.
28 * \author Andrew Borodin
29 * \date 2013
32 #include <config.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
38 #include "lib/global.h"
40 #include "lib/vfs/vfs.h"
42 #include "edit-impl.h"
43 #include "editbuffer.h"
45 /* --------------------------------------------------------------------------------------------- */
46 /*-
48 * here's a quick sketch of the layout: (don't run this through indent.)
50 * |
51 * \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
52 * ______________________________________|______________________________________
53 * |
54 * ... | b2[2] | b2[1] | b2[0] | b1[0] | b1[1] | b1[2] | ...
55 * |-> |-> |-> |-> |-> |-> |
56 * |
57 * _<------------------------->|<----------------->_
58 * curs2 | curs1
59 * ^ | ^
60 * | ^|^ |
61 * cursor ||| cursor
62 * |||
63 * file end|||file beginning
64 * |
65 * |
67 * _
68 * This_is_some_file
69 * fin.
72 * This is called a "gap buffer".
73 * See also:
74 * http://en.wikipedia.org/wiki/Gap_buffer
75 * http://stackoverflow.com/questions/4199694/data-structure-for-text-editor
78 /*** global variables ****************************************************************************/
80 /*** file scope macro definitions ****************************************************************/
83 * The editor keeps data in two arrays of buffers.
84 * All buffers have the same size, which must be a power of 2.
87 /* Configurable: log2 of the buffer size in bytes */
88 #ifndef S_EDIT_BUF_SIZE
89 #define S_EDIT_BUF_SIZE 16
90 #endif
92 /* Size of the buffer */
93 #define EDIT_BUF_SIZE (((off_t) 1) << S_EDIT_BUF_SIZE)
95 /* Buffer mask (used to find cursor position relative to the buffer) */
96 #define M_EDIT_BUF_SIZE (EDIT_BUF_SIZE - 1)
98 /*** file scope type declarations ****************************************************************/
100 /*** file scope variables ************************************************************************/
102 /* --------------------------------------------------------------------------------------------- */
103 /*** file scope functions ************************************************************************/
104 /* --------------------------------------------------------------------------------------------- */
106 * Get pointer to byte at specified index
108 * @param buf pointer to editor buffer
109 * @param byte_index byte index
111 * @return NULL if byte_index is negative or larger than file size; pointer to byte otherwise.
113 static char *
114 edit_buffer_get_byte_ptr (const edit_buffer_t * buf, off_t byte_index)
116 void *b;
118 if (byte_index >= (buf->curs1 + buf->curs2) || byte_index < 0)
119 return NULL;
121 if (byte_index >= buf->curs1)
123 off_t p;
125 p = buf->curs1 + buf->curs2 - byte_index - 1;
126 b = g_ptr_array_index (buf->b2, p >> S_EDIT_BUF_SIZE);
127 return (char *) b + EDIT_BUF_SIZE - 1 - (p & M_EDIT_BUF_SIZE);
130 b = g_ptr_array_index (buf->b1, byte_index >> S_EDIT_BUF_SIZE);
131 return (char *) b + (byte_index & M_EDIT_BUF_SIZE);
134 /* --------------------------------------------------------------------------------------------- */
135 /*** public functions ****************************************************************************/
136 /* --------------------------------------------------------------------------------------------- */
138 * Initialize editor buffers.
140 * @param buf pointer to editor buffer
143 void
144 edit_buffer_init (edit_buffer_t * buf, off_t size)
146 buf->b1 = g_ptr_array_sized_new (32);
147 buf->b2 = g_ptr_array_sized_new (32);
149 buf->curs1 = 0;
150 buf->curs2 = 0;
152 buf->size = size;
153 buf->lines = 0;
156 /* --------------------------------------------------------------------------------------------- */
158 * Clean editor buffers.
160 * @param buf pointer to editor buffer
163 void
164 edit_buffer_clean (edit_buffer_t * buf)
166 if (buf->b1 != NULL)
168 g_ptr_array_foreach (buf->b1, (GFunc) g_free, NULL);
169 g_ptr_array_free (buf->b1, TRUE);
172 if (buf->b2 != NULL)
174 g_ptr_array_foreach (buf->b2, (GFunc) g_free, NULL);
175 g_ptr_array_free (buf->b2, TRUE);
179 /* --------------------------------------------------------------------------------------------- */
181 * Get byte at specified index
183 * @param buf pointer to editor buffer
184 * @param byte_index byte index
186 * @return '\n' if byte_index is negative or larger than file size; byte at byte_index otherwise.
190 edit_buffer_get_byte (const edit_buffer_t * buf, off_t byte_index)
192 char *p;
194 p = edit_buffer_get_byte_ptr (buf, byte_index);
196 return (p != NULL) ? *(unsigned char *) p : '\n';
199 /* --------------------------------------------------------------------------------------------- */
201 #ifdef HAVE_CHARSET
203 * Get utf-8 symbol at specified index
205 * @param buf pointer to editor buffer
206 * @param byte_index byte index
207 * @param char_length length of returned symbol
209 * @return '\n' if byte_index is negative or larger than file size;
210 * 0 if utf-8 symbol at specified index is invalid;
211 * utf-8 symbol otherwise
215 edit_buffer_get_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length)
217 gchar *str = NULL;
218 gunichar res;
219 gunichar ch;
220 gchar *next_ch = NULL;
222 if (byte_index >= (buf->curs1 + buf->curs2) || byte_index < 0)
224 *char_length = 0;
225 return '\n';
228 str = edit_buffer_get_byte_ptr (buf, byte_index);
229 if (str == NULL)
231 *char_length = 0;
232 return 0;
235 res = g_utf8_get_char_validated (str, -1);
236 if (res == (gunichar) (-2) || res == (gunichar) (-1))
238 /* Retry with explicit bytes to make sure it's not a buffer boundary */
239 size_t i;
240 gchar utf8_buf[UTF8_CHAR_LEN + 1];
242 for (i = 0; i < UTF8_CHAR_LEN; i++)
243 utf8_buf[i] = edit_buffer_get_byte (buf, byte_index + i);
244 utf8_buf[i] = '\0';
245 res = g_utf8_get_char_validated (utf8_buf, -1);
248 if (res == (gunichar) (-2) || res == (gunichar) (-1))
250 ch = *str;
251 *char_length = 0;
253 else
255 ch = res;
256 /* Calculate UTF-8 char length */
257 next_ch = g_utf8_next_char (str);
258 *char_length = next_ch - str;
261 return (int) ch;
264 /* --------------------------------------------------------------------------------------------- */
266 * Get utf-8 symbol before specified index
268 * @param buf pointer to editor buffer
269 * @param byte_index byte index
270 * @param char_length length of returned symbol
272 * @return 0 if byte_index is negative or larger than file size;
273 * 1-byte value before specified index if utf-8 symbol before specified index is invalid;
274 * utf-8 symbol otherwise
278 edit_buffer_get_prev_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length)
280 size_t i;
281 gchar utf8_buf[3 * UTF8_CHAR_LEN + 1];
282 gchar *str;
283 gchar *cursor_buf_ptr;
284 gunichar res;
286 if (byte_index > (buf->curs1 + buf->curs2) || byte_index <= 0)
288 *char_length = 0;
289 return 0;
292 for (i = 0; i < (3 * UTF8_CHAR_LEN); i++)
293 utf8_buf[i] = edit_buffer_get_byte (buf, byte_index + i - (2 * UTF8_CHAR_LEN));
294 utf8_buf[i] = '\0';
296 cursor_buf_ptr = utf8_buf + (2 * UTF8_CHAR_LEN);
297 str = g_utf8_find_prev_char (utf8_buf, cursor_buf_ptr);
299 if (str == NULL || g_utf8_next_char (str) != cursor_buf_ptr)
301 *char_length = 1;
302 return *(cursor_buf_ptr - 1);
305 res = g_utf8_get_char_validated (str, -1);
306 if (res == (gunichar) (-2) || res == (gunichar) (-1))
308 *char_length = 1;
309 return *(cursor_buf_ptr - 1);
312 *char_length = cursor_buf_ptr - str;
313 return (int) res;
315 #endif /* HAVE_CHARSET */
317 /* --------------------------------------------------------------------------------------------- */
319 * Count lines in editor buffer.
321 * @param buf editor buffer
322 * @param first start byte offset
323 * @param last finish byte offset
325 * @return line numbers between "first" and "last" bytes
328 long
329 edit_buffer_count_lines (const edit_buffer_t * buf, off_t first, off_t last)
331 long lines = 0;
333 first = max (first, 0);
334 last = min (last, buf->size);
336 while (first < last)
337 if (edit_buffer_get_byte (buf, first++) == '\n')
338 lines++;
340 return lines;
343 /* --------------------------------------------------------------------------------------------- */
345 * Get "begin-of-line" offset of line contained specified byte offset
347 * @param buf editor buffer
348 * @param current byte offset
350 * @return index of first char of line
353 off_t
354 edit_buffer_get_bol (const edit_buffer_t * buf, off_t current)
356 if (current <= 0)
357 return 0;
359 for (; edit_buffer_get_byte (buf, current - 1) != '\n'; current--)
362 return current;
365 /* --------------------------------------------------------------------------------------------- */
367 * Get "end-of-line" offset of line contained specified byte offset
369 * @param buf editor buffer
370 * @param current byte offset
372 * @return index of last char of line + 1
375 off_t
376 edit_buffer_get_eol (const edit_buffer_t * buf, off_t current)
378 if (current >= buf->size)
379 return buf->size;
381 for (; edit_buffer_get_byte (buf, current) != '\n'; current++)
384 return current;
387 /* --------------------------------------------------------------------------------------------- */
389 * Get word from specified offset.
391 * @param buf editor buffer
392 * @param current start_pos offset
393 * @param start actual start word ofset
394 * @param cut
396 * @return word as newly allocated object
399 GString *
400 edit_buffer_get_word_from_pos (const edit_buffer_t * buf, off_t start_pos, off_t * start,
401 gsize * cut)
403 off_t word_start;
404 gsize cut_len = 0;
405 GString *match_expr;
406 int c1, c2;
408 for (word_start = start_pos; word_start != 0; word_start--, cut_len++)
410 c1 = edit_buffer_get_byte (buf, word_start);
411 c2 = edit_buffer_get_byte (buf, word_start - 1);
413 if (is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n')
414 break;
417 match_expr = g_string_sized_new (16);
421 c1 = edit_buffer_get_byte (buf, word_start + match_expr->len);
422 c2 = edit_buffer_get_byte (buf, word_start + match_expr->len + 1);
423 g_string_append_c (match_expr, c1);
425 while (!(is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n'));
427 *start = word_start;
428 *cut = cut_len;
430 return match_expr;
433 /* --------------------------------------------------------------------------------------------- */
435 * Basic low level single character buffer alterations and movements at the cursor: insert character
436 * at the cursor position and move right.
438 * @param buf pointer to editor buffer
439 * @param c character to insert
442 void
443 edit_buffer_insert (edit_buffer_t * buf, int c)
445 void *b;
446 off_t i;
448 i = buf->curs1 & M_EDIT_BUF_SIZE;
450 /* add a new buffer if we've reached the end of the last one */
451 if (i == 0)
452 g_ptr_array_add (buf->b1, g_malloc0 (EDIT_BUF_SIZE));
454 /* perform the insertion */
455 b = g_ptr_array_index (buf->b1, buf->curs1 >> S_EDIT_BUF_SIZE);
456 *((unsigned char *) b + i) = (unsigned char) c;
458 /* update cursor position */
459 buf->curs1++;
461 /* update file length */
462 buf->size++;
465 /* --------------------------------------------------------------------------------------------- */
467 * Basic low level single character buffer alterations and movements at the cursor: insert character
468 * at the cursor position and move left.
470 * @param buf pointer to editor buffer
471 * @param c character to insert
474 void
475 edit_buffer_insert_ahead (edit_buffer_t * buf, int c)
477 void *b;
478 off_t i;
480 i = buf->curs2 & M_EDIT_BUF_SIZE;
482 /* add a new buffer if we've reached the end of the last one */
483 if (i == 0)
484 g_ptr_array_add (buf->b2, g_malloc0 (EDIT_BUF_SIZE));
486 /* perform the insertion */
487 b = g_ptr_array_index (buf->b2, buf->curs2 >> S_EDIT_BUF_SIZE);
488 *((unsigned char *) b + EDIT_BUF_SIZE - 1 - i) = (unsigned char) c;
490 /* update cursor position */
491 buf->curs2++;
493 /* update file length */
494 buf->size++;
497 /* --------------------------------------------------------------------------------------------- */
499 * Basic low level single character buffer alterations and movements at the cursor: delete character
500 * at the cursor position.
502 * @param buf pointer to editor buffer
503 * @param c character to insert
507 edit_buffer_delete (edit_buffer_t * buf)
509 void *b;
510 unsigned char c;
511 off_t prev;
512 off_t i;
514 prev = buf->curs2 - 1;
516 b = g_ptr_array_index (buf->b2, prev >> S_EDIT_BUF_SIZE);
517 i = prev & M_EDIT_BUF_SIZE;
518 c = *((unsigned char *) b + EDIT_BUF_SIZE - 1 - i);
520 if (i == 0)
522 guint j;
524 j = buf->b2->len - 1;
525 b = g_ptr_array_index (buf->b2, j);
526 g_ptr_array_remove_index (buf->b2, j);
527 g_free (b);
530 buf->curs2 = prev;
532 /* update file length */
533 buf->size--;
535 return c;
538 /* --------------------------------------------------------------------------------------------- */
540 * Basic low level single character buffer alterations and movements at the cursor: delete character
541 * before the cursor position and move left.
543 * @param buf pointer to editor buffer
544 * @param c character to insert
548 edit_buffer_backspace (edit_buffer_t * buf)
550 void *b;
551 unsigned char c;
552 off_t prev;
553 off_t i;
555 prev = buf->curs1 - 1;
557 b = g_ptr_array_index (buf->b1, prev >> S_EDIT_BUF_SIZE);
558 i = prev & M_EDIT_BUF_SIZE;
559 c = *((unsigned char *) b + i);
561 if (i == 0)
563 guint j;
565 j = buf->b1->len - 1;
566 b = g_ptr_array_index (buf->b1, j);
567 g_ptr_array_remove_index (buf->b1, j);
568 g_free (b);
571 buf->curs1 = prev;
573 /* update file length */
574 buf->size--;
576 return c;
579 /* --------------------------------------------------------------------------------------------- */
581 * Calculate forward offset with specified number of lines.
583 * @param buf editor buffer
584 * @param current current offset
585 * @param lines number of lines to move forward
586 * @param upto offset to count lines between current and upto.
588 * @return If lines is zero returns the count of lines from current to upto.
589 * If upto is zero returns offset of lines forward current.
590 * Else returns forward offset with specified number of lines
593 off_t
594 edit_buffer_get_forward_offset (const edit_buffer_t * buf, off_t current, long lines, off_t upto)
596 if (upto != 0)
597 return (off_t) edit_buffer_count_lines (buf, current, upto);
599 lines = max (lines, 0);
601 while (lines-- != 0)
603 long next;
605 next = edit_buffer_get_eol (buf, current) + 1;
606 if (next > buf->size)
607 break;
608 current = next;
611 return current;
614 /* --------------------------------------------------------------------------------------------- */
616 * Calculate backward offset with specified number of lines.
618 * @param buf editor buffer
619 * @param current current offset
620 * @param lines number of lines to move backward
622 * @return backward offset with specified number of lines.
625 off_t
626 edit_buffer_get_backward_offset (const edit_buffer_t * buf, off_t current, long lines)
628 lines = max (lines, 0);
629 current = edit_buffer_get_bol (buf, current);
631 while (lines-- != 0 && current != 0)
632 current = edit_buffer_get_bol (buf, current - 1);
634 return current;
637 /* --------------------------------------------------------------------------------------------- */
639 * Load file into editor buffer
641 * @param buf pointer to editor buffer
642 * @param fd file descriptor
643 * @param size file size
645 * @return number of read bytes
648 off_t
649 edit_buffer_read_file (edit_buffer_t * buf, int fd, off_t size,
650 edit_buffer_read_file_status_msg_t * sm, gboolean * aborted)
652 off_t ret = 0;
653 off_t i, j;
654 off_t data_size;
655 void *b;
656 status_msg_t *s = STATUS_MSG (sm);
657 unsigned short update_cnt = 0;
659 *aborted = FALSE;
661 buf->lines = 0;
662 buf->curs2 = size;
663 i = buf->curs2 >> S_EDIT_BUF_SIZE;
665 /* fill last part of b2 */
666 data_size = buf->curs2 & M_EDIT_BUF_SIZE;
667 if (data_size != 0)
669 b = g_malloc0 (EDIT_BUF_SIZE);
670 g_ptr_array_add (buf->b2, b);
671 b = (char *) b + EDIT_BUF_SIZE - data_size;
672 ret = mc_read (fd, b, data_size);
674 /* count lines */
675 for (j = 0; j < ret; j++)
676 if (*((char *) b + j) == '\n')
677 buf->lines++;
679 if (ret < 0 || ret != data_size)
680 return ret;
683 /* fulfill other parts of b2 from end to begin */
684 data_size = EDIT_BUF_SIZE;
685 for (--i; i >= 0; i--)
687 off_t sz;
689 b = g_malloc0 (data_size);
690 g_ptr_array_add (buf->b2, b);
691 sz = mc_read (fd, b, data_size);
692 if (sz >= 0)
693 ret += sz;
695 /* count lines */
696 for (j = 0; j < sz; j++)
697 if (*((char *) b + j) == '\n')
698 buf->lines++;
700 if (s != NULL && s->update != NULL)
702 update_cnt = (update_cnt + 1) & 0xf;
703 if (update_cnt == 0)
705 /* FIXME: overcare */
706 if (sm->buf == NULL)
707 sm->buf = buf;
709 sm->loaded = ret;
710 if (s->update (s) == B_CANCEL)
712 *aborted = TRUE;
713 return (-1);
718 if (sz != data_size)
719 break;
722 /* reverse buffer */
723 for (i = 0; i < (off_t) buf->b2->len / 2; i++)
725 void **b1, **b2;
727 b1 = &g_ptr_array_index (buf->b2, i);
728 b2 = &g_ptr_array_index (buf->b2, buf->b2->len - 1 - i);
730 b = *b1;
731 *b1 = *b2;
732 *b2 = b;
734 if (s != NULL && s->update != NULL)
736 update_cnt = (update_cnt + 1) & 0xf;
737 if (update_cnt == 0)
739 sm->loaded = ret;
740 if (s->update (s) == B_CANCEL)
742 *aborted = TRUE;
743 return (-1);
749 return ret;
752 /* --------------------------------------------------------------------------------------------- */
754 * Write editor buffer content to file
756 * @param buf pointer to editor buffer
757 * @param fd file descriptor
759 * @return number of written bytes
762 off_t
763 edit_buffer_write_file (edit_buffer_t * buf, int fd)
765 off_t ret = 0;
766 off_t i;
767 off_t data_size, sz;
768 void *b;
770 /* write all fulfilled parts of b1 from begin to end */
771 if (buf->b1->len != 0)
773 data_size = EDIT_BUF_SIZE;
774 for (i = 0; i < (off_t) buf->b1->len - 1; i++)
776 b = g_ptr_array_index (buf->b1, i);
777 sz = mc_write (fd, b, data_size);
778 if (sz >= 0)
779 ret += sz;
780 else if (i == 0)
781 ret = sz;
782 if (sz != data_size)
783 return ret;
786 /* write last partially filled part of b1 */
787 data_size = ((buf->curs1 - 1) & M_EDIT_BUF_SIZE) + 1;
788 b = g_ptr_array_index (buf->b1, i);
789 sz = mc_write (fd, b, data_size);
790 if (sz >= 0)
791 ret += sz;
792 if (sz != data_size)
793 return ret;
796 /* write b2 from end to begin, if b2 contains some data */
797 if (buf->b2->len != 0)
799 /* write last partially filled part of b2 */
800 i = buf->b2->len - 1;
801 b = g_ptr_array_index (buf->b2, i);
802 data_size = ((buf->curs2 - 1) & M_EDIT_BUF_SIZE) + 1;
803 sz = mc_write (fd, (char *) b + EDIT_BUF_SIZE - data_size, data_size);
804 if (sz >= 0)
805 ret += sz;
807 if (sz == data_size)
809 /* write other fulfilled parts of b2 from end to begin */
810 data_size = EDIT_BUF_SIZE;
811 while (--i >= 0)
813 b = g_ptr_array_index (buf->b2, i);
814 sz = mc_write (fd, b, data_size);
815 if (sz >= 0)
816 ret += sz;
817 if (sz != data_size)
818 break;
823 return ret;
826 /* --------------------------------------------------------------------------------------------- */
828 * Calculate percentage of specified character offset
830 * @param buf pointer to editor buffer
831 * @param p character offset
833 * @return percentage of specified character offset
837 edit_buffer_calc_percent (const edit_buffer_t * buf, off_t offset)
839 int percent;
841 if (buf->size == 0)
842 percent = 0;
843 else if (offset >= buf->size)
844 percent = 100;
845 else if (offset > (INT_MAX / 100))
846 percent = offset / (buf->size / 100);
847 else
848 percent = offset * 100 / buf->size;
850 return percent;
853 /* --------------------------------------------------------------------------------------------- */