commit 3572b3d7dd300642a94067f72f30fc83c8651e39
parent 123a3087ad0bc4f772f0cd0a17caf95304092f87
Author: lumidify <nobody@lumidify.org>
Date: Thu, 9 Dec 2021 11:01:05 +0100
Move undo handling to buffer
Diffstat:
M | buffer.c | | | 336 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
M | buffer.h | | | 197 | ++++++++++++++++++++++++++++--------------------------------------------------- |
M | keys_basic.c | | | 109 | +++++++++++++++++++++++++++++++------------------------------------------------ |
M | keys_command.c | | | 47 | +++++++++++++++++++---------------------------- |
M | undo.c | | | 6 | ++++++ |
M | undo.h | | | 9 | +++++++++ |
M | view.c | | | 361 | +++++++++++++++++++++++++++---------------------------------------------------- |
M | view.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
);
/*