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 f46f28e4d372daa0353489d65a29078e69fe0376
parent a52f6845ba83e6df5d60d22844b1a2546ca97af1
Author: lumidify <nobody@lumidify.org>
Date:   Fri, 12 Nov 2021 23:31:39 +0100

Improve bottom bar text input

Diffstat:
Mbuffer.c | 9+++++++++
Mkeys_basic.c | 4+++-
Mkeys_command.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mkeys_command_config.h | 28++++++++++++++++++++++++++++
Mundo.c | 12++++++++++++
Mwindow.c | 123++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mwindow.h | 5+++++
7 files changed, 233 insertions(+), 12 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -2239,10 +2239,19 @@ ledit_buffer_redo(ledit_buffer *buffer) { static void paste_callback(void *data, char *text, int len) { ledit_buffer *buffer = (ledit_buffer *)data; + txtbuf ins_buf = {.text = text, .len = len, .cap = len}; + ledit_range cur_range, ins_range; + cur_range.line1 = ins_range.line1 = buffer->cur_line; + cur_range.byte1 = ins_range.byte1 = buffer->cur_index; ledit_buffer_insert_text_with_newlines( buffer, buffer->cur_line, buffer->cur_index, text, len, &buffer->cur_line, &buffer->cur_index ); + cur_range.line2 = ins_range.line2 = buffer->cur_line; + cur_range.byte2 = ins_range.byte2 = buffer->cur_index; + ledit_push_undo_insert( + buffer->undo, &ins_buf, ins_range, cur_range, 1, buffer->common->mode + ); } /* FIXME: guard against buffer being destroyed before paste callback is nulled */ diff --git a/keys_basic.c b/keys_basic.c @@ -421,7 +421,6 @@ insert_text( int cur_line2, int cur_index2, int start_group) { if (len < 0) len = strlen(text); - /* FIXME: this is kind of hacky... */ txtbuf ins_buf = {.text = text, .len = len, .cap = len}; ledit_range cur_range, del_range; if (cur_line1 >= 0 && cur_index1 >= 0) { @@ -1804,6 +1803,7 @@ enter_commandedit(ledit_buffer *buffer, char *text, int len) { (void)text; (void)len; ledit_window_set_bottom_bar_text(buffer->window, ":", -1); + ledit_window_set_bottom_bar_min_pos(buffer->window, 1); ledit_window_set_bottom_bar_cursor(buffer->window, 1); ledit_command_set_type(CMD_EDIT); ledit_window_set_bottom_bar_text_shown(buffer->window, 1); @@ -1816,6 +1816,7 @@ enter_searchedit_forward(ledit_buffer *buffer, char *text, int len) { (void)text; (void)len; ledit_window_set_bottom_bar_text(buffer->window, "/", -1); + ledit_window_set_bottom_bar_min_pos(buffer->window, 1); ledit_window_set_bottom_bar_cursor(buffer->window, 1); ledit_command_set_type(CMD_EDITSEARCH); ledit_window_set_bottom_bar_text_shown(buffer->window, 1); @@ -1828,6 +1829,7 @@ enter_searchedit_backward(ledit_buffer *buffer, char *text, int len) { (void)text; (void)len; ledit_window_set_bottom_bar_text(buffer->window, "?", -1); + ledit_window_set_bottom_bar_min_pos(buffer->window, 1); ledit_window_set_bottom_bar_cursor(buffer->window, 1); ledit_command_set_type(CMD_EDITSEARCHB); ledit_window_set_bottom_bar_text_shown(buffer->window, 1); diff --git a/keys_command.c b/keys_command.c @@ -26,6 +26,8 @@ #include "keys_command.h" #include "keys_command_config.h" +/* FIXME: history for search and commands */ + /* FIXME: THIS WON'T WORK WHEN THERE ARE MULTIPLE BUFFERS! */ /* this must first be set by caller before jumping to key handler */ static enum ledit_command_type cur_type; @@ -244,10 +246,57 @@ edit_insert_text(ledit_buffer *buffer, char *key_text, int len) { } static int +edit_cursor_to_end(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_window_bottom_bar_cursor_to_end(buffer->window); + return 1; +} + +static int +edit_cursor_to_beginning(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_window_bottom_bar_cursor_to_beginning(buffer->window); + return 1; +} + +static int +edit_cursor_left(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_window_move_bottom_bar_cursor(buffer->window, -1); + return 1; +} + +static int +edit_cursor_right(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_window_move_bottom_bar_cursor(buffer->window, 1); + return 1; +} + +static int +edit_backspace(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_window_delete_bottom_bar_char(buffer->window, -1); + return 1; +} + +static int +edit_delete(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_window_delete_bottom_bar_char(buffer->window, 1); + return 1; +} + +static int edit_submit(ledit_buffer *buffer, char *key_text, int len) { (void)key_text; (void)len; - ledit_buffer_set_mode(buffer, NORMAL); ledit_window_set_bottom_bar_text_shown(buffer->window, 0); /* FIXME: this is hacky */ return handle_cmd(buffer, ledit_window_get_bottom_bar_text(buffer->window) + 1, -1); @@ -278,7 +327,6 @@ static int editsearch_submit(ledit_buffer *buffer, char *key_text, int len) { (void)key_text; (void)len; - ledit_buffer_set_mode(buffer, NORMAL); ledit_window_set_bottom_bar_text_shown(buffer->window, 0); ledit_set_search_forward(ledit_window_get_bottom_bar_text(buffer->window) + 1); search_next(buffer); @@ -289,13 +337,21 @@ static int editsearchb_submit(ledit_buffer *buffer, char *key_text, int len) { (void)key_text; (void)len; - ledit_buffer_set_mode(buffer, NORMAL); ledit_window_set_bottom_bar_text_shown(buffer->window, 0); ledit_set_search_backward(ledit_window_get_bottom_bar_text(buffer->window) + 1); search_next(buffer); return 0; } +static int +edit_discard(ledit_buffer *buffer, char *key_text, int len) { + (void)buffer; + (void)key_text; + (void)len; + ledit_window_set_bottom_bar_text_shown(buffer->window, 0); + return 0; +} + struct action ledit_command_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) { char buf[64]; @@ -305,7 +361,7 @@ ledit_command_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) { int num_keys = keys[lang_index].num_keys; unsigned int key_state = event->xkey.state; preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n); - int grabkey = 0; + int grabkey = 1; for (int i = 0; i < num_keys; i++) { if (cur_keys[i].text) { if (n > 0 && diff --git a/keys_command_config.h b/keys_command_config.h @@ -3,9 +3,16 @@ static int substitute_yes_all(ledit_buffer *buffer, char *key_text, int len); static int substitute_no(ledit_buffer *buffer, char *key_text, int len); static int substitute_no_all(ledit_buffer *buffer, char *key_text, int len); static int edit_insert_text(ledit_buffer *buffer, char *key_text, int len); +static int edit_cursor_left(ledit_buffer *buffer, char *key_text, int len); +static int edit_cursor_right(ledit_buffer *buffer, char *key_text, int len); +static int edit_cursor_to_end(ledit_buffer *buffer, char *key_text, int len); +static int edit_cursor_to_beginning(ledit_buffer *buffer, char *key_text, int len); +static int edit_backspace(ledit_buffer *buffer, char *key_text, int len); +static int edit_delete(ledit_buffer *buffer, char *key_text, int len); static int edit_submit(ledit_buffer *buffer, char *key_text, int len); static int editsearch_submit(ledit_buffer *buffer, char *key_text, int len); static int editsearchb_submit(ledit_buffer *buffer, char *key_text, int len); +static int edit_discard(ledit_buffer *buffer, char *key_text, int len); struct key { char *text; /* for keys that correspond with text */ @@ -24,6 +31,27 @@ static struct key keys_en[] = { {NULL, 0, XK_Return, CMD_EDIT, &edit_submit}, {NULL, 0, XK_Return, CMD_EDITSEARCH, &editsearch_submit}, {NULL, 0, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit}, + {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left}, + {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left}, + {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left}, + {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right}, + {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right}, + {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right}, + {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace}, + {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace}, + {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace}, + {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete}, + {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete}, + {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete}, + {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end}, + {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end}, + {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end}, + {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning}, + {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning}, + {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning}, + {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard}, + {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard}, + {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard}, {"", 0, 0, CMD_EDIT, &edit_insert_text}, {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text}, {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text} diff --git a/undo.c b/undo.c @@ -12,6 +12,18 @@ #include "cache.h" #include "undo.h" +/* FIXME: more sanity checks in case text is + inserted/deleted without adding to undo stack */ +/* FIXME: cursor positions can be a bit weird when + undo is used across different insert sessions in + insert mode - e.g. if some text is inserted, then + 'o' is used in normal mode to append a new line and + type some text, the cursor position at a certain + undo position will differ in insert mode depending + on whether it was reached with undo or redo since + 'o' saves the position at which it was pressed, + not the position of the last insert */ + enum operation { UNDO_INSERT, UNDO_DELETE diff --git a/window.c b/window.c @@ -18,12 +18,13 @@ #include "window.h" #include "util.h" +/* FIXME: Everything to do with the bottom bar is extremely hacky */ struct bottom_bar { /* FIXME: encapsulate layout, width, draw a bit */ PangoLayout *mode; ledit_draw *mode_draw; int mode_w, mode_h; - PangoLayout *ruler; + PangoLayout *ruler; /* not implemented yet */ ledit_draw *ruler_draw; int ruler_w, ruler_h; PangoLayout *line; @@ -32,6 +33,7 @@ struct bottom_bar { char *line_text; int line_alloc, line_len; int line_cur_pos; + int min_pos; /* minimum position cursor can be at */ }; /* clipboard handling largely stolen from st (simple terminal) */ @@ -72,7 +74,7 @@ recalc_text_size(ledit_window *window) { void ledit_window_insert_bottom_bar_text(ledit_window *window, char *text, int len) { assert(len >= -1); - assert(window->bb->line_cur_pos <= window->bb->line_alloc); + assert(window->bb->line_cur_pos <= window->bb->line_len); if (len == -1) len = strlen(text); @@ -102,6 +104,90 @@ ledit_window_insert_bottom_bar_text(ledit_window *window, char *text, int len) { } void +ledit_window_move_bottom_bar_cursor(ledit_window *window, int movement) { + assert(window->bb->line_cur_pos <= window->bb->line_len); + int trailing = 0; + int new_index = window->bb->line_cur_pos; + pango_layout_move_cursor_visually( + window->bb->line, TRUE, + new_index, trailing, movement, + &new_index, &trailing + ); + while (trailing > 0) { + trailing--; + /* FIXME: move to common/util */ + new_index++; + while (new_index < window->bb->line_len && + (window->bb->line_text[new_index] & 0xC0) == 0x80) + new_index++; + } + if (new_index < window->bb->min_pos) + new_index = window->bb->min_pos; + if (new_index > window->bb->line_len) + new_index = window->bb->line_len; + window->bb->line_cur_pos = new_index; +} + +void +ledit_window_set_bottom_bar_min_pos(ledit_window *window, int pos) { + window->bb->min_pos = pos; +} + +void +ledit_window_bottom_bar_cursor_to_beginning(ledit_window *window) { + window->bb->line_cur_pos = window->bb->min_pos; +} + +void +ledit_window_bottom_bar_cursor_to_end(ledit_window *window) { + window->bb->line_cur_pos = window->bb->line_len; +} + +/* FIXME: respect PangoLogAttr.backspace_deletes_character */ +void +ledit_window_delete_bottom_bar_char(ledit_window *window, int dir) { + int byte = window->bb->line_cur_pos; + if (dir < 0) { + byte--; + while (byte > 0 && + (window->bb->line_text[byte] & 0xC0) == 0x80) { + byte--; + } + if (byte < window->bb->min_pos) + byte = window->bb->min_pos; + memmove( + window->bb->line_text + byte, + window->bb->line_text + window->bb->line_cur_pos, + window->bb->line_len - window->bb->line_cur_pos + ); + window->bb->line_len -= (window->bb->line_cur_pos - byte); + window->bb->line_cur_pos = byte; + } else if (dir > 0) { + byte++; + while (byte < window->bb->line_len && + (window->bb->line_text[byte] & 0xC0) == 0x80) { + byte++; + } + if (byte >= window->bb->line_len) + byte = window->bb->line_len; + memmove( + window->bb->line_text + window->bb->line_cur_pos, + window->bb->line_text + byte, + window->bb->line_len - byte + ); + window->bb->line_len -= (byte - window->bb->line_cur_pos); + } + /* FIXME: move to separate function */ + window->bb->line_text[window->bb->line_len] = '\0'; + pango_layout_set_text(window->bb->line, window->bb->line_text, window->bb->line_len); + pango_layout_get_pixel_size(window->bb->line, &window->bb->line_w, &window->bb->line_h); + ledit_draw_grow(window, window->bb->line_draw, window->bb->line_w, window->bb->line_h); + XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->line_w, window->bb->line_h); + pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->text_fg, window->bb->line, 0, 0); + recalc_text_size(window); +} + +void ledit_window_set_bottom_bar_cursor(ledit_window *window, int byte_pos) { /* FIXME: check if valid? */ window->bb->line_cur_pos = byte_pos; @@ -380,6 +466,7 @@ ledit_window_create(ledit_common *common, ledit_theme *theme) { window->bb->line_text = NULL; window->bb->line_alloc = window->bb->line_len = 0; window->bb->line_cur_pos = 0; + window->bb->min_pos = 0; window->bottom_text_shown = 0; window->message_shown = 0; @@ -473,8 +560,16 @@ ledit_window_redraw(ledit_window *window) { 0, window->text_h, window->w, window->h - window->text_h ); - if (window->bottom_text_shown) { - /* move input method position to cursor */ + if (window->message_shown) { + XCopyArea( + window->common->dpy, window->bb->line_draw->pixmap, + window->drawable, window->gc, + 0, 0, window->bb->line_w, window->bb->line_h, + 0, window->text_h + ); + } else if (window->bottom_text_shown) { + XSetForeground(window->common->dpy, window->gc, t->text_fg.pixel); + /* move input method position to cursor and draw cursor */ PangoRectangle strong, weak; pango_layout_get_cursor_pos( window->bb->line, window->bb->line_cur_pos, &strong, &weak @@ -484,14 +579,28 @@ ledit_window_redraw(ledit_window *window) { have to be moved out of the way anyways (fcitx just moves it up a bit so it sort of works) */ xximspot(window, strong.x / PANGO_SCALE, window->h); - } - if (window->bottom_text_shown || window->message_shown) { + int x = 0; + int w = window->bb->line_w; + int cur_x = strong.x / PANGO_SCALE; + if (w > window->w) { + /* FIXME: try to keep some space on the edges */ + x = (cur_x / window->w) * window->w; + w = window->w; + if (x + w > window->bb->line_w) + w = window->bb->line_w - x; + } XCopyArea( window->common->dpy, window->bb->line_draw->pixmap, window->drawable, window->gc, - 0, 0, window->bb->line_w, window->bb->line_h, + x, 0, w, window->bb->line_h, 0, window->text_h ); + XDrawLine( + window->common->dpy, window->drawable, window->gc, + cur_x - x, window->text_h + strong.y / PANGO_SCALE, + cur_x - x, + window->text_h + (strong.y + strong.height) / PANGO_SCALE + ); } else { XCopyArea( window->common->dpy, window->bb->mode_draw->pixmap, diff --git a/window.h b/window.h @@ -46,6 +46,11 @@ void ledit_window_cleanup(void); /* FIXME: this is a bit confusing because there's a difference between editable text shown and non-editable message shown */ +void ledit_window_move_bottom_bar_cursor(ledit_window *window, int movement); +void ledit_window_delete_bottom_bar_char(ledit_window *window, int dir); +void ledit_window_bottom_bar_cursor_to_beginning(ledit_window *window); +void ledit_window_bottom_bar_cursor_to_end(ledit_window *window); +void ledit_window_set_bottom_bar_min_pos(ledit_window *window, int pos); void ledit_window_set_bottom_bar_text_shown(ledit_window *window, int shown); int ledit_window_bottom_bar_text_shown(ledit_window *window); void ledit_window_set_bottom_bar_cursor(ledit_window *window, int byte_pos);