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 00de6deacf3f962dbf3b3d89926fe69798ad2893
parent 4f2001a6c39e051d22db3d88377e5de414aac97d
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 31 Oct 2021 21:36:43 +0100

Implement Ctrl-e and Ctrl-y

Diffstat:
Mbuffer.c | 128++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mbuffer.h | 3+++
Mkeys.c | 7+------
Mkeys.h | 8+++++++-
Mkeys_basic.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mkeys_basic_config.h | 6+++++-
6 files changed, 200 insertions(+), 14 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -987,6 +987,7 @@ ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) { pango_line, x_relative, pos_ret, &trailing ); /* if in insert mode, snap to the nearest border between graphemes */ + /* FIXME: add parameter for this instead of checking mode */ if (line->parent_buffer->common->mode == INSERT) { while (trailing > 0) { trailing--; @@ -1380,6 +1381,101 @@ ledit_buffer_scroll(ledit_buffer *buffer, long new_offset) { ledit_window_set_scroll_pos(buffer->window, buffer->display_offset); } +/* FIXME: there's gotta be a better/more efficient way to do this... */ +void +ledit_buffer_get_nearest_legal_pos( + ledit_buffer *buffer, + int line, int byte, + /*int snap_to_nearest, int snap_middle, FIXME: take these parameters */ + int *line_ret, int *byte_ret) { + PangoRectangle strong, weak; + int text_w, text_h; + int x, sl_useless; + ledit_window_get_textview_size(buffer->window, &text_w, &text_h); + ledit_line *lline = ledit_buffer_get_line(buffer, line); + pango_layout_get_cursor_pos( + lline->layout, byte, &strong, &weak + ); + ledit_pos_to_x_softline(lline, byte, &x, &sl_useless); + long cursor_y = strong.y / PANGO_SCALE + lline->y_offset; + PangoRectangle ink, log; + if (cursor_y < buffer->display_offset) { + /* search for the hard line covering the top of the screen */ + int hline = line; + while (lline->y_offset + lline->h <= buffer->display_offset && hline < buffer->lines_num - 1) { + lline = ledit_buffer_get_line(buffer, ++hline); + } + /* the current hard line is now the one at the very top of the screen*/ + int num_sl = pango_layout_get_line_count(lline->layout); + int cur_y_off = 0; + int sl_index = -1; + PangoLayoutLine *sl; + /* search for first soft line completely on-screen */ + for (int i = 0; i < num_sl; i++) { + sl = pango_layout_get_line_readonly(lline->layout, i); + if (cur_y_off + lline->y_offset >= buffer->display_offset) { + sl_index = i; + break; + } + pango_layout_line_get_pixel_extents(sl, &ink, &log); + cur_y_off += log.height; + } + if (sl_index >= 0) { + /* we found the correct soft line */ + *line_ret = hline; + ledit_x_softline_to_pos(lline, x, sl_index, byte_ret); + } else if (hline < buffer->lines_num - 1) { + /* need to move to next hard line */ + *line_ret = hline + 1; + lline = ledit_buffer_get_line(buffer, hline + 1); + ledit_x_softline_to_pos(lline, x, 0, byte_ret); + } else { + /* no idea if this can happen, but just fail and use + the last soft line of the last hard line */ + *line_ret = hline; + ledit_x_softline_to_pos(lline, x, num_sl - 1, byte_ret); + } + } else if (cursor_y + strong.height / PANGO_SCALE > + buffer->display_offset + text_h) { + /* search for the hard line covering the bottom of the screen */ + int hline = line; + while (lline->y_offset > buffer->display_offset + text_h && hline > 0) { + lline = ledit_buffer_get_line(buffer, --hline); + } + /* the current hard line is now the one at the very bottom of the screen*/ + int num_sl = pango_layout_get_line_count(lline->layout); + int cur_y_off = 0; + int sl_index = -1; + PangoLayoutLine *sl; + /* search for last soft line completely on-screen */ + for (int i = num_sl - 1; i >= 0; i--) { + sl = pango_layout_get_line_readonly(lline->layout, i); + if (lline->y_offset + lline->h - cur_y_off < buffer->display_offset + text_h) { + sl_index = i; + break; + } + pango_layout_line_get_pixel_extents(sl, &ink, &log); + cur_y_off += log.height; + } + if (sl_index >= 0) { + /* we found the correct soft line */ + *line_ret = hline; + ledit_x_softline_to_pos(lline, x, sl_index, byte_ret); + } else if (hline > 0) { + /* need to move to previous hard line */ + *line_ret = hline - 1; + lline = ledit_buffer_get_line(buffer, hline - 1); + num_sl = pango_layout_get_line_count(lline->layout); + ledit_x_softline_to_pos(lline, x, num_sl - 1, byte_ret); + } else { + /* no idea if this can happen, but just fail and use + the first soft line of the first hard line */ + *line_ret = hline; + ledit_x_softline_to_pos(lline, x, 0, byte_ret); + } + } +} + void ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int snap_to_nearest, int *line_ret, int *byte_ret) { /* FIXME: store current line offset to speed this up */ @@ -1390,6 +1486,7 @@ ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int snap_to_nearest, i ledit_line *line = ledit_buffer_get_line(buffer, i); if ((h <= pos && h + line->h > pos) || i == buffer->lines_num - 1) { int index, trailing; + /* FIXME: what if i == buffer->lines_num - 1 but pos - h < 0? */ pango_layout_xy_to_index( line->layout, x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE, @@ -1409,6 +1506,31 @@ ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int snap_to_nearest, i } } +static void +scroll_to_pos(ledit_buffer *buffer, int line, int byte, int top) { + PangoRectangle strong, weak; + int text_w, text_h; + ledit_window_get_textview_size(buffer->window, &text_w, &text_h); + ledit_line *ll = ledit_buffer_get_line(buffer, line); + pango_layout_get_cursor_pos(ll->layout, byte, &strong, &weak); + long cursor_y = strong.y / PANGO_SCALE + ll->y_offset; + if (top) { + ledit_buffer_scroll(buffer, cursor_y); + } else { + ledit_buffer_scroll(buffer, cursor_y - text_h + strong.height / PANGO_SCALE); + } +} + +void +ledit_buffer_scroll_to_pos_top(ledit_buffer *buffer, int line, int byte) { + scroll_to_pos(buffer, line, byte, 1); +} + +void +ledit_buffer_scroll_to_pos_bottom(ledit_buffer *buffer, int line, int byte) { + scroll_to_pos(buffer, line, byte, 0); +} + void ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer) { PangoRectangle strong, weak; @@ -1420,13 +1542,11 @@ ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer) { ); long cursor_y = strong.y / PANGO_SCALE + line->y_offset; if (cursor_y < buffer->display_offset) { - buffer->display_offset = cursor_y; + ledit_buffer_scroll(buffer, cursor_y); } else if (cursor_y + strong.height / PANGO_SCALE > buffer->display_offset + text_h) { - buffer->display_offset = - cursor_y - text_h + strong.height / PANGO_SCALE; + ledit_buffer_scroll(buffer, cursor_y - text_h + strong.height / PANGO_SCALE); } - ledit_window_set_scroll_pos(buffer->window, buffer->display_offset); } static void diff --git a/buffer.h b/buffer.h @@ -131,6 +131,7 @@ void ledit_buffer_resize_width(ledit_buffer *buffer, int width); is at the nearest grapheme boundary, if it is zero, the byte is always the beginning of the grapheme under the position */ void ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int snap_to_nearest, int *line_ret, int *byte_ret); +void ledit_buffer_get_nearest_legal_pos(ledit_buffer *buffer, int line, int byte, int *line_ret, int *byte_ret); void ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer); void ledit_buffer_scroll_handler(void *buffer, long pos); void ledit_buffer_button_handler(void *data, XEvent *event); @@ -143,3 +144,5 @@ void ledit_buffer_paste_clipboard(ledit_buffer *buffer); void ledit_buffer_paste_primary(ledit_buffer *buffer); void ledit_buffer_resize_textview(ledit_buffer *buffer); void ledit_buffer_scroll(ledit_buffer *buffer, long new_offset); +void ledit_buffer_scroll_to_pos_top(ledit_buffer *buffer, int line, int byte); +void ledit_buffer_scroll_to_pos_bottom(ledit_buffer *buffer, int line, int byte); diff --git a/keys.c b/keys.c @@ -14,12 +14,7 @@ #include "window.h" #include "keys.h" -static char *key_langs[] = { - "English (US)", - "German", - "Urdu (Pakistan)", - "Hindi (Bolnagri)" -}; +KEY_LANGS; int get_language_index(char *lang) { diff --git a/keys.h b/keys.h @@ -1,6 +1,12 @@ #define LENGTH(X) (sizeof(X) / sizeof(X[0])) -/* IMPORTANT: Also edit key_langs in keys.c when changing languages! */ +#define KEY_LANGS \ +static char *key_langs[] = { \ + "English (US)", \ + "German", \ + "Urdu (Pakistan)", \ + "Hindi (Bolnagri)" \ +} #define GEN_KEY_ARRAY(key_struct, en, de, ur, hi) \ static struct { \ diff --git a/keys_basic.c b/keys_basic.c @@ -368,6 +368,62 @@ get_key_repeat(void) { return num; } +static void +scroll_with_cursor(ledit_buffer *buffer, int movement) { + PangoRectangle strong, weak; + ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); + pango_layout_get_cursor_pos(ll->layout, buffer->cur_index, &strong, &weak); + int pix_movement = movement * (strong.height / PANGO_SCALE); + ledit_buffer_scroll(buffer, buffer->display_offset + pix_movement); + int old_line = buffer->cur_line; + int old_index = buffer->cur_index; + ledit_buffer_get_nearest_legal_pos( + buffer, old_line, old_index, + &buffer->cur_line, &buffer->cur_index + ); + if (old_line != buffer->cur_line || old_index != buffer->cur_index) { + ledit_buffer_wipe_line_cursor_attrs(buffer, old_line); + /* if cursor is at top or bottom of screen, snap it to the + very edge to avoid it looking weird */ + if (movement > 0) { + ledit_buffer_scroll_to_pos_top( + buffer, buffer->cur_line, buffer->cur_index + ); + } else { + ledit_buffer_scroll_to_pos_bottom( + buffer, buffer->cur_line, buffer->cur_index + ); + } + } + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); +} + +static struct action +scroll_with_cursor_up(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + int repeat = get_key_repeat(); + if (repeat >= 0) + scroll_with_cursor(buffer, -repeat); + else + ledit_window_show_message(buffer->window, "Invalid key", -1); + discard_repetition_stack(); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +scroll_with_cursor_down(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + int repeat = get_key_repeat(); + if (repeat >= 0) + scroll_with_cursor(buffer, repeat); + else + ledit_window_show_message(buffer->window, "Invalid key", -1); + discard_repetition_stack(); + return (struct action){ACTION_NONE, NULL}; +} + /* movement is multiplied with the window height and the result is added to the display offset the cursor is moved to the bottom if movement is upwards, to the top otherwise (unless the screen is already at the very top or bottom - then it is the other way around) */ @@ -378,8 +434,10 @@ move_screen(ledit_buffer *buffer, int movement) { /* FIXME: overflow */ long total = movement * (long)h; /* new pixel position of cursor */ - /* FIXME: in certain cases, just using h as the y position could make it - move slightly further down than a screen once ensure_cursor_shown is called */ + /* Note: this usually causes at least part of a line of overlap + because ensure_cursor_shown scrolls back a bit if the line + isn't completely shown (this behavior could be changed using + ledit_buffer_get_nearest_legal_pos) */ int y = movement > 0 ? 0 : h; if (buffer->display_offset + total < 0) y = 0; diff --git a/keys_basic_config.h b/keys_basic_config.h @@ -59,6 +59,8 @@ static struct action insert_mode_insert_text(ledit_buffer *buffer, char *text, i static struct action repeat_command(ledit_buffer *buffer, char *text, int len); static struct action screen_up(ledit_buffer *buffer, char *text, int len); static struct action screen_down(ledit_buffer *buffer, char *text, int len); +static struct action scroll_with_cursor_up(ledit_buffer *buffer, char *text, int len); +static struct action scroll_with_cursor_down(ledit_buffer *buffer, char *text, int len); /* FIXME: maybe sort these and use binary search -> but that would mess with the catch-all keys */ @@ -103,9 +105,11 @@ static struct key keys_en[] = { {"U", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &redo}, {".", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &repeat_command}, /* FIXME: only allow after finished key sequence */ {"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &undo}, - {"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo}, + {"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo}, /* FIXME: this is confusing with ctrl+y in normal mode */ {"b", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &screen_up}, {"f", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &screen_down}, + {"e", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &scroll_with_cursor_down}, + {"y", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &scroll_with_cursor_up}, {"", 0, 0, INSERT, KEY_ANY, KEY_ANY, &insert_mode_insert_text} };