tAbstract text operations a bit - 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 0e379b0cf1ba991e2024b2541a9d5d4f80068d5b | |
parent e181351df92df2596bd48578667ce195f4cf34d0 | |
Author: lumidify <[email protected]> | |
Date: Sat, 5 Jun 2021 20:16:59 +0200 | |
Abstract text operations a bit | |
Diffstat: | |
M buffer.c | 128 ++++++++++++++++++++++++++---… | |
M buffer.h | 16 ++++++++++++++++ | |
M ledit.c | 82 +++++++++--------------------… | |
3 files changed, 149 insertions(+), 77 deletions(-) | |
--- | |
diff --git a/buffer.c b/buffer.c | |
t@@ -1,6 +1,7 @@ | |
/* FIXME: shrink buffers when text length less than a fourth of the size */ | |
#include <string.h> | |
+#include <assert.h> | |
#include <X11/Xlib.h> | |
#include <X11/Xutil.h> | |
t@@ -101,6 +102,18 @@ ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int li… | |
} | |
void | |
+ledit_insert_text_from_line( | |
+ ledit_buffer *buffer, | |
+ int dst_line, int dst_index, | |
+ int src_line, int src_index, int src_len) { | |
+ assert(dst_line != src_line); | |
+ ledit_line *ll = ledit_get_line(buffer, src_line); | |
+ if (src_len == -1) | |
+ src_len = ll->len - src_index; | |
+ ledit_insert_text(buffer, dst_line, dst_index, ll->text, src_len); | |
+} | |
+ | |
+void | |
ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text,… | |
ledit_line *line = &buffer->lines[line_index]; | |
if (len == -1) | |
t@@ -333,22 +346,109 @@ ledit_line_visible(ledit_buffer *buffer, int index) { | |
line->y_offset + line->h > buffer->display_offset; | |
} | |
+/* get needed length of text range, including newlines | |
+ * - NUL is not included | |
+ * - if the last range ends at the end of a line, the newline is *not* included | |
+ * - the range must be sorted already */ | |
+size_t | |
+ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2… | |
+ assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); | |
+ size_t len = 0; | |
+ ledit_line *ll = ledit_get_line(buffer, line1); | |
+ if (line1 == line2) { | |
+ len = byte2 - byte1; | |
+ } else { | |
+ /* + 1 for newline */ | |
+ len = ll->len - byte1 + byte2 + 1; | |
+ for (int i = line1 + 1; i < line2; i++) { | |
+ ll = ledit_get_line(buffer, i); | |
+ len += ll->len + 1; | |
+ } | |
+ } | |
+ return len; | |
+} | |
+ | |
+/* copy text range into given buffer | |
+ * - dst is null-terminated | |
+ * - dst must be large enough to contain the text and NUL | |
+ * - the range must be sorted already */ | |
+void | |
+ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int lin… | |
+ assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); | |
+ ledit_line *ll1 = ledit_get_line(buffer, line1); | |
+ ledit_line *ll2 = ledit_get_line(buffer, line2); | |
+ if (line1 == line2) { | |
+ memcpy(dst, ll1->text + byte1, byte2 - byte1); | |
+ dst[byte2 - byte1] = '\0'; | |
+ } else { | |
+ size_t cur_pos = 0; | |
+ memcpy(dst, ll1->text + byte1, ll1->len - byte1); | |
+ cur_pos += ll1->len - byte1; | |
+ dst[cur_pos] = '\n'; | |
+ cur_pos++; | |
+ for (int i = line1 + 1; i < line2; i++) { | |
+ ledit_line *ll = ledit_get_line(buffer, i); | |
+ memcpy(dst + cur_pos, ll->text, ll->len); | |
+ cur_pos += ll->len; | |
+ dst[cur_pos] = '\n'; | |
+ cur_pos++; | |
+ } | |
+ memcpy(dst + cur_pos, ll2->text, byte2); | |
+ cur_pos += byte2; | |
+ dst[cur_pos] = '\0'; | |
+ } | |
+} | |
+ | |
+/* copy text range into given buffer and resize it if necessary | |
+ * - *dst is reallocated and *alloc adjusted if the text doesn't fit | |
+ * - *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( | |
+ ledit_buffer *buffer, | |
+ char **dst, size_t *alloc, | |
+ 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; | |
+} | |
+ | |
+int | |
+ledit_prev_utf8(ledit_line *line, int index) { | |
+ int i = index - 1; | |
+ /* find valid utf8 char - this probably needs to be improved */ | |
+ while (i > 0 && ((line->text[i] & 0xC0) == 0x80)) | |
+ i--; | |
+ return i; | |
+} | |
+ | |
+int | |
+ledit_next_utf8(ledit_line *line, int index) { | |
+ int i = index + 1; | |
+ while (i < line->len && ((line->text[i] & 0xC0) == 0x80)) | |
+ i++; | |
+ return i; | |
+} | |
+ | |
int | |
ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index… | |
ledit_line *l = ledit_get_line(buffer, line_index); | |
int new_index = byte_index; | |
if (dir < 0) { | |
- int i = byte_index - 1; | |
- /* find valid utf8 char - this probably needs to be improved */ | |
- while (i > 0 && ((l->text[i] & 0xC0) == 0x80)) | |
- i--; | |
+ int i = ledit_prev_utf8(l, byte_index); | |
memmove(l->text + i, l->text + byte_index, l->len - byte_index… | |
l->len -= byte_index - i; | |
new_index = i; | |
} else { | |
- int i = byte_index + 1; | |
- while (i < l->len && ((l->text[i] & 0xC0) == 0x80)) | |
- i++; | |
+ int i = ledit_next_utf8(l, byte_index); | |
memmove(l->text + byte_index, l->text + i, l->len - i); | |
l->len -= i - byte_index; | |
} | |
t@@ -410,11 +510,7 @@ ledit_x_softline_to_pos(ledit_line *line, int x, int soft… | |
if (line->parent_buffer->state->mode == INSERT) { | |
while (trailing > 0) { | |
trailing--; | |
- (*pos_ret)++; | |
- /* utf8 stuff */ | |
- while (*pos_ret < line->len && | |
- ((line->text[*pos_ret] & 0xC0) == 0x80)) | |
- (*pos_ret)++; | |
+ *pos_ret = ledit_next_utf8(line, *pos_ret); | |
} | |
} | |
} | |
t@@ -429,14 +525,10 @@ ledit_get_legal_normal_pos(ledit_buffer *buffer, int lin… | |
const PangoLogAttr *attrs = | |
pango_layout_get_log_attrs_readonly(final_line->layout, &n… | |
int cur = nattrs - 2; | |
- ret--; | |
- while (ret > 0 && ((final_line->text[ret] & 0xC0) == 0x80)) | |
- ret--; | |
+ ret = ledit_prev_utf8(final_line, ret); | |
while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) { | |
cur--; | |
- ret--; | |
- while (ret > 0 && ((final_line->text[ret] & 0xC0) == 0… | |
- ret--; | |
+ ret = ledit_prev_utf8(final_line, ret); | |
} | |
} | |
return ret; | |
diff --git a/buffer.h b/buffer.h | |
t@@ -66,3 +66,19 @@ void ledit_delete_range( | |
); | |
void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softl… | |
void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_r… | |
+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( | |
+ ledit_buffer *buffer, | |
+ char **dst, size_t *alloc, | |
+ int line1, int byte1, | |
+ int line2, int byte2 | |
+); | |
+void | |
+ledit_insert_text_from_line( | |
+ ledit_buffer *buffer, | |
+ int dst_line, int dst_index, | |
+ int src_line, int src_index, int src_len | |
+); | |
diff --git a/ledit.c b/ledit.c | |
t@@ -707,7 +707,7 @@ mainloop(void) { | |
fprintf(stderr, "XKB not supported."); | |
exit(1); | |
} | |
- printf("XKB (%d.%d) supported.\n", major, minor); | |
+ /*printf("XKB (%d.%d) supported.\n", major, minor);*/ | |
/* This should select the events when the keyboard mapping changes. | |
* When e.g. 'setxkbmap us' is executed, two events are sent, but I | |
* haven't figured out how to change that. When the xkb layout | |
t@@ -821,10 +821,12 @@ setup(int argc, char *argv[]) { | |
/* based on http://wili.cc/blog/xdbe.html */ | |
int major, minor; | |
if (XdbeQueryExtension(state.dpy, &major, &minor)) { | |
+ /* | |
printf( | |
"Xdbe (%d.%d) supported, using double buffering.\n", | |
major, minor | |
); | |
+ */ | |
int num_screens = 1; | |
Drawable screens[] = { DefaultRootWindow(state.dpy) }; | |
XdbeScreenVisualInfo *info = XdbeGetVisualInfo( | |
t@@ -1116,12 +1118,9 @@ xy_to_line_byte(int x, int y, int *line_ret, int *byte_… | |
x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE, | |
&index, &trailing | |
); | |
- /* FIXME: make this a separate, reusable function */ | |
while (trailing > 0) { | |
trailing--; | |
- index++; | |
- while (index < line->len && ((line->text[index… | |
- index++; | |
+ index = ledit_next_utf8(line, index); | |
} | |
*line_ret = i; | |
*byte_ret = index; | |
t@@ -1153,45 +1152,10 @@ 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) { | |
- size_t len = 0; | |
- ledit_line *ll1 = ledit_get_line(buffer, line1); | |
- ledit_line *ll2 = ledit_get_line(buffer, line2); | |
- if (line1 == line2) { | |
- len = byte2 - byte1; | |
- } else { | |
- /* + 1 for newline */ | |
- len = ll1->len - byte1 + byte2 + 1; | |
- for (int i = line1 + 1; i < line2; i++) { | |
- ledit_line *ll = ledit_get_line(buffer, i); | |
- len += ll->len + 1; | |
- } | |
- } | |
- len += 1; /* nul */ | |
- if (len > xsel.primary_alloc) { | |
- /* FIXME: maybe allocate a bit more */ | |
- xsel.primary = ledit_realloc(xsel.primary, len); | |
- xsel.primary_alloc = len; | |
- } | |
- if (line1 == line2) { | |
- memcpy(xsel.primary, ll1->text + byte1, byte2 - byte1); | |
- xsel.primary[byte2 - byte1] = '\0'; | |
- } else { | |
- size_t cur_pos = 0; | |
- memcpy(xsel.primary, ll1->text + byte1, ll1->len - byte1); | |
- cur_pos += ll1->len - byte1; | |
- xsel.primary[cur_pos] = '\n'; | |
- cur_pos++; | |
- for (int i = line1 + 1; i < line2; i++) { | |
- ledit_line *ll = ledit_get_line(buffer, i); | |
- memcpy(xsel.primary + cur_pos, ll->text, ll->len); | |
- cur_pos += ll->len; | |
- xsel.primary[cur_pos] = '\n'; | |
- cur_pos++; | |
- } | |
- memcpy(xsel.primary + cur_pos, ll2->text, byte2); | |
- cur_pos += byte2; | |
- xsel.primary[cur_pos] = '\0'; | |
- } | |
+ (void)ledit_copy_text_with_resize( | |
+ buffer, &xsel.primary, &xsel.primary_alloc, | |
+ line1, byte1, line2, byte2 | |
+ ); | |
XSetSelectionOwner(state.dpy, XA_PRIMARY, state.win, CurrentTime); | |
/* | |
FIXME | |
t@@ -1378,12 +1342,10 @@ backspace(void) { | |
} else if (buffer->cur_index == 0) { | |
if (buffer->cur_line != 0) { | |
ledit_line *l1 = ledit_get_line(buffer, buffer->cur_li… | |
- ledit_line *l2 = ledit_get_line(buffer, buffer->cur_li… | |
int old_len = l1->len; | |
- ledit_insert_text_with_newlines( | |
- buffer, buffer->cur_line - 1, | |
- l1->len, l2->text, l2->len, | |
- NULL, NULL | |
+ ledit_insert_text_from_line( | |
+ buffer, buffer->cur_line - 1, l1->len, | |
+ buffer->cur_line, 0, -1 | |
); | |
ledit_delete_line_entry(buffer, buffer->cur_line); | |
buffer->cur_line--; | |
t@@ -1404,14 +1366,10 @@ delete_key(void) { | |
/* NOP */ | |
} else if (buffer->cur_index == cur_line->len) { | |
if (buffer->cur_line != buffer->lines_num - 1) { | |
- ledit_line *next_line = ledit_get_line( | |
- buffer, buffer->cur_line + 1 | |
- ); | |
int old_len = cur_line->len; | |
- ledit_insert_text_with_newlines( | |
+ ledit_insert_text_from_line( | |
buffer, buffer->cur_line, cur_line->len, | |
- next_line->text, next_line->len, | |
- NULL, NULL | |
+ buffer->cur_line + 1, 0, -1 | |
); | |
ledit_delete_line_entry(buffer, buffer->cur_line + 1); | |
buffer->cur_index = old_len; | |
t@@ -1463,10 +1421,7 @@ move_cursor_left_right(int dir) { | |
/* FIXME: spaces at end of softlines are weird in normal mode */ | |
while (trailing > 0) { | |
trailing--; | |
- new_index++; | |
- while (new_index < cur_line->len && | |
- ((cur_line->text[new_index] & 0xC0) == 0x80)) | |
- new_index++; | |
+ new_index = ledit_next_utf8(cur_line, new_index); | |
} | |
if (new_index < 0) | |
new_index = 0; | |
t@@ -1725,6 +1680,14 @@ end_lineedit(void) { | |
} | |
} | |
+static void | |
+show_line(void) { | |
+ int len = snprintf(NULL, 0, "Line %d of %d", buffer->cur_line + 1, buf… | |
+ char *str = ledit_malloc(len + 1); | |
+ snprintf(str, len + 1, "Line %d of %d", buffer->cur_line + 1, buffer->… | |
+ show_message(str, len); | |
+} | |
+ | |
/* FIXME: maybe sort these and use binary search */ | |
static struct key keys_en[] = { | |
{NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace}, | |
t@@ -1757,6 +1720,7 @@ static struct key keys_en[] = { | |
{"o", 0, 0, VISUAL, KEY_ANY, KEY_ANY, &switch_selection_end}, | |
{"c", ControlMask, 0, INSERT|VISUAL, KEY_ANY, KEY_ANY, &clipcopy}, | |
{"v", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &clippaste}, | |
+ {"g", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &show_line}, | |
{":", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_commandedit}, | |
{"?", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_backwa… | |
{"/", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forwar… |