tAdd basic undo/redo support - ledit - Text editor (WIP) | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 5d691e9f97b979d571d38c101554d72e91cb7db5 | |
parent 8779bb819724ae7d05491112ae700063f49cde93 | |
Author: lumidify <[email protected]> | |
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 | |
t@@ -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 | |
t@@ -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… | |
LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm | |
diff --git a/buffer.c b/buffer.c | |
t@@ -11,8 +11,10 @@ | |
#include "memory.h" | |
#include "common.h" | |
+#include "lbuf.h" | |
#include "buffer.h" | |
#include "cache.h" | |
+#include "undo.h" | |
/* | |
* Important notes: | |
t@@ -147,13 +149,16 @@ ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int l… | |
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); | |
} | |
t@@ -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( | |
t@@ -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 | |
+ ); | |
+ } | |
} | |
} | |
t@@ -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 … | |
/* 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 … | |
cur_index = 0; | |
cur_line++; | |
last = cur + 1; | |
t@@ -453,7 +489,7 @@ ledit_append_line_base(ledit_buffer *buffer, int line_inde… | |
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, | |
t@@ -653,21 +689,17 @@ ledit_copy_text(ledit_buffer *buffer, char *dst, int lin… | |
* - *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 */ | |
t@@ -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 */ | |
t@@ -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); | |
t@@ -865,51 +904,76 @@ ledit_delete_range_base( | |
PangoLayoutLine *pl2 = pango_layout_get_line_readonly(… | |
/* don't delete entire line if it is the last one rema… | |
if (l1 == 0 && l2 == softlines - 1 && buffer->lines_nu… | |
- ledit_delete_line_entry_base(buffer, line_inde… | |
- /* note: line_index1 is now the index of the n… | |
- 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 li… | |
+ 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… | |
+ x, 0, &new_byte | |
); | |
+ rgl1 = line_index1; | |
+ rgb1 = 0; | |
+ rgl2 = line_index1 + 1; | |
+ rgb2 = 0; | |
} else { | |
- /* note: logically, this must be >= 0 … | |
- buffer->lines_num > 1 && line_index… | |
- *new_line_ret = line_index1 - 1; | |
+ /* cursor has to be be moved to previo… | |
+ because last line in buffer is dele… | |
+ /* note: logically, line_index1 - 1 mu… | |
+ buffer->lines_num > 1 && line_index… | |
+ new_line = line_index1 - 1; | |
ledit_line *prevline = ledit_get_line(… | |
softlines = pango_layout_get_line_coun… | |
- ledit_x_softline_to_pos(prevline, x, s… | |
+ ledit_x_softline_to_pos(prevline, x, s… | |
+ 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_inde… | |
} else { | |
- /* FIXME: sanity checks that the length is act… | |
+ assert(pl2->start_index + pl2->length - pl1->s… | |
+ 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->star… | |
+ buffer, line_index1, rgb1, rgb2 - rgb1 | |
); | |
if (l2 == softlines - 1 && line_index1 < buffe… | |
- *new_line_ret = line_index1 + 1; | |
+ new_line = line_index1 + 1; | |
ledit_x_softline_to_pos( | |
ledit_get_line(buffer, line_index1… | |
- 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 th… | |
- *new_line_ret = 0; | |
- *new_byte_ret = 0; | |
+ new_line = 0; | |
+ new_byte = 0; | |
} | |
} | |
} else { | |
t@@ -937,96 +1001,186 @@ ledit_delete_range_base( | |
int softlines = pango_layout_get_line_count(ll2->layou… | |
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… | |
ledit_delete_line_entries_base(buffer,… | |
- *new_line_ret = 0; | |
- *new_byte_ret = 0; | |
+ new_line = 0; | |
+ new_byte = 0; | |
} else { | |
- ledit_delete_line_entries_base(buffer,… | |
- if (l1 >= buffer->lines_num) { | |
- *new_line_ret = buffer->lines_… | |
- ledit_line *new_lline = ledit_… | |
+ if (l2 == buffer->lines_num - 1) { | |
+ new_line = l1 - 1; | |
+ ledit_line *new_lline = ledit_… | |
int new_softlines = pango_layo… | |
- ledit_x_softline_to_pos(new_ll… | |
+ ledit_x_softline_to_pos(new_ll… | |
+ rgl1 = l1 - 1; | |
+ rgb1 = new_lline->len; | |
+ rgl2 = l2; | |
+ rgb2 = ll2->len; | |
} else { | |
- *new_line_ret = l1; | |
+ new_line = l1; | |
+ ledit_line *nextline = ledit_g… | |
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,… | |
} | |
} 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->s… | |
+ new_line = l1; | |
+ ledit_x_softline_to_pos(ll2, x, 0, &new_byte); | |
ledit_delete_line_entries_base(buffer, l1, l2 … | |
- *new_line_ret = l1; | |
- ledit_x_softline_to_pos(ledit_get_line(buffer,… | |
} else if (sl2 == softlines - 1) { | |
- delete_line_section_base(buffer, l1, pl1->star… | |
- ledit_delete_line_entries_base(buffer, l1 + 1,… | |
- if (l1 + 1 >= buffer->lines_num) { | |
- *new_line_ret = buffer->lines_num - 1; | |
- ledit_line *new_lline = ledit_get_line… | |
- int new_softlines = pango_layout_get_l… | |
- ledit_x_softline_to_pos(new_lline, x, … | |
+ 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 - … | |
} 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->star… | |
+ ledit_delete_line_entries_base(buffer, l1 + 1,… | |
} else { | |
- /* FIXME: should this join the two lines? */ | |
+ /* FIXME: this could be made nicer by just usi… | |
+ 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->star… | |
- delete_line_section_base(buffer, l2, 0, pl2->s… | |
- if (l2 > l1 + 1) | |
- ledit_delete_line_entries_base(buffer,… | |
- *new_line_ret = l1 + 1; | |
- ledit_x_softline_to_pos(ledit_get_line(buffer,… | |
+ ledit_insert_text_from_line_base( | |
+ buffer, | |
+ l1, pl1->start_index, | |
+ l2, pl2->start_index + pl2->length, | |
+ ll2->len - (pl2->start_index + pl2->length… | |
+ ); | |
+ ledit_delete_line_entries_base(buffer, l1 + 1,… | |
+ new_line = l1; | |
+ int new_softlines = pango_layout_get_line_coun… | |
+ /* it's technically possible that the remainin… | |
+ second line is so small that it doesn't gen… | |
+ softline, so there needs to be a special ca… | |
+ a bit weird because the cursor will seem to… | |
+ same line, but it now includes the rest of … | |
+ (FIXME: this is probably not the best thing… | |
+ ledit_x_softline_to_pos( | |
+ ll1, x, sl1 + 1 < new_softlines ? sl1 + 1 … | |
+ ); | |
} | |
} | |
} 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 -… | |
- *new_line_ret = line_index1; | |
- *new_byte_ret = b1; | |
+ delete_line_section_base(buffer, line_index1, rgb1, rg… | |
+ 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->le… | |
+ ledit_insert_text_from_line_base( | |
+ buffer, rgl1, rgb1, rgl2, rgb2, line2->len - rgb2,… | |
+ ); | |
+ 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, *ne… | |
+ new_byte = ledit_get_legal_normal_pos(buffer, new_line… | |
+ } | |
+ 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 | |
t@@ -3,7 +3,7 @@ typedef struct { | |
int byte1; | |
int line2; | |
int byte2; | |
-} ledit_selection; | |
+} ledit_range; | |
typedef struct ledit_buffer ledit_buffer; | |
t@@ -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 select… | |
+ ledit_range sel; /* current selection; all entries -1 if no selection … | |
+ ledit_undo_stack *undo; | |
}; | |
ledit_buffer *ledit_create_buffer(ledit_common_state *state); | |
t@@ -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, in… | |
void ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, in… | |
-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 | |
); | |
t@@ -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 *… | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 … | |
t@@ -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, | |
t@@ -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; | |
t@@ -216,8 +224,7 @@ show_message(char *text, int len) { | |
struct { | |
Atom xtarget; | |
- char *primary; | |
- size_t primary_alloc; | |
+ lbuf *primary; | |
char *clipboard; | |
} xsel; | |
t@@ -243,6 +250,7 @@ set_mode(enum ledit_mode mode) { | |
XftDrawRect(bottom_bar.mode_draw->xftdraw, &state.bg, 0, 0, bottom_bar… | |
pango_xft_render_layout(bottom_bar.mode_draw->xftdraw, &state.fg, bott… | |
recalc_text_size(); | |
+ ledit_change_mode_group(buffer); | |
} | |
void | |
t@@ -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, CurrentTim… | |
} | |
t@@ -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 { | |
t@@ -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 != buf… | |
- 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); | |
t@@ -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_inde… | |
} | |
t@@ -565,6 +621,7 @@ key_x(void) { | |
num = e->count; | |
if (num <= 0) | |
num = 1; | |
+ /* FIXME: actually do something */ | |
} | |
static void | |
t@@ -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; | |
} | |
t@@ -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) | |
t@@ -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 siz… | |
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); | |
t@@ -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) | |
t@@ -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); | |
t@@ -1153,10 +1216,7 @@ sort_selection(int *line1, int *byte1, int *line2, int … | |
/* 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, byt… | |
XSetSelectionOwner(state.dpy, XA_PRIMARY, state.win, CurrentTime); | |
/* | |
FIXME | |
t@@ -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_li… | |
+ delete_range(0, 0, buffer->cur_line - 1, l1->len, buff… | |
+ /* | |
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… | |
+ /* | |
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_inde… | |
} | |
t@@ -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, bu… | |
+ /* | |
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… | |
+ /* | |
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_inde… | |
} | |
t@@ -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_inde… | |
- buffer->cur_index = 0; | |
+ buffer->cur_index = 0; */ | |
} | |
static void | |
t@@ -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_inde… | |
+} | |
+ | |
+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_inde… | |
+} | |
+ | |
/* FIXME: maybe sort these and use binary search */ | |
static struct key keys_en[] = { | |
{NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace}, | |
t@@ -1728,7 +1821,11 @@ static struct key keys_en[] = { | |
{"/", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forwar… | |
{NULL, 0, XK_Return, COMMANDEDIT|SEARCHEDIT, KEY_ANY, KEY_ANY, &end_li… | |
{"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[] = { | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 : l… | |
+ 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 happ… | |
+ 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 happ… | |
+ 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 | |
t@@ -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); |