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:
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);