ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 3572b3d7dd300642a94067f72f30fc83c8651e39
parent 123a3087ad0bc4f772f0cd0a17caf95304092f87
Author: lumidify <nobody@lumidify.org>
Date:   Thu,  9 Dec 2021 11:01:05 +0100

Move undo handling to buffer

Diffstat:
Mbuffer.c | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mbuffer.h | 197++++++++++++++++++++++++++++---------------------------------------------------
Mkeys_basic.c | 109+++++++++++++++++++++++++++++++------------------------------------------------
Mkeys_command.c | 47+++++++++++++++++++----------------------------
Mundo.c | 6++++++
Mundo.h | 9+++++++++
Mview.c | 361+++++++++++++++++++++++++++----------------------------------------------------
Mview.h | 23++++++++++++++++-------
8 files changed, 553 insertions(+), 535 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -73,10 +73,103 @@ static char *strchr_len(char *text, char c, size_t len); */ static void init_line(ledit_buffer *buffer, ledit_line *line); +/* + * Insert 'src_len' bytes from 'src_line' starting at byte position 'src_index' + * into line 'dst_line' at byte position 'dst_index'. + * 'dst_line' must not be the same as 'src_line'. + * If 'text_ret' is not NULL, the copied text is additionally copied into 'text_ret'. + * This function does not update the views or normalize the lines, so it should + * only be used for efficiency purposes when performing multiple operations. + */ +static void buffer_insert_text_from_line_base( + ledit_buffer *buffer, + size_t dst_line, size_t dst_index, + size_t src_line, size_t src_index, size_t src_len, + txtbuf *text_ret +); + +/* + * Insert text 'text' with length 'len' at line 'line_index' and byte position 'index'. + * The text must not contain newlines. + * This function does not update the views or normalize the lines, so it should + * only be used for efficiency purposes when performing multiple operations. + */ +static void buffer_insert_text_base( + ledit_buffer *buffer, + size_t line_index, size_t index, + char *text, size_t len +); + +/* Insert text 'text' with length 'len' at line 'line_index' and byte position 'index. + * The text may contain newlines. + * If end_line_ret is not NULL, the line index at the end of the insertion is + * written into it. + * If end_byte_ret is not NULL, the byte position at the end of the insertion is + * written into it. + * This function does not update the views or normalize the lines, so it should + * only be used for efficiency purposes when performing multiple operations. + */ +static void buffer_insert_text_with_newlines_base( + ledit_buffer *buffer, + size_t line_index, size_t index, + char *text, size_t len, + size_t *end_line_ret, size_t *end_char_ret +); + +/* + * Same as buffer_insert_text_with_newlines_base, but the views are updated afterwards. + */ +static void buffer_insert_text_with_newlines( + ledit_buffer *buffer, + size_t line_index, size_t index, + char *text, size_t len, + size_t *end_line_ret, size_t *end_char_ret +); + +/* + * Append line after line at 'line_index'. + * if 'break_text' is not 0, the text on line 'line_index' starting at + * byte index 'text_index' is moved to the newly appended line. + * The views are notified that a line has been appended, but not told to update + * their line heights and offsets. + */ +static void buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text); + +/* + * Delete lines between 'index1' and 'index2' (inclusive). + * The views are notified of the deletion but not told to + * update their line heights and offsets. + */ +static void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2); + +/* + * Delete the section of line 'line' starting at byte 'start' with length 'length' + * and notify the views. + * Note that this does not tell the views to recalculate their line heights and offsets. + */ +static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length); + static void marklist_destroy(ledit_buffer_marklist *marklist); static ledit_buffer_marklist *marklist_create(void); static void +swap_sz(size_t *a, size_t *b) { + size_t tmp = *a; + *a = *b; + *b = tmp; +} + +static void +sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2) { + if (*l1 == *l2 && *b1 > *b2) { + swap_sz(b1, b2); + } else if (*l1 > *l2) { + swap_sz(l1, l2); + swap_sz(b1, b2); + } +} + +static void marklist_destroy(ledit_buffer_marklist *marklist) { for (size_t i = 0; i < marklist->len; i++) { free(marklist->marks[i].text); @@ -324,22 +417,10 @@ buffer_normalize_line(ledit_line *line) { /* FIXME: To simplify this a bit, maybe just copy text to txtbuf first and then insert it in one go instead of having this complex logic */ -void -buffer_insert_text_from_line( - ledit_buffer *buffer, - size_t dst_line, size_t dst_index, - size_t src_line, size_t src_index, size_t src_len, - txtbuf *text_ret) { - buffer_insert_text_from_line_base( - buffer, dst_line, dst_index, src_line, src_index, src_len, text_ret - ); - buffer_recalc_line(buffer, dst_line); -} - /* FIXME: check if there can be bugs when a newline is inserted in some way other than pasting or pressing enter */ -void +static void buffer_insert_text_from_line_base( ledit_buffer *buffer, size_t dst_line, size_t dst_index, @@ -452,13 +533,7 @@ resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index) { ); } -void -buffer_insert_text(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) { - buffer_insert_text_base(buffer, line_index, index, text, len); - buffer_recalc_line(buffer, line_index); -} - -void +static void buffer_insert_text_base(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) { ledit_line *line = buffer_get_line(buffer, line_index); /* \0 is not included in line->len */ @@ -487,7 +562,7 @@ strchr_len(char *text, char c, size_t len) { } /* FIXME: make these functions that call recalc* also be final as described above */ -void +static void buffer_insert_text_with_newlines( ledit_buffer *buffer, size_t line_index, size_t index, @@ -507,7 +582,7 @@ buffer_insert_text_with_newlines( } /* FIXME: also look for \r */ -void +static void buffer_insert_text_with_newlines_base( ledit_buffer *buffer, size_t line_index, size_t index, @@ -546,14 +621,8 @@ init_line(ledit_buffer *buffer, ledit_line *line) { line->len = 0; } -void -buffer_append_line(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) { - buffer_append_line_base(buffer, line_index, text_index, break_text); - buffer_recalc_from_line(buffer, line_index); -} - /* FIXME: error checking (index out of bounds, etc.) */ -void +static void buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) { size_t new_len = buffer->lines_num + 1; if (new_len <= buffer->lines_num) @@ -582,15 +651,8 @@ buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_ind } } -/* FIXME: set offset to 0 when recalculating first line? */ -void -buffer_delete_line_entries(ledit_buffer *buffer, size_t index1, size_t index2) { - buffer_delete_line_entries_base(buffer, index1, index2); - buffer_recalc_from_line(buffer, index1 > 0 ? index1 - 1 : 0); -} - /* IMPORTANT: buffer_recalc_from_line needs to be called sometime after this! */ -void +static void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2) { ledit_line *l; assert (index2 >= index1); @@ -607,16 +669,6 @@ buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t inde } } -void -buffer_delete_line_entry(ledit_buffer *buffer, size_t index) { - buffer_delete_line_entries(buffer, index, index); -} - -void -buffer_delete_line_entry_base(ledit_buffer *buffer, size_t index) { - buffer_delete_line_entries_base(buffer, index, index); -} - ledit_line * buffer_get_line(ledit_buffer *buffer, size_t index) { assert(index < buffer->lines_num); @@ -753,7 +805,7 @@ line_byte_to_char(ledit_line *line, size_t byte) { return c; } -void +static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) { ledit_line *l = buffer_get_line(buffer, line); if (start <= l->gap && start + length >= l->gap) { @@ -778,24 +830,178 @@ buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, } } -size_t -buffer_delete_unicode_char(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir) { - size_t new_index = buffer_delete_unicode_char_base(buffer, line_index, byte_index, dir); - buffer_recalc_line(buffer, line_index); - return new_index; +static void +delete_range_base( + ledit_buffer *buffer, + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + txtbuf *text_ret) { + sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2); + if (line_index1 == line_index2) { + if (text_ret) { + buffer_copy_text_to_txtbuf( + buffer, text_ret, + line_index1, byte_index1, + line_index2, byte_index2 + ); + } + buffer_delete_line_section_base( + buffer, line_index1, byte_index1, byte_index2 - byte_index1 + ); + } else { + if (text_ret) { + buffer_copy_text_to_txtbuf( + buffer, text_ret, + line_index1, byte_index1, + line_index2, byte_index2 + ); + } + ledit_line *line1 = buffer_get_line(buffer, line_index1); + ledit_line *line2 = buffer_get_line(buffer, line_index2); + buffer_delete_line_section_base( + buffer, line_index1, byte_index1, line1->len - byte_index1 + ); + buffer_insert_text_from_line_base( + buffer, + line_index1, byte_index1, + line_index2, byte_index2, + line2->len - byte_index2, NULL + ); + buffer_delete_line_entries_base( + buffer, line_index1 + 1, line_index2 + ); + } } -size_t -buffer_delete_unicode_char_base(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir) { - ledit_line *l = buffer_get_line(buffer, line_index); - size_t new_index = byte_index; - if (dir < 0) { - size_t i = line_prev_utf8(l, byte_index); - buffer_delete_line_section_base(buffer, line_index, i, byte_index - i); - new_index = i; - } else { - size_t i = line_next_utf8(l, byte_index); - buffer_delete_line_section_base(buffer, line_index, byte_index, i - byte_index); +static void +undo_insert_helper(void *data, size_t line, size_t byte, char *text, size_t text_len) { + ledit_buffer *buffer = (ledit_buffer *)data; + buffer_insert_text_with_newlines_base(buffer, line, byte, text, text_len, NULL, NULL); +} + +static void +undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t byte2) { + ledit_buffer *buffer = (ledit_buffer *)data; + delete_range_base(buffer, line1, byte1, line2, byte2, NULL); +} + +void +buffer_undo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte) { + size_t min_line; + ledit_undo( + buffer->undo, mode, buffer, &undo_insert_helper, + &undo_delete_helper, cur_line, cur_byte, &min_line + ); + /* FIXME: why is this check here? */ + if (min_line < buffer->lines_num) { + buffer_recalc_all_views_from_line( + buffer, min_line > 0 ? min_line - 1 : min_line + ); } - return new_index; +} + +void +buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte) { + size_t min_line; + ledit_redo( + buffer->undo, mode, buffer, &undo_insert_helper, + &undo_delete_helper, cur_line, cur_byte, &min_line + ); + if (min_line < buffer->lines_num) { + buffer_recalc_all_views_from_line( + buffer, min_line > 0 ? min_line - 1 : min_line + ); + } +} + +void +buffer_delete_with_undo_base( + ledit_buffer *buffer, ledit_range cur_range, + int start_undo_group, enum ledit_mode mode, /* for undo */ + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + txtbuf *text_ret) { + /* FIXME: global txtbuf to avoid allocating each time */ + txtbuf *buf = text_ret != NULL ? text_ret : txtbuf_new(); + sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2); + delete_range_base( + buffer, line_index1, byte_index1, line_index2, byte_index2, buf + ); + ledit_range del_range = { + .line1 = line_index1, .byte1 = byte_index1, + .line2 = line_index2, .byte2 = byte_index2 + }; + undo_push_delete( + buffer->undo, buf, del_range, cur_range, start_undo_group, mode + ); + if (text_ret == NULL) + txtbuf_destroy(buf); +} + +void +buffer_delete_with_undo( + ledit_buffer *buffer, ledit_range cur_range, + int start_undo_group, enum ledit_mode mode, /* for undo */ + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + txtbuf *text_ret) { + buffer_delete_with_undo_base( + buffer, cur_range, + start_undo_group, mode, + line_index1, byte_index1, + line_index2, byte_index2, + text_ret + ); + size_t min = line_index1 < line_index2 ? line_index1 : line_index2; + buffer_recalc_all_views_from_line(buffer, min); +} + +void +buffer_insert_with_undo_base( + ledit_buffer *buffer, + ledit_range cur_range, int set_range_end, + int start_undo_group, enum ledit_mode mode, + size_t line, size_t byte, + char *text, size_t len, + size_t *line_ret, size_t *byte_ret) { + txtbuf ins_buf = {.text = text, .len = len, .cap = len}; + ledit_range ins_range; + ins_range.line1 = line; + ins_range.byte1 = byte; + size_t new_line, new_byte; + buffer_insert_text_with_newlines_base( + buffer, line, byte, text, len, + &new_line, &new_byte + ); + if (set_range_end) { + cur_range.line2 = new_line; + cur_range.byte2 = new_byte; + } + ins_range.line2 = new_line; + ins_range.byte2 = new_byte; + undo_push_insert( + buffer->undo, &ins_buf, ins_range, cur_range, start_undo_group, mode + ); + if (line_ret != NULL) + *line_ret = new_line; + if (byte_ret != NULL) + *byte_ret = new_byte; +} + +void +buffer_insert_with_undo( + ledit_buffer *buffer, + ledit_range cur_range, int set_range_end, + int start_undo_group, enum ledit_mode mode, + size_t line, size_t byte, + char *text, size_t len, + size_t *line_ret, size_t *byte_ret) { + buffer_insert_with_undo_base( + buffer, + cur_range, set_range_end, + start_undo_group, mode, + line, byte, text, len, + line_ret, byte_ret + ); + buffer_recalc_all_views_from_line(buffer, line); } diff --git a/buffer.h b/buffer.h @@ -5,7 +5,6 @@ typedef struct ledit_buffer ledit_buffer; #include "view.h" -/* FIXME: size_t for len, etc. */ typedef struct { ledit_buffer *parent_buffer; char *text; /* text, stored as gap buffer */ @@ -29,7 +28,7 @@ typedef struct { struct ledit_buffer { ledit_common *common; /* common stuff, e.g. display, etc. */ char *filename; /* last opened filename */ - undo_stack *undo; /* undo manager */ + undo_stack *undo; /* undo manager */ ledit_buffer_marklist *marklist; /* list of mark positions set */ ledit_line *lines; /* array of lines */ ledit_view **views; /* array of registered views */ @@ -115,118 +114,6 @@ void buffer_destroy(ledit_buffer *buffer); void buffer_normalize_line(ledit_line *line); /* - * Insert 'src_len' bytes from 'src_line' starting at byte position 'src_index' - * into line 'dst_line' at byte position 'dst_index'. - * 'dst_line' must not be the same as 'src_line'. - * If 'text_ret' is not NULL, the copied text is additionally copied into 'text_ret'. - * This function does not update the views or normalize the lines, so it should - * only be used for efficiency purposes when performing multiple operations. - */ -void buffer_insert_text_from_line_base( - ledit_buffer *buffer, - size_t dst_line, size_t dst_index, - size_t src_line, size_t src_index, size_t src_len, - txtbuf *text_ret -); - -/* - * Same as buffer_insert_text_from_line_base, but the views are updated afterwards. - */ -void buffer_insert_text_from_line( - ledit_buffer *buffer, - size_t dst_line, size_t dst_index, - size_t src_line, size_t src_index, size_t src_len, - txtbuf *text_ret -); - -/* - * Insert text 'text' with length 'len' at line 'line_index' and byte position 'index'. - * The text must not contain newlines. - * This function does not update the views or normalize the lines, so it should - * only be used for efficiency purposes when performing multiple operations. - */ -void buffer_insert_text_base( - ledit_buffer *buffer, - size_t line_index, size_t index, - char *text, size_t len -); - -/* - * Same as buffer_insert_text_base, but the views are updated afterwards. - */ -void buffer_insert_text( - ledit_buffer *buffer, - size_t line_index, size_t index, - char *text, size_t len -); - -/* Insert text 'text' with length 'len' at line 'line_index' and byte position 'index. - * The text may contain newlines. - * If end_line_ret is not NULL, the line index at the end of the insertion is - * written into it. - * If end_byte_ret is not NULL, the byte position at the end of the insertion is - * written into it. - * This function does not update the views or normalize the lines, so it should - * only be used for efficiency purposes when performing multiple operations. - */ -void buffer_insert_text_with_newlines_base( - ledit_buffer *buffer, - size_t line_index, size_t index, - char *text, size_t len, - size_t *end_line_ret, size_t *end_char_ret -); - -/* - * Same as buffer_insert_text_with_newlines_base, but the views are updated afterwards. - */ -void buffer_insert_text_with_newlines( - ledit_buffer *buffer, - size_t line_index, size_t index, - char *text, size_t len, - size_t *end_line_ret, size_t *end_char_ret -); - -/* - * Append line after line at 'line_index'. - * if 'break_text' is not 0, the text on line 'line_index' starting at - * byte index 'text_index' is moved to the newly appended line. - * The views are notified that a line has been appended, but not told to update - * their line heights and offsets. - */ -void buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text); - -/* - * Same as buffer_append_line_base, but the views are told to update - * their line heights and offsets afterwards. - */ -void buffer_append_line(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text); - -/* - * Delete lines between 'index1' and 'index2' (inclusive). - * The views are notified of the deletion but not told to - * update their line heights and offsets. - */ -void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2); - -/* - * Same as buffer_delete_line_entries_base, but the views are told to - * update their line heights and offsets. - */ -void buffer_delete_line_entries(ledit_buffer *buffer, size_t index1, size_t index2); - -/* - * Convenience function to call buffer_delete_line_entries_base - * with two times the same line index. - */ -void buffer_delete_line_entry_base(ledit_buffer *buffer, size_t index); - -/* - * Convenience function to call buffer_delete_line_entries - * with two times the same line index. - */ -void buffer_delete_line_entry(ledit_buffer *buffer, size_t index); - -/* * Get the line at logical index 'index'. * The returned line is only valid until the next * action that appends or deletes line entries. @@ -292,33 +179,87 @@ size_t line_prev_utf8(ledit_line *line, size_t index); /* * Get the unicode character index of a byte position. + * Note that the time complexity of this is linear. */ size_t line_byte_to_char(ledit_line *line, size_t byte); /* - * Delete the section of line 'line' starting at byte 'start' with length 'length' - * and notify the views. - * Note that this does not tell the views to recalculate their line heights and offsets. + * Insert a mark with key 'mark' at line 'line' and byte 'byte'. */ -void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length); +void buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte); /* - * Delete the unicode char at 'line_index' and 'byte_index' if 'dir' is >= 0, - * or the previous char if 'dir' is < 0. - * Returns the byte index where the deleted text was located. - * This function only notifies the views of the deletion, but does not tell - * them to recalculate their line heights and offsets. + * Perform one undo step. + * 'mode' should be the current mode of the calling view. + * 'cur_line' and 'cur_byte' are filled with the new line and cursor + * position after the undo. */ -size_t buffer_delete_unicode_char_base(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir); +void buffer_undo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte); /* - * Same as buffer_delete_unicode_char_base, but the views are updated. + * Same as 'buffer_undo', but for redo. */ -size_t buffer_delete_unicode_char(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir); +void buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte); /* - * Insert a mark with key 'mark' at line 'line' and byte 'byte'. + * Delete the given range (which does not need to be sorted yet) and + * add the operation to the undo stack. + * 'cur_range' is the cursor range to be added to the undo stack. + * 'start_undo_group' and 'mode' are also used for the undo stack. + * If 'text_ret' is not NULL, the deleted text is written to it. + * This function does not tell the views to update their line heights + * and offsets. */ -void buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte); +void buffer_delete_with_undo_base( + ledit_buffer *buffer, ledit_range cur_range, + int start_undo_group, enum ledit_mode mode, /* for undo */ + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + txtbuf *text_ret +); + +/* + * Same as 'buffer_delete_with_undo_base', but the views are told to + * update their line heights and offsets afterwards. + */ +void buffer_delete_with_undo( + ledit_buffer *buffer, ledit_range cur_range, + int start_undo_group, enum ledit_mode mode, /* for undo */ + size_t line_index1, size_t byte_index1, + size_t line_index2, size_t byte_index2, + txtbuf *text_ret +); + +/* + * Insert the given 'text' with length 'len' at line 'line' and + * byte index 'byte and add the operation to the undo stack. + * 'cur_range', 'start_undo_group', and 'mode' are used for the + * undo stack. If 'set_range_end' is set, the end position of + * 'cur_range' is set to the end position of the insertion before + * adding the operation to the undo stack. + * If 'line_ret' and 'byte_ret' are not NULL, they are filled with + * the end position of the insertion. + */ +void buffer_insert_with_undo_base( + ledit_buffer *buffer, + ledit_range cur_range, int set_range_end, + int start_undo_group, enum ledit_mode mode, + size_t line, size_t byte, + char *text, size_t len, + size_t *line_ret, size_t *byte_ret +); + +/* + * Same as 'buffer_insert_with_undo_base', but the views are told to + * update their line heights and offsets afterwards. + */ +void buffer_insert_with_undo( + ledit_buffer *buffer, + ledit_range cur_range, int set_range_end, + int start_undo_group, enum ledit_mode mode, + size_t line, size_t byte, + char *text, size_t len, + size_t *line_ret, size_t *byte_ret +); #endif diff --git a/keys_basic.c b/keys_basic.c @@ -37,7 +37,7 @@ #include "keys_command.h" #include "keys_basic_config.h" -/* note: this is supposed to be global for all buffers */ +/* note: this is supposed to be global for all views/buffers */ int paste_buffer_line_based = 0; static txtbuf *paste_buffer = NULL; static int last_lines_scrolled = -1; @@ -77,7 +77,6 @@ static struct { } key_stack = {0, 0, NULL}; static struct action (*grab_char_cb)(ledit_view *view, char *text, size_t len) = NULL; -static int hard_line_based = 1; void basic_key_cleanup(void) { @@ -334,7 +333,7 @@ static void get_new_line_softline( ledit_view *view, size_t cur_line, size_t cur_index, int movement, size_t *new_line_ret, int *new_softline_ret) { - if (hard_line_based) { + if (view->buffer->hard_line_based) { if (movement < 0 && (size_t)-movement > cur_line) *new_line_ret = 0; else @@ -403,31 +402,21 @@ delete_range( (void)selected; /* FIXME */ if (copy_to_buffer && !paste_buffer) paste_buffer = txtbuf_new(); - txtbuf *buf = copy_to_buffer ? paste_buffer : txtbuf_new(); - ledit_range cur_range, del_range; - cur_range.line1 = view->cur_line; - cur_range.byte1 = view->cur_index; + txtbuf *buf = copy_to_buffer ? paste_buffer : NULL; enum delete_mode delmode = DELETE_CHAR; if (line_based) { - if (hard_line_based) + if (view->buffer->hard_line_based) delmode = DELETE_HARDLINE; else delmode = DELETE_SOFTLINE; } view_delete_range( - view, delmode, + view, delmode, 1, line_index1, byte_index1, line_index2, byte_index2, &view->cur_line, &view->cur_index, - &del_range, buf + buf ); - cur_range.line2 = view->cur_line; - cur_range.byte2 = view->cur_index; - undo_push_delete( - view->buffer->undo, buf, del_range, cur_range, 1, view->mode - ); - if (!copy_to_buffer) - txtbuf_destroy(buf); } /* FIXME: better interface for this; documentation */ @@ -439,8 +428,7 @@ insert_text( size_t cur_line1, size_t cur_index1, size_t cur_line2, size_t cur_index2, int set_range_start, int set_range_end, int start_group) { - txtbuf ins_buf = {.text = text, .len = len, .cap = len}; - ledit_range cur_range, del_range; + ledit_range cur_range; if (set_range_start) { cur_range.line1 = cur_line1; cur_range.byte1 = cur_index1; @@ -448,27 +436,24 @@ insert_text( cur_range.line1 = view->cur_line; cur_range.byte1 = view->cur_index; } - del_range.line1 = line; - del_range.byte1 = index; - size_t cur_line, cur_index; - buffer_insert_text_with_newlines( - view->buffer, line, index, text, len, - &cur_line, &cur_index - ); /* this is mainly for pasting, where the new line and index should not be at the end of the pasted text */ if (set_range_end) { - cur_range.line2 = view->cur_line = cur_line2; - cur_range.byte2 = view->cur_index = cur_index2; - } else { - cur_range.line2 = view->cur_line = cur_line; - cur_range.byte2 = view->cur_index = cur_index; + cur_range.line2 = cur_line2; + cur_range.byte2 = cur_index2; } - del_range.line2 = cur_line; - del_range.byte2 = cur_index; - undo_push_insert( - view->buffer->undo, &ins_buf, del_range, cur_range, start_group, view->mode + /* FIXME: why did I ever decide to make set_range_end + mean exactly the opposite for the two functions? */ + buffer_insert_with_undo( + view->buffer, cur_range, !set_range_end, + start_group, view->mode, + line, index, text, len, + &view->cur_line, &view->cur_index ); + if (set_range_end) { + view->cur_line = cur_line2; + view->cur_index = cur_index2; + } } static int @@ -562,7 +547,7 @@ append_line_above(ledit_view *view, char *text, size_t len) { /* do this here already so the mode group is the same for the newline insertion */ enter_insert(view, text, len); view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); - if (hard_line_based || start == 0) { + if (view->buffer->hard_line_based || start == 0) { insert_text(view, view->cur_line, 0, "\n", 1, 0, 0, view->cur_line, 0, 0, 1, 1); } else { /* FIXME: this interface really is horrible */ @@ -577,7 +562,7 @@ append_line_below(ledit_view *view, char *text, size_t len) { enter_insert(view, text, len); view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); ledit_line *ll = buffer_get_line(view->buffer, view->cur_line); - if (hard_line_based || end == ll->len) { + if (view->buffer->hard_line_based || end == ll->len) { insert_text(view, view->cur_line, ll->len, "\n", 1, 0, 0, view->cur_line + 1, 0, 0, 1, 1); } else { insert_text(view, view->cur_line, end, "\n\n", 2, 0, 0, view->cur_line + 1, 0, 0, 1, 1); @@ -604,7 +589,7 @@ append_after_eol(ledit_view *view, char *text, size_t len) { /* make cursor jump back to original position on undo */ push_undo_empty_insert(view, view->cur_line, view->cur_index, 1); ledit_line *ll = buffer_get_line(view->buffer, view->cur_line); - if (hard_line_based) + if (view->buffer->hard_line_based) view->cur_index = ll->len; else view->cur_index = end; @@ -852,7 +837,7 @@ delete_to_eol(ledit_view *view, char *text, size_t len) { return err_invalid_key(view); size_t start, end; ledit_line *ll = buffer_get_line(view->buffer, view->cur_line); - if (hard_line_based) { + if (view->buffer->hard_line_based) { end = ll->len; } else { view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); @@ -879,7 +864,7 @@ change_to_eol(ledit_view *view, char *text, size_t len) { view_set_mode(view, INSERT); size_t start, end; ledit_line *ll = buffer_get_line(view->buffer, view->cur_line); - if (hard_line_based) { + if (view->buffer->hard_line_based) { end = ll->len; } else { view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end); @@ -942,11 +927,11 @@ change_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { /* this hackery is needed to avoid deleting the entire last line and instead leave an empty line - this should be made nicer (FIXME) */ size_t pos1 = view->cur_index, pos2 = char_pos; - if (line_based && !hard_line_based) { + if (line_based && !view->buffer->hard_line_based) { size_t pos1, pos2, tmp; view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &pos1, &tmp); view_get_pos_softline_bounds(view, line, char_pos, &tmp, &pos2); - } else if (line_based && hard_line_based) { + } else if (line_based && view->buffer->hard_line_based) { pos1 = 0; ledit_line *ll = buffer_get_line(view->buffer, line); pos2 = ll->len; @@ -1046,7 +1031,7 @@ yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { swap_sz(&l1, &l2); swap_sz(&b1, &b2); } - if (line_based && !hard_line_based) { + if (line_based && !view->buffer->hard_line_based) { size_t start1, end2, tmp; view_get_pos_softline_bounds(view, l1, b1, &start1, &tmp); view_get_pos_softline_bounds(view, l2, b2, &tmp, &end2); @@ -1058,7 +1043,7 @@ yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { buffer_copy_text_to_txtbuf( view->buffer, paste_buffer, l1, start1, l2, end2 ); - } else if (line_based && hard_line_based) { + } else if (line_based && view->buffer->hard_line_based) { ledit_line *ll = buffer_get_line(view->buffer, l2); size_t end = ll->len; if (l2 < view->lines_num - 1) { @@ -1119,6 +1104,7 @@ delete(ledit_view *view, char *text, size_t len) { /* FIXME: should this get number of lines to remove or actual end line? */ static void delete_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) { + view_wipe_line_cursor_attrs(view, view->cur_line); int line_based = type == KEY_MOTION_LINE ? 1 : 0; delete_range( view, line_based, 0, @@ -1147,12 +1133,14 @@ paste_normal(ledit_view *view, char *text, size_t len) { view_wipe_line_cursor_attrs(view, view->cur_line); ledit_line *ll = buffer_get_line(view->buffer, view->cur_line); size_t brk = 0; - if (hard_line_based) { + if (view->buffer->hard_line_based) { brk = ll->len; } else { size_t tmp; view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &tmp, &brk); } + /* FIXME: this is a bit inefficient because insert_text does not + use the *_base functions, but maybe this way is a bit safer */ insert_text( view, view->cur_line, brk, "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 1 @@ -1203,7 +1191,7 @@ paste_normal_backwards(ledit_view *view, char *text, size_t len) { view_wipe_line_cursor_attrs(view, view->cur_line); ledit_line *ll = buffer_get_line(view->buffer, view->cur_line); size_t brk = 0; - if (!hard_line_based) { + if (!view->buffer->hard_line_based) { size_t tmp; view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &brk, &tmp); } @@ -1414,7 +1402,7 @@ move_to_eol(ledit_view *view, char *text, size_t len) { ); ledit_line *ll = buffer_get_line(view->buffer, new_line); size_t end_index = ll->len; - if (!hard_line_based) { + if (!view->buffer->hard_line_based) { size_t tmp; view_get_softline_bounds(view, new_line, new_softline, &tmp, &end_index); } @@ -1683,11 +1671,6 @@ join_lines(ledit_view *view, char *text, size_t len) { int start_group = 1; ledit_line *ll1; size_t cur_line = view->cur_line; - /* FIXME: have a general tmp buf for everyone to use */ - txtbuf *buf = txtbuf_new(); - size_t oldlen; - ledit_range cur_range, del_range; - cur_range.line1 = cur_range.line2 = cur_line; for (int i = 0; i < num; i++) { if (cur_line == view->lines_num - 1) break; @@ -1695,26 +1678,18 @@ join_lines(ledit_view *view, char *text, size_t len) { I'll just leave it in case I change the way lines are stored later */ ll1 = buffer_get_line(view->buffer, cur_line); - oldlen = ll1->len; /* FIXME: truncate whitespace to one space */ - view_delete_range( - view, DELETE_CHAR, + view_delete_range_base( + view, DELETE_CHAR, start_group, cur_line, ll1->len, cur_line + 1, 0, - NULL, NULL, &del_range, buf - ); - cur_range.byte1 = view->cur_index; - cur_range.byte2 = view->cur_index = - view_get_legal_normal_pos(view, view->cur_line, oldlen); - undo_push_delete( - view->buffer->undo, buf, del_range, cur_range, - start_group, view->mode + &view->cur_line, &view->cur_index, NULL ); start_group = 0; } + buffer_recalc_all_views_from_line(view->buffer, cur_line); view_set_line_cursor_attrs( view, view->cur_line, view->cur_index ); - txtbuf_destroy(buf); finalize_repetition_stack(); return (struct action){ACTION_NONE, NULL}; } @@ -1725,7 +1700,7 @@ insert_at_beginning(ledit_view *view, char *text, size_t len) { return err_invalid_key(view); enter_insert(view, text, len); size_t new_index = 0; - if (!hard_line_based) { + if (!view->buffer->hard_line_based) { size_t tmp; view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &new_index, &tmp); } @@ -1744,7 +1719,7 @@ cursor_to_first_non_ws(ledit_view *view, char *text, size_t len) { if (num != 0) return err_invalid_key(view); size_t new_index = 0; - if (hard_line_based) { + if (view->buffer->hard_line_based) { new_index = view_line_next_non_whitespace(view, view->cur_line, 0); } else { size_t start, end; @@ -1779,7 +1754,7 @@ cursor_to_beginning(ledit_view *view, char *text, size_t len) { return err_invalid_key(view); /* FIXME: should anything be done with num? */ size_t start_index = 0; - if (!hard_line_based) { + if (!view->buffer->hard_line_based) { size_t tmp; view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start_index, &tmp); } diff --git a/keys_command.c b/keys_command.c @@ -162,44 +162,34 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { size_t rlen = strlen(last_replacement); txtbuf *buf = txtbuf_new(); /* FIXME: don't allocate new every time */ view_wipe_line_cursor_attrs(view, view->cur_line); + size_t min_line = SIZE_MAX; for (size_t i = l1 - 1; i < l2; i++) { ledit_line *ll = buffer_get_line(view->buffer, i); buffer_normalize_line(ll); char *pos = strstr(ll->text, last_search); + if (pos != NULL && i < min_line) + min_line = i; while (pos != NULL) { size_t index = (size_t)(pos - ll->text); - ledit_range cur_range, del_range; + ledit_range cur_range; cur_range.line1 = view->cur_line; cur_range.byte1 = view->cur_line; - view_delete_range( - view, DELETE_CHAR, - i, index, - i, index + slen, - &view->cur_line, &view->cur_index, - &del_range, buf - ); - cur_range.line2 = view->cur_line; - cur_range.byte2 = view->cur_index; - undo_push_delete( - view->buffer->undo, buf, del_range, cur_range, start_undo_group, view->mode + cur_range.line2 = i; + cur_range.byte2 = index; + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + i, index, i, index + slen, NULL ); + view->cur_line = i; + view->cur_index = index; start_undo_group = 0; - txtbuf ins_buf = {.text = last_replacement, .len = rlen, .cap = rlen}; - cur_range.line1 = view->cur_line; - cur_range.byte1 = view->cur_index; - del_range.line1 = i; - del_range.byte1 = index; - size_t cur_line, cur_index; - buffer_insert_text_with_newlines( - view->buffer, i, index, last_replacement, rlen, - &cur_line, &cur_index - ); - cur_range.line2 = view->cur_line; - cur_range.byte2 = view->cur_index; - del_range.line2 = cur_line; - del_range.byte2 = cur_index; - undo_push_insert( - view->buffer->undo, &ins_buf, del_range, cur_range, 0, view->mode + cur_range.line1 = i; + cur_range.byte1 = index; + buffer_insert_with_undo_base( + view->buffer, cur_range, 0, 0, view->mode, + i, index, last_replacement, rlen, + NULL, NULL ); num++; if (!global) break; @@ -207,6 +197,7 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { pos = strstr(ll->text + index + rlen, last_search); } } + buffer_recalc_all_views_from_line(view->buffer, min_line); /* FIXME: show number replaced */ /* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */ view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); diff --git a/undo.c b/undo.c @@ -267,3 +267,9 @@ ledit_redo(undo_stack *undo, enum ledit_mode mode, void *callback_data, *min_line_ret = min_line; return UNDO_NORMAL; } + +void +undo_change_last_cur_range(undo_stack *undo, ledit_range cur_range) { + undo_elem *e = peek_undo_elem(undo); + e->cursor_range = cur_range; +} diff --git a/undo.h b/undo.h @@ -106,3 +106,12 @@ undo_status ledit_redo( void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb, size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret ); + +/* + * Change the cursor range stored for the last operation. + * This is sort of a hack used by view_delete_range and some other + * functions to set the cursor range later if it isn't known yet before + * inserting/deleting text. + * Fails silently if the stack is empty. + */ +void undo_change_last_cur_range(undo_stack *undo, ledit_range cur_range); diff --git a/view.c b/view.c @@ -188,14 +188,23 @@ resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t index) { ); } +/* Checking vl->cursor_index_valid in these notify functions is needed + to avoid re-setting the cursor index for lines that were wiped but + where the line/index of the view hasn't been updated yet (e.g. when + the current line is wiped, then view_delete_range is called to delete + a part, which may cause these notification functions to be called) */ void view_notify_insert_text(ledit_view *view, size_t line, size_t index, size_t len) { ledit_view_line *vl = view_get_line(view, line); vl->text_dirty = 1; if (line == view->cur_line && index < view->cur_index) { view->cur_index += len; - view_set_line_cursor_attrs(view, line, view->cur_index); + ledit_view_line *vl = view_get_line(view, line); + if (vl->cursor_index_valid) + view_set_line_cursor_attrs(view, line, view->cur_index); } + /* FIXME: maybe just wipe selection completely, or at least + when in insert mode? */ if (view->sel_valid) view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); } @@ -216,7 +225,9 @@ view_notify_delete_text(ledit_view *view, size_t line, size_t index, size_t len) view, view->cur_line, view->cur_index ); } - view_set_line_cursor_attrs(view, line, view->cur_index); + ledit_view_line *vl = view_get_line(view, line); + if (vl->cursor_index_valid) + view_set_line_cursor_attrs(view, line, view->cur_index); } if (view->sel_valid) view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); @@ -255,6 +266,9 @@ view_notify_delete_lines(ledit_view *view, size_t index1, size_t index2) { view->cur_line = 0; view->cur_index = 0; } + ledit_view_line *vl = view_get_line(view, view->cur_line); + if (vl->cursor_index_valid) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } if (view->sel_valid) view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); @@ -1132,22 +1146,23 @@ view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos) { void view_delete_range( - ledit_view *view, enum delete_mode delmode, + ledit_view *view, + enum delete_mode delmode, int start_undo_group, size_t line_index1, size_t byte_index1, size_t line_index2, size_t byte_index2, size_t *new_line_ret, size_t *new_byte_ret, - ledit_range *final_range_ret, txtbuf *text_ret) { + txtbuf *text_ret) { view_delete_range_base( - view, delmode, + view, + delmode, start_undo_group, line_index1, byte_index1, line_index2, byte_index2, new_line_ret, new_byte_ret, - final_range_ret, text_ret + text_ret ); /* need to start recalculating one line before in case first line was deleted and offset is now wrong */ size_t min = line_index1 < line_index2 ? line_index1 : line_index2; - /* FIXME: a bit ugly to do this here */ buffer_recalc_all_views_from_line( view->buffer, min > 0 ? min - 1 : min ); @@ -1162,29 +1177,31 @@ view_delete_range( given, but I couldn't reproduce this bug */ void view_delete_range_base( - ledit_view *view, enum delete_mode delmode, + ledit_view *view, + enum delete_mode delmode, int start_undo_group, size_t line_index1, size_t byte_index1, size_t line_index2, size_t byte_index2, size_t *new_line_ret, size_t *new_byte_ret, - ledit_range *final_range_ret, txtbuf *text_ret) { + txtbuf *text_ret) { /* FIXME: Oh boy, this is nasty */ /* range line x, range byte x */ size_t rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0; + /* line_index1 and byte_index1 are used as cursor start position + -> FIXME: why not just use view->cur_line, view->cur_index here? */ + size_t cur_line = line_index1; + size_t cur_byte = byte_index1; + view_sort_selection(&line_index1, &byte_index1, &line_index2, &byte_index2); size_t new_line = 0, new_byte = 0; assert(line_index1 < view->lines_num); assert(line_index2 < view->lines_num); + ledit_range cur_range = {0, 0, 0, 0}; /* FIXME: could this be simplified by just calculating the range and then using the non-line-based version? */ if (delmode == DELETE_HARDLINE) { int x, sl_useless; size_t l1 = line_index1, l2 = line_index2; - if (line_index1 > line_index2) { - l1 = line_index2; - l2 = line_index1; - } - size_t dell1 = l1, dell2 = l2; - ledit_line *ll = buffer_get_line(view->buffer, line_index1); - view_pos_to_x_softline(view, line_index1, byte_index1, &x, &sl_useless); + ledit_line *ll; + view_pos_to_x_softline(view, cur_line, cur_byte, &x, &sl_useless); if (l1 > 0 && l2 < view->lines_num - 1) { rgl1 = l1; rgb1 = 0; @@ -1209,13 +1226,6 @@ view_delete_range_base( ll = buffer_get_line(view->buffer, rgl2); rgb2 = ll->len; } - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, rgl2, rgb2 - ); - } - /* default is dell1 = l1, dell2 = l2 */ if (l2 < view->lines_num - 1) { new_line = l1; new_byte = view_x_softline_to_pos( @@ -1227,30 +1237,24 @@ view_delete_range_base( view, l1 - 1, x, 0 ); } else { - dell1 = l1 + 1; - dell2 = l2; - new_line = l1; + new_line = 0; new_byte = 0; - /* happens when all lines are deleted, so one line has to be cleared */ - ll = buffer_get_line(view->buffer, l1); - buffer_delete_line_section_base( - view->buffer, l1, 0, ll->len - ); - } - if (dell1 <= dell2) { - buffer_delete_line_entries_base(view->buffer, dell1, dell2); } + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + rgl1, rgb1, rgl2, rgb2, text_ret + ); } else if (delmode == DELETE_SOFTLINE) { - int x, softline1, softline2; - ledit_line *line1 = buffer_get_line(view->buffer, line_index1); - ledit_view_line *vline1 = view_get_line(view, line_index1); - view_pos_to_x_softline(view, line_index1, byte_index1, &x, &softline1); + int x, sl_useless; + view_pos_to_x_softline(view, cur_line, cur_byte, &x, &sl_useless); if (line_index1 == line_index2) { - int x_useless; + int x_useless, l1, l2; + ledit_line *line1 = buffer_get_line(view->buffer, line_index1); + ledit_view_line *vline1 = view_get_line(view, line_index1); + view_pos_to_x_softline(view, line_index1, byte_index1, &x_useless, &l1); + view_pos_to_x_softline(view, line_index2, byte_index2, &x_useless, &l2); PangoLayout *layout = get_pango_layout(view, line_index1); - pango_layout_index_to_line_x(layout, byte_index2, 0, &softline2, &x_useless); - int l1 = softline1 < softline2 ? softline1 : softline2; - int l2 = softline1 < softline2 ? softline2 : softline1; PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout, l1); PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout, l2); /* don't delete entire line if it is the last one remaining */ @@ -1258,11 +1262,9 @@ view_delete_range_base( if (line_index1 < view->lines_num - 1) { /* cursor can be moved to next hard line */ new_line = line_index1; - size_t tmp_byte; - tmp_byte = view_x_softline_to_pos( + new_byte = view_x_softline_to_pos( view, line_index1 + 1, x, 0 ); - new_byte = (size_t)tmp_byte; rgl1 = line_index1; rgb1 = 0; rgl2 = line_index1 + 1; @@ -1283,50 +1285,41 @@ view_delete_range_base( rgl2 = line_index1; rgb2 = line1->len; } - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - buffer_delete_line_entry_base(view->buffer, line_index1); + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + rgl1, rgb1, rgl2, rgb2, text_ret + ); } else { assert(pl2->start_index + pl2->length >= pl1->start_index); rgl1 = rgl2 = line_index1; rgb1 = (size_t)pl1->start_index; rgb2 = (size_t)(pl2->start_index + pl2->length); - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - buffer_delete_line_section_base( - view->buffer, line_index1, rgb1, rgb2 - rgb1 + /* the deletion has to happen before calculating the new cursor + position because deleting softlines could change the line + wrapping as well */ + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + rgl1, rgb1, rgl2, rgb2, text_ret ); - if (l2 == vline1->softlines - 1 && line_index1 < view->lines_num - 1) { + set_pango_text_and_highlight(view, line_index1); + vline1 = view_get_line(view, line_index1); + if (l1 == vline1->softlines && line_index1 < view->lines_num - 1) { new_line = line_index1 + 1; - size_t tmp_byte; - tmp_byte = view_x_softline_to_pos( + new_byte = view_x_softline_to_pos( view, line_index1 + 1, x, 0 ); - new_byte = (size_t)tmp_byte; - } else if (l2 < vline1->softlines - 1) { + } else if (l1 <= vline1->softlines - 1) { new_line = line_index1; - size_t tmp_byte; - tmp_byte = view_x_softline_to_pos( + new_byte = view_x_softline_to_pos( view, line_index1, x, l1 ); - new_byte = (size_t)tmp_byte; } else if (l1 > 0) { new_line = line_index1; - size_t tmp_byte; - tmp_byte = view_x_softline_to_pos( + new_byte = view_x_softline_to_pos( view, line_index1, x, l1 - 1 ); - new_byte = (size_t)tmp_byte; } else { /* the line has been emptied and is the last line remaining */ new_line = 0; @@ -1335,19 +1328,8 @@ view_delete_range_base( } } else { int x_useless, sl1, sl2; - size_t l1, l2, b1, b2; - if (line_index1 < line_index2) { - l1 = line_index1; - b1 = byte_index1; - l2 = line_index2; - b2 = byte_index2; - } else { - l1 = line_index2; - b1 = byte_index2; - l2 = line_index1; - b2 = byte_index1; - } - ledit_line *ll1 = buffer_get_line(view->buffer, l1); + size_t l1 = line_index1, b1 = byte_index1; + size_t l2 = line_index2, b2 = byte_index2; ledit_line *ll2 = buffer_get_line(view->buffer, l2); ledit_view_line *vl1 = view_get_line(view, l1); ledit_view_line *vl2 = view_get_line(view, l2); @@ -1363,15 +1345,6 @@ view_delete_range_base( rgl2 = l2; rgb1 = 0; rgb2 = ll2->len; - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - buffer_delete_line_section_base(view->buffer, l1, 0, ll1->len); - buffer_delete_line_entries_base(view->buffer, l1 + 1, l2); new_line = 0; new_byte = 0; } else { @@ -1396,31 +1369,24 @@ view_delete_range_base( rgl2 = l2 + 1; rgb2 = 0; } - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - buffer_delete_line_entries_base(view->buffer, l1, l2); } + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + rgl1, rgb1, rgl2, rgb2, text_ret + ); } else if (sl1 == 0) { rgl1 = l1; rgb1 = 0; rgl2 = l2; rgb2 = (size_t)(pl2->start_index + pl2->length); - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - buffer_delete_line_section_base(view->buffer, l2, 0, rgb2); + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + rgl1, rgb1, rgl2, rgb2, text_ret + ); new_line = l1; - new_byte = view_x_softline_to_pos(view, l2, x, 0); - buffer_delete_line_entries_base(view->buffer, l1, l2 - 1); + new_byte = view_x_softline_to_pos(view, l1, x, 0); } else if (sl2 == vl2->softlines - 1) { rgl1 = l1; rgb1 = (size_t)pl1->start_index; @@ -1435,38 +1401,27 @@ view_delete_range_base( view, l2 + 1, x, 0 ); } - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - buffer_delete_line_section_base(view->buffer, l1, rgb1, ll1->len - rgb1); - buffer_delete_line_entries_base(view->buffer, l1 + 1, l2); + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + rgl1, rgb1, rgl2, rgb2, text_ret + ); } else { - /* FIXME: this could be made nicer by just using the range to - delete all in one go at the end */ rgl1 = l1; rgb1 = (size_t)pl1->start_index; rgl2 = l2; rgb2 = (size_t)(pl2->start_index + pl2->length); - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - buffer_delete_line_section_base(view->buffer, l1, rgb1, ll1->len - rgb1); - buffer_insert_text_from_line_base( - view->buffer, - l1, rgb1, l2, rgb2, - ll2->len - rgb2, NULL + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + rgl1, rgb1, rgl2, rgb2, text_ret ); - buffer_delete_line_entries_base(view->buffer, l1 + 1, l2); new_line = l1; + /* important so vl1->softlines is updated */ set_pango_text_and_highlight(view, l1); + /* important because line pointers may only stay + valid until something is deleted or inserted */ + vl1 = view_get_line(view, l1); /* it's technically possible that the remaining part of the second line is so small that it doesn't generate a new softline, so there needs to be a special case - this is @@ -1479,64 +1434,26 @@ view_delete_range_base( } } } else { - if (line_index1 == line_index2) { - rgl1 = rgl2 = line_index1; - if (byte_index1 < byte_index2) { - rgb1 = byte_index1; - rgb2 = byte_index2; - } else { - rgb1 = byte_index2; - rgb2 = byte_index1; - } - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - buffer_delete_line_section_base(view->buffer, line_index1, rgb1, rgb2 - rgb1); - new_line = line_index1; - new_byte = rgb1; - } else { - if (line_index1 < line_index2) { - rgl1 = line_index1; - rgb1 = byte_index1; - rgl2 = line_index2; - rgb2 = byte_index2; - } else { - rgl1 = line_index2; - rgb1 = byte_index2; - rgl2 = line_index1; - rgb2 = byte_index1; - } - if (text_ret) { - buffer_copy_text_to_txtbuf( - view->buffer, text_ret, - rgl1, rgb1, - rgl2, rgb2 - ); - } - ledit_line *line1 = buffer_get_line(view->buffer, rgl1); - ledit_line *line2 = buffer_get_line(view->buffer, rgl2); - buffer_delete_line_section_base(view->buffer, rgl1, rgb1, line1->len - rgb1); - buffer_insert_text_from_line_base( - view->buffer, rgl1, rgb1, rgl2, rgb2, line2->len - rgb2, NULL - ); - new_line = rgl1; - new_byte = rgb1; - buffer_delete_line_entries_base(view->buffer, rgl1 + 1, rgl2); - } - /* FIXME: too much magic - maybe don't include this here */ - if (view->mode == NORMAL) - new_byte = view_get_legal_normal_pos(view, new_line, new_byte); - } - if (final_range_ret) { - final_range_ret->line1 = rgl1; - final_range_ret->byte1 = rgb1; - final_range_ret->line2 = rgl2; - final_range_ret->byte2 = rgb2; + rgl1 = line_index1; + rgb1 = byte_index1; + rgl2 = line_index2; + rgb2 = byte_index2; + new_line = rgl1; + new_byte = rgb1; + buffer_delete_with_undo_base( + view->buffer, cur_range, + start_undo_group, view->mode, + rgl1, rgb1, rgl2, rgb2, text_ret + ); } + cur_range.line1 = view->cur_line; + cur_range.byte1 = view->cur_index; + cur_range.line2 = new_line; + cur_range.byte2 = new_byte; + undo_change_last_cur_range(view->buffer->undo, cur_range); + /* FIXME: too much magic - maybe don't include this here */ + if (view->mode == NORMAL) + new_byte = view_get_legal_normal_pos(view, new_line, new_byte); if (new_line_ret) *new_line_ret = new_line; if (new_byte_ret) @@ -2040,27 +1957,11 @@ view_redraw(ledit_view *view) { } } -static void -undo_insert_helper(void *data, size_t line, size_t byte, char *text, size_t text_len) { - ledit_view *view = (ledit_view *)data; - buffer_insert_text_with_newlines_base(view->buffer, line, byte, text, text_len, NULL, NULL); -} - -static void -undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t byte2) { - ledit_view *view = (ledit_view *)data; - view_delete_range_base(view, DELETE_CHAR, line1, byte1, line2, byte2, NULL, NULL, NULL, NULL); -} - void view_undo(ledit_view *view) { /* FIXME: maybe wipe selection */ - size_t min_line; size_t old_line = view->cur_line; - ledit_undo( - view->buffer->undo, view->mode, view, &undo_insert_helper, - &undo_delete_helper, &view->cur_line, &view->cur_index, &min_line - ); + buffer_undo(view->buffer, view->mode, &view->cur_line, &view->cur_index); if (view->mode == NORMAL) { view->cur_index = view_get_legal_normal_pos( view, view->cur_line, view->cur_index @@ -2068,23 +1969,13 @@ view_undo(ledit_view *view) { } view_wipe_line_cursor_attrs(view, old_line); view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); - /* FIXME: why is this check here? */ - if (min_line < view->lines_num) { - buffer_recalc_all_views_from_line( - view->buffer, min_line > 0 ? min_line - 1 : min_line - ); - } /* FIXME: show undo message */ } void view_redo(ledit_view *view) { - size_t min_line; size_t old_line = view->cur_line; - ledit_redo( - view->buffer->undo, view->mode, view, &undo_insert_helper, - &undo_delete_helper, &view->cur_line, &view->cur_index, &min_line - ); + buffer_redo(view->buffer, view->mode, &view->cur_line, &view->cur_index); if (view->mode == NORMAL) { view->cur_index = view_get_legal_normal_pos( view, view->cur_line, view->cur_index @@ -2092,33 +1983,23 @@ view_redo(ledit_view *view) { } view_wipe_line_cursor_attrs(view, old_line); view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); - if (min_line < view->lines_num) { - buffer_recalc_all_views_from_line( - view->buffer, min_line > 0 ? min_line - 1 : min_line - ); - } /* FIXME: show undo message */ } static void paste_callback(void *data, char *text, size_t len) { ledit_view *view = (ledit_view *)data; - txtbuf ins_buf = {.text = text, .len = len, .cap = len}; - ledit_range cur_range, ins_range; - cur_range.line1 = ins_range.line1 = view->cur_line; - cur_range.byte1 = ins_range.byte1 = view->cur_index; - buffer_insert_text_with_newlines( - view->buffer, view->cur_line, view->cur_index, - text, len, &view->cur_line, &view->cur_index - ); - cur_range.line2 = ins_range.line2 = view->cur_line; - cur_range.byte2 = ins_range.byte2 = view->cur_index; - undo_push_insert( - view->buffer->undo, &ins_buf, ins_range, cur_range, 1, view->mode + ledit_range cur_range; + cur_range.line1 = view->cur_line; + cur_range.byte1 = view->cur_index; + buffer_insert_with_undo( + view->buffer, cur_range, 1, 1, view->mode, + view->cur_line, view->cur_index, text, len, + &view->cur_line, &view->cur_index ); } -/* FIXME: guard against buffer being destroyed before paste callback is nulled */ +/* FIXME: guard against view being destroyed before paste callback is nulled */ void view_paste_clipboard(ledit_view *view) { diff --git a/view.h b/view.h @@ -339,7 +339,7 @@ size_t view_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline size_t view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos); /* - * Delete a range accorxing to a delete_mode. + * Delete a range according to a delete_mode. * The line and byte indeces do not need to be sorted (in fact, they often * shouldn't be, as shown in the next sentence). * If 'delmode' is DELETE_HARDLINE or DELETE_SOFTLINE, 'line_index1' and @@ -352,19 +352,27 @@ size_t view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos); * in the buffer afterwards, although it may have its text deleted. * If 'delmode' is DELETE_CHAR, the exact specified range is deleted, and * the new line and byte are simply the beginning of the range. - * In all cases, the final deletion range is written to 'final_range_ret', - * and the deleted text is written to 'text_ret'. + * The deleted text is written to 'text_ret'. * In normal mode, the new cursor index is always at a valid normal mode * position. * All return arguments may be NULL. + * The deletion is added to the undo stack with 'start_undo_group' + * specifying whether a new undo group should be started. + * The start cursor position for the undo stack is taken directly from + * the view's current position. The end cursor position is the same as + * what is returned in 'new_line_ret' and 'new_byte_ret', except that it + * is set before calling 'view_get_legal_normal_pos'. + * If different cursor positions are needed, call + * 'undo_change_last_cur_range' afterwards. * This function does not recalculate the line heights or offsets. */ void view_delete_range_base( - ledit_view *view, enum delete_mode delmode, + ledit_view *view, + enum delete_mode delmode, int start_undo_group, size_t line_index1, size_t byte_index1, size_t line_index2, size_t byte_index2, size_t *new_line_ret, size_t *new_byte_ret, - ledit_range *final_range_ret, txtbuf *text_ret + txtbuf *text_ret ); /* @@ -372,11 +380,12 @@ void view_delete_range_base( * all views are recalculated afterwards. */ void view_delete_range( - ledit_view *view, enum delete_mode delmode, + ledit_view *view, + enum delete_mode delmode, int start_undo_group, size_t line_index1, size_t byte_index1, size_t line_index2, size_t byte_index2, size_t *new_line_ret, size_t *new_byte_ret, - ledit_range *final_range_ret, txtbuf *text_ret + txtbuf *text_ret ); /*