Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ If no file is given, the editor opens an empty, unnamed buffer.
| Tab / Shift-Tab | Insert tab / indent or dedent the selection |
| Ctrl-S | Save (prompts for a name if the file is new) |
| Ctrl-Q | Quit (press again to discard unsaved changes) |
| Ctrl-F | Find (Enter confirms, Ctrl-F finds next, Esc cancels) |
| Ctrl-F | Find: all matches are highlighted with a count in the prompt; Ctrl-F again jumps to the next match (wraps around), Enter confirms, Esc cancels |
| Ctrl-G | Jump to a line number (Enter jumps, Esc cancels) |
| Ctrl-Z / Ctrl-Y | Undo / redo |
| Ctrl-C / Ctrl-X | Copy / cut the selection (also pushed to the system clipboard) |
Expand Down
14 changes: 14 additions & 0 deletions src/editor.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ void editor_free(editor_t *ed)
free(ed->undo_stack);
free(ed->redo_stack);
free(ed->clipboard);
free(ed->search_matches);
buffer_free(&ed->buf);
}

Expand All @@ -239,6 +240,7 @@ void editor_open(editor_t *ed, const char *filename)
ed->sel.active = false;
clear_edit_stack(&ed->undo_stack, &ed->undo_count);
clear_edit_stack(&ed->redo_stack, &ed->redo_count);
editor_clear_search_matches(ed);
}

void editor_update_screen_size(editor_t *ed, int screen_rows, int screen_cols)
Expand Down Expand Up @@ -323,6 +325,18 @@ void editor_clear_selection(editor_t *ed)
ed->sel.active = false;
}

void editor_set_search_matches(editor_t *ed, search_match_t *matches, size_t count)
{
free(ed->search_matches);
ed->search_matches = matches;
ed->search_match_count = count;
}

void editor_clear_search_matches(editor_t *ed)
{
editor_set_search_matches(ed, NULL, 0);
}

static void begin_or_extend_selection(editor_t *ed, bool extend)
{
if (extend) {
Expand Down
6 changes: 6 additions & 0 deletions src/editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <stddef.h>

#include "buffer.h"
#include "search.h"

typedef enum {
MODE_EDIT,
Expand Down Expand Up @@ -51,6 +52,8 @@ typedef struct {
size_t prompt_len;
size_t search_start_cy;
size_t search_start_cx;
search_match_t *search_matches;
size_t search_match_count;

char status_msg[256];
const char *filetype;
Expand Down Expand Up @@ -87,6 +90,9 @@ bool editor_has_selection(const editor_t *ed);
void editor_selection_range(const editor_t *ed, size_t *r1, size_t *c1, size_t *r2, size_t *c2);
void editor_clear_selection(editor_t *ed);

void editor_set_search_matches(editor_t *ed, search_match_t *matches, size_t count);
void editor_clear_search_matches(editor_t *ed);

void editor_move_left(editor_t *ed, bool extend);
void editor_move_right(editor_t *ed, bool extend);
void editor_move_up(editor_t *ed, bool extend);
Expand Down
8 changes: 8 additions & 0 deletions src/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,28 @@ static void do_incremental_search(editor_t *ed)
if (ed->prompt_len == 0) {
ed->cy = ed->search_start_cy;
ed->cx = ed->search_start_cx;
editor_clear_search_matches(ed);
return;
}

size_t row, col;
if (search_find_forward(&ed->buf, ed->search_start_cy, ed->search_start_cx, ed->prompt_input,
&row, &col)) {
ed->cy = row;
ed->cx = col;
}

size_t count;
search_match_t *matches = search_find_all(&ed->buf, ed->prompt_input, &count);
editor_set_search_matches(ed, matches, count);
}

static void confirm_prompt(editor_t *ed)
{
switch (ed->mode) {
case MODE_PROMPT_FIND:
ed->mode = MODE_EDIT;
editor_clear_search_matches(ed);
editor_set_status(ed, "Ln %zu, Col %zu", ed->cy + 1, ed->cx + 1);
break;

Expand Down Expand Up @@ -133,6 +140,7 @@ static void process_prompt_key(editor_t *ed, key_event_t key)
if (ed->mode == MODE_PROMPT_FIND) {
ed->cy = ed->search_start_cy;
ed->cx = ed->search_start_cx;
editor_clear_search_matches(ed);
}
ed->mode = MODE_EDIT;
editor_set_status(ed, "Cancelled");
Expand Down
28 changes: 28 additions & 0 deletions src/render.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ static void render_empty_gutter(abuf_t *ab, const editor_t *ed)
abuf_append_str(ab, "~");
}

static bool byte_in_search_match(const editor_t *ed, size_t row, size_t byte_col)
{
for (size_t i = 0; i < ed->search_match_count; i++) {
const search_match_t *m = &ed->search_matches[i];
if (m->row == row && byte_col >= m->col && byte_col < m->col + ed->prompt_len) {
return true;
}
}
return false;
}

static void render_text_row(abuf_t *ab, const editor_t *ed, size_t file_row)
{
const line_t *line = &ed->buf.lines[file_row];
Expand All @@ -118,19 +129,26 @@ static void render_text_row(abuf_t *ab, const editor_t *ed, size_t file_row)
hl_end = (file_row == r2) ? c2 : line->len;
}
}
bool show_matches = ed->mode == MODE_PROMPT_FIND && ed->search_match_count > 0;

size_t i = 0;
size_t rx = 0;
int emitted = 0;
bool hl_active = false;
bool match_active = false;

while (i < line->len && emitted < text_cols) {
unsigned char c = (unsigned char)line->chars[i];
bool should_hl = row_has_hl && i >= hl_start && i < hl_end;
bool should_match = show_matches && byte_in_search_match(ed, file_row, i);
if (should_hl != hl_active) {
abuf_append_str(ab, should_hl ? "\x1b[7m" : "\x1b[27m");
hl_active = should_hl;
}
if (should_match != match_active) {
abuf_append_str(ab, should_match ? "\x1b[4m" : "\x1b[24m");
match_active = should_match;
}

if (c == '\t') {
size_t width = DAF_TAB_STOP - (rx % DAF_TAB_STOP);
Expand Down Expand Up @@ -159,6 +177,9 @@ static void render_text_row(abuf_t *ab, const editor_t *ed, size_t file_row)
if (hl_active) {
abuf_append_str(ab, "\x1b[27m");
}
if (match_active) {
abuf_append_str(ab, "\x1b[24m");
}
}

static void render_status_bar(abuf_t *ab, const editor_t *ed)
Expand Down Expand Up @@ -204,6 +225,13 @@ static void render_message_bar(abuf_t *ab, const editor_t *ed)
if (ed->mode != MODE_EDIT) {
abuf_append_str(ab, prompt_label(ed->mode));
abuf_append(ab, ed->prompt_input, ed->prompt_len);
if (ed->mode == MODE_PROMPT_FIND && ed->prompt_len > 0) {
char count_buf[32];
int n = snprintf(count_buf, sizeof(count_buf), " (%zu found)", ed->search_match_count);
if (n > 0) {
abuf_append(ab, count_buf, (size_t)n);
}
}
} else {
size_t len = strlen(ed->status_msg);
size_t max_len = ed->screen_cols > 0 ? (size_t)ed->screen_cols : 0;
Expand Down
35 changes: 35 additions & 0 deletions src/search.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <string.h>

#include "common.h"

static long find_substring(const char *hay, size_t hay_len, const char *needle, size_t needle_len,
size_t from)
{
Expand Down Expand Up @@ -54,3 +56,36 @@ bool search_find_forward(const buffer_t *buf, size_t start_row, size_t start_col

return false;
}

search_match_t *search_find_all(const buffer_t *buf, const char *query, size_t *out_count)
{
size_t query_len = strlen(query);
search_match_t *matches = NULL;
size_t count = 0;
size_t cap = 0;

if (query_len > 0) {
for (size_t row = 0; row < buf->num_lines; row++) {
const line_t *line = &buf->lines[row];
size_t from = 0;
for (;;) {
long pos = find_substring(line->chars, line->len, query, query_len, from);
if (pos < 0) {
break;
}
if (count == cap) {
size_t new_cap = cap == 0 ? 16 : cap * 2;
matches = xrealloc(matches, new_cap * sizeof(search_match_t));
cap = new_cap;
}
matches[count].row = row;
matches[count].col = (size_t)pos;
count++;
from = (size_t)pos + query_len;
}
}
}

*out_count = count;
return matches;
}
7 changes: 7 additions & 0 deletions src/search.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@

#include "buffer.h"

typedef struct {
size_t row;
size_t col;
} search_match_t;

bool search_find_forward(const buffer_t *buf, size_t start_row, size_t start_col,
const char *query, size_t *out_row, size_t *out_col);

search_match_t *search_find_all(const buffer_t *buf, const char *query, size_t *out_count);

#endif /* DAF_SEARCH_H */
52 changes: 51 additions & 1 deletion tests/test_search.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
*/

#include <assert.h>
#include <stdlib.h>

#include "buffer.h"
#include "search.h"

int main(void)
static void test_find_forward(void)
{
buffer_t buf;
buffer_init(&buf);
Expand All @@ -31,5 +32,54 @@ int main(void)
assert(!search_find_forward(&buf, 0, 0, "", &row, &col));

buffer_free(&buf);
}

static void test_find_all(void)
{
buffer_t buf;
buffer_init(&buf);
buffer_insert_bytes(&buf, 0, 0, "foo bar", 7);
buffer_split_line(&buf, 0, 7);
buffer_insert_bytes(&buf, 1, 0, "baz foo", 7);

size_t count = 0;
search_match_t *matches = search_find_all(&buf, "foo", &count);
assert(count == 2);
assert(matches[0].row == 0 && matches[0].col == 0);
assert(matches[1].row == 1 && matches[1].col == 4);
free(matches);

matches = search_find_all(&buf, "missing", &count);
assert(count == 0);
assert(matches == NULL);

matches = search_find_all(&buf, "", &count);
assert(count == 0);
assert(matches == NULL);

buffer_free(&buf);
}

static void test_find_all_non_overlapping(void)
{
buffer_t buf;
buffer_init(&buf);
buffer_insert_bytes(&buf, 0, 0, "aaaa", 4);

size_t count = 0;
search_match_t *matches = search_find_all(&buf, "aa", &count);
assert(count == 2);
assert(matches[0].col == 0);
assert(matches[1].col == 2);
free(matches);

buffer_free(&buf);
}

int main(void)
{
test_find_forward();
test_find_all();
test_find_all_non_overlapping();
return 0;
}
Loading