2 Editor text keep buffer.
4 Copyright (C) 2013-2016
5 Free Software Foundation, Inc.
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/>.
27 * \brief Source: editor text keep buffer.
28 * \author Andrew Borodin
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 /* --------------------------------------------------------------------------------------------- */
48 * here's a quick sketch of the layout: (don't run this through indent.)
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 * ______________________________________|______________________________________
54 * ... | b2[2] | b2[1] | b2[0] | b1[0] | b1[1] | b1[2] | ...
55 * |-> |-> |-> |-> |-> |-> |
57 * _<------------------------->|<----------------->_
63 * file end|||file beginning
72 * This is called a "gap buffer".
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
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.
114 edit_buffer_get_byte_ptr (const edit_buffer_t
* buf
, off_t byte_index
)
118 if (byte_index
>= (buf
->curs1
+ buf
->curs2
) || byte_index
< 0)
121 if (byte_index
>= buf
->curs1
)
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
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);
156 /* --------------------------------------------------------------------------------------------- */
158 * Clean editor buffers.
160 * @param buf pointer to editor buffer
164 edit_buffer_clean (edit_buffer_t
* buf
)
168 g_ptr_array_foreach (buf
->b1
, (GFunc
) g_free
, NULL
);
169 g_ptr_array_free (buf
->b1
, TRUE
);
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
)
194 p
= edit_buffer_get_byte_ptr (buf
, byte_index
);
196 return (p
!= NULL
) ? *(unsigned char *) p
: '\n';
199 /* --------------------------------------------------------------------------------------------- */
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
)
220 gchar
*next_ch
= NULL
;
222 if (byte_index
>= (buf
->curs1
+ buf
->curs2
) || byte_index
< 0)
228 str
= edit_buffer_get_byte_ptr (buf
, byte_index
);
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 */
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
);
245 res
= g_utf8_get_char_validated (utf8_buf
, -1);
248 if (res
== (gunichar
) (-2) || res
== (gunichar
) (-1))
256 /* Calculate UTF-8 char length */
257 next_ch
= g_utf8_next_char (str
);
258 *char_length
= next_ch
- str
;
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
)
281 gchar utf8_buf
[3 * UTF8_CHAR_LEN
+ 1];
283 gchar
*cursor_buf_ptr
;
286 if (byte_index
> (buf
->curs1
+ buf
->curs2
) || byte_index
<= 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
));
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
)
302 return *(cursor_buf_ptr
- 1);
305 res
= g_utf8_get_char_validated (str
, -1);
306 if (res
== (gunichar
) (-2) || res
== (gunichar
) (-1))
309 return *(cursor_buf_ptr
- 1);
312 *char_length
= cursor_buf_ptr
- str
;
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
329 edit_buffer_count_lines (const edit_buffer_t
* buf
, off_t first
, off_t last
)
333 first
= max (first
, 0);
334 last
= min (last
, buf
->size
);
337 if (edit_buffer_get_byte (buf
, first
++) == '\n')
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
354 edit_buffer_get_bol (const edit_buffer_t
* buf
, off_t current
)
359 for (; edit_buffer_get_byte (buf
, current
- 1) != '\n'; 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
376 edit_buffer_get_eol (const edit_buffer_t
* buf
, off_t current
)
378 if (current
>= buf
->size
)
381 for (; edit_buffer_get_byte (buf
, current
) != '\n'; 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
396 * @return word as newly allocated object
400 edit_buffer_get_word_from_pos (const edit_buffer_t
* buf
, off_t start_pos
, off_t
* start
,
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')
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'));
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
443 edit_buffer_insert (edit_buffer_t
* buf
, int c
)
448 i
= buf
->curs1
& M_EDIT_BUF_SIZE
;
450 /* add a new buffer if we've reached the end of the last one */
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 */
461 /* update file length */
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
475 edit_buffer_insert_ahead (edit_buffer_t
* buf
, int c
)
480 i
= buf
->curs2
& M_EDIT_BUF_SIZE
;
482 /* add a new buffer if we've reached the end of the last one */
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 */
493 /* update file length */
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
)
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
);
524 j
= buf
->b2
->len
- 1;
525 b
= g_ptr_array_index (buf
->b2
, j
);
526 g_ptr_array_remove_index (buf
->b2
, j
);
532 /* update file length */
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
)
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
);
565 j
= buf
->b1
->len
- 1;
566 b
= g_ptr_array_index (buf
->b1
, j
);
567 g_ptr_array_remove_index (buf
->b1
, j
);
573 /* update file length */
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
594 edit_buffer_get_forward_offset (const edit_buffer_t
* buf
, off_t current
, long lines
, off_t upto
)
597 return (off_t
) edit_buffer_count_lines (buf
, current
, upto
);
599 lines
= max (lines
, 0);
605 next
= edit_buffer_get_eol (buf
, current
) + 1;
606 if (next
> buf
->size
)
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.
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);
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
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
)
656 status_msg_t
*s
= STATUS_MSG (sm
);
657 unsigned short update_cnt
= 0;
663 i
= buf
->curs2
>> S_EDIT_BUF_SIZE
;
665 /* fill last part of b2 */
666 data_size
= buf
->curs2
& M_EDIT_BUF_SIZE
;
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
);
675 for (j
= 0; j
< ret
; j
++)
676 if (*((char *) b
+ j
) == '\n')
679 if (ret
< 0 || ret
!= data_size
)
683 /* fulfill other parts of b2 from end to begin */
684 data_size
= EDIT_BUF_SIZE
;
685 for (--i
; i
>= 0; i
--)
689 b
= g_malloc0 (data_size
);
690 g_ptr_array_add (buf
->b2
, b
);
691 sz
= mc_read (fd
, b
, data_size
);
696 for (j
= 0; j
< sz
; j
++)
697 if (*((char *) b
+ j
) == '\n')
700 if (s
!= NULL
&& s
->update
!= NULL
)
702 update_cnt
= (update_cnt
+ 1) & 0xf;
705 /* FIXME: overcare */
710 if (s
->update (s
) == B_CANCEL
)
723 for (i
= 0; i
< (off_t
) buf
->b2
->len
/ 2; i
++)
727 b1
= &g_ptr_array_index (buf
->b2
, i
);
728 b2
= &g_ptr_array_index (buf
->b2
, buf
->b2
->len
- 1 - i
);
734 if (s
!= NULL
&& s
->update
!= NULL
)
736 update_cnt
= (update_cnt
+ 1) & 0xf;
740 if (s
->update (s
) == B_CANCEL
)
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
763 edit_buffer_write_file (edit_buffer_t
* buf
, int fd
)
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
);
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
);
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
);
809 /* write other fulfilled parts of b2 from end to begin */
810 data_size
= EDIT_BUF_SIZE
;
813 b
= g_ptr_array_index (buf
->b2
, i
);
814 sz
= mc_write (fd
, b
, data_size
);
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
)
843 else if (offset
>= buf
->size
)
845 else if (offset
> (INT_MAX
/ 100))
846 percent
= offset
/ (buf
->size
/ 100);
848 percent
= offset
* 100 / buf
->size
;
853 /* --------------------------------------------------------------------------------------------- */