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 <nobody@lumidify.org>
Date:   Sat,  5 Jun 2021 20:16:59 +0200

Abstract text operations a bit

Diffstat:
Mbuffer.c | 128++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mbuffer.h | 16++++++++++++++++
Mledit.c | 82++++++++++++++++++++++---------------------------------------------------------
3 files changed, 149 insertions(+), 77 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -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> @@ -101,6 +102,18 @@ ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) { } 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, int len) { ledit_line *line = &buffer->lines[line_index]; if (len == -1) @@ -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 line2, int byte2) { + 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, int dir) { 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; } @@ -410,11 +510,7 @@ ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) { 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); } } } @@ -429,14 +525,10 @@ ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) { const PangoLogAttr *attrs = pango_layout_get_log_attrs_readonly(final_line->layout, &nattrs); 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) == 0x80)) - ret--; + ret = ledit_prev_utf8(final_line, ret); } } return ret; diff --git a/buffer.h b/buffer.h @@ -66,3 +66,19 @@ void ledit_delete_range( ); void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret); void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret); +int ledit_next_utf8(ledit_line *line, int index); +int ledit_prev_utf8(ledit_line *line, int index); +size_t ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); +void ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2); +size_t ledit_copy_text_with_resize( + 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 @@ -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 @@ -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( @@ -1116,12 +1118,9 @@ xy_to_line_byte(int x, int y, int *line_ret, int *byte_ret) { 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] & 0xC0) == 0x80)) - index++; + index = ledit_next_utf8(line, index); } *line_ret = i; *byte_ret = index; @@ -1153,45 +1152,10 @@ sort_selection(int *line1, int *byte1, int *line2, int *byte2) { /* lines and bytes need to be sorted already! */ static void copy_selection_to_x_primary(int line1, int byte1, int line2, int byte2) { - 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 @@ -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_line - 1); - ledit_line *l2 = ledit_get_line(buffer, buffer->cur_line); 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--; @@ -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; @@ -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; @@ -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, buffer->lines_num); + char *str = ledit_malloc(len + 1); + snprintf(str, len + 1, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num); + 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}, @@ -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_backward}, {"/", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forward},