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 b7f7a516c405f90fb1294a6b7cf4463373294cb0
parent 4e39cee7c2670c566dd05abc926f01aa8444e018
Author: lumidify <nobody@lumidify.org>
Date:   Sat, 22 Oct 2022 11:47:14 +0200

Support highlighting entire words when searching and replacing

Diffstat:
Mbuffer.c | 1+
Mconfigparser.c | 29+++++++++++++++++++++++++++++
Mconfigparser.h | 1+
Mkeys_basic.c | 14++++++++------
Mkeys_command.c | 65++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mledit.1 | 2--
Mleditrc.5 | 16+++++++++++-----
Mleditrc.example | 5+++--
Msearch.c | 21+++++++++++++--------
Msearch.h | 4++--
Mtheme_config.h | 3+++
Mutil.c | 2+-
Mutil.h | 2+-
Mview.c | 14+++++++++++---
14 files changed, 134 insertions(+), 45 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -274,6 +274,7 @@ buffer_set_hard_line_based(ledit_buffer *buffer, int hl) { buffer->hard_line_based = hl; } +/* FIXME: lock view if others are locked! */ void buffer_add_view(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos, long scroll_offset) { size_t new_num = add_sz(buffer->views_num, 1); diff --git a/configparser.c b/configparser.c @@ -522,6 +522,34 @@ destroy_theme_string(ledit_common *common, void *obj) { free(*obj_str); } +static int +parse_theme_bool( + ledit_common *common, + void *obj, const char *val, size_t val_len, char *key, + char *filename, size_t line, size_t line_offset, char **errstr) { + (void)common; + int *num = (int *)obj; + if (str_array_equal("true", val, val_len)) { + *num = 1; + return 0; + } else if (str_array_equal("false", val, val_len)) { + *num = 0; + return 0; + } + *errstr = print_fmt( + "%s: Invalid boolean '%.*s' " + "for '%s' at line %zu, position %zu", + filename, val_len, val, key, line, line_offset + ); + return 1; +} + +static void +destroy_theme_bool(ledit_common *common, void *obj) { + (void)common; + (void)obj; +} + /* FIXME: This interface is absolutely horrible - it's mainly this way to reuse the theme array for the destroy function */ /* If theme is NULL, a new theme is loaded, else it is destroyed */ @@ -560,6 +588,7 @@ load_destroy_theme(ledit_common *common, ast_list *theme_list, ledit_theme *them {"bar-fmt", &theme->bar_fmt, &parse_theme_string, &destroy_theme_string, BAR_FMT, default_init}, {"scrollbar-fg", &theme->scrollbar_fg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_FG, default_init}, {"scrollbar-bg", &theme->scrollbar_bg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_BG, default_init}, + {"highlight-search", &theme->highlight_search, &parse_theme_bool, &destroy_theme_bool, HIGHLIGHT_SEARCH, default_init}, }; if (default_init) diff --git a/configparser.h b/configparser.h @@ -10,6 +10,7 @@ typedef struct { int scrollbar_width; int scrollbar_step; int text_size; + int highlight_search; XftColor text_fg; XftColor text_bg; XftColor cursor_fg; diff --git a/keys_basic.c b/keys_basic.c @@ -1,3 +1,4 @@ +/* FIXME: all movement commands that modify the selection should first check if the selection is valid! */ /* FIXME: should motion callbacks be ignored in visual mode as they currently are? */ /* FIXME: check allowed modes at beginning of functions */ /* FIXME: the stacks here are shared for all views which can cause weird @@ -180,8 +181,8 @@ static struct basic_key_cb basic_key_cb_map[] = { {"delete-to-eol", &delete_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, {"enter-commandedit", &enter_commandedit, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"enter-insert", &enter_insert, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL}, - {"enter-searchedit-backwards", &enter_searchedit_backward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, - {"enter-searchedit-forwards", &enter_searchedit_forward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"enter-searchedit-backwards", &enter_searchedit_backward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL}, + {"enter-searchedit-forwards", &enter_searchedit_forward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL}, {"enter-visual", &enter_visual, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT}, {"return-to-normal", &return_to_normal, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"find-char-backwards", &find_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, @@ -224,8 +225,8 @@ static struct basic_key_cb basic_key_cb_map[] = { {"scroll-lines-up", &scroll_lines_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, {"scroll-with-cursor-down", &scroll_with_cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, {"scroll-with-cursor-up", &scroll_with_cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, - {"search-next", &key_search_next, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, - {"search-previous", &key_search_prev, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT}, + {"search-next", &key_search_next, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL}, + {"search-previous", &key_search_prev, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL}, {"show-line", &show_line, KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, {"switch-selection-end", &switch_selection_end, KEY_FLAG_JUMP_TO_CURSOR, VISUAL}, {"toggle-hard-line-based", &toggle_hard_line_based, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT}, @@ -695,8 +696,8 @@ delete_selection(ledit_view *view) { ); paste_buffer_line_based = 0; view->sel_valid = 0; - view->sel.line1 = view->sel.line2 = 0; - view->sel.byte1 = view->sel.byte2 = 0; + view->sel.line1 = view->sel.line2 = view->cur_line; + view->sel.byte1 = view->sel.byte2 = view->cur_index; view_wipe_line_cursor_attrs(view, view->cur_line); return 1; } @@ -1797,6 +1798,7 @@ move_cursor_left_right(ledit_view *view, int dir, int allow_illegal_index) { } else { view->cur_index = new_index; if (view->mode == VISUAL) { + /* FIXME: check if view->sel_valid and only use it then (also change in other places) */ view_set_selection(view, view->sel.line1, view->sel.byte1, view->cur_line, new_index); } else if (view->mode == INSERT && view->sel_valid) { /* FIXME: I guess this is unnecessary now that no diff --git a/keys_command.c b/keys_command.c @@ -355,20 +355,36 @@ next_replace_pos( /* returns whether keys should continue being captured */ static int move_to_next_substitution(ledit_view *view) { - view_wipe_line_cursor_attrs(view, view->cur_line); + ledit_theme *theme = config_get_theme(); + if (view->mode == NORMAL) + view_wipe_line_cursor_attrs(view, view->cur_line); + else if (view->mode == VISUAL) + view_wipe_selection(view); if (!next_replace_pos(view, sub_state.line, sub_state.byte, sub_state.max_line, &sub_state.line, &sub_state.byte)) { + /* FIXME: why are these set here? */ view->cur_line = sub_state.line; view->cur_index = sub_state.byte; - if (view->mode == NORMAL) + if (view->mode == NORMAL) { + view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } else if (view->mode == VISUAL) { + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); + } window_show_message(view->window, "No more matches", -1); buffer_unlock_all_views(view->buffer); return 0; } + if (theme->highlight_search && view->mode != VISUAL) { + view_wipe_line_cursor_attrs(view, view->cur_line); + view_set_mode(view, VISUAL); + } view->cur_line = sub_state.line; view->cur_index = sub_state.byte; - if (view->mode == NORMAL) + if (view->mode == NORMAL) { view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } else if (view->mode == VISUAL && theme->highlight_search) { + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + sub_state.slen); + } window_show_message(view->window, "Replace? (y/Y/n/N)", -1); view_ensure_cursor_shown(view); return 1; @@ -402,7 +418,10 @@ substitute_single(ledit_view *view) { static void substitute_all_remaining(ledit_view *view) { - view_wipe_line_cursor_attrs(view, view->cur_line); + if (view->mode == NORMAL) + view_wipe_line_cursor_attrs(view, view->cur_line); + else if (view->mode == VISUAL) + view_wipe_selection(view); size_t min_line = SIZE_MAX; while (next_replace_pos(view, sub_state.line, sub_state.byte, sub_state.max_line, &sub_state.line, &sub_state.byte)) { if (sub_state.line < min_line) @@ -424,6 +443,8 @@ substitute_all_remaining(ledit_view *view) { /* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */ view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index); view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } else if (view->mode == VISUAL) { + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); } view_ensure_cursor_shown(view); buffer_unlock_all_views(view->buffer); @@ -462,6 +483,8 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { } c++; } + free(sep); + sep = NULL; free(sub_state.search); free(sub_state.replace); sub_state.search = ledit_strdup(cmd); @@ -477,12 +500,6 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { sub_state.num = 0; sub_state.start_group = 1; - /* trying to perform substitution in visual mode would make - it unnecessarily complicated */ - if (view->mode == VISUAL) { - view_wipe_selection(view); - view_set_mode(view, NORMAL); - } if (confirm) { buffer_lock_all_views_except(view->buffer, view, "Ongoing substitution in other view."); view->cur_command_type = CMD_SUBSTITUTE; @@ -490,7 +507,6 @@ 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); @@ -871,13 +887,24 @@ edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index) return 1; } +/* FIXME: the current "highlight_search" support is a bit weird and will probably conflict + in some way if other support for visual mode (e.g. only search in selection) is added */ /* FIXME: support visual mode, i.e. change selection to new place? */ void search_next(ledit_view *view) { view_wipe_line_cursor_attrs(view, view->cur_line); - search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index); - if (view->mode == NORMAL) + size_t len = 0; + search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index, &len); + ledit_theme *theme = config_get_theme(); + /* FIXME: figure out key stack handling when modes are also changed here */ + if (theme->highlight_search && len > 0 && (ret == SEARCH_NORMAL || ret == SEARCH_WRAPPED)) { + view_set_mode(view, VISUAL); + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + len); + } else if (view->mode == VISUAL) { + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); + } else if (view->mode == NORMAL) { view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } view_ensure_cursor_shown(view); if (ret != SEARCH_NORMAL) window_show_message(view->window, search_state_to_str(ret), -1); @@ -886,9 +913,17 @@ search_next(ledit_view *view) { void search_prev(ledit_view *view) { view_wipe_line_cursor_attrs(view, view->cur_line); - search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index); - if (view->mode == NORMAL) + size_t len = 0; + search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index, &len); + ledit_theme *theme = config_get_theme(); + if (theme->highlight_search && len > 0 && (ret == SEARCH_NORMAL || ret == SEARCH_WRAPPED)) { + view_set_mode(view, VISUAL); + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + len); + } else if (view->mode == VISUAL) { + view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index); + } if (view->mode == NORMAL) { view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + } view_ensure_cursor_shown(view); if (ret != SEARCH_NORMAL) window_show_message(view->window, search_state_to_str(ret), -1); diff --git a/ledit.1 b/ledit.1 @@ -1,5 +1,3 @@ -.\" WARNING: Some parts of this are stolen shamelessly from OpenBSD's -.\" vi(1) manpage! .Dd May 27, 2022 .Dt LEDIT 1 .Os diff --git a/leditrc.5 b/leditrc.5 @@ -1,4 +1,4 @@ -.Dd September 2, 2022 +.Dd October 22, 2022 .Dt LEDITRC 5 .Os .Sh NAME @@ -169,6 +169,12 @@ Default: #CCCCCC .It Ar scrollbar-fg Color of scrollbar handle. Default: #000000 +.It Ar highlight-search +Whether entire words should be highlighted when searching or replacing with confirmation (true/false). +Note that the mode is automatically switched to visual when this is set and a word needs to be highlighted. +This is a bit weird, but in order to keep everything a bit more consistent, selections are curently +only allowed in visual mode. +Default: true .El .Sh BINDINGS The key bindings may be configured by assigning @@ -423,10 +429,10 @@ In visual mode, the selection range is automatically pasted into the line editor so commands can be performed on it. .It Ar enter-insert Op normal, visual Enter insert mode. -.It Ar enter-searchedit-backwards Op normal, insert +.It Ar enter-searchedit-backwards Op normal, insert, visual Open the line editor for searching backwards. Note that no regex is currently supported. -.It Ar enter-searchedit-forwards Op normal, insert +.It Ar enter-searchedit-forwards Op normal, insert, visual Open the line editor for searching forwards. Note that no regex is currently supported. .It Ar enter-visual Op normal, insert @@ -652,9 +658,9 @@ Move .Ar num lines up, attempting to leave the cursor in its current line and character position. Note that this command works with soft lines, regardless of the current mode. -.It Ar search-next Op normal, insert +.It Ar search-next Op normal, insert, visual Move to the next search result. -.It Ar search-previous Op normal, insert +.It Ar search-previous Op normal, insert, visual Move to the previous search result. .It Ar show-line Op normal, visual, insert Show the current file name, whether the buffer has been modified since the last diff --git a/leditrc.example b/leditrc.example @@ -18,6 +18,7 @@ theme = { scrollbar-step = 20 scrollbar-bg = CCCCCC scrollbar-fg = 000000 + highlight-search = true } bindings = { @@ -66,8 +67,8 @@ bindings = { bind enter-commandedit text ":" modes normal|visual bind enter-searchedit-backwards text "?" modes normal bind enter-searchedit-forwards text "/" modes normal - bind search-next text "n" modes normal - bind search-previous text "N" modes normal + bind search-next text "n" modes normal|visual + bind search-previous text "N" modes normal|visual bind undo text "u" modes normal bind redo text "U" modes normal bind repeat-command text "." modes normal diff --git a/search.c b/search.c @@ -7,6 +7,7 @@ /* FIXME: make sure only whole utf8 chars are matched */ static char *last_search = NULL; +static size_t last_search_len = 0; enum { FORWARD, BACKWARD @@ -22,6 +23,7 @@ set_search_forward(char *pattern) { last_dir = FORWARD; free(last_search); last_search = ledit_strdup(pattern); + last_search_len = strlen(last_search); } void @@ -29,12 +31,14 @@ set_search_backward(char *pattern) { last_dir = BACKWARD; free(last_search); last_search = ledit_strdup(pattern); + last_search_len = strlen(last_search); } static search_state -search_forward(ledit_view *view, size_t *line_ret, size_t *byte_ret) { +search_forward(ledit_view *view, size_t *line_ret, size_t *byte_ret, size_t *len_ret) { *line_ret = view->cur_line; *byte_ret = view->cur_index; + *len_ret = last_search_len; /* if last_search is empty, strstr will find the ending '\0' */ if (last_search == NULL || last_search[0] == '\0') return SEARCH_NO_PATTERN; @@ -81,9 +85,10 @@ search_forward(ledit_view *view, size_t *line_ret, size_t *byte_ret) { /* FIXME: this is insanely inefficient */ /* FIXME: just go backwards char-by-char and compare */ static search_state -search_backward(ledit_view *view, size_t *line_ret, size_t *byte_ret) { +search_backward(ledit_view *view, size_t *line_ret, size_t *byte_ret, size_t *len_ret) { *line_ret = view->cur_line; *byte_ret = view->cur_index; + *len_ret = last_search_len; if (last_search == NULL || last_search[0] == '\0') return SEARCH_NO_PATTERN; size_t line = view->cur_line; @@ -145,19 +150,19 @@ search_backward(ledit_view *view, size_t *line_ret, size_t *byte_ret) { } search_state -ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret) { +ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret, size_t *len_ret) { if (last_dir == FORWARD) - return search_forward(view, line_ret, byte_ret); + return search_forward(view, line_ret, byte_ret, len_ret); else - return search_backward(view, line_ret, byte_ret); + return search_backward(view, line_ret, byte_ret, len_ret); } search_state -ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret) { +ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret, size_t *len_ret) { if (last_dir == FORWARD) - return search_backward(view, line_ret, byte_ret); + return search_backward(view, line_ret, byte_ret, len_ret); else - return search_forward(view, line_ret, byte_ret); + return search_forward(view, line_ret, byte_ret, len_ret); } char * diff --git a/search.h b/search.h @@ -13,8 +13,8 @@ typedef enum { void search_cleanup(void); void set_search_forward(char *pattern); void set_search_backward(char *pattern); -search_state ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret); -search_state ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret); +search_state ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret, size_t *len_ret); +search_state ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret, size_t *len_ret); /* * Get a string corresponding to a search_state. diff --git a/theme_config.h b/theme_config.h @@ -45,5 +45,8 @@ static const char *SCROLLBAR_STEP = "20"; static const char *SCROLLBAR_BG = "#CCCCCC"; /* color of scrollbar handle */ static const char *SCROLLBAR_FG = "#000000"; +/* whether to highlight entire words when searching/replacing */ +/* FIXME: this should maybe be in a separate "general config" section */ +static const char *HIGHLIGHT_SEARCH = "true"; #endif /* _THEME_CONFIG_H_ */ diff --git a/util.c b/util.c @@ -50,7 +50,7 @@ sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2) { } int -str_array_equal(char *terminated, char *array, size_t len) { +str_array_equal(const char *terminated, const char *array, size_t len) { if (!strncmp(terminated, array, len)) { /* this is kind of inefficient, but there's no way to know otherwise if strncmp just stopped comparing after a '\0' */ diff --git a/util.h b/util.h @@ -32,7 +32,7 @@ void sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2); * Returns non-zero if they are equal, 0 otherwise. */ /* Note: this doesn't work if array contains '\0'. */ -int str_array_equal(char *terminated, char *array, size_t len); +int str_array_equal(const char *terminated, const char *array, size_t len); #define LEDIT_MIN(x, y) ((x) < (y) ? (x) : (y)) #define LEDIT_MAX(x, y) ((x) > (y) ? (x) : (y)) diff --git a/view.c b/view.c @@ -24,6 +24,9 @@ #include "assert.h" #include "configparser.h" +/* FIXME: handle selections better - it can happen that the cursor is moved independently of + the selection, leading to weird "jumping selection" - this should be made impossible */ + /* Basic attributes set for all text. */ static PangoAttrList *basic_attrs = NULL; @@ -1746,9 +1749,14 @@ view_wipe_selection(ledit_view *view) { } } view->sel_valid = 0; - view->sel.line1 = view->sel.line2 = 0; - view->sel.byte1 = view->sel.byte2 = 0; - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); + /* FIXME: check what makes most sense here - this used to be set to 0, but that + caused "jumping effects" because some functions (cursor movement) use these + values without checking view->sel_valid before (which should actually be + changed because it is an error) */ + view->sel.line1 = view->sel.line2 = view->cur_line; + view->sel.byte1 = view->sel.byte2 = view->cur_index; + if (view->mode == NORMAL) + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); } void