URI: 
       tAdd very basic search functionality and start working on commands - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit e181351df92df2596bd48578667ce195f4cf34d0
   DIR parent b8f2762e5d7b778ba858d790f40c85aeab2f57ab
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Wed, 26 May 2021 21:39:22 +0200
       
       Add very basic search functionality and start working on commands
       
       Diffstat:
         M Makefile                            |       4 ++--
         M buffer.c                            |      31 ++++++++++++++++++++++++++-----
         A commands.c                          |      68 +++++++++++++++++++++++++++++++
         M common.h                            |       6 +++++-
         M ledit.c                             |     188 ++++++++++++++++++++++++++++---
         A search.c                            |     171 +++++++++++++++++++++++++++++++
         A search.h                            |      13 +++++++++++++
       
       7 files changed, 459 insertions(+), 22 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       t@@ -9,8 +9,8 @@ MANPREFIX = ${PREFIX}/man
        BIN = ${NAME}
        MAN1 = ${BIN:=.1}
        
       -OBJ = ${BIN:=.o} cache.o buffer.o memory.o util.o
       -HDR = cache.h buffer.h memory.h common.h util.h
       +OBJ = ${BIN:=.o} cache.o buffer.o memory.o util.o search.o
       +HDR = cache.h buffer.h memory.h common.h util.h search.h
        
        CFLAGS_LEDIT = -g -Wall -Wextra -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext`
        LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -105,14 +105,16 @@ ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, i
                ledit_line *line = &buffer->lines[line_index];
                if (len == -1)
                        len = strlen(text);
       -        if (line->len + len > line->cap || line->text == NULL) {
       +        /* \0 is not included in line->len */
       +        if (line->len + len + 1 > line->cap || line->text == NULL) {
                        /* FIXME: read up on what the best values are here */
       -                line->cap = line->cap * 2 > line->len + len ? line->cap * 2 : line->len + len;
       +                line->cap = line->cap * 2 > line->len + len + 1 ? line->cap * 2 : line->len + len + 1;
                        line->text = ledit_realloc(line->text, line->cap);
                }
                memmove(line->text + index + len, line->text + index, line->len - index);
                memcpy(line->text + index, text, len);
                line->len += len;
       +        line->text[line->len] = '\0';
                pango_layout_set_text(line->layout, line->text, line->len);
                /*recalc_single_line_size(buffer, line_index);*/
                line->dirty = 1;
       t@@ -120,6 +122,16 @@ ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, i
        
        static void append_line_impl(ledit_buffer *buffer, int line_index, int text_index);
        
       +/* FIXME: this isn't optimized like the standard version, but whatever */
       +static char *
       +strchr_len(char *text, char c, int len) {
       +        for (int i = 0; i < len; i++) {
       +                if (text[i] == c)
       +                        return text + i;
       +        }
       +        return NULL;
       +}
       +
        void
        ledit_insert_text_with_newlines(
            ledit_buffer *buffer,
       t@@ -128,16 +140,18 @@ ledit_insert_text_with_newlines(
            int *end_line_ret, int *end_char_ret) {
                if (len == -1)
                        len = strlen(text);
       +        int rem_len = len;
                char *cur, *last = text;
                int cur_line = line_index;
                int cur_index = index;
       -        while ((cur = strchr(last, '\n'))) {
       +        while ((cur = strchr_len(last, '\n', rem_len)) != NULL) {
                        ledit_insert_text(buffer, cur_line, cur_index, last, cur - last);
                        /* FIXME: inefficient because there's no gap buffer yet */
                        append_line_impl(buffer, cur_line, -1);
                        cur_index = 0;
                        cur_line++;
                        last = cur + 1;
       +                rem_len -= cur - last + 1;
                }
                /* FIXME: check how legal this casting between pointers and ints is */
                ledit_insert_text(buffer, cur_line, cur_index, last, text + len - last);
       t@@ -193,8 +207,10 @@ init_line(ledit_buffer *buffer, ledit_line *line) {
                pango_layout_set_font_description(line->layout, buffer->state->font);
                pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR);
                pango_layout_set_attributes(line->layout, basic_attrs);
       -        line->text = NULL;
       -        line->cap = line->len = 0;
       +        line->cap = 2; /* arbitrary */
       +        line->text = ledit_malloc(line->cap);
       +        line->text[0] = '\0';
       +        line->len = 0;
                line->cache_index = -1;
                line->dirty = 1;
                /* FIXME: does this set line height reasonably when no text yet? */
       t@@ -223,13 +239,16 @@ append_line_impl(ledit_buffer *buffer, int line_index, int text_index) {
                init_line(buffer, new_l);
                buffer->lines_num++;
                if (text_index != -1) {
       +                /* FIXME: use ledit_insert... here */
                        ledit_line *l = &buffer->lines[line_index];
                        int len = l->len - text_index;
                        new_l->len = len;
                        new_l->cap = len + 10;
                        new_l->text = ledit_malloc(new_l->cap);
                        memcpy(new_l->text, l->text + text_index, len);
       +                new_l->text[new_l->len] = '\0';
                        l->len = text_index;
       +                l->text[l->len] = '\0';
                        pango_layout_set_text(new_l->layout, new_l->text, new_l->len);
                        pango_layout_set_text(l->layout, l->text, l->len);
                        /* FIXME: set height here */
       t@@ -333,6 +352,7 @@ ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, 
                        memmove(l->text + byte_index, l->text + i, l->len - i);
                        l->len -= i - byte_index;
                }
       +        l->text[l->len] = '\0';
                pango_layout_set_text(l->layout, l->text, l->len);
                recalc_single_line_size(buffer, line_index);
                return new_index;
       t@@ -343,6 +363,7 @@ delete_line_section(ledit_buffer *buffer, int line, int start, int length) {
                ledit_line *l = &buffer->lines[line];
                memmove(l->text + start, l->text + start + length, l->len - start - length);
                l->len -= length;
       +        l->text[l->len] = '\0';
                pango_layout_set_text(l->layout, l->text, l->len);
                recalc_single_line_size(buffer, line);
                l->dirty = 1;
   DIR diff --git a/commands.c b/commands.c
       t@@ -0,0 +1,68 @@
       +/* FIXME: Parse commands properly and allow combinations of commands */
       +
       +static int
       +handle_write_quit(ledit_buffer *buffer, char *cmd, int l1, int l2) {
       +        return 0;
       +}
       +
       +static int
       +handle_substitute(ledit_buffer *buffer, char *cmd, int l1, int l2) {
       +        return 0;
       +}
       +
       +enum cmd_type {
       +        CMD_NORMAL,
       +        CMD_OPTIONAL_RANGE
       +};
       +
       +static const struct {
       +        char *cmd;
       +        enum cmd_type type;
       +        int (*handler)(ledit_buffer *buffer, char *cmd, int l1, int l2);
       +} cmds[] = {
       +        {"w", CMD_NORMAL, &handle_write_quit},
       +        {"q", CMD_NORMAL, &handle_write_quit},
       +        {"s", CMD_OPTIONAL_RANGE, &handle_substitute}
       +};
       +
       +#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
       +
       +int
       +ledit_handle_cmd(ledit_buffer *buffer, char *cmd, int len) {
       +        if (len < 0)
       +                len = strlen(cmd);
       +        if (len < 1)
       +                return;
       +        char *cur_pos = cmd;
       +        int l1 = buffer->cur_line;
       +        int l2 = buffer->cur_line;
       +        int range_given = 0;
       +        switch (cur_pos[0]) {
       +        case '%':
       +                l1 = 0;
       +                l2 = buffer->lines_num - 1;
       +                range_given = 1;
       +                cur_pos++;
       +                break;
       +        case '&'
       +                l1 = buffer->sel.line1;
       +                l2 = buffer->sel.line2;
       +                if (l1 < 0 || l2 < 0)
       +                        return 1;
       +                if (l1 < l2) {
       +                        int tmp = l1;
       +                        l1 = l2;
       +                        l2 = tmp;
       +                }
       +                range_given = 1;
       +                cur_pos++;
       +                break
       +        }
       +        for (int i = 0; i < LENGTH(cmds); i++) {
       +                if (!strncmp(cmds[i].cmd, cur_pos, strlen(cmds[i].cmd)) &&
       +                    (!range_given || cmds[i].type == CMD_NORMAL)) {
       +                        return cmds[i].handler(buffer, cur_pos, l1, l2);
       +                }
       +        }
       +        return 1;
       +}
   DIR diff --git a/common.h b/common.h
       t@@ -1,7 +1,9 @@
        enum ledit_mode {
                NORMAL = 1,
                INSERT = 2,
       -        VISUAL = 4
       +        VISUAL = 4,
       +        COMMANDEDIT = 8,
       +        SEARCHEDIT = 16
        };
        
        typedef struct {
       t@@ -24,6 +26,8 @@ typedef struct {
                int scroll_dragging;
                int scroll_grab_handle;
                int selecting;
       +        int bottom_text_shown;
       +        int message_shown;
                enum ledit_mode mode;
                XIM xim;
                XIC xic;
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -1,3 +1,4 @@
       +/* FIXME: overflow in repeated commands */
        /* FIXME: Fix lag when scrolling */
        /* FIXME: Fix lag when selecting with mouse */
        /* FIXME: Use PANGO_PIXELS() */
       t@@ -8,6 +9,7 @@
        #include <math.h>
        #include <stdio.h>
        #include <errno.h>
       +#include <assert.h>
        #include <string.h>
        #include <stdlib.h>
        #include <limits.h>
       t@@ -29,6 +31,7 @@
        #include "memory.h"
        #include "common.h"
        #include "buffer.h"
       +#include "search.h"
        #include "cache.h"
        #include "util.h"
        
       t@@ -52,6 +55,12 @@ struct {
                PangoLayout *ruler;
                ledit_draw *ruler_draw;
                int ruler_w, ruler_h;
       +        PangoLayout *line;
       +        ledit_draw *line_draw;
       +        int line_w, line_h;
       +        char *line_text;
       +        int line_alloc, line_len;
       +        int line_cur_pos;
        } bottom_bar;
        
        struct key {
       t@@ -142,13 +151,64 @@ static void get_new_line_softline(
        #define SCROLLBAR_WIDTH 10
        #define SCROLL_STEP 10
        
       +/* FIXME: shouldn't state.bottom_text_shown also be true when message_shown? */
        static void
        recalc_text_size(void) {
                int bar_h = bottom_bar.mode_h;
       +        if ((state.bottom_text_shown || state.message_shown) && bottom_bar.line_h > bar_h)
       +                bar_h = bottom_bar.line_h;
                state.text_w = state.w - SCROLLBAR_WIDTH;
                state.text_h = state.h - bar_h;
        }
        
       +/* FIXME: allow lines longer than window width to be displayed properly */
       +static void
       +insert_bottom_bar_text(char *text, int len) {
       +        assert(len >= -1);
       +        assert(bottom_bar.line_cur_pos <= bottom_bar.line_alloc);
       +
       +        if (len == -1)
       +                len = strlen(text);
       +        /* \0 not included in len */
       +        if (bottom_bar.line_len + len + 1 > bottom_bar.line_alloc || bottom_bar.line_text == NULL) {
       +                /* FIXME: read up on what the best values are here */
       +                bottom_bar.line_alloc =
       +                    bottom_bar.line_alloc * 2 > bottom_bar.line_len + len + 1 ?
       +                    bottom_bar.line_alloc * 2 :
       +                    bottom_bar.line_len + len + 1;
       +                bottom_bar.line_text = ledit_realloc(bottom_bar.line_text, bottom_bar.line_alloc);
       +        }
       +        memmove(
       +            bottom_bar.line_text + bottom_bar.line_cur_pos + len,
       +            bottom_bar.line_text + bottom_bar.line_cur_pos,
       +            bottom_bar.line_len - bottom_bar.line_cur_pos
       +        );
       +        memcpy(bottom_bar.line_text + bottom_bar.line_cur_pos, text, len);
       +        bottom_bar.line_len += len;
       +        bottom_bar.line_text[bottom_bar.line_len] = '\0';
       +        pango_layout_set_text(bottom_bar.line, bottom_bar.line_text, bottom_bar.line_len);
       +        pango_layout_get_pixel_size(bottom_bar.line, &bottom_bar.line_w, &bottom_bar.line_h);
       +        ledit_grow_draw(&state, bottom_bar.line_draw, bottom_bar.line_w, bottom_bar.line_h);
       +        XftDrawRect(bottom_bar.line_draw->xftdraw, &state.bg, 0, 0, bottom_bar.line_w, bottom_bar.line_h);
       +        pango_xft_render_layout(bottom_bar.line_draw->xftdraw, &state.fg, bottom_bar.line, 0, 0);
       +        recalc_text_size();
       +}
       +
       +static void
       +set_bottom_bar_text(char *text, int len) {
       +        bottom_bar.line_len = 0;
       +        bottom_bar.line_cur_pos = 0;
       +        insert_bottom_bar_text(text, len);
       +}
       +
       +static void
       +show_message(char *text, int len) {
       +        set_bottom_bar_text(text, len);
       +        /* FIXME: rename these */
       +        state.bottom_text_shown = 0;
       +        state.message_shown = 2;
       +}
       +
        /* clipboard handling largely stolen from st (simple terminal) */
        
        #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
       t@@ -181,7 +241,7 @@ set_mode(enum ledit_mode mode) {
                ledit_grow_draw(&state, bottom_bar.mode_draw, bottom_bar.mode_w, bottom_bar.mode_h);
                XftDrawRect(bottom_bar.mode_draw->xftdraw, &state.bg, 0, 0, bottom_bar.mode_w, bottom_bar.mode_h);
                pango_xft_render_layout(bottom_bar.mode_draw->xftdraw, &state.fg, bottom_bar.mode, 0, 0);
       -        recalc_text_size(); /* probably not necessary, but whatever */
       +        recalc_text_size();
        }
        
        void
       t@@ -885,6 +945,15 @@ setup(int argc, char *argv[]) {
                /* FIXME: only create "dummy draw" at first and create with proper size when needed */
                bottom_bar.mode_draw = ledit_create_draw(&state, 10, 10);
                set_mode(INSERT);
       +        bottom_bar.line = pango_layout_new(state.context);
       +        pango_layout_set_font_description(bottom_bar.line, state.font);
       +        bottom_bar.line_draw = ledit_create_draw(&state, 10, 10);
       +        bottom_bar.line_w = bottom_bar.line_h = 10;
       +        bottom_bar.line_text = NULL;
       +        bottom_bar.line_alloc = bottom_bar.line_len = 0;
       +        bottom_bar.line_cur_pos = 0;
       +        state.bottom_text_shown = 0;
       +        state.message_shown = 0;
        
                XMapWindow(state.dpy, state.win);
        
       t@@ -908,6 +977,7 @@ setup(int argc, char *argv[]) {
        static void
        cleanup(void) {
                /* FIXME: cleanup everything else */
       +        ledit_cleanup_search();
                ledit_destroy_cache();
                ledit_destroy_buffer(buffer);
                XDestroyWindow(state.dpy, state.win);
       t@@ -1006,12 +1076,21 @@ redraw(void) {
                    0, state.text_h,
                    state.w, state.h - state.text_h
                );
       -        XCopyArea(
       -            state.dpy, bottom_bar.mode_draw->pixmap,
       -            state.drawable, state.gc,
       -            0, 0, bottom_bar.mode_w, bottom_bar.mode_h,
       -            state.w - bottom_bar.mode_w, state.text_h
       -        );
       +        if (state.bottom_text_shown || state.message_shown) {
       +                XCopyArea(
       +                    state.dpy, bottom_bar.line_draw->pixmap,
       +                    state.drawable, state.gc,
       +                    0, 0, bottom_bar.line_w, bottom_bar.line_h,
       +                    0, state.text_h
       +                );
       +        } else {
       +                XCopyArea(
       +                    state.dpy, bottom_bar.mode_draw->pixmap,
       +                    state.drawable, state.gc,
       +                    0, 0, bottom_bar.mode_w, bottom_bar.mode_h,
       +                    state.w - bottom_bar.mode_w, state.text_h
       +                );
       +        }
        
                XdbeSwapInfo swap_info;
                swap_info.swap_window = state.win;
       t@@ -1582,6 +1661,71 @@ switch_selection_end(void) {
        #define XK_ANY_MOD    UINT_MAX
        #define XK_NO_MOD     0
        
       +static void
       +enter_commandedit(void) {
       +        set_bottom_bar_text(":", -1);
       +        bottom_bar.line_cur_pos = 1;
       +        state.mode = COMMANDEDIT;
       +        state.bottom_text_shown = 1;
       +}
       +
       +static void
       +enter_searchedit_forward(void) {
       +        set_bottom_bar_text("/", -1);
       +        bottom_bar.line_cur_pos = 1;
       +        state.mode = SEARCHEDIT;
       +        state.bottom_text_shown = 1;
       +}
       +
       +static void
       +enter_searchedit_backward(void) {
       +        set_bottom_bar_text("?", -1);
       +        bottom_bar.line_cur_pos = 1;
       +        state.mode = SEARCHEDIT;
       +        state.bottom_text_shown = 1;
       +}
       +
       +/* FIXME: support visual mode, i.e. change selection to new place? */
       +static void
       +search_next(void) {
       +        /* FIXME: avoid this when line doesn't change */
       +        ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
       +        enum ledit_search_state ret = ledit_search_next(buffer, &buffer->cur_line, &buffer->cur_index);
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        if (ret != SEARCH_NORMAL)
       +                show_message(ledit_search_state_to_str(ret), -1);
       +}
       +
       +static void
       +search_prev(void) {
       +        /* FIXME: avoid this when line doesn't change */
       +        ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
       +        enum ledit_search_state ret = ledit_search_prev(buffer, &buffer->cur_line, &buffer->cur_index);
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        if (ret != SEARCH_NORMAL)
       +                show_message(ledit_search_state_to_str(ret), -1);
       +}
       +
       +static void
       +end_lineedit(void) {
       +        enum ledit_mode old_mode = state.mode;
       +        /* FIXME: go to last mode (visual or normal) */
       +        set_mode(NORMAL);
       +        state.bottom_text_shown = 0;
       +
       +        /* FIXME: forward/backward;  check for empty string;
       +           end edit mode when / or ? is deleted with backspace */
       +        if (old_mode == SEARCHEDIT) {
       +                /* FIXME: this is all so horrible */
       +                if (bottom_bar.line_text[0] == '/')
       +                        ledit_set_search_forward(bottom_bar.line_text + 1);
       +                else
       +                        ledit_set_search_backward(bottom_bar.line_text + 1);
       +                search_next();
       +        }
       +}
       +
       +/* FIXME: maybe sort these and use binary search */
        static struct key keys_en[] = {
                {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace},
                {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left},
       t@@ -1612,7 +1756,13 @@ static struct key keys_en[] = {
                {"v",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_visual},
                {"o",  0, 0, VISUAL, KEY_ANY, KEY_ANY, &switch_selection_end},
                {"c",  ControlMask, 0, INSERT|VISUAL, KEY_ANY, KEY_ANY, &clipcopy},
       -        {"v",  ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &clippaste}
       +        {"v",  ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &clippaste},
       +        {":",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_commandedit},
       +        {"?",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_backward},
       +        {"/",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forward},
       +        {NULL, 0, XK_Return, COMMANDEDIT|SEARCHEDIT, KEY_ANY, KEY_ANY, &end_lineedit},
       +        {"n",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &search_next},
       +        {"N",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &search_prev}
        };
        
        static struct key keys_ur[] = {
       t@@ -1720,7 +1870,9 @@ key_press(XEvent event) {
                                    (cur_keys->keys[i].modes & state.mode) &&
                                    (!e || (e->key & cur_keys->keys[i].prev_keys)) &&
                                     !strncmp(cur_keys->keys[i].text, buf, n) &&
       -                             match(cur_keys->keys[i].mods, key_state)) {
       +                             match(cur_keys->keys[i].mods, key_state & ~ShiftMask)) {
       +                                /* FIXME: seems a bit hacky to remove shift, but it
       +                                   is needed to make keys that use shift match */
                                        cur_keys->keys[i].func();
                                        found = 1;
                                }
       t@@ -1730,10 +1882,14 @@ key_press(XEvent event) {
                                cur_keys->keys[i].func();
                                found = 1;
                        }
       -                if (found)
       -                        break;
                }
       -        if (state.mode == INSERT && !found && n > 0) {
       +        if (found) {
       +                /* FIXME: only do this when necessary */
       +                ensure_cursor_shown();
       +                /* FIXME: this is a bit hacky */
       +                if (state.message_shown > 0)
       +                        state.message_shown--;
       +        } else if (state.mode == INSERT && !found && n > 0) {
                        delete_selection();
                        ledit_insert_text_with_newlines(
                            buffer,
       t@@ -1741,7 +1897,11 @@ key_press(XEvent event) {
                            buf, n,
                            &buffer->cur_line, &buffer->cur_index
                        );
       +                ensure_cursor_shown();
       +                if (state.message_shown > 0)
       +                        state.message_shown--;
       +        } else if (!found && (state.mode == COMMANDEDIT || state.mode == SEARCHEDIT) && n > 0) {
       +                insert_bottom_bar_text(buf, n);
       +                bottom_bar.line_cur_pos += n;
                }
       -        /* FIXME: only do this when necessary */
       -        ensure_cursor_shown();
        }
   DIR diff --git a/search.c b/search.c
       t@@ -0,0 +1,171 @@
       +#include <string.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include <pango/pangoxft.h>
       +#include <X11/extensions/Xdbe.h>
       +
       +#include "memory.h"
       +#include "common.h"
       +#include "buffer.h"
       +#include "search.h"
       +
       +/* FIXME: make sure only whole utf8 chars are matched */
       +/* FIXME: clean this up */
       +char *last_search = NULL;
       +enum {
       +        FORWARD,
       +        BACKWARD
       +} last_dir = FORWARD;
       +
       +void
       +ledit_set_search_forward(char *pattern) {
       +        last_dir = FORWARD;
       +        free(last_search);
       +        last_search = ledit_strdup(pattern);
       +}
       +
       +void
       +ledit_cleanup_search(void) {
       +        free(last_search);
       +}
       +
       +void
       +ledit_set_search_backward(char *pattern) {
       +        last_dir = BACKWARD;
       +        free(last_search);
       +        last_search = ledit_strdup(pattern);
       +}
       +
       +static enum ledit_search_state
       +search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) {
       +        *line_ret = buffer->cur_line;
       +        *byte_ret = buffer->cur_index;
       +        if (last_search == NULL)
       +                return SEARCH_NO_PATTERN;
       +        int line = buffer->cur_line;
       +        /* start one byte later so it doesn't get stuck on a match
       +           note: since the string ends with '\0', this is always valid */
       +        int byte = buffer->cur_index + 1;
       +        char *res;
       +        ledit_line *lline = ledit_get_line(buffer, line);
       +        if ((res = strstr(lline->text + byte, last_search)) != NULL) {
       +                *line_ret = line;
       +                *byte_ret = (int)(res - lline->text);
       +                return SEARCH_NORMAL;
       +        }
       +        for (int i = line + 1; i < buffer->lines_num; i++) {
       +                lline = ledit_get_line(buffer, i);
       +                if ((res = strstr(lline->text, last_search)) != NULL) {
       +                        *line_ret = i;
       +                        *byte_ret = (int)(res - lline->text);
       +                        return SEARCH_NORMAL;
       +                }
       +        }
       +        for (int i = 0; i < line; i++) {
       +                lline = ledit_get_line(buffer, i);
       +                if ((res = strstr(lline->text, last_search)) != NULL) {
       +                        *line_ret = i;
       +                        *byte_ret = (int)(res - lline->text);
       +                        return SEARCH_WRAPPED;
       +                }
       +        }
       +        lline = ledit_get_line(buffer, line);
       +        if ((res = strstr(lline->text, last_search)) != NULL) {
       +                *line_ret = line;
       +                *byte_ret = (int)(res - lline->text);
       +                return SEARCH_WRAPPED;
       +        }
       +        return SEARCH_NOT_FOUND;
       +}
       +
       +/* FIXME: this is insanely inefficient */
       +static enum ledit_search_state
       +search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) {
       +        *line_ret = buffer->cur_line;
       +        *byte_ret = buffer->cur_index;
       +        if (last_search == NULL)
       +                return SEARCH_NO_PATTERN;
       +        int line = buffer->cur_line;
       +        int byte = buffer->cur_index;
       +        ledit_line *lline = ledit_get_line(buffer, line);
       +        char *last = NULL, *res = lline->text;
       +        while ((res = strstr(res, last_search)) != NULL && res < lline->text + byte) {
       +                last = res;
       +                res++;
       +        }
       +        if (last != NULL) {
       +                *line_ret = line;
       +                /* FIXME: check if this is safe */
       +                *byte_ret = (int)(last - lline->text);
       +                return SEARCH_NORMAL;
       +        }
       +        for (int i = line - 1; i >= 0; i--) {
       +                lline = ledit_get_line(buffer, i);
       +                res = lline->text;
       +                while ((res = strstr(res, last_search)) != NULL) {
       +                        last = res;
       +                        res++;
       +                }
       +                if (last != NULL) {
       +                        *line_ret = i;
       +                        *byte_ret = (int)(last - lline->text);
       +                        return SEARCH_NORMAL;
       +                }
       +        }
       +        for (int i = buffer->lines_num - 1; i > line; i--) {
       +                lline = ledit_get_line(buffer, i);
       +                res = lline->text;
       +                while ((res = strstr(res, last_search)) != NULL) {
       +                        last = res;
       +                        res++;
       +                }
       +                if (last != NULL) {
       +                        *line_ret = i;
       +                        *byte_ret = (int)(last - lline->text);
       +                        return SEARCH_WRAPPED;
       +                }
       +        }
       +        lline = ledit_get_line(buffer, line);
       +        res = lline->text + byte;
       +        while ((res = strstr(res, last_search)) != NULL) {
       +                last = res;
       +                res++;
       +        }
       +        if (last != NULL) {
       +                *line_ret = line;
       +                *byte_ret = (int)(last - lline->text);
       +                return SEARCH_WRAPPED;
       +        }
       +        return SEARCH_NOT_FOUND;
       +}
       +
       +enum ledit_search_state
       +ledit_search_next(ledit_buffer *buffer, int *line_ret, int *byte_ret) {
       +        if (last_dir == FORWARD)
       +                return search_forward(buffer, line_ret, byte_ret);
       +        else
       +                return search_backward(buffer, line_ret, byte_ret);
       +}
       +
       +enum ledit_search_state
       +ledit_search_prev(ledit_buffer *buffer, int *line_ret, int *byte_ret) {
       +        if (last_dir == FORWARD)
       +                return search_backward(buffer, line_ret, byte_ret);
       +        else
       +                return search_forward(buffer, line_ret, byte_ret);
       +}
       +
       +char *
       +ledit_search_state_to_str(enum ledit_search_state state) {
       +        switch (state) {
       +                case SEARCH_WRAPPED:
       +                        return "Search wrapped";
       +                case SEARCH_NOT_FOUND:
       +                        return "Pattern not found";
       +                case SEARCH_NO_PATTERN:
       +                        return "No previous search pattern";
       +                default:
       +                        return "This message should not be shown. "
       +                               "Please bug lumidify about it.";
       +        }
       +}
   DIR diff --git a/search.h b/search.h
       t@@ -0,0 +1,13 @@
       +enum ledit_search_state {
       +        SEARCH_NORMAL,
       +        SEARCH_WRAPPED,
       +        SEARCH_NOT_FOUND,
       +        SEARCH_NO_PATTERN
       +};
       +
       +void ledit_cleanup_search(void);
       +void ledit_set_search_forward(char *pattern);
       +void ledit_set_search_backward(char *pattern);
       +enum ledit_search_state ledit_search_next(ledit_buffer *buffer, int *line_ret, int *byte_ret);
       +enum ledit_search_state ledit_search_prev(ledit_buffer *buffer, int *line_ret, int *byte_ret);
       +char *ledit_search_state_to_str(enum ledit_search_state state);