tImprove bottom bar text input - 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 <[email protected]> | |
Date: Fri, 12 Nov 2021 23:31:39 +0100 | |
Improve bottom bar text input | |
Diffstat: | |
M buffer.c | 9 +++++++++ | |
M keys_basic.c | 4 +++- | |
M keys_command.c | 64 +++++++++++++++++++++++++++++… | |
M keys_command_config.h | 28 ++++++++++++++++++++++++++++ | |
M undo.c | 12 ++++++++++++ | |
M window.c | 123 +++++++++++++++++++++++++++++… | |
M window.h | 5 +++++ | |
7 files changed, 233 insertions(+), 12 deletions(-) | |
--- | |
diff --git a/buffer.c b/buffer.c | |
t@@ -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->m… | |
+ ); | |
} | |
/* FIXME: guard against buffer being destroyed before paste callback is nulled… | |
diff --git a/keys_basic.c b/keys_basic.c | |
t@@ -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) { | |
t@@ -1804,6 +1803,7 @@ enter_commandedit(ledit_buffer *buffer, char *text, int … | |
(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); | |
t@@ -1816,6 +1816,7 @@ enter_searchedit_forward(ledit_buffer *buffer, char *tex… | |
(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); | |
t@@ -1828,6 +1829,7 @@ enter_searchedit_backward(ledit_buffer *buffer, char *te… | |
(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 | |
t@@ -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; | |
t@@ -244,10 +246,57 @@ edit_insert_text(ledit_buffer *buffer, char *key_text, i… | |
} | |
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->win… | |
t@@ -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->wind… | |
search_next(buffer); | |
t@@ -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->win… | |
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]; | |
t@@ -305,7 +361,7 @@ ledit_command_key_handler(ledit_buffer *buffer, XEvent *ev… | |
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 | |
t@@ -3,9 +3,16 @@ static int substitute_yes_all(ledit_buffer *buffer, char *key… | |
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 … | |
+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… | |
t@@ -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 | |
t@@ -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 | |
t@@ -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; | |
t@@ -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) */ | |
t@@ -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); | |
t@@ -102,6 +104,90 @@ ledit_window_insert_bottom_bar_text(ledit_window *window,… | |
} | |
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-… | |
+ pango_layout_get_pixel_size(window->bb->line, &window->bb->line_w, &wi… | |
+ ledit_draw_grow(window, window->bb->line_draw, window->bb->line_w, win… | |
+ XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->text_bg, 0… | |
+ pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme… | |
+ 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; | |
t@@ -380,6 +466,7 @@ ledit_window_create(ledit_common *common, ledit_theme *the… | |
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; | |
t@@ -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.pix… | |
+ /* 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 | |
t@@ -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 | |
t@@ -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); |