tImprove range parsing; add search and command history - ledit - Text editor (W… | |
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 <[email protected]> | |
Date: Thu, 9 Dec 2021 20:10:59 +0100 | |
Improve range parsing; add search and command history | |
Diffstat: | |
M keys_basic.c | 5 +++-- | |
M keys_command.c | 233 +++++++++++++++++++++++++++--… | |
M keys_command.h | 1 + | |
M keys_command_config.h | 10 ++++++++++ | |
M ledit.c | 1 + | |
M search.c | 5 +++-- | |
M undo.c | 2 +- | |
M util.c | 7 +++++++ | |
M util.h | 2 ++ | |
M window.c | 14 ++++++++++++++ | |
M window.h | 14 +++++++++++++- | |
11 files changed, 262 insertions(+), 32 deletions(-) | |
--- | |
diff --git a/keys_basic.c b/keys_basic.c | |
t@@ -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 | |
t@@ -22,6 +22,7 @@ | |
#include "view.h" | |
#include "search.h" | |
#include "cleanup.h" | |
+#include "util.h" | |
#include "keys.h" | |
#include "keys_command.h" | |
t@@ -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 : h… | |
+ 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); | |
t@@ -231,18 +279,20 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1… | |
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') { | |
t@@ -280,9 +330,11 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1,… | |
} else { | |
substitute_all_remaining(view); | |
} | |
+ free(sep); | |
return 0; | |
error: | |
window_show_message(view->window, "Invalid command", -1); | |
+ free(sep); | |
return 0; | |
} | |
t@@ -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 *l… | |
(void)len; | |
t@@ -325,7 +378,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char … | |
*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) { | |
t@@ -338,21 +391,43 @@ parse_range(ledit_view *view, char *cmd, size_t len, cha… | |
} 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_val… | |
+ l = view->sel.line1 < view->sel.line2 ? view->… | |
+ } else if (!strncmp(c + 1, ">", strlen(">")) && view->… | |
+ l = view->sel.line1 > view->sel.line2 ? view->… | |
+ } else { | |
+ if (buffer_get_mark(view->buffer, c + 1, markl… | |
+ /* 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) { | |
t@@ -367,20 +442,33 @@ parse_range(ledit_view *view, char *cmd, size_t len, cha… | |
} 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; | |
t@@ -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; | |
t@@ -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; | |
} | |
t@@ -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, … | |
+ 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[c… | |
+ 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[c… | |
+ } 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.cmd… | |
+ 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.cmd… | |
+ } 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? */ | |
t@@ -561,8 +716,21 @@ editsearch_submit(ledit_view *view, char *key_text, size_… | |
(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; | |
} | |
t@@ -571,8 +739,21 @@ editsearchb_submit(ledit_view *view, char *key_text, size… | |
(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 | |
t@@ -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_in… | |
diff --git a/keys_command_config.h b/keys_command_config.h | |
t@@ -16,6 +16,10 @@ static int edit_cursor_to_beginning(ledit_view *view, char … | |
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); | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 | |
t@@ -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_ele… | |
+ undo->stack = ledit_reallocarray(undo->stack, cap, sizeof(undo… | |
for (size_t i = undo->cap; i < cap; i++) { | |
undo->stack[i].text = NULL; | |
} | |
diff --git a/util.c b/util.c | |
t@@ -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 | |
t@@ -24,3 +24,5 @@ void draw_grow(ledit_window *window, ledit_draw *draw, int w… | |
* 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 | |
t@@ -165,6 +165,11 @@ window_set_bottom_bar_min_pos(ledit_window *window, int p… | |
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; | |
t@@ -254,6 +259,15 @@ window_set_bottom_bar_text(ledit_window *window, char *te… | |
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 | |
t@@ -116,13 +116,18 @@ void window_bottom_bar_cursor_to_beginning(ledit_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); | |
t@@ -153,6 +158,13 @@ void window_insert_bottom_bar_text(ledit_window *window, … | |
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); |