commit 5d691e9f97b979d571d38c101554d72e91cb7db5
parent 8779bb819724ae7d05491112ae700063f49cde93
Author: lumidify <nobody@lumidify.org>
Date: Tue, 29 Jun 2021 19:06:30 +0200
Add basic undo/redo support
Diffstat:
M | IDEAS | | | 1 | + |
M | Makefile | | | 4 | ++-- |
M | buffer.c | | | 350 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------- |
M | buffer.h | | | 21 | +++++++++++++-------- |
M | cache.c | | | 1 | + |
M | common.h | | | 2 | ++ |
A | lbuf.c | | | 51 | +++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | lbuf.h | | | 12 | ++++++++++++ |
M | ledit.c | | | 160 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------- |
M | search.c | | | 1 | + |
A | undo.c | | | 250 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | undo.h | | | 23 | +++++++++++++++++++++++ |
12 files changed, 738 insertions(+), 138 deletions(-)
diff --git a/IDEAS b/IDEAS
@@ -1,3 +1,4 @@
* allow editing same file in multiple places at same time (like in acme)
* add different (more basic) text backend
* visual selection mode - allow to switch cursor between selection ends
+* https://drewdevault.com/2021/06/27/You-cant-capture-the-nuance.html
diff --git a/Makefile b/Makefile
@@ -9,8 +9,8 @@ MANPREFIX = ${PREFIX}/man
BIN = ${NAME}
MAN1 = ${BIN:=.1}
-OBJ = ${BIN:=.o} cache.o buffer.o memory.o util.o search.o
-HDR = cache.h buffer.h memory.h common.h util.h search.h
+OBJ = ${BIN:=.o} cache.o buffer.o memory.o util.o search.o lbuf.o undo.o
+HDR = cache.h buffer.h memory.h common.h util.h search.h lbuf.h undo.h
CFLAGS_LEDIT = -g -Wall -Wextra -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext`
LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm
diff --git a/buffer.c b/buffer.c
@@ -11,8 +11,10 @@
#include "memory.h"
#include "common.h"
+#include "lbuf.h"
#include "buffer.h"
#include "cache.h"
+#include "undo.h"
/*
* Important notes:
@@ -147,13 +149,16 @@ ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) {
l->dirty = 1;
}
+/* FIXME: To simplify this a bit, maybe just copy text to lbuf first and
+ then insert it in one go instead of having this complex logic */
void
ledit_insert_text_from_line(
ledit_buffer *buffer,
int dst_line, int dst_index,
- int src_line, int src_index, int src_len) {
+ int src_line, int src_index, int src_len,
+ lbuf *text_ret) {
ledit_insert_text_from_line_base(
- buffer, dst_line, dst_index, src_line, src_index, src_len
+ buffer, dst_line, dst_index, src_line, src_index, src_len, text_ret
);
ledit_recalc_line(buffer, dst_line);
}
@@ -162,23 +167,42 @@ void
ledit_insert_text_from_line_base(
ledit_buffer *buffer,
int dst_line, int dst_index,
- int src_line, int src_index, int src_len) {
+ int src_line, int src_index, int src_len,
+ lbuf *text_ret) {
assert(dst_line != src_line);
ledit_line *ll = ledit_get_line(buffer, src_line);
if (src_len == -1)
src_len = ll->len - src_index;
+ if (text_ret != NULL) {
+ lbuf_grow(text_ret, src_len);
+ text_ret->len = src_len;
+ }
if (src_index >= ll->gap) {
/* all text to insert is after gap */
ledit_insert_text_base(
buffer, dst_line, dst_index,
ll->text + src_index + ll->cap - ll->len, src_len
);
+ if (text_ret != NULL) {
+ memcpy(
+ text_ret->text,
+ ll->text + src_index + ll->cap - ll->len,
+ src_len
+ );
+ }
} else if (ll->gap - src_index >= src_len) {
/* all text to insert is before gap */
ledit_insert_text_base(
buffer, dst_line, dst_index,
ll->text + src_index, src_len
);
+ if (text_ret != NULL) {
+ memcpy(
+ text_ret->text,
+ ll->text + src_index,
+ src_len
+ );
+ }
} else {
/* insert part of text before gap */
ledit_insert_text_base(
@@ -191,6 +215,18 @@ ledit_insert_text_from_line_base(
ll->text + ll->gap + ll->cap - ll->len,
src_len - ll->gap + src_index
);
+ if (text_ret != NULL) {
+ memcpy(
+ text_ret->text,
+ ll->text + src_index,
+ ll->gap - src_index
+ );
+ memcpy(
+ text_ret + ll->gap - src_index,
+ ll->text + ll->gap + ll->cap - ll->len,
+ src_len - ll->gap + src_index
+ );
+ }
}
}
@@ -343,9 +379,9 @@ ledit_insert_text_with_newlines_base(
int cur_line = line_index;
int cur_index = index;
while ((cur = strchr_len(last, '\n', rem_len)) != NULL) {
- ledit_insert_text_base(buffer, cur_line, cur_index, last, cur - last);
/* FIXME: inefficient because there's no gap buffer yet */
- ledit_append_line_base(buffer, cur_line, -1);
+ ledit_append_line_base(buffer, cur_line, cur_index);
+ ledit_insert_text_base(buffer, cur_line, cur_index, last, cur - last);
cur_index = 0;
cur_line++;
last = cur + 1;
@@ -453,7 +489,7 @@ ledit_append_line_base(ledit_buffer *buffer, int line_index, int text_index) {
ledit_line *l = ledit_get_line(buffer, line_index);
ledit_insert_text_from_line_base(
buffer, line_index + 1, 0,
- line_index, text_index, -1
+ line_index, text_index, -1, NULL
);
delete_line_section_base(
buffer, line_index,
@@ -653,21 +689,17 @@ ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2
* - *dst is null-terminated
* - the range must be sorted already
* - returns the length of the text, not including the NUL */
-size_t
-ledit_copy_text_with_resize(
+void
+ledit_copy_text_to_lbuf(
ledit_buffer *buffer,
- char **dst, size_t *alloc,
+ lbuf *buf,
int line1, int byte1,
int line2, int byte2) {
assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
size_t len = ledit_textlen(buffer, line1, byte1, line2, byte2);
- /* len + 1 because of nul */
- if (len + 1 > *alloc) {
- *alloc = *alloc * 2 > len + 1 ? *alloc * 2 : len + 1;
- *dst = ledit_realloc(*dst, *alloc);
- }
- ledit_copy_text(buffer, *dst, line1, byte1, line2, byte2);
- return len;
+ lbuf_grow(buf, len + 1);
+ ledit_copy_text(buffer, buf->text, line1, byte1, line2, byte2);
+ buf->len = len;
}
/* get char with logical index i from line */
@@ -830,12 +862,14 @@ ledit_delete_range(
ledit_buffer *buffer, int line_based,
int line_index1, int byte_index1,
int line_index2, int byte_index2,
- int *new_line_ret, int *new_byte_ret) {
+ int *new_line_ret, int *new_byte_ret,
+ ledit_range *final_range_ret, lbuf *text_ret) {
ledit_delete_range_base(
buffer, line_based,
line_index1, byte_index1,
line_index2, byte_index2,
- new_line_ret, new_byte_ret
+ new_line_ret, new_byte_ret,
+ final_range_ret, text_ret
);
/* need to start recalculating one line before in case first
line was deleted and offset is now wrong */
@@ -849,7 +883,12 @@ ledit_delete_range_base(
ledit_buffer *buffer, int line_based,
int line_index1, int byte_index1,
int line_index2, int byte_index2,
- int *new_line_ret, int *new_byte_ret) {
+ int *new_line_ret, int *new_byte_ret,
+ ledit_range *final_range_ret, lbuf *text_ret) {
+ /* FIXME: Oh boy, this is nasty */
+ /* range line x, range byte x */
+ int rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0;
+ int new_line = 0, new_byte = 0;
if (line_based) {
int x, softline1, softline2;
ledit_line *line1 = ledit_get_line(buffer, line_index1);
@@ -865,51 +904,76 @@ ledit_delete_range_base(
PangoLayoutLine *pl2 = pango_layout_get_line_readonly(line1->layout, l2);
/* don't delete entire line if it is the last one remaining */
if (l1 == 0 && l2 == softlines - 1 && buffer->lines_num > 1) {
- ledit_delete_line_entry_base(buffer, line_index1);
- /* note: line_index1 is now the index of the next
- line since the current one was just deleted */
- if (line_index1 < buffer->lines_num) {
- *new_line_ret = line_index1;
+ if (line_index1 < buffer->lines_num - 1) {
+ /* cursor can be moved to next hard line */
+ new_line = line_index1;
ledit_x_softline_to_pos(
- ledit_get_line(buffer, line_index1),
- x, 0, new_byte_ret
+ ledit_get_line(buffer, line_index1 + 1),
+ x, 0, &new_byte
);
+ rgl1 = line_index1;
+ rgb1 = 0;
+ rgl2 = line_index1 + 1;
+ rgb2 = 0;
} else {
- /* note: logically, this must be >= 0 because
- buffer->lines_num > 1 && line_index1 >= buffer->lines_num */
- *new_line_ret = line_index1 - 1;
+ /* cursor has to be be moved to previous hard line
+ because last line in buffer is deleted */
+ /* note: logically, line_index1 - 1 must be >= 0 because
+ buffer->lines_num > 1 && line_index1 >= buffer->lines_num - 1 */
+ new_line = line_index1 - 1;
ledit_line *prevline = ledit_get_line(buffer, line_index1 - 1);
softlines = pango_layout_get_line_count(prevline->layout);
- ledit_x_softline_to_pos(prevline, x, softlines - 1, new_byte_ret);
+ ledit_x_softline_to_pos(prevline, x, softlines - 1, &new_byte);
+ rgl1 = line_index1 - 1;
+ rgb1 = prevline->len;
+ rgl2 = line_index1;
+ rgb2 = line1->len;
}
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
+ );
+ }
+ ledit_delete_line_entry_base(buffer, line_index1);
} else {
- /* FIXME: sanity checks that the length is actually positive, etc. */
+ assert(pl2->start_index + pl2->length - pl1->start_index >= 0);
+ rgl1 = rgl2 = line_index1;
+ rgb1 = pl1->start_index;
+ rgb2 = pl2->start_index + pl2->length;
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
+ );
+ }
delete_line_section_base(
- buffer, line_index1, pl1->start_index,
- pl2->start_index + pl2->length - pl1->start_index
+ buffer, line_index1, rgb1, rgb2 - rgb1
);
if (l2 == softlines - 1 && line_index1 < buffer->lines_num - 1) {
- *new_line_ret = line_index1 + 1;
+ new_line = line_index1 + 1;
ledit_x_softline_to_pos(
ledit_get_line(buffer, line_index1 + 1),
- x, 0, new_byte_ret
+ x, 0, &new_byte
);
} else if (l2 < softlines - 1) {
- *new_line_ret = line_index1;
+ new_line = line_index1;
ledit_x_softline_to_pos(
ledit_get_line(buffer, line_index1),
- x, l1, new_byte_ret
+ x, l1, &new_byte
);
} else if (l1 > 0) {
- *new_line_ret = line_index1;
+ new_line = line_index1;
ledit_x_softline_to_pos(
ledit_get_line(buffer, line_index1),
- x, l1 - 1, new_byte_ret
+ x, l1 - 1, &new_byte
);
} else {
/* the line has been emptied and is the last line remaining */
- *new_line_ret = 0;
- *new_byte_ret = 0;
+ new_line = 0;
+ new_byte = 0;
}
}
} else {
@@ -937,96 +1001,186 @@ ledit_delete_range_base(
int softlines = pango_layout_get_line_count(ll2->layout);
if (sl1 == 0 && sl2 == softlines - 1) {
if (l1 == 0 && l2 == buffer->lines_num - 1) {
+ rgl1 = l1;
+ rgl2 = l2;
+ rgb1 = 0;
+ rgb2 = ll2->len;
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
+ );
+ }
delete_line_section_base(buffer, l1, 0, ll1->len);
ledit_delete_line_entries_base(buffer, l1 + 1, l2);
- *new_line_ret = 0;
- *new_byte_ret = 0;
+ new_line = 0;
+ new_byte = 0;
} else {
- ledit_delete_line_entries_base(buffer, l1, l2);
- if (l1 >= buffer->lines_num) {
- *new_line_ret = buffer->lines_num - 1;
- ledit_line *new_lline = ledit_get_line(buffer, *new_line_ret);
+ if (l2 == buffer->lines_num - 1) {
+ new_line = l1 - 1;
+ ledit_line *new_lline = ledit_get_line(buffer, new_line);
int new_softlines = pango_layout_get_line_count(new_lline->layout);
- ledit_x_softline_to_pos(new_lline, x, new_softlines - 1, new_byte_ret);
+ ledit_x_softline_to_pos(new_lline, x, new_softlines - 1, &new_byte);
+ rgl1 = l1 - 1;
+ rgb1 = new_lline->len;
+ rgl2 = l2;
+ rgb2 = ll2->len;
} else {
- *new_line_ret = l1;
+ new_line = l1;
+ ledit_line *nextline = ledit_get_line(buffer, l2 + 1);
ledit_x_softline_to_pos(
- ledit_get_line(buffer, l1),
- x, 0, new_byte_ret
+ nextline, x, 0, &new_byte
);
+ rgl1 = l1;
+ rgb1 = 0;
+ rgl2 = l2 + 1;
+ rgb2 = nextline->len;
}
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
+ );
+ }
+ ledit_delete_line_entries_base(buffer, l1, l2);
}
} else if (sl1 == 0) {
+ rgl1 = l1;
+ rgb1 = 0;
+ rgl2 = l2;
+ rgb2 = pl2->start_index + pl2->length;
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
+ );
+ }
delete_line_section_base(buffer, l2, 0, pl2->start_index + pl2->length);
+ new_line = l1;
+ ledit_x_softline_to_pos(ll2, x, 0, &new_byte);
ledit_delete_line_entries_base(buffer, l1, l2 - 1);
- *new_line_ret = l1;
- ledit_x_softline_to_pos(ledit_get_line(buffer, l1), x, 0, new_byte_ret);
} else if (sl2 == softlines - 1) {
- delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index);
- ledit_delete_line_entries_base(buffer, l1 + 1, l2);
- if (l1 + 1 >= buffer->lines_num) {
- *new_line_ret = buffer->lines_num - 1;
- ledit_line *new_lline = ledit_get_line(buffer, *new_line_ret);
- int new_softlines = pango_layout_get_line_count(new_lline->layout);
- ledit_x_softline_to_pos(new_lline, x, new_softlines - 1, new_byte_ret);
+ rgl1 = l1;
+ rgb1 = pl1->start_index;
+ rgl2 = l2;
+ rgb2 = ll2->len;
+ if (l2 + 1 == buffer->lines_num) {
+ new_line = l1;
+ ledit_x_softline_to_pos(ll1, x, sl1 - 1, &new_byte);
} else {
- *new_line_ret = l1 + 1;
+ new_line = l1 + 1;
ledit_x_softline_to_pos(
- ledit_get_line(buffer, l1 + 1),
- x, 0, new_byte_ret
+ ledit_get_line(buffer, l2 + 1),
+ x, 0, &new_byte
+ );
+ }
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
);
}
+ delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index);
+ ledit_delete_line_entries_base(buffer, l1 + 1, l2);
} else {
- /* FIXME: should this join the two lines? */
+ /* FIXME: this could be made nicer by just using the range to
+ delete all in one go at the end */
+ rgl1 = l1;
+ rgb1 = pl1->start_index;
+ rgl2 = l2;
+ rgb2 = pl2->start_index + pl2->length;
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
+ );
+ }
delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index);
- delete_line_section_base(buffer, l2, 0, pl2->start_index + pl2->length);
- if (l2 > l1 + 1)
- ledit_delete_line_entries_base(buffer, l1 + 1, l2 - 1);
- *new_line_ret = l1 + 1;
- ledit_x_softline_to_pos(ledit_get_line(buffer, l1 + 1), x, 0, new_byte_ret);
+ ledit_insert_text_from_line_base(
+ buffer,
+ l1, pl1->start_index,
+ l2, pl2->start_index + pl2->length,
+ ll2->len - (pl2->start_index + pl2->length), NULL
+ );
+ ledit_delete_line_entries_base(buffer, l1 + 1, l2);
+ new_line = l1;
+ int new_softlines = pango_layout_get_line_count(ll1->layout);
+ /* 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
+ a bit weird because the cursor will seem to stay on the
+ same line, but it now includes the rest of the second line
+ (FIXME: this is probably not the best thing to do) */
+ ledit_x_softline_to_pos(
+ ll1, x, sl1 + 1 < new_softlines ? sl1 + 1 : sl1, &new_byte
+ );
}
}
} else {
if (line_index1 == line_index2) {
- int b1, b2;
+ rgl1 = rgl2 = line_index1;
if (byte_index1 < byte_index2) {
- b1 = byte_index1;
- b2 = byte_index2;
+ rgb1 = byte_index1;
+ rgb2 = byte_index2;
} else {
- b1 = byte_index2;
- b2 = byte_index1;
+ rgb1 = byte_index2;
+ rgb2 = byte_index1;
+ }
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
+ );
}
- delete_line_section_base(buffer, line_index1, b1, b2 - b1);
- *new_line_ret = line_index1;
- *new_byte_ret = b1;
+ delete_line_section_base(buffer, line_index1, rgb1, rgb2 - rgb1);
+ new_line = line_index1;
+ new_byte = rgb1;
} else {
- int l1, l2, b1, b2;
if (line_index1 < line_index2) {
- l1 = line_index1;
- b1 = byte_index1;
- l2 = line_index2;
- b2 = byte_index2;
+ rgl1 = line_index1;
+ rgb1 = byte_index1;
+ rgl2 = line_index2;
+ rgb2 = byte_index2;
} else {
- l1 = line_index2;
- b1 = byte_index2;
- l2 = line_index1;
- b2 = byte_index1;
+ rgl1 = line_index2;
+ rgb1 = byte_index2;
+ rgl2 = line_index1;
+ rgb2 = byte_index1;
}
- ledit_line *line1 = ledit_get_line(buffer, l1);
- ledit_line *line2 = ledit_get_line(buffer, l2);
- line1->len = b1;
- if (b2 > 0) {
- ledit_insert_text_base(
- buffer, l1, b1,
- line2->text + b2,
- line2->len - b2
+ if (text_ret) {
+ ledit_copy_text_to_lbuf(
+ buffer, text_ret,
+ rgl1, rgb1,
+ rgl2, rgb2
);
}
- *new_line_ret = l1;
- *new_byte_ret = b1;
- ledit_delete_line_entries_base(buffer, l1 + 1, l2);
+ ledit_line *line1 = ledit_get_line(buffer, rgl1);
+ ledit_line *line2 = ledit_get_line(buffer, rgl2);
+ delete_line_section_base(buffer, rgl1, rgb1, line1->len - rgb1);
+ ledit_insert_text_from_line_base(
+ buffer, rgl1, rgb1, rgl2, rgb2, line2->len - rgb2, NULL
+ );
+ new_line = rgl1;
+ new_byte = rgb1;
+ ledit_delete_line_entries_base(buffer, rgl1 + 1, rgl2);
}
if (buffer->state->mode == NORMAL)
- *new_byte_ret = ledit_get_legal_normal_pos(buffer, *new_line_ret, *new_byte_ret);
+ new_byte = ledit_get_legal_normal_pos(buffer, 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;
}
+ if (new_line_ret)
+ *new_line_ret = new_line;
+ if (new_byte_ret)
+ *new_byte_ret = new_byte;
}
diff --git a/buffer.h b/buffer.h
@@ -3,7 +3,7 @@ typedef struct {
int byte1;
int line2;
int byte2;
-} ledit_selection;
+} ledit_range;
typedef struct ledit_buffer ledit_buffer;
@@ -39,7 +39,8 @@ struct ledit_buffer {
long total_height; /* total pixel height of all lines */
double display_offset; /* current pixel offset of viewport - this
* is a double to make scrolling smoother */
- ledit_selection sel; /* current selection; all entries -1 if no selection */
+ ledit_range sel; /* current selection; all entries -1 if no selection */
+ ledit_undo_stack *undo;
};
ledit_buffer *ledit_create_buffer(ledit_common_state *state);
@@ -58,9 +59,9 @@ int ledit_next_utf8(ledit_line *line, int index);
int ledit_prev_utf8(ledit_line *line, int index);
size_t ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2);
void ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2);
-size_t ledit_copy_text_with_resize(
+void ledit_copy_text_to_lbuf(
ledit_buffer *buffer,
- char **dst, size_t *alloc,
+ lbuf *buf, /* oh, isn't that a very non-confusing name? */
int line1, int byte1,
int line2, int byte2
);
@@ -90,12 +91,14 @@ void ledit_delete_range_base(
ledit_buffer *buffer, int line_based,
int line_index1, int byte_index1,
int line_index2, int byte_index2,
- int *new_line_ret, int *new_byte_ret
+ int *new_line_ret, int *new_byte_ret,
+ ledit_range *final_range_ret, lbuf *text_ret
);
void ledit_insert_text_from_line_base(
ledit_buffer *buffer,
int dst_line, int dst_index,
- int src_line, int src_index, int src_len
+ int src_line, int src_index, int src_len,
+ lbuf *text_ret
);
void ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len);
@@ -113,10 +116,12 @@ void ledit_delete_range(
ledit_buffer *buffer, int line_based,
int line_index1, int byte_index1,
int line_index2, int byte_index2,
- int *new_line_ret, int *new_byte_ret
+ int *new_line_ret, int *new_byte_ret,
+ ledit_range *final_range_ret, lbuf *text_ret
);
void ledit_insert_text_from_line(
ledit_buffer *buffer,
int dst_line, int dst_index,
- int src_line, int src_index, int src_len
+ int src_line, int src_index, int src_len,
+ lbuf *text_ret
);
diff --git a/cache.c b/cache.c
@@ -6,6 +6,7 @@
#include "common.h"
#include "memory.h"
+#include "lbuf.h"
#include "buffer.h"
#include "cache.h"
diff --git a/common.h b/common.h
@@ -1,3 +1,5 @@
+/* FIXME: it's ugly to put this here */
+typedef struct ledit_undo_stack ledit_undo_stack;
enum ledit_mode {
NORMAL = 1,
INSERT = 2,
diff --git a/lbuf.c b/lbuf.c
@@ -0,0 +1,51 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "memory.h"
+#include "lbuf.h"
+
+lbuf *
+lbuf_new(void) {
+ lbuf *buf = ledit_malloc(sizeof(lbuf));
+ buf->text = NULL;
+ buf->cap = buf->len = 0;
+ return buf;
+}
+
+void
+lbuf_grow(lbuf *buf, size_t sz) {
+ /* always leave room for extra \0 */
+ if (sz + 1 > buf->cap) {
+ /* FIXME: what are the best values here? */
+ buf->cap = buf->cap * 2 > sz + 1 ? buf->cap * 2 : sz + 1;
+ buf->text = ledit_realloc(buf->text, buf->cap);
+ }
+}
+
+void
+lbuf_shrink(lbuf *buf) {
+ if ((buf->len + 1) * 4 < buf->cap) {
+ buf->cap /= 2;
+ buf->text = ledit_realloc(buf->text, buf->cap);
+ }
+}
+
+void
+lbuf_destroy(lbuf *buf) {
+ free(buf->text);
+ free(buf);
+}
+
+void
+lbuf_cpy(lbuf *dst, lbuf *src) {
+ lbuf_grow(dst, src->len);
+ memcpy(dst->text, src->text, src->len);
+ dst->len = src->len;
+}
+
+lbuf *
+lbuf_dup(lbuf *src) {
+ lbuf *dst = lbuf_new();
+ lbuf_cpy(dst, src);
+ return dst;
+}
diff --git a/lbuf.h b/lbuf.h
@@ -0,0 +1,12 @@
+/* FIXME: RENAME THIS */
+typedef struct {
+ size_t len, cap;
+ char *text;
+} lbuf;
+
+lbuf *lbuf_new(void);
+void lbuf_grow(lbuf *buf, size_t sz);
+void lbuf_shrink(lbuf *buf);
+void lbuf_destroy(lbuf *buf);
+void lbuf_cpy(lbuf *dst, lbuf *src);
+lbuf *lbuf_dup(lbuf *src);
diff --git a/ledit.c b/ledit.c
@@ -1,6 +1,6 @@
/* FIXME: Only redraw part of screen if needed */
/* FIXME: overflow in repeated commands */
-/* FIXME: Fix lag when scrolling */
+/* FIXME: Fix lag when scrolling - combine repeated mouse motion events */
/* FIXME: Fix lag when selecting with mouse */
/* FIXME: Use PANGO_PIXELS() */
/* FIXME: Fix cursor movement, especially buffer->trailing and writing at end of line */
@@ -31,10 +31,12 @@
#include "memory.h"
#include "common.h"
+#include "lbuf.h"
#include "buffer.h"
#include "search.h"
#include "cache.h"
#include "util.h"
+#include "undo.h"
enum key_type {
KEY_NONE = 0,
@@ -87,6 +89,12 @@ struct key_stack_elem {
int data2; /* misc. data 2 */
};
+/* buffer for storing yanked text */
+lbuf *paste_buffer = NULL;
+/* temporary buffer used for storing text
+ in order to add it to the undo stack */
+lbuf *tmp_buffer = NULL;
+
static struct {
size_t len, alloc;
struct key_stack_elem *stack;
@@ -216,8 +224,7 @@ show_message(char *text, int len) {
struct {
Atom xtarget;
- char *primary;
- size_t primary_alloc;
+ lbuf *primary;
char *clipboard;
} xsel;
@@ -243,6 +250,7 @@ set_mode(enum ledit_mode mode) {
XftDrawRect(bottom_bar.mode_draw->xftdraw, &state.bg, 0, 0, bottom_bar.mode_w, bottom_bar.mode_h);
pango_xft_render_layout(bottom_bar.mode_draw->xftdraw, &state.fg, bottom_bar.mode, 0, 0);
recalc_text_size();
+ ledit_change_mode_group(buffer);
}
void
@@ -253,8 +261,9 @@ clipcopy(void)
free(xsel.clipboard);
xsel.clipboard = NULL;
- if (xsel.primary != NULL) {
- xsel.clipboard = ledit_strdup(xsel.primary);
+ /* FIXME: don't copy if text empty (no selection)? */
+ if (xsel.primary->text != NULL) {
+ xsel.clipboard = ledit_strdup(xsel.primary->text);
clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0);
XSetSelectionOwner(state.dpy, clipboard, state.win, CurrentTime);
}
@@ -402,7 +411,7 @@ selrequest(XEvent *e)
*/
clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0);
if (xsre->selection == XA_PRIMARY) {
- seltext = xsel.primary;
+ seltext = xsel.primary->text;
} else if (xsre->selection == clipboard) {
seltext = xsel.clipboard;
} else {
@@ -482,15 +491,63 @@ get_new_line_softline(
}
}
+/* FIXME: don't overwrite buffer->cur_line, etc. here? */
+static void
+delete_range(
+ int line_based, int selected,
+ int line_index1, int byte_index1,
+ int line_index2, int byte_index2) {
+ (void)selected; /* FIXME */
+ ledit_range cur_range, del_range;
+ cur_range.line1 = buffer->cur_line;
+ cur_range.byte1 = buffer->cur_index;
+ ledit_delete_range(
+ buffer, line_based,
+ line_index1, byte_index1,
+ line_index2, byte_index2,
+ &buffer->cur_line, &buffer->cur_index,
+ &del_range, paste_buffer
+ );
+ cur_range.line2 = buffer->cur_line;
+ cur_range.byte2 = buffer->cur_index;
+ ledit_push_undo_delete(
+ buffer, paste_buffer, del_range, cur_range, 1
+ );
+}
+
+static void
+insert_text(int line, int index, char *text, int len, int start_group) {
+ if (len < 0)
+ len = strlen(text);
+ /* FIXME: this is kind of hacky... */
+ lbuf ins_buf = {.text = text, .len = len, .cap = len};
+ ledit_range cur_range, del_range;
+ cur_range.line1 = buffer->cur_line;
+ cur_range.byte1 = buffer->cur_index;
+ del_range.line1 = line;
+ del_range.byte1 = index;
+ ledit_insert_text_with_newlines(
+ buffer, line, index, text, len,
+ &buffer->cur_line, &buffer->cur_index
+ );
+ cur_range.line2 = buffer->cur_line;
+ cur_range.byte2 = buffer->cur_index;
+ del_range.line2 = buffer->cur_line;
+ del_range.byte2 = buffer->cur_index;
+ ledit_push_undo_insert(
+ buffer, &ins_buf, del_range, cur_range, start_group
+ );
+}
+
static int
delete_selection(void) {
if (buffer->sel.line1 != buffer->sel.line2 || buffer->sel.byte1 != buffer->sel.byte2) {
- ledit_delete_range(
- buffer, 0,
+ delete_range(
+ 0, 0,
buffer->sel.line1, buffer->sel.byte1,
- buffer->sel.line2, buffer->sel.byte2,
- &buffer->cur_line, &buffer->cur_index
+ buffer->sel.line2, buffer->sel.byte2
);
+ /* FIXME: maybe just set this to the current cursor pos? */
buffer->sel.line1 = buffer->sel.line2 = -1;
buffer->sel.byte1 = buffer->sel.byte2 = -1;
ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
@@ -545,11 +602,10 @@ key_d(void) {
static void
key_d_cb(int line, int char_pos, enum key_type type) {
int line_based = type == KEY_MOTION_LINE ? 1 : 0;
- ledit_delete_range(
- buffer, line_based,
+ delete_range(
+ line_based, 0,
buffer->cur_line, buffer->cur_index,
- line, char_pos,
- &buffer->cur_line, &buffer->cur_index
+ line, char_pos
);
ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
}
@@ -565,6 +621,7 @@ key_x(void) {
num = e->count;
if (num <= 0)
num = 1;
+ /* FIXME: actually do something */
}
static void
@@ -575,7 +632,7 @@ push_num(int num) {
e->key = KEY_NUMBER;
e->followup = KEY_NUMBER|KEY_NUMBERALLOWED;
}
- /* FIXME: error checking */
+ /* FIXME: error (overflow) checking */
e->count *= 10;
e->count += num;
}
@@ -662,6 +719,7 @@ push_key_stack(void) {
/* Note: for peek and pop, the returned element is only valid
* until the next element is pushed */
+/* Note on the note: that's not entirely true for peek */
static struct key_stack_elem *
peek_key_stack(void) {
if (key_stack.len > 0)
@@ -947,7 +1005,6 @@ setup(int argc, char *argv[]) {
pango_layout_set_font_description(bottom_bar.mode, state.font);
/* FIXME: only create "dummy draw" at first and create with proper size when needed */
bottom_bar.mode_draw = ledit_create_draw(&state, 10, 10);
- set_mode(INSERT);
bottom_bar.line = pango_layout_new(state.context);
pango_layout_set_font_description(bottom_bar.line, state.font);
bottom_bar.line_draw = ledit_create_draw(&state, 10, 10);
@@ -962,12 +1019,17 @@ setup(int argc, char *argv[]) {
ledit_init_cache(&state);
buffer = ledit_create_buffer(&state);
+ /* FIXME: move this to create_buffer */
+ ledit_init_undo_stack(buffer);
+ set_mode(INSERT);
key_stack.len = key_stack.alloc = 0;
key_stack.stack = NULL;
- xsel.primary = NULL;
- xsel.primary_alloc = 0;
+ paste_buffer = lbuf_new();
+ tmp_buffer = lbuf_new();
+
+ xsel.primary = lbuf_new();
xsel.clipboard = NULL;
xsel.xtarget = XInternAtom(state.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None)
@@ -982,6 +1044,7 @@ cleanup(void) {
/* FIXME: cleanup everything else */
ledit_cleanup_search();
ledit_destroy_cache();
+ ledit_destroy_undo_stack(buffer);
ledit_destroy_buffer(buffer);
XDestroyWindow(state.dpy, state.win);
XCloseDisplay(state.dpy);
@@ -1153,10 +1216,7 @@ sort_selection(int *line1, int *byte1, int *line2, int *byte2) {
/* lines and bytes need to be sorted already! */
static void
copy_selection_to_x_primary(int line1, int byte1, int line2, int byte2) {
- (void)ledit_copy_text_with_resize(
- buffer, &xsel.primary, &xsel.primary_alloc,
- line1, byte1, line2, byte2
- );
+ ledit_copy_text_to_lbuf(buffer, xsel.primary, line1, byte1, line2, byte2);
XSetSelectionOwner(state.dpy, XA_PRIMARY, state.win, CurrentTime);
/*
FIXME
@@ -1343,19 +1403,27 @@ backspace(void) {
} else if (buffer->cur_index == 0) {
if (buffer->cur_line != 0) {
ledit_line *l1 = ledit_get_line(buffer, buffer->cur_line - 1);
+ delete_range(0, 0, buffer->cur_line - 1, l1->len, buffer->cur_line, 0);
+ /*
int old_len = l1->len;
ledit_insert_text_from_line(
buffer, buffer->cur_line - 1, l1->len,
- buffer->cur_line, 0, -1
+ buffer->cur_line, 0, l2->len, tmp_buffer
);
ledit_delete_line_entry(buffer, buffer->cur_line);
buffer->cur_line--;
buffer->cur_index = old_len;
+ */
}
} else {
+ ledit_line *l = ledit_get_line(buffer, buffer->cur_line);
+ int i = ledit_prev_utf8(l, buffer->cur_index);
+ delete_range(0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i);
+ /*
buffer->cur_index = ledit_delete_unicode_char(
buffer, buffer->cur_line, buffer->cur_index, -1
);
+ */
}
ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
}
@@ -1367,19 +1435,24 @@ delete_key(void) {
/* NOP */
} else if (buffer->cur_index == cur_line->len) {
if (buffer->cur_line != buffer->lines_num - 1) {
+ delete_range(0, 0, buffer->cur_line, cur_line->len, buffer->cur_line + 1, 0);
+ /*
int old_len = cur_line->len;
- /* FIXME: THIS CURRENTLY DOESN'T RECALC LINE SIZE! */
ledit_insert_text_from_line(
buffer, buffer->cur_line, cur_line->len,
buffer->cur_line + 1, 0, -1
);
ledit_delete_line_entry(buffer, buffer->cur_line + 1);
- buffer->cur_index = old_len;
+ */
}
} else {
+ int i = ledit_next_utf8(cur_line, buffer->cur_index);
+ delete_range(0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i);
+ /*
buffer->cur_index = ledit_delete_unicode_char(
buffer, buffer->cur_line, buffer->cur_index, 1
);
+ */
}
ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
}
@@ -1466,14 +1539,17 @@ cursor_right(void) {
static void
return_key(void) {
- delete_selection();
- ledit_append_line(buffer, buffer->cur_line, buffer->cur_index);
+ int start_group = 1;
+ if (delete_selection())
+ start_group = 0;
+ insert_text(buffer->cur_line, buffer->cur_index, "\n", -1, start_group);
+ /* ledit_append_line(buffer, buffer->cur_line, buffer->cur_index); */
/* FIXME: these aren't needed, right? This only works in insert mode
* anyways, so there's nothing to wipe */
- ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
+ /* ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
buffer->cur_line++;
ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
- buffer->cur_index = 0;
+ buffer->cur_index = 0; */
}
static void
@@ -1690,6 +1766,23 @@ show_line(void) {
show_message(str, len);
}
+/* FIXME: return status! */
+static void
+undo(void) {
+ set_selection(0, 0, 0, 0);
+ ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
+ ledit_undo(buffer);
+ ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
+}
+
+static void
+redo(void) {
+ set_selection(0, 0, 0, 0);
+ ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
+ ledit_redo(buffer);
+ ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
+}
+
/* FIXME: maybe sort these and use binary search */
static struct key keys_en[] = {
{NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace},
@@ -1728,7 +1821,11 @@ static struct key keys_en[] = {
{"/", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forward},
{NULL, 0, XK_Return, COMMANDEDIT|SEARCHEDIT, KEY_ANY, KEY_ANY, &end_lineedit},
{"n", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &search_next},
- {"N", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &search_prev}
+ {"N", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &search_prev},
+ {"u", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &undo},
+ {"U", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &redo},
+ {"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &undo},
+ {"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo}
};
static struct key keys_ur[] = {
@@ -1859,12 +1956,15 @@ key_press(XEvent event) {
state.message_shown--;
} else if (state.mode == INSERT && !found && n > 0) {
delete_selection();
+ insert_text(buffer->cur_line, buffer->cur_index, buf, n, 1);
+ /*
ledit_insert_text_with_newlines(
buffer,
buffer->cur_line, buffer->cur_index,
buf, n,
&buffer->cur_line, &buffer->cur_index
);
+ */
ensure_cursor_shown();
if (state.message_shown > 0)
state.message_shown--;
diff --git a/search.c b/search.c
@@ -6,6 +6,7 @@
#include "memory.h"
#include "common.h"
+#include "lbuf.h"
#include "buffer.h"
#include "search.h"
diff --git a/undo.c b/undo.c
@@ -0,0 +1,250 @@
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <pango/pangoxft.h>
+/* FIXME: move some parts of common to ledit.c so
+ this include isn't needed */
+#include <X11/extensions/Xdbe.h>
+
+#include "memory.h"
+#include "common.h"
+#include "lbuf.h"
+#include "buffer.h"
+#include "cache.h"
+#include "undo.h"
+
+enum operation {
+ UNDO_INSERT,
+ UNDO_DELETE
+};
+
+typedef struct {
+ lbuf *text;
+ enum operation type;
+ enum ledit_mode mode;
+ ledit_range op_range;
+ ledit_range cursor_range;
+ int group;
+ int mode_group;
+} undo_elem;
+
+struct ledit_undo_stack {
+ /* FIXME: size_t? */
+ int len, cur, cap;
+ undo_elem *stack;
+ int change_mode_group;
+};
+
+/* FIXME: maybe make these work directly on the stack instead of buffer */
+void
+ledit_init_undo_stack(ledit_buffer *buffer) {
+ buffer->undo = ledit_malloc(sizeof(ledit_undo_stack));
+ buffer->undo->len = buffer->undo->cap = 0;
+ buffer->undo->cur = -1;
+ buffer->undo->stack = NULL;
+ buffer->undo->change_mode_group = 0;
+}
+
+void
+ledit_destroy_undo_stack(ledit_buffer *buffer) {
+ free(buffer->undo->stack);
+ free(buffer->undo);
+}
+
+/* FIXME: resize text buffers when they aren't needed anymore */
+static undo_elem *
+push_undo_elem(ledit_buffer *buffer) {
+ ledit_undo_stack *s = buffer->undo;
+ assert(s->cur >= -1);
+ s->cur++;
+ s->len = s->cur + 1;
+ if (s->len > s->cap) {
+ size_t cap = s->len * 2;
+ s->stack = ledit_realloc(s->stack, cap * sizeof(undo_elem));
+ for (size_t i = s->cap; i < cap; i++) {
+ s->stack[i].text = NULL;
+ }
+ s->cap = cap;
+ }
+ return &s->stack[s->cur];
+}
+
+static undo_elem *
+peek_undo_elem(ledit_buffer *buffer) {
+ ledit_undo_stack *s = buffer->undo;
+ if (s->cur < 0)
+ return NULL;
+ return &s->stack[s->cur];
+}
+
+void
+ledit_change_mode_group(ledit_buffer *buffer) {
+ buffer->undo->change_mode_group = 1;
+}
+
+/* FIXME: The current cursor position could be taken directly from the
+ buffer, but maybe it's better this way to make it a bit more explicit? */
+static void
+push_undo(
+ ledit_buffer *buffer, lbuf *text,
+ ledit_range insert_range,
+ ledit_range cursor_range,
+ int start_group, enum operation type) {
+ undo_elem *old = peek_undo_elem(buffer);
+ int last_group = old == NULL ? 0 : old->group;
+ int last_mode_group = old == NULL ? 0 : old->mode_group;
+ undo_elem *e = push_undo_elem(buffer);
+ e->group = start_group ? !last_group : last_group;
+ e->mode_group = buffer->undo->change_mode_group ? !last_mode_group : last_mode_group;
+ buffer->undo->change_mode_group = 0;
+ e->op_range = insert_range;
+ e->cursor_range = cursor_range;
+ e->mode = buffer->state->mode;
+ e->type = type;
+ if (e->text != NULL)
+ lbuf_cpy(e->text, text);
+ else
+ e->text = lbuf_dup(text);
+}
+
+void
+ledit_push_undo_insert(
+ ledit_buffer *buffer, lbuf *text,
+ ledit_range insert_range,
+ ledit_range cursor_range,
+ int start_group) {
+ push_undo(
+ buffer, text, insert_range,
+ cursor_range, start_group, UNDO_INSERT
+ );
+}
+
+void
+ledit_push_undo_delete(
+ ledit_buffer *buffer, lbuf *text,
+ ledit_range insert_range,
+ ledit_range cursor_range,
+ int start_group) {
+ push_undo(
+ buffer, text, insert_range,
+ cursor_range, start_group, UNDO_DELETE
+ );
+}
+
+ledit_undo_status
+ledit_undo(ledit_buffer *buffer) {
+ undo_elem *e;
+ ledit_undo_stack *s = buffer->undo;
+ if (s->cur < 0)
+ return UNDO_OLDEST_CHANGE;
+ int group = s->stack[s->cur].group;
+ int mode_group = s->stack[s->cur].mode_group;
+ int min_line = buffer->lines_num - 1;
+ int mode_group_same = 0;
+ while (s->cur >= 0 &&
+ (s->stack[s->cur].group == group || (mode_group_same =
+ ((buffer->state->mode == NORMAL ||
+ buffer->state->mode == VISUAL) &&
+ s->stack[s->cur].mode == INSERT &&
+ s->stack[s->cur].mode_group == mode_group)))) {
+ e = &s->stack[s->cur];
+ /* if the mode group is the same, we need to update the group,
+ otherwise it can happen that some iterations are performed
+ because the mode group (but not the normal group) is the
+ same, and then the next normal group is also undone because
+ it has the same group id as the original group here */
+ if (mode_group_same)
+ group = e->group;
+ switch (e->type) {
+ case UNDO_INSERT:
+ /* FIXME: should the paste buffer also be modified? */
+ ledit_delete_range_base(
+ buffer, 0,
+ e->op_range.line1, e->op_range.byte1,
+ e->op_range.line2, e->op_range.byte2,
+ NULL, NULL, NULL, NULL
+ );
+ break;
+ case UNDO_DELETE:
+ ledit_insert_text_with_newlines_base(
+ buffer, e->op_range.line1, e->op_range.byte1,
+ e->text->text, e->text->len, NULL, NULL
+ );
+ break;
+ default:
+ fprintf(stderr, "Error with undo. This should not happen. Fix the code please.\n");
+ break;
+ }
+ /* FIXME: make sure this is always sorted already */
+ if (e->op_range.line1 < min_line)
+ min_line = e->op_range.line1;
+ s->cur--;
+ buffer->cur_line = e->cursor_range.line1;
+ buffer->cur_index = e->cursor_range.byte1;
+ }
+ if (buffer->state->mode == NORMAL) {
+ buffer->cur_index = ledit_get_legal_normal_pos(
+ buffer, buffer->cur_line, buffer->cur_index
+ );
+ }
+ ledit_recalc_from_line(buffer, min_line > 0 ? min_line - 1 : min_line);
+ return UNDO_NORMAL;
+}
+
+ledit_undo_status
+ledit_redo(ledit_buffer *buffer) {
+ undo_elem *e;
+ ledit_undo_stack *s = buffer->undo;
+ if (s->cur >= s->len - 1)
+ return UNDO_NEWEST_CHANGE;
+ s->cur++;
+ int group = s->stack[s->cur].group;
+ int mode_group = s->stack[s->cur].mode_group;
+ int min_line = buffer->lines_num - 1;
+ int mode_group_same = 0;
+ while (s->cur < s->len &&
+ (s->stack[s->cur].group == group || (mode_group_same =
+ ((buffer->state->mode == NORMAL ||
+ buffer->state->mode == VISUAL) &&
+ s->stack[s->cur].mode == INSERT &&
+ s->stack[s->cur].mode_group == mode_group)))) {
+ e = &s->stack[s->cur];
+ if (mode_group_same)
+ group = e->group;
+ switch (e->type) {
+ case UNDO_INSERT:
+ ledit_insert_text_with_newlines_base(
+ buffer, e->op_range.line1, e->op_range.byte1,
+ e->text->text, e->text->len, NULL, NULL
+ );
+ break;
+ case UNDO_DELETE:
+ ledit_delete_range_base(
+ buffer, 0,
+ e->op_range.line1, e->op_range.byte1,
+ e->op_range.line2, e->op_range.byte2,
+ NULL, NULL, NULL, NULL
+ );
+ break;
+ default:
+ fprintf(stderr, "Error with redo. This should not happen. Fix the code please.\n");
+ break;
+ }
+ if (e->op_range.line1 < min_line)
+ min_line = e->op_range.line1;
+ s->cur++;
+ buffer->cur_line = e->cursor_range.line2;
+ buffer->cur_index = e->cursor_range.byte2;
+ }
+ s->cur--;
+ if (buffer->state->mode == NORMAL) {
+ buffer->cur_index = ledit_get_legal_normal_pos(
+ buffer, buffer->cur_line, buffer->cur_index
+ );
+ }
+ ledit_recalc_from_line(buffer, min_line > 0 ? min_line - 1 : min_line);
+ return UNDO_NORMAL;
+}
diff --git a/undo.h b/undo.h
@@ -0,0 +1,23 @@
+typedef enum {
+ UNDO_NORMAL,
+ UNDO_OLDEST_CHANGE,
+ UNDO_NEWEST_CHANGE
+} ledit_undo_status;
+
+void ledit_init_undo_stack(ledit_buffer *buffer);
+void ledit_destroy_undo_stack(ledit_buffer *buffer);
+ledit_undo_status ledit_undo(ledit_buffer *buffer);
+ledit_undo_status ledit_redo(ledit_buffer *buffer);
+void ledit_push_undo_insert(
+ ledit_buffer *buffer, lbuf *text,
+ ledit_range insert_range,
+ ledit_range cursor_range,
+ int start_group
+);
+void ledit_push_undo_delete(
+ ledit_buffer *buffer, lbuf *text,
+ ledit_range insert_range,
+ ledit_range cursor_range,
+ int start_group
+);
+void ledit_change_mode_group(ledit_buffer *buffer);