Introduction
Introduction Statistics Contact Development Disclaimer Help
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);
You are viewing proxied material from lumidify.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.