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 7501cea8774e0b58705da53357947723f213cbf5
parent 0498ed82f507017d8a5c6d83da08c66b2520bf95
Author: lumidify <nobody@lumidify.org>
Date:   Thu,  9 Dec 2021 20:10:59 +0100

Improve range parsing; add search and command history

Diffstat:
Mkeys_basic.c | 5+++--
Mkeys_command.c | 233++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mkeys_command.h | 1+
Mkeys_command_config.h | 10++++++++++
Mledit.c | 1+
Msearch.c | 5+++--
Mundo.c | 2+-
Mutil.c | 7+++++++
Mutil.h | 2++
Mwindow.c | 14++++++++++++++
Mwindow.h | 14+++++++++++++-
11 files changed, 262 insertions(+), 32 deletions(-)

diff --git a/keys_basic.c b/keys_basic.c @@ -1839,9 +1839,10 @@ static struct action enter_commandedit(ledit_view *view, char *text, size_t len) { (void)text; (void)len; - window_set_bottom_bar_text(view->window, ":", -1); + char *str = view->sel_valid ? ":'<,'>" : ":"; + window_set_bottom_bar_text(view->window, str, -1); + window_set_bottom_bar_cursor(view->window, strlen(str)); window_set_bottom_bar_min_pos(view->window, 1); - window_set_bottom_bar_cursor(view->window, 1); view->cur_command_type = CMD_EDIT; window_set_bottom_bar_text_shown(view->window, 1); discard_repetition_stack(); diff --git a/keys_command.c b/keys_command.c @@ -22,6 +22,7 @@ #include "view.h" #include "search.h" #include "cleanup.h" +#include "util.h" #include "keys.h" #include "keys_command.h" @@ -42,6 +43,53 @@ static struct { int start_group; /* only set for the first replacement */ } sub_state = {NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; +typedef struct { + size_t len, cur, cap; + char **cmds; +} history; + +history cmdhistory = {0, 0, 0, NULL}; + +history searchhistory = {0, 0, 0, NULL}; + +static void +push_history(history *hist, char *cmd, size_t len) { + if (hist->len >= hist->cap) { + size_t cap = hist->cap * 2 > hist->cap + 2 ? hist->cap * 2 : hist->cap + 2; + if (cap <= hist->len) + exit(1); /* FIXME: overflow */ + hist->cmds = ledit_reallocarray(hist->cmds, cap, sizeof(char *)); + hist->cap = cap; + } + hist->cmds[hist->len] = ledit_strndup(cmd, len); + hist->len++; + hist->cur = hist->len; +} + +static void +push_cmdhistory(char *cmd, size_t len) { + push_history(&cmdhistory, cmd, len); +} + +static void +push_searchhistory(char *search, size_t len) { + push_history(&searchhistory, search, len); +} + +void +command_key_cleanup(void) { + free(sub_state.search); + free(sub_state.replace); + for (size_t i = 0; i < cmdhistory.len; i++) { + free(cmdhistory.cmds[i]); + } + for (size_t i = 0; i < searchhistory.len; i++) { + free(searchhistory.cmds[i]); + } + free(cmdhistory.cmds); + free(searchhistory.cmds); +} + static int view_locked_error(ledit_view *view) { window_show_message(view->window, view->lock_text, -1); @@ -231,18 +279,20 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { CHECK_VIEW_LOCKED(view); cmd++; /* remove 's' at beginning */ size_t len = strlen(cmd); + char *sep = NULL; if (len == 0) goto error; - /* FIXME: utf8 */ - char sep = cmd[0]; - cmd++; - char *next = strchr(cmd, sep); + char *sepend = next_utf8(cmd + 1); + size_t seplen = sepend - cmd; + sep = ledit_strndup(cmd, seplen); + cmd += seplen; + char *next = strstr(cmd, sep); if (next == NULL) goto error; *next = '\0'; - next++; - char *last = strchr(next, sep); + next += seplen; + char *last = strstr(next, sep); if (last == NULL) goto error; *last = '\0'; - last++; + last += seplen; int confirm = 0, global = 0; char *c = last; while (*c != '\0') { @@ -280,9 +330,11 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { } else { substitute_all_remaining(view); } + free(sep); return 0; error: window_show_message(view->window, "Invalid command", -1); + free(sep); return 0; } @@ -305,13 +357,14 @@ static const struct { }; /* -. current line - FIXME: implement +. current line $ last line % all lines */ /* FIXME: ACTUALLY USE LEN!!! */ -/* FIXME: allow using marks and selection range here */ +/* NOTE: Marks are only recognized here if they are one unicode character! */ +/* NOTE: Only the line range of the selection is used at the moment. */ static int parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid) { (void)len; @@ -325,7 +378,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin *l1_valid = 0; *l2_valid = 0; char *c = cmd; - for (; *c != '\0'; c++) { + while (*c != '\0') { if (isdigit(*c)) { /* FIXME: integer overflow */ if (s & IN_LINENO) { @@ -338,21 +391,43 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin } else if ((s & START_LINENO) && (s & START_RANGE)) { l1 = *c - '0'; *l1_valid = 1; - s &= ~START_RANGE; - s &= ~START_LINENO; - s |= IN_RANGE | IN_LINENO; + s = IN_RANGE | IN_LINENO; } else if ((s & START_LINENO)) { l2 = *c - '0'; *l2_valid = 1; - s &= ~START_LINENO; - s |= IN_LINENO; + s = IN_LINENO; + } + } else if (*c == '\'' && (s & START_LINENO)) { + if (c[1] == '\0' || c[2] == '\0') + return 1; + char *aftermark = next_utf8(c + 2); + size_t marklen = aftermark - (c + 1); + size_t l, b; + if (!strncmp(c + 1, "<", strlen("<")) && view->sel_valid) { + l = view->sel.line1 < view->sel.line2 ? view->sel.line1 : view->sel.line2; + } else if (!strncmp(c + 1, ">", strlen(">")) && view->sel_valid) { + l = view->sel.line1 > view->sel.line2 ? view->sel.line1 : view->sel.line2; + } else { + if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) { + /* FIXME: show better error message */ + return 1; + } + } + if (!*l1_valid) { + l1 = l + 1; + *l1_valid = 1; + } else { + l2 = l + 1; + *l2_valid = 1; } + c = aftermark; + s = 0; + continue; } else if (*c == ',' && !(s & START_RANGE)) { if (*l1_valid && *l2_valid) { return 1; } else { - s |= START_LINENO; - s &= ~IN_LINENO; + s = START_LINENO; } } else if (*c == '%') { if (s & START_RANGE) { @@ -367,20 +442,33 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin } else if (*c == '$') { if (s & START_LINENO) { if (!*l1_valid) { + l1 = view->cur_line + 1; + *l1_valid = 1; + } else { + l2 = view->cur_line + 1; + *l2_valid = 1; + } + s = 0; + } else { + return 1; + } + } else if (*c == '$') { + if (s & START_LINENO) { + if (!*l1_valid) { l1 = view->lines_num; *l1_valid = 1; } else { l2 = view->lines_num; *l2_valid = 1; } - s &= ~START_LINENO; - s &= ~IN_LINENO; + s = 0; } else { return 1; } } else { break; } + c++; } if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE)) return 1; @@ -397,12 +485,14 @@ static int handle_cmd(ledit_view *view, char *cmd, size_t len) { if (len < 1) return 0; + push_cmdhistory(cmd, len); char *c; size_t l1, l2; int l1_valid, l2_valid; - /* FIXME: show error msg here */ - if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid)) + if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid)) { + window_show_message(view->window, "Error parsing command", -1); return 0; + } int range_given = l1_valid && l2_valid; if (!range_given) { l1 = l2 = view->cur_line; @@ -413,6 +503,7 @@ handle_cmd(ledit_view *view, char *cmd, size_t len) { return cmds[i].handler(view, c, l1, l2); } } + window_show_message(view->window, "Invalid command", -1); return 0; } @@ -531,8 +622,72 @@ edit_submit(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; window_set_bottom_bar_text_shown(view->window, 0); + char *text = window_get_bottom_bar_text(view->window); + int min_pos = window_get_bottom_bar_min_pos(view->window); + int textlen = strlen(text); + /* this should never happen */ + if (min_pos > textlen) { + textlen = 0; + } else { + textlen -= min_pos; + text += min_pos; + } /* FIXME: this is hacky */ - return handle_cmd(view, window_get_bottom_bar_text(view->window) + 1, -1); + return handle_cmd(view, text, (size_t)textlen); +} + +static int +edit_prevcommand(ledit_view *view, char *key_text, size_t len) { + (void)key_text; + (void)len; + if (cmdhistory.cur > 0) { + cmdhistory.cur--; + window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1); + window_bottom_bar_cursor_to_end(view->window); + } + return 1; +} + +static int +edit_nextcommand(ledit_view *view, char *key_text, size_t len) { + (void)key_text; + (void)len; + if (cmdhistory.len > 0 && cmdhistory.cur < cmdhistory.len - 1) { + cmdhistory.cur++; + window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1); + } else { + cmdhistory.cur = cmdhistory.len; + window_set_bottom_bar_realtext(view->window, "", -1); + } + window_bottom_bar_cursor_to_end(view->window); + return 1; +} + +static int +edit_prevsearch(ledit_view *view, char *key_text, size_t len) { + (void)key_text; + (void)len; + if (searchhistory.cur > 0) { + searchhistory.cur--; + window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1); + window_bottom_bar_cursor_to_end(view->window); + } + return 1; +} + +static int +edit_nextsearch(ledit_view *view, char *key_text, size_t len) { + (void)key_text; + (void)len; + if (searchhistory.len > 0 && searchhistory.cur < searchhistory.len - 1) { + searchhistory.cur++; + window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1); + } else { + searchhistory.cur = searchhistory.len; + window_set_bottom_bar_realtext(view->window, "", -1); + } + window_bottom_bar_cursor_to_end(view->window); + return 1; } /* FIXME: support visual mode, i.e. change selection to new place? */ @@ -561,8 +716,21 @@ editsearch_submit(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; window_set_bottom_bar_text_shown(view->window, 0); - set_search_forward(window_get_bottom_bar_text(view->window) + 1); - search_next(view); + char *text = window_get_bottom_bar_text(view->window); + int min_pos = window_get_bottom_bar_min_pos(view->window); + int textlen = strlen(text); + /* this should always be the case */ + if (min_pos <= textlen) { + if (min_pos < textlen) + push_searchhistory(text + min_pos, textlen - min_pos); + set_search_forward(text + min_pos); + search_next(view); + } else { + window_show_message( + view->window, + "Error in program. Tell lumidify about it.", -1 + ); + } return 0; } @@ -571,8 +739,21 @@ editsearchb_submit(ledit_view *view, char *key_text, size_t len) { (void)key_text; (void)len; window_set_bottom_bar_text_shown(view->window, 0); - set_search_backward(window_get_bottom_bar_text(view->window) + 1); - search_next(view); + char *text = window_get_bottom_bar_text(view->window); + int min_pos = window_get_bottom_bar_min_pos(view->window); + int textlen = strlen(text); + /* this should always be the case */ + if (min_pos <= textlen) { + if (min_pos < textlen) + push_searchhistory(text + min_pos, textlen - min_pos); + set_search_backward(text + min_pos); + search_next(view); + } else { + window_show_message( + view->window, + "Error in program. Tell lumidify about it.", -1 + ); + } return 0; } diff --git a/keys_command.h b/keys_command.h @@ -2,4 +2,5 @@ void search_next(ledit_view *view); void search_prev(ledit_view *view); +void command_key_cleanup(void); struct action command_key_handler(ledit_view *view, XEvent *event, int lang_index); diff --git a/keys_command_config.h b/keys_command_config.h @@ -16,6 +16,10 @@ static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len static int edit_backspace(ledit_view *view, char *key_text, size_t len); static int edit_delete(ledit_view *view, char *key_text, size_t len); static int edit_submit(ledit_view *view, char *key_text, size_t len); +static int edit_prevcommand(ledit_view *view, char *key_text, size_t len); +static int edit_nextcommand(ledit_view *view, char *key_text, size_t len); +static int edit_prevsearch(ledit_view *view, char *key_text, size_t len); +static int edit_nextsearch(ledit_view *view, char *key_text, size_t len); static int editsearch_submit(ledit_view *view, char *key_text, size_t len); static int editsearchb_submit(ledit_view *view, char *key_text, size_t len); static int edit_discard(ledit_view *view, char *key_text, size_t len); @@ -43,6 +47,12 @@ static struct key keys_en[] = { {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_Up, CMD_EDIT, &edit_prevcommand}, + {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch}, + {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch}, + {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand}, + {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch}, + {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch}, {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace}, {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace}, {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace}, diff --git a/ledit.c b/ledit.c @@ -259,6 +259,7 @@ ledit_cleanup(void) { /* FIXME: check for other things to clean up */ search_cleanup(); basic_key_cleanup(); + command_key_cleanup(); if (buffer) buffer_destroy(buffer); if (theme) diff --git a/search.c b/search.c @@ -48,7 +48,8 @@ static enum ledit_search_state search_forward(ledit_view *view, size_t *line_ret, size_t *byte_ret) { *line_ret = view->cur_line; *byte_ret = view->cur_index; - if (last_search == NULL) + /* if last_search is empty, strstr will find the ending '\0' */ + if (last_search == NULL || last_search[0] == '\0') return SEARCH_NO_PATTERN; size_t line = view->cur_line; /* start one byte later so it doesn't get stuck on a match @@ -96,7 +97,7 @@ static enum ledit_search_state search_backward(ledit_view *view, size_t *line_ret, size_t *byte_ret) { *line_ret = view->cur_line; *byte_ret = view->cur_index; - if (last_search == NULL) + if (last_search == NULL || last_search[0] == '\0') return SEARCH_NO_PATTERN; size_t line = view->cur_line; size_t byte = view->cur_index; diff --git a/undo.c b/undo.c @@ -72,7 +72,7 @@ push_undo_elem(undo_stack *undo) { if (undo->len > undo->cap) { /* FIXME: wait, why is it size_t here already? */ size_t cap = undo->len * 2; - undo->stack = ledit_realloc(undo->stack, cap * sizeof(undo_elem)); + undo->stack = ledit_reallocarray(undo->stack, cap, sizeof(undo_elem)); for (size_t i = undo->cap; i < cap; i++) { undo->stack[i].text = NULL; } diff --git a/util.c b/util.c @@ -48,3 +48,10 @@ draw_destroy(ledit_window *window, ledit_draw *draw) { XftDrawDestroy(draw->xftdraw); free(draw); } + +char * +next_utf8(char *str) { + while ((*str & 0xC0) == 0x80) + str++; + return str; +} diff --git a/util.h b/util.h @@ -24,3 +24,5 @@ void draw_grow(ledit_window *window, ledit_draw *draw, int w, int h); * Destroy a draw. */ void draw_destroy(ledit_window *window, ledit_draw *draw); + +char *next_utf8(char *str); diff --git a/window.c b/window.c @@ -165,6 +165,11 @@ window_set_bottom_bar_min_pos(ledit_window *window, int pos) { window->bb->min_pos = pos; } +int +window_get_bottom_bar_min_pos(ledit_window *window) { + return window->bb->min_pos; +} + void window_bottom_bar_cursor_to_beginning(ledit_window *window) { window->bb->line_cur_pos = window->bb->min_pos; @@ -254,6 +259,15 @@ window_set_bottom_bar_text(ledit_window *window, char *text, int len) { window->redraw = 1; } +void +window_set_bottom_bar_realtext(ledit_window *window, char *text, int len) { + if (window->bb->min_pos <= window->bb->line_len) + window->bb->line_len = window->bb->min_pos; + window->bb->line_cur_pos = window->bb->line_len; + window_insert_bottom_bar_text(window, text, len); + window->redraw = 1; +} + char * window_get_bottom_bar_text(ledit_window *window) { return window->bb->line_text; diff --git a/window.h b/window.h @@ -116,13 +116,18 @@ void window_bottom_bar_cursor_to_beginning(ledit_window *window); void window_bottom_bar_cursor_to_end(ledit_window *window); /* - * Set the minimum byte position of the cursor of the editable text to 'pos'. + * Set the minimum byte position of the cursor of the editable text to 'pos'. * This means that the cursor will not be allowed to go further left * than that position (used e.g. for the ':' in commands). */ void window_set_bottom_bar_min_pos(ledit_window *window, int pos); /* + * Get the mininum position (see above). + */ +int window_get_bottom_bar_min_pos(ledit_window *window); + +/* * Set whether the editable text is shown. */ void window_set_bottom_bar_text_shown(ledit_window *window, int shown); @@ -153,6 +158,13 @@ void window_insert_bottom_bar_text(ledit_window *window, char *text, int len); void window_set_bottom_bar_text(ledit_window *window, char *text, int len); /* + * Set the text after the minimum position. + * If the set minimum position is after the current length + * of the text in the bar, the text is just appended. + */ +void window_set_bottom_bar_realtext(ledit_window *window, char *text, int len); + +/* * Get the text of the editable line. */ char *window_get_bottom_bar_text(ledit_window *window);