diff options
Diffstat (limited to 'examples/dte/search.c')
| -rw-r--r-- | examples/dte/search.c | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/examples/dte/search.c b/examples/dte/search.c new file mode 100644 index 0000000..80ad5c3 --- /dev/null +++ b/examples/dte/search.c @@ -0,0 +1,244 @@ +#include <stdlib.h> +#include "search.h" +#include "block-iter.h" +#include "buffer.h" +#include "error.h" +#include "regexp.h" +#include "util/ascii.h" +#include "util/xmalloc.h" + +static bool do_search_fwd(View *view, regex_t *regex, BlockIter *bi, bool skip) +{ + int flags = block_iter_is_bol(bi) ? 0 : REG_NOTBOL; + + do { + if (block_iter_is_eof(bi)) { + return false; + } + + regmatch_t match; + StringView line; + fill_line_ref(bi, &line); + + // NOTE: If this is the first iteration then line.data contains + // partial line (text starting from the cursor position) and + // if match.rm_so is 0 then match is at beginning of the text + // which is same as the cursor position. + if (regexp_exec(regex, line.data, line.length, 1, &match, flags)) { + if (skip && match.rm_so == 0) { + // Ignore match at current cursor position + regoff_t count = match.rm_eo; + if (count == 0) { + // It is safe to skip one byte because every line + // has one extra byte (newline) that is not in line.data + count = 1; + } + block_iter_skip_bytes(bi, (size_t)count); + return do_search_fwd(view, regex, bi, false); + } + + block_iter_skip_bytes(bi, match.rm_so); + view->cursor = *bi; + view->center_on_scroll = true; + view_reset_preferred_x(view); + return true; + } + + skip = false; // Not at cursor position any more + flags = 0; + } while (block_iter_next_line(bi)); + + return false; +} + +static bool do_search_bwd(View *view, regex_t *regex, BlockIter *bi, ssize_t cx, bool skip) +{ + if (block_iter_is_eof(bi)) { + goto next; + } + + do { + regmatch_t match; + StringView line; + int flags = 0; + regoff_t offset = -1; + regoff_t pos = 0; + + fill_line_ref(bi, &line); + while ( + pos <= line.length + && regexp_exec(regex, line.data + pos, line.length - pos, 1, &match, flags) + ) { + flags = REG_NOTBOL; + if (cx >= 0) { + if (pos + match.rm_so >= cx) { + // Ignore match at or after cursor + break; + } + if (skip && pos + match.rm_eo > cx) { + // Search -rw should not find word under cursor + break; + } + } + + // This might be what we want (last match before cursor) + offset = pos + match.rm_so; + pos += match.rm_eo; + + if (match.rm_so == match.rm_eo) { + // Zero length match + break; + } + } + + if (offset >= 0) { + block_iter_skip_bytes(bi, offset); + view->cursor = *bi; + view->center_on_scroll = true; + view_reset_preferred_x(view); + return true; + } + + next: + cx = -1; + } while (block_iter_prev_line(bi)); + + return false; +} + +bool search_tag(View *view, const char *pattern) +{ + regex_t regex; + if (!regexp_compile_basic(®ex, pattern, REG_NEWLINE)) { + return false; + } + + BlockIter bi = block_iter(view->buffer); + bool found = do_search_fwd(view, ®ex, &bi, false); + regfree(®ex); + + if (!found) { + // Don't center view to cursor unnecessarily + view->force_center = false; + return error_msg("Tag not found"); + } + + view->center_on_scroll = true; + return true; +} + +static void free_regex(SearchState *search) +{ + if (search->re_flags) { + regfree(&search->regex); + search->re_flags = 0; + } +} + +static bool has_upper(const char *str) +{ + for (size_t i = 0; str[i]; i++) { + if (ascii_isupper(str[i])) { + return true; + } + } + return false; +} + +static bool update_regex(SearchState *search, SearchCaseSensitivity cs) +{ + int re_flags = REG_NEWLINE; + switch (cs) { + case CSS_TRUE: + break; + case CSS_FALSE: + re_flags |= REG_ICASE; + break; + case CSS_AUTO: + if (!has_upper(search->pattern)) { + re_flags |= REG_ICASE; + } + break; + default: + BUG("unhandled case sensitivity value"); + } + + if (re_flags == search->re_flags) { + return true; + } + + free_regex(search); + + search->re_flags = re_flags; + if (regexp_compile(&search->regex, search->pattern, search->re_flags)) { + return true; + } + + free_regex(search); + return false; +} + +void search_free_regexp(SearchState *search) +{ + free_regex(search); + free(search->pattern); +} + +void search_set_regexp(SearchState *search, const char *pattern) +{ + search_free_regexp(search); + search->pattern = xstrdup(pattern); +} + +static bool do_search_next(View *view, SearchState *search, SearchCaseSensitivity cs, bool skip) +{ + if (!search->pattern) { + return error_msg("No previous search pattern"); + } + if (!update_regex(search, cs)) { + return false; + } + + BlockIter bi = view->cursor; + regex_t *regex = &search->regex; + if (!search->reverse) { + if (do_search_fwd(view, regex, &bi, true)) { + return true; + } + block_iter_bof(&bi); + if (do_search_fwd(view, regex, &bi, false)) { + info_msg("Continuing at top"); + return true; + } + } else { + size_t cursor_x = block_iter_bol(&bi); + if (do_search_bwd(view, regex, &bi, cursor_x, skip)) { + return true; + } + block_iter_eof(&bi); + if (do_search_bwd(view, regex, &bi, -1, false)) { + info_msg("Continuing at bottom"); + return true; + } + } + + return error_msg("Pattern '%s' not found", search->pattern); +} + +bool search_prev(View *view, SearchState *search, SearchCaseSensitivity cs) +{ + toggle_search_direction(search); + bool r = search_next(view, search, cs); + toggle_search_direction(search); + return r; +} + +bool search_next(View *view, SearchState *search, SearchCaseSensitivity cs) +{ + return do_search_next(view, search, cs, false); +} + +bool search_next_word(View *view, SearchState *search, SearchCaseSensitivity cs) +{ + return do_search_next(view, search, cs, true); +} |
