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 413b7e3a74968e128ede783a25145dc5aea70c99
parent 10a6b45de3f15406bb82817a1bf28c79d492145f
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 16 May 2021 20:26:05 +0200

Add visual mode and make current keys work with it

Diffstat:
Mbuffer.c | 45+++++++++++++++++++++++++--------------------
Mbuffer.h | 1+
Mledit.c | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
3 files changed, 195 insertions(+), 91 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -360,7 +360,29 @@ ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) { } } -/* FIXME: cursor jumps weirdly */ +int +ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) { + /* move back one grapheme if at end of line */ + int ret = pos; + ledit_line *final_line = ledit_get_line(buffer, line); + if (pos == final_line->len && pos > 0) { + int nattrs; + 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--; + while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) { + cur--; + ret--; + while (ret > 0 && ((final_line->text[ret] & 0xC0) == 0x80)) + ret--; + } + } + return ret; +} + /* FIXME: use at least somewhat sensible variable names */ void ledit_delete_range( @@ -541,24 +563,7 @@ ledit_delete_range( *new_byte_ret = b1; ledit_delete_line_entries(buffer, l1 + 1, l2); } - /* move back one grapheme if at end of line and in normal mode */ - ledit_line *final_line = ledit_get_line(buffer, *new_line_ret); - if (buffer->state->mode == NORMAL && - *new_byte_ret == final_line->len && - *new_byte_ret > 0) { - int nattrs; - const PangoLogAttr *attrs = - pango_layout_get_log_attrs_readonly(final_line->layout, &nattrs); - int cur = nattrs - 2; - (*new_byte_ret)--; - while (*new_byte_ret > 0 && ((final_line->text[*new_byte_ret] & 0xC0) == 0x80)) - (*new_byte_ret)--; - while (*new_byte_ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) { - cur--; - (*new_byte_ret)--; - while (*new_byte_ret > 0 && ((final_line->text[*new_byte_ret] & 0xC0) == 0x80)) - (*new_byte_ret)--; - } - } + if (buffer->state->mode == NORMAL) + *new_byte_ret = ledit_get_legal_normal_pos(buffer, *new_line_ret, *new_byte_ret); } } diff --git a/buffer.h b/buffer.h @@ -51,6 +51,7 @@ void ledit_delete_line_entry(ledit_buffer *buffer, int index); ledit_line *ledit_get_line(ledit_buffer *buffer, int index); int ledit_line_visible(ledit_buffer *buffer, int index); int ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir); +int ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos); void ledit_delete_range( ledit_buffer *buffer, int line_based, int line_index1, int byte_index1, diff --git a/ledit.c b/ledit.c @@ -180,39 +180,63 @@ get_new_line_softline( } } +static int +delete_selection(void) { + if (buffer->sel.line1 != buffer->sel.line2 || buffer->sel.byte1 != buffer->sel.byte2) { + ledit_delete_range( + buffer, 0, + buffer->sel.line1, buffer->sel.byte1, + buffer->sel.line2, buffer->sel.byte2, + &buffer->cur_line, &buffer->cur_index + ); + buffer->sel.line1 = buffer->sel.line2 = -1; + buffer->sel.byte1 = buffer->sel.byte2 = -1; + ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); + return 1; + } + return 0; +} + static void key_d(void) { int num = 0; - struct key_stack_elem *e = pop_key_stack(); - if (e != NULL) { - if (e->key & KEY_NUMBER) { - num = e->count; - e = pop_key_stack(); + if (delete_selection()) { + state.mode = NORMAL; + buffer->cur_index = ledit_get_legal_normal_pos(buffer, buffer->cur_line, buffer->cur_index); + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + clear_key_stack(); + } else { + struct key_stack_elem *e = pop_key_stack(); + if (e != NULL) { + if (e->key & KEY_NUMBER) { + num = e->count; + e = pop_key_stack(); + } + /* FIXME: checking equality of the function pointer may be a bit risky */ + if (e != NULL && e->motion_cb == &key_d_cb) { + int prevnum = e->count > 0 ? e->count : 1; + num = num > 0 ? num : 1; + int lines = num * prevnum; + int new_line, new_softline; + get_new_line_softline( + buffer->cur_line, buffer->cur_index, lines - 1, + &new_line, &new_softline + ); + ledit_line *ll = ledit_get_line(buffer, new_line); + PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, new_softline); + e->motion_cb(new_line, pl->start_index, KEY_MOTION_LINE); + clear_key_stack(); + } else if (e != NULL) { + clear_key_stack(); + } } - /* FIXME: checking equality of the function pointer may be a bit risky */ - if (e != NULL && e->motion_cb == &key_d_cb) { - int prevnum = e->count > 0 ? e->count : 1; - num = num > 0 ? num : 1; - int lines = num * prevnum; - int new_line, new_softline; - get_new_line_softline( - buffer->cur_line, buffer->cur_index, lines - 1, - &new_line, &new_softline - ); - ledit_line *ll = ledit_get_line(buffer, new_line); - PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, new_softline); - e->motion_cb(new_line, pl->start_index, KEY_MOTION_LINE); - clear_key_stack(); - } else if (e != NULL) { - clear_key_stack(); + if (e == NULL) { + e = push_key_stack(); + e->key = KEY_MOTION; /* ? */ + e->count = num; + e->motion_cb = &key_d_cb; } } - if (e == NULL) { - e = push_key_stack(); - e->key = KEY_MOTION; /* ? */ - e->count = num; - e->motion_cb = &key_d_cb; - } } /* FIXME: should this get number of lines to remove or actual end line? */ @@ -239,7 +263,6 @@ key_x(void) { num = e->count; if (num <= 0) num = 1; - printf("delete %d\n", num); } static void @@ -680,7 +703,7 @@ redraw(void) { strong.x / PANGO_SCALE, cursor_y, 10, strong.height / PANGO_SCALE ); - } else if (state.mode == INSERT) { + } else if (state.mode == INSERT || state.mode == VISUAL) { XDrawLine( state.dpy, state.drawable, state.gc, strong.x / PANGO_SCALE, cursor_y, @@ -825,6 +848,12 @@ button_press(XEvent *event) { int l, b; xy_to_line_byte(x, y, &l, &b); set_selection(l, b, l, b); + if (state.mode == NORMAL) { + ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); + state.mode = VISUAL; + } + buffer->cur_line = l; + buffer->cur_index = b; state.selecting = 1; return 1; } @@ -872,6 +901,8 @@ drag_motion(XEvent *event) { int y = event->xbutton.y >= 0 ? event->xbutton.y : 0; xy_to_line_byte(event->xbutton.x, y, &l, &b); set_selection(buffer->sel.line1, buffer->sel.byte1, l, b); + buffer->cur_line = l; + buffer->cur_index = b; return 1; } return 0; @@ -922,7 +953,9 @@ resize_window(int w, int h) { static void backspace(void) { - if (buffer->cur_index == 0) { + if (delete_selection()) { + /* NOP */ + } 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); @@ -946,7 +979,9 @@ backspace(void) { static void delete_key(void) { ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); - if (buffer->cur_index == cur_line->len) { + if (delete_selection()) { + /* 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 @@ -1027,7 +1062,15 @@ move_cursor_left_right(int dir) { e->motion_cb(buffer->cur_line, new_index, KEY_MOTION_CHAR); } else { buffer->cur_index = new_index; - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + if (state.mode == VISUAL) { + set_selection(buffer->sel.line1, buffer->sel.byte1, buffer->sel.line2, new_index); + } else if (state.mode == INSERT && + (buffer->sel.line1 != buffer->sel.line2 || + buffer->sel.byte1 != buffer->sel.byte2)) { + set_selection(buffer->cur_line, new_index, buffer->cur_line, new_index); + } else if (state.mode == NORMAL) { + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + } } clear_key_stack(); } @@ -1044,6 +1087,7 @@ cursor_right(void) { static void return_key(void) { + delete_selection(); 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 */ @@ -1055,27 +1099,36 @@ return_key(void) { static void escape_key(void) { - state.mode = NORMAL; - clear_key_stack(); - PangoDirection dir = PANGO_DIRECTION_RTL; - int tmp_index = buffer->cur_index; - ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); - if (buffer->cur_index >= cur_line->len) - tmp_index--; - if (tmp_index >= 0) - dir = pango_layout_get_direction(cur_line->layout, tmp_index); - if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) { - cursor_right(); + clear_key_stack(); /* just in case... */ + if (state.mode == INSERT && + (buffer->sel.line1 != buffer->sel.line2 || + buffer->sel.byte1 != buffer->sel.byte2)) { + state.mode = VISUAL; } else { - cursor_left(); + state.mode = NORMAL; + clear_key_stack(); + PangoDirection dir = PANGO_DIRECTION_RTL; + int tmp_index = buffer->cur_index; + ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); + if (buffer->cur_index >= cur_line->len) + tmp_index--; + if (tmp_index >= 0) + dir = pango_layout_get_direction(cur_line->layout, tmp_index); + if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) { + cursor_right(); + } else { + cursor_left(); + } + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); } static void enter_insert(void) { + if (state.mode == NORMAL) + ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); state.mode = INSERT; - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); + clear_key_stack(); } /* FIXME: Check if previous key allows motion command - or is this checked automatically before? */ @@ -1112,7 +1165,16 @@ move_cursor_up_down(int dir) { if (buffer->cur_line != new_line) ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); buffer->cur_line = new_line; - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + + if (state.mode == VISUAL) { + set_selection(buffer->sel.line1, buffer->sel.byte1, buffer->cur_line, buffer->cur_index); + } else if (state.mode == INSERT && + (buffer->sel.line1 != buffer->sel.line2 || + buffer->sel.byte1 != buffer->sel.byte2)) { + set_selection(buffer->cur_line, buffer->cur_index, buffer->cur_line, buffer->cur_index); + } else if (state.mode == NORMAL) { + ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + } } clear_key_stack(); } @@ -1129,37 +1191,72 @@ cursor_up(void) { static void cursor_to_beginning(void) { - buffer->cur_index = 0; - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + struct key_stack_elem *e = pop_key_stack(); + /* FIXME: error when no callback? */ + if (e != NULL & e->motion_cb != NULL) { + e->motion_cb(buffer->cur_line, 0, KEY_MOTION_CHAR); + } else { + buffer->cur_index = 0; + if (state.mode == VISUAL) { + set_selection( + buffer->sel.line1, buffer->sel.byte1, + buffer->cur_line, buffer->cur_index + ); + } else { + ledit_set_line_cursor_attrs( + buffer, buffer->cur_line, buffer->cur_index + ); + } + } + clear_key_stack(); +} + +static void +enter_visual(void) { + state.mode = VISUAL; + buffer->sel.line1 = buffer->sel.line2 = buffer->cur_line; + buffer->sel.byte1 = buffer->sel.byte2 = buffer->cur_index; + ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); + clear_key_stack(); /* FIXME: error if not empty? */ +} + +static void +switch_selection_end(void) { + swap(&buffer->sel.line1, &buffer->sel.line2); + swap(&buffer->sel.byte1, &buffer->sel.byte2); + buffer->cur_line = buffer->sel.line2; + buffer->cur_index = buffer->sel.byte2; } static struct key keys_en[] = { {NULL, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace}, - {NULL, XK_Left, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left}, - {NULL, XK_Right, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_right}, - {NULL, XK_Up, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_up}, - {NULL, XK_Down, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_down}, + {NULL, XK_Left, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left}, + {NULL, XK_Right, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_right}, + {NULL, XK_Up, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_up}, + {NULL, XK_Down, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_down}, {NULL, XK_Return, INSERT, KEY_ANY, KEY_ANY, &return_key}, {NULL, XK_Delete, INSERT, KEY_ANY, KEY_ANY, &delete_key}, - {NULL, XK_Escape, INSERT, KEY_ANY, KEY_ANY, &escape_key}, - {"i", 0, NORMAL, KEY_ANY, KEY_ANY, &enter_insert}, - {"h", 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left}, - {"l", 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right}, - {"j", 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down}, - {"k", 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_up}, - {"0", 0, NORMAL, ~KEY_NUMBER, KEY_ANY, &cursor_to_beginning}, - {"0", 0, NORMAL, KEY_NUMBER, KEY_NUMBER, &push_0}, - {"1", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_1}, - {"2", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_2}, - {"3", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_3}, - {"4", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_4}, - {"5", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_5}, - {"6", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_6}, - {"7", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_7}, - {"8", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_8}, - {"9", 0, NORMAL, KEY_ANY, KEY_NUMBER, &push_9}, + {NULL, XK_Escape, VISUAL|INSERT, KEY_ANY, KEY_ANY, &escape_key}, + {"i", 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_insert}, + {"h", 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left}, + {"l", 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right}, + {"j", 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down}, + {"k", 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_up}, + {"0", 0, NORMAL|VISUAL, ~KEY_NUMBER, KEY_ANY, &cursor_to_beginning}, + {"0", 0, NORMAL|VISUAL, KEY_NUMBER, KEY_NUMBER, &push_0}, + {"1", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_1}, + {"2", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_2}, + {"3", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_3}, + {"4", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_4}, + {"5", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_5}, + {"6", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_6}, + {"7", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_7}, + {"8", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_8}, + {"9", 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_9}, {"x", 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &key_x}, - {"d", 0, NORMAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &key_d} + {"d", 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &key_d}, + {"v", 0, NORMAL, KEY_ANY, KEY_ANY, &enter_visual}, + {"o", 0, VISUAL, KEY_ANY, KEY_ANY, &switch_selection_end}, }; static struct key keys_ur[] = { @@ -1251,6 +1348,7 @@ key_press(XEvent event) { break; } if (state.mode == INSERT && !found && n > 0) { + delete_selection(); ledit_insert_text( buffer, buffer->cur_line, buffer->cur_index, buf, n );