tImplement Ctrl-e and Ctrl-y - 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 <[email protected]> | |
Date: Sun, 31 Oct 2021 21:36:43 +0100 | |
Implement Ctrl-e and Ctrl-y | |
Diffstat: | |
M buffer.c | 128 +++++++++++++++++++++++++++++… | |
M buffer.h | 3 +++ | |
M keys.c | 7 +------ | |
M keys.h | 8 +++++++- | |
M keys_basic.c | 62 +++++++++++++++++++++++++++++… | |
M keys_basic_config.h | 6 +++++- | |
6 files changed, 200 insertions(+), 14 deletions(-) | |
--- | |
diff --git a/buffer.c b/buffer.c | |
t@@ -987,6 +987,7 @@ ledit_x_softline_to_pos(ledit_line *line, int x, int softl… | |
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--; | |
t@@ -1380,6 +1381,101 @@ ledit_buffer_scroll(ledit_buffer *buffer, long new_off… | |
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 &&… | |
+ lline = ledit_buffer_get_line(buffer, ++hline); | |
+ } | |
+ /* the current hard line is now the one at the very top of the… | |
+ 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_off… | |
+ 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 && hl… | |
+ lline = ledit_buffer_get_line(buffer, --hline); | |
+ } | |
+ /* the current hard line is now the one at the very bottom of … | |
+ 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->d… | |
+ 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,… | |
/* FIXME: store current line offset to speed this up */ | |
t@@ -1390,6 +1486,7 @@ ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y… | |
ledit_line *line = ledit_buffer_get_line(buffer, i); | |
if ((h <= pos && h + line->h > pos) || i == buffer->lines_num … | |
int index, trailing; | |
+ /* FIXME: what if i == buffer->lines_num - 1 but pos -… | |
pango_layout_xy_to_index( | |
line->layout, | |
x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE, | |
t@@ -1409,6 +1506,31 @@ ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int … | |
} | |
} | |
+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 … | |
+ } | |
+} | |
+ | |
+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; | |
t@@ -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 … | |
} | |
- ledit_window_set_scroll_pos(buffer->window, buffer->display_offset); | |
} | |
static void | |
diff --git a/buffer.h b/buffer.h | |
t@@ -131,6 +131,7 @@ void ledit_buffer_resize_width(ledit_buffer *buffer, int w… | |
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_nea… | |
+void ledit_buffer_get_nearest_legal_pos(ledit_buffer *buffer, int line, int by… | |
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); | |
t@@ -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 byt… | |
diff --git a/keys.c b/keys.c | |
t@@ -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 | |
t@@ -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 | |
t@@ -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, &w… | |
+ 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->c… | |
+} | |
+ | |
+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 th… | |
the cursor is moved to the bottom if movement is upwards, to the top otherw… | |
(unless the screen is already at the very top or bottom - then it is the ot… | |
t@@ -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 … | |
- move slightly further down than a screen once ensure_cursor_shown i… | |
+ /* 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 | |
t@@ -59,6 +59,8 @@ static struct action insert_mode_insert_text(ledit_buffer *b… | |
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, i… | |
+static struct action scroll_with_cursor_down(ledit_buffer *buffer, char *text,… | |
/* FIXME: maybe sort these and use binary search | |
-> but that would mess with the catch-all keys */ | |
t@@ -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: onl… | |
{"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: thi… | |
{"b", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &screen_up}, | |
{"f", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &screen_dow… | |
+ {"e", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &scroll_wit… | |
+ {"y", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &scroll_wit… | |
{"", 0, 0, INSERT, KEY_ANY, KEY_ANY, &insert_mode_insert_text} | |
}; | |