summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--src/dged/buffer.c197
-rw-r--r--src/dged/buffer.h44
-rw-r--r--src/dged/display.c133
-rw-r--r--src/dged/minibuffer.c19
-rw-r--r--src/dged/minibuffer.h4
-rw-r--r--src/dged/path.h4
-rw-r--r--src/dged/window.c73
-rw-r--r--src/dged/window.h8
-rw-r--r--src/main/cmds.c403
-rw-r--r--src/main/search-replace.c365
-rw-r--r--src/main/search-replace.h14
12 files changed, 1010 insertions, 259 deletions
diff --git a/Makefile b/Makefile
index 28c2286..b094db3 100644
--- a/Makefile
+++ b/Makefile
@@ -11,14 +11,15 @@ HEADERS = src/dged/settings.h src/dged/minibuffer.h src/dged/keyboard.h src/dged
src/dged/buffers.h src/dged/text.h src/dged/display.h src/dged/hashmap.h src/dged/path.h \
src/dged/buffer.h src/dged/btree.h src/dged/command.h src/dged/allocator.h src/dged/reactor.h \
src/dged/vec.h src/dged/window.h src/dged/hash.h src/dged/undo.h src/dged/lang.h \
- src/dged/settings-parse.h src/dged/utf8.h src/main/cmds.h src/main/bindings.h
+ src/dged/settings-parse.h src/dged/utf8.h src/main/cmds.h src/main/bindings.h \
+ src/main/search-replace.h
SOURCES = src/dged/binding.c src/dged/buffer.c src/dged/command.c src/dged/display.c \
src/dged/keyboard.c src/dged/minibuffer.c src/dged/text.c \
src/dged/utf8.c src/dged/buffers.c src/dged/window.c src/dged/allocator.c src/dged/undo.c \
src/dged/settings.c src/dged/lang.c src/dged/settings-parse.c
-MAIN_SOURCES = src/main/main.c src/main/cmds.c src/main/bindings.c
+MAIN_SOURCES = src/main/main.c src/main/cmds.c src/main/bindings.c src/main/search-replace.c
TEST_SOURCES = test/assert.c test/buffer.c test/text.c test/utf8.c test/main.c \
test/command.c test/keyboard.c test/fake-reactor.c test/allocator.c \
diff --git a/src/dged/buffer.c b/src/dged/buffer.c
index 39a1422..c826401 100644
--- a/src/dged/buffer.c
+++ b/src/dged/buffer.c
@@ -126,10 +126,13 @@ struct buffer create_internal(char *name, char *filename) {
.text = text_create(10),
.modified = false,
.readonly = false,
- .lang = lang_from_id("fnd"),
+ .lang =
+ filename != NULL ? lang_from_filename(filename) : lang_from_id("fnd"),
.last_write = {0},
};
+ VEC_INIT(&b.text_properties, 32);
+
undo_init(&b.undo, 100);
return b;
@@ -147,6 +150,7 @@ struct buffer buffer_create(char *name) {
}
void buffer_destroy(struct buffer *buffer) {
+ VEC_DESTROY(&buffer->text_properties);
text_destroy(buffer->text);
buffer->text = NULL;
@@ -162,6 +166,7 @@ void buffer_destroy(struct buffer *buffer) {
void buffer_clear(struct buffer_view *view) {
text_clear(view->buffer->text);
view->dot.col = view->dot.line = 0;
+ view->scroll.col = view->scroll.line = 0;
}
void buffer_static_init() {
@@ -512,7 +517,7 @@ void buffer_beginning_of_line(struct buffer_view *view) { view->dot.col = 0; }
void buffer_read_from_file(struct buffer *b) {
struct stat sb;
- char *fullname = expanduser(b->filename);
+ char *fullname = to_abspath(b->filename);
if (stat(fullname, &sb) == 0) {
FILE *file = fopen(fullname, "r");
free(fullname);
@@ -546,7 +551,6 @@ void buffer_read_from_file(struct buffer *b) {
return;
}
- b->lang = lang_from_filename(b->filename);
undo_push_boundary(&b->undo, (struct undo_boundary){.save_point = true});
}
@@ -643,7 +647,7 @@ void search_line(struct text_chunk *chunk, void *userdata) {
uint32_t pattern_nchars = utf8_nchars((uint8_t *)data->pattern, pattern_len);
char *line = malloc(chunk->nbytes + 1);
- memcpy(line, chunk->text, chunk->nbytes);
+ strncpy(line, chunk->text, chunk->nbytes);
line[chunk->nbytes] = '\0';
char *hit = NULL;
uint32_t byteidx = 0;
@@ -652,7 +656,7 @@ void search_line(struct text_chunk *chunk, void *userdata) {
uint32_t begin = utf8_nchars(chunk->text, byteidx);
struct match match = (struct match){
.begin = {.col = begin, .line = chunk->line},
- .end = {.col = begin + pattern_nchars, .line = chunk->line},
+ .end = {.col = begin + pattern_nchars - 1, .line = chunk->line},
};
VEC_PUSH(&data->matches, match);
@@ -660,6 +664,8 @@ void search_line(struct text_chunk *chunk, void *userdata) {
// proceed to after match
byteidx += pattern_len;
}
+
+ free(line);
}
void buffer_find(struct buffer *buffer, const char *pattern,
@@ -832,6 +838,8 @@ struct cmdbuf {
struct line_render_hook *line_render_hooks;
uint32_t nlinerender_hooks;
+
+ struct buffer *buffer;
};
static uint32_t visual_char_width(uint8_t *byte, uint32_t maxlen) {
@@ -855,41 +863,6 @@ static uint32_t visual_string_width(uint8_t *txt, uint32_t len,
return width;
}
-static bool is_in_mark(struct buffer_location mark_begin,
- struct buffer_location mark_end,
- struct buffer_location pos) {
- if (mark_begin.line == mark_end.line && mark_begin.col == mark_end.col) {
- return false;
- }
-
- if (pos.line >= mark_begin.line && pos.line <= mark_end.line) {
- if (pos.line == mark_end.line && pos.col <= mark_end.col &&
- pos.line == mark_begin.line && pos.col >= mark_begin.col) {
- // only one line is marked
- return true;
- } else if (pos.line == mark_begin.line && pos.line != mark_end.line &&
- pos.col >= mark_begin.col) {
- // we are on the first line marked
- return true;
- } else if (pos.line == mark_end.line && pos.line != mark_begin.line &&
- pos.col <= mark_end.col) {
- // we are on the last line marked
- return true;
- } else if (pos.line != mark_begin.line && pos.line != mark_end.line) {
- // we are on fully marked lines
- return true;
- }
- }
-
- return false;
-}
-
-// TODO: temporary
-enum property {
- Prop_None,
- Prop_Marked,
-};
-
void render_line(struct text_chunk *line, void *userdata) {
struct cmdbuf *cmdbuf = (struct cmdbuf *)userdata;
uint32_t visual_line = line->line - cmdbuf->scroll.line + cmdbuf->line_offset;
@@ -913,7 +886,9 @@ void render_line(struct text_chunk *line, void *userdata) {
uint32_t visual_col_start = cmdbuf->left_margin;
uint32_t cur_visual_col = visual_col_start;
uint32_t start_byte = 0, text_nbytes = 0;
- enum property curprop = Prop_None;
+ struct text_property *properties[16] = {0};
+ struct text_property *prev_properties[16] = {0};
+ uint32_t prev_nproperties;
for (uint32_t cur_byte = start_byte, coli = 0;
cur_byte < text_nbytes_scroll && cur_visual_col < cmdbuf->width &&
coli < line->nchars - cmdbuf->scroll.col;
@@ -924,28 +899,56 @@ void render_line(struct text_chunk *line, void *userdata) {
uint32_t char_vwidth = visual_char_width(text + cur_byte, bytes_remaining);
// calculate character properties
- enum property prevprop = curprop;
- curprop = cmdbuf->mark_set &&
- is_in_mark(*begin, *end,
- (struct buffer_location){.col = coli,
- .line = line->line})
- ? Prop_Marked
- : Prop_None;
+ uint32_t nproperties = 0;
+ buffer_get_text_properties(
+ cmdbuf->buffer,
+ (struct buffer_location){.line = line->line, .col = coli}, properties,
+ 16, &nproperties);
// handle changes to properties
- if (curprop != prevprop) {
+ uint32_t nnew_props = 0;
+ struct text_property *new_props[16] = {0};
+ for (uint32_t propi = 0; propi < nproperties; ++propi) {
+ if (propi >= prev_nproperties ||
+ prev_properties[propi] != properties[propi]) {
+ new_props[nnew_props] = properties[propi];
+ ++nnew_props;
+ }
+ }
+
+ // if we have any new or lost props, flush text up until now
+ if (nnew_props > 0 || nproperties < prev_nproperties) {
command_list_draw_text(cmdbuf->cmds, visual_col_start, visual_line,
text + start_byte, cur_byte - start_byte);
visual_col_start = cur_visual_col;
start_byte = cur_byte;
}
- if (curprop == Prop_Marked && prevprop == Prop_None) {
- command_list_set_index_color_bg(cmdbuf->cmds, 5);
- } else if (curprop == Prop_None && curprop != prevprop) {
+ // apply new properties
+ for (uint32_t propi = 0; propi < nnew_props; ++propi) {
+ struct text_property *prop = new_props[propi];
+ switch (prop->type) {
+ case TextProperty_Colors:
+ struct text_property_colors *colors = &prop->colors;
+ if (colors->set_bg) {
+ command_list_set_index_color_bg(cmdbuf->cmds, colors->bg);
+ }
+
+ if (colors->set_fg) {
+ command_list_set_index_color_fg(cmdbuf->cmds, colors->fg);
+ }
+ break;
+ }
+ }
+
+ if (nproperties == 0 && prev_nproperties > 0) {
command_list_reset_color(cmdbuf->cmds);
}
+ memcpy(prev_properties, properties,
+ nproperties * sizeof(struct text_property *));
+ prev_nproperties = nproperties;
+
cur_byte += char_nbytes;
text_nbytes += char_nbytes;
cur_visual_col += char_vwidth;
@@ -1044,9 +1047,9 @@ void linenum_render_hook(struct text_chunk *line_data, uint32_t line,
struct command_list *commands, void *userdata) {
struct linenumdata *data = (struct linenumdata *)userdata;
static char buf[16];
- command_list_set_index_color_bg(commands, 236);
- command_list_set_index_color_fg(
- commands, line_data->line == data->dot_line ? 253 : 244);
+ command_list_set_index_color_bg(commands, 8);
+ command_list_set_index_color_fg(commands,
+ line_data->line == data->dot_line ? 15 : 7);
uint32_t chars =
snprintf(buf, 16, "%*d", data->longest_nchars + 1, line_data->line + 1);
command_list_draw_text_copy(commands, 0, line, (uint8_t *)buf, chars);
@@ -1157,6 +1160,20 @@ void buffer_update(struct buffer_view *view, uint32_t window_id, uint32_t width,
struct setting *show_ws = settings_get("editor.show-whitespace");
+ if (buffer_region_has_size(view)) {
+ struct region reg = to_region(view->dot, view->mark);
+ buffer_add_text_property(view->buffer, reg.begin, reg.end,
+ (struct text_property){
+ .type = TextProperty_Colors,
+ .colors =
+ (struct text_property_colors){
+ .set_bg = true,
+ .bg = 5,
+ .set_fg = false,
+ },
+ });
+ }
+
struct cmdbuf cmdbuf = (struct cmdbuf){
.cmds = commands,
.scroll = view->scroll,
@@ -1168,6 +1185,7 @@ void buffer_update(struct buffer_view *view, uint32_t window_id, uint32_t width,
.mark_set = view->mark_set,
.region = to_region(view->dot, view->mark),
.show_ws = show_ws != NULL ? show_ws->value.bool_value : true,
+ .buffer = view->buffer,
};
text_for_each_line(view->buffer->text, view->scroll.line, height, render_line,
&cmdbuf);
@@ -1179,7 +1197,9 @@ void buffer_update(struct buffer_view *view, uint32_t window_id, uint32_t width,
for (uint32_t hooki = 0; hooki < nlinehooks; ++hooki) {
struct line_render_hook *hook = &line_hooks[hooki];
- hook->empty_callback(linei, commands, hook->userdata);
+ if (hook->empty_callback != NULL) {
+ hook->empty_callback(linei, commands, hook->userdata);
+ }
}
command_list_draw_repeated(commands, total_margins.left, linei, ' ',
@@ -1189,13 +1209,14 @@ void buffer_update(struct buffer_view *view, uint32_t window_id, uint32_t width,
// update the visual cursor position
to_relative(view, view->dot.line, view->dot.col, &rel_line, &rel_col);
uint32_t visual_col = visual_dot_col(view, view->dot.col);
+
// TODO: fix this shit, should not need to add scroll_col back here
// only to subtract it in the function
to_relative(view, view->dot.line, visual_col + view->scroll.col, &rel_line,
&rel_col);
- *relline = rel_line < 0 ? 0 : (uint32_t)rel_line + total_margins.top;
- *relcol = rel_col < 0 ? 0 : (uint32_t)rel_col + total_margins.left;
+ *relline = (rel_line < 0 ? 0 : (uint32_t)rel_line) + total_margins.top;
+ *relcol = (rel_col < 0 ? 0 : (uint32_t)rel_col) + total_margins.left;
}
struct text_chunk buffer_get_line(struct buffer *buffer, uint32_t line) {
@@ -1206,7 +1227,67 @@ void buffer_view_scroll_down(struct buffer_view *view, uint32_t height) {
buffer_goto(view, view->dot.line + height, view->dot.col);
scroll(view, height, 0);
}
+
void buffer_view_scroll_up(struct buffer_view *view, uint32_t height) {
buffer_goto(view, view->dot.line - height, view->dot.col);
scroll(view, -height, 0);
}
+
+void buffer_clear_text_properties(struct buffer *buffer) {
+ VEC_CLEAR(&buffer->text_properties);
+}
+
+void buffer_add_text_property(struct buffer *buffer,
+ struct buffer_location start,
+ struct buffer_location end,
+ struct text_property property) {
+ struct text_property_entry entry = {
+ .start = start,
+ .end = end,
+ .property = property,
+ };
+ VEC_PUSH(&buffer->text_properties, entry);
+}
+
+bool buffer_location_is_between(struct buffer_location location,
+ struct buffer_location start,
+ struct buffer_location end) {
+ if (location.line >= start.line && location.line <= end.line) {
+ if (location.line == end.line && location.col <= end.col &&
+ location.line == start.line && location.col >= start.col) {
+ // only one line
+ return true;
+ } else if (location.line == start.line && location.line != end.line &&
+ location.col >= start.col) {
+ // we are on the first line
+ return true;
+ } else if (location.line == end.line && location.line != start.line &&
+ location.col <= end.col) {
+ // we are on the last line
+ return true;
+ } else if (location.line != end.line && location.line != start.line) {
+ // we are on lines in between
+ return true;
+ }
+ }
+ return false;
+}
+
+void buffer_get_text_properties(struct buffer *buffer,
+ struct buffer_location location,
+ struct text_property **properties,
+ uint32_t max_nproperties,
+ uint32_t *nproperties) {
+ uint32_t nres = 0;
+ VEC_FOR_EACH(&buffer->text_properties, struct text_property_entry * prop) {
+ if (buffer_location_is_between(location, prop->start, prop->end)) {
+ properties[nres] = &prop->property;
+ ++nres;
+
+ if (nres == max_nproperties) {
+ break;
+ }
+ }
+ }
+ *nproperties = nres;
+}
diff --git a/src/dged/buffer.h b/src/dged/buffer.h
index dad6ef1..28d9797 100644
--- a/src/dged/buffer.h
+++ b/src/dged/buffer.h
@@ -3,7 +3,6 @@
#include <stdio.h>
#include <time.h>
-#include "bits/stdint-uintn.h"
#include "command.h"
#include "lang.h"
#include "text.h"
@@ -13,6 +12,24 @@
struct keymap;
struct command_list;
+enum text_property_type {
+ TextProperty_Colors,
+};
+
+struct text_property_colors {
+ bool set_fg;
+ uint32_t fg;
+ bool set_bg;
+ uint32_t bg;
+};
+
+struct text_property {
+ enum text_property_type type;
+ union {
+ struct text_property_colors colors;
+ };
+};
+
/**
* Margins where buffer text should not be
*/
@@ -90,6 +107,10 @@ struct buffer_location {
uint32_t col;
};
+bool buffer_location_is_between(struct buffer_location location,
+ struct buffer_location start,
+ struct buffer_location end);
+
struct match {
struct buffer_location begin;
struct buffer_location end;
@@ -125,6 +146,12 @@ void buffer_view_scroll_up(struct buffer_view *view, uint32_t height);
void buffer_view_destroy(struct buffer_view *view);
+struct text_property_entry {
+ struct buffer_location start;
+ struct buffer_location end;
+ struct text_property property;
+};
+
/**
* A buffer of text that can be modified, read from and written to disk.
*
@@ -158,6 +185,8 @@ struct buffer {
/** Buffer programming language */
struct language lang;
+
+ VEC(struct text_property_entry) text_properties;
};
struct buffer buffer_create(char *name);
@@ -209,6 +238,19 @@ void buffer_paste(struct buffer_view *view);
void buffer_paste_older(struct buffer_view *view);
void buffer_cut(struct buffer_view *view);
+void buffer_clear_text_properties(struct buffer *buffer);
+
+void buffer_add_text_property(struct buffer *buffer,
+ struct buffer_location start,
+ struct buffer_location end,
+ struct text_property property);
+
+void buffer_get_text_properties(struct buffer *buffer,
+ struct buffer_location location,
+ struct text_property **properties,
+ uint32_t max_nproperties,
+ uint32_t *nproperties);
+
struct text_chunk buffer_get_line(struct buffer *buffer, uint32_t line);
uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook,
diff --git a/src/dged/display.c b/src/dged/display.c
index d9eeb11..cf2e5d5 100644
--- a/src/dged/display.c
+++ b/src/dged/display.c
@@ -3,6 +3,7 @@
#include "buffer.h"
+#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -73,6 +74,8 @@ struct command_list {
void *(*allocator)(size_t);
char name[16];
+
+ struct command_list *next_list;
};
struct winsize getsize() {
@@ -169,19 +172,21 @@ void display_clear(struct display *display) {
putbytes(bytes, 3, false);
}
-struct command_list *command_list_create(uint32_t capacity,
+struct command_list *command_list_create(uint32_t initial_capacity,
void *(*allocator)(size_t),
uint32_t xoffset, uint32_t yoffset,
const char *name) {
struct command_list *command_list = allocator(sizeof(struct command_list));
- command_list->capacity = capacity;
+ command_list->capacity = initial_capacity;
command_list->ncmds = 0;
command_list->xoffset = xoffset;
command_list->yoffset = yoffset;
+ command_list->next_list = NULL;
strncpy(command_list->name, name, 15);
- command_list->cmds = allocator(sizeof(struct render_command) * capacity);
+ command_list->cmds =
+ allocator(sizeof(struct render_command) * initial_capacity);
command_list->allocator = allocator;
return command_list;
@@ -189,33 +194,41 @@ struct command_list *command_list_create(uint32_t capacity,
struct render_command *add_command(struct command_list *list,
enum render_cmd_type tp) {
- if (list->ncmds == list->capacity) {
- /* TODO: better. Currently a bit tricky to provide dynamic scaling of this
- * since it is initially allocated with the frame allocator that does not
- * support realloc.
- */
- return NULL;
+ struct command_list *l = list;
+ struct command_list *n = l->next_list;
+
+ // scan through lists for one with capacity
+ while (l->ncmds == l->capacity && n != NULL) {
+ l = n;
+ n = l->next_list;
+ }
+
+ if (l->ncmds == l->capacity && n == NULL) {
+ l->next_list = command_list_create(l->capacity, l->allocator, l->xoffset,
+ l->yoffset, l->name);
+ l = l->next_list;
}
- struct render_command *cmd = &list->cmds[list->ncmds];
+ struct render_command *cmd = &l->cmds[l->ncmds];
cmd->type = tp;
switch (tp) {
case RenderCommand_DrawText:
- cmd->draw_txt = list->allocator(sizeof(struct draw_text_cmd));
+ cmd->draw_txt = l->allocator(sizeof(struct draw_text_cmd));
break;
case RenderCommand_Repeat:
- cmd->repeat = list->allocator(sizeof(struct repeat_cmd));
+ cmd->repeat = l->allocator(sizeof(struct repeat_cmd));
break;
case RenderCommand_PushFormat:
- cmd->push_fmt = list->allocator(sizeof(struct push_fmt_cmd));
+ cmd->push_fmt = l->allocator(sizeof(struct push_fmt_cmd));
break;
case RenderCommand_SetShowWhitespace:
- cmd->show_ws = list->allocator(sizeof(struct show_ws_cmd));
+ cmd->show_ws = l->allocator(sizeof(struct show_ws_cmd));
break;
case RenderCommand_ClearFormat:
break;
}
- ++list->ncmds;
+
+ ++l->ncmds;
return cmd;
}
@@ -306,58 +319,62 @@ void display_render(struct display *display,
uint32_t fmt_stack_len = 3;
bool show_whitespace_state = false;
- for (uint64_t cmdi = 0; cmdi < cl->ncmds; ++cmdi) {
- struct render_command *cmd = &cl->cmds[cmdi];
- switch (cmd->type) {
- case RenderCommand_DrawText: {
- struct draw_text_cmd *txt_cmd = cmd->draw_txt;
- display_move_cursor(display, txt_cmd->row + cl->yoffset,
- txt_cmd->col + cl->xoffset);
- putbytes(fmt_stack, fmt_stack_len, false);
- putbyte('m');
- putbytes(txt_cmd->data, txt_cmd->len, show_whitespace_state);
- break;
- }
+ while (cl != NULL) {
+
+ for (uint64_t cmdi = 0; cmdi < cl->ncmds; ++cmdi) {
+ struct render_command *cmd = &cl->cmds[cmdi];
+ switch (cmd->type) {
+ case RenderCommand_DrawText: {
+ struct draw_text_cmd *txt_cmd = cmd->draw_txt;
+ display_move_cursor(display, txt_cmd->row + cl->yoffset,
+ txt_cmd->col + cl->xoffset);
+ putbytes(fmt_stack, fmt_stack_len, false);
+ putbyte('m');
+ putbytes(txt_cmd->data, txt_cmd->len, show_whitespace_state);
+ break;
+ }
- case RenderCommand_Repeat: {
- struct repeat_cmd *repeat_cmd = cmd->repeat;
- display_move_cursor(display, repeat_cmd->row + cl->yoffset,
- repeat_cmd->col + cl->xoffset);
- putbytes(fmt_stack, fmt_stack_len, false);
- putbyte('m');
- if (show_whitespace_state) {
- for (uint32_t i = 0; i < repeat_cmd->nrepeat; ++i) {
- putbyte_ws(repeat_cmd->c, show_whitespace_state);
+ case RenderCommand_Repeat: {
+ struct repeat_cmd *repeat_cmd = cmd->repeat;
+ display_move_cursor(display, repeat_cmd->row + cl->yoffset,
+ repeat_cmd->col + cl->xoffset);
+ putbytes(fmt_stack, fmt_stack_len, false);
+ putbyte('m');
+ if (show_whitespace_state) {
+ for (uint32_t i = 0; i < repeat_cmd->nrepeat; ++i) {
+ putbyte_ws(repeat_cmd->c, show_whitespace_state);
+ }
+ } else {
+ char *buf = malloc(repeat_cmd->nrepeat + 1);
+ memset(buf, repeat_cmd->c, repeat_cmd->nrepeat);
+ buf[repeat_cmd->nrepeat] = '\0';
+ fputs(buf, stdout);
+ free(buf);
}
- } else {
- char *buf = malloc(repeat_cmd->nrepeat + 1);
- memset(buf, repeat_cmd->c, repeat_cmd->nrepeat);
- buf[repeat_cmd->nrepeat] = '\0';
- fputs(buf, stdout);
- free(buf);
+ break;
}
- break;
- }
- case RenderCommand_PushFormat: {
- struct push_fmt_cmd *fmt_cmd = cmd->push_fmt;
+ case RenderCommand_PushFormat: {
+ struct push_fmt_cmd *fmt_cmd = cmd->push_fmt;
- fmt_stack[fmt_stack_len] = ';';
- ++fmt_stack_len;
+ fmt_stack[fmt_stack_len] = ';';
+ ++fmt_stack_len;
- memcpy(fmt_stack + fmt_stack_len, fmt_cmd->fmt, fmt_cmd->len);
- fmt_stack_len += fmt_cmd->len;
- break;
- }
+ memcpy(fmt_stack + fmt_stack_len, fmt_cmd->fmt, fmt_cmd->len);
+ fmt_stack_len += fmt_cmd->len;
+ break;
+ }
- case RenderCommand_ClearFormat:
- fmt_stack_len = 3;
- break;
+ case RenderCommand_ClearFormat:
+ fmt_stack_len = 3;
+ break;
- case RenderCommand_SetShowWhitespace:
- show_whitespace_state = cmd->show_ws->show;
- break;
+ case RenderCommand_SetShowWhitespace:
+ show_whitespace_state = cmd->show_ws->show;
+ break;
+ }
}
+ cl = cl->next_list;
}
}
diff --git a/src/dged/minibuffer.c b/src/dged/minibuffer.c
index 0ff32a8..3c5a291 100644
--- a/src/dged/minibuffer.c
+++ b/src/dged/minibuffer.c
@@ -18,7 +18,8 @@ static struct minibuffer {
bool prompt_active;
bool clear;
- void (*update_callback)();
+ void (*update_callback)(void *);
+ void *update_callback_userdata;
} g_minibuffer = {0};
@@ -88,7 +89,7 @@ struct update_hook_result update(struct buffer_view *view,
}
if (mb->update_callback != NULL) {
- mb->update_callback();
+ mb->update_callback(mb->update_callback_userdata);
}
return res;
@@ -153,7 +154,8 @@ void minibuffer_set_prompt_internal(const char *fmt, va_list args) {
}
int32_t minibuffer_prompt_internal(struct command_ctx command_ctx,
- void (*update_callback)(), const char *fmt,
+ void (*update_callback)(void *),
+ void *userdata, const char *fmt,
va_list args) {
if (g_minibuffer.buffer == NULL) {
return 1;
@@ -169,6 +171,7 @@ int32_t minibuffer_prompt_internal(struct command_ctx command_ctx,
if (update_callback != NULL) {
g_minibuffer.update_callback = update_callback;
+ g_minibuffer.update_callback_userdata = userdata;
}
return 0;
@@ -178,19 +181,19 @@ int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt,
...) {
va_list args;
va_start(args, fmt);
- int32_t r = minibuffer_prompt_internal(command_ctx, NULL, fmt, args);
+ int32_t r = minibuffer_prompt_internal(command_ctx, NULL, NULL, fmt, args);
va_end(args);
return r;
}
int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx,
- void (*update_callback)(),
- const char *fmt, ...) {
+ void (*update_callback)(void *),
+ void *userdata, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
- int32_t r =
- minibuffer_prompt_internal(command_ctx, update_callback, fmt, args);
+ int32_t r = minibuffer_prompt_internal(command_ctx, update_callback, userdata,
+ fmt, args);
va_end(args);
return r;
diff --git a/src/dged/minibuffer.h b/src/dged/minibuffer.h
index 24f54cf..98a4db8 100644
--- a/src/dged/minibuffer.h
+++ b/src/dged/minibuffer.h
@@ -59,8 +59,8 @@ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...);
int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...);
int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx,
- void (*update_callback)(),
- const char *fmt, ...);
+ void (*update_callback)(void *),
+ void *userdata, const char *fmt, ...);
void minibuffer_set_prompt(const char *fmt, ...);
diff --git a/src/dged/path.h b/src/dged/path.h
index 6e11d6a..da62457 100644
--- a/src/dged/path.h
+++ b/src/dged/path.h
@@ -29,10 +29,12 @@ static char *expanduser(const char *path) {
}
static char *to_abspath(const char *path) {
+ char *exp = expanduser(path);
char *p = realpath(path, NULL);
if (p != NULL) {
+ free(exp);
return p;
} else {
- return strdup(path);
+ return exp;
}
}
diff --git a/src/dged/window.c b/src/dged/window.c
index 5ea4085..e928e42 100644
--- a/src/dged/window.c
+++ b/src/dged/window.c
@@ -36,6 +36,9 @@ static struct windows {
static struct window g_minibuffer_window;
+static struct window g_popup_window = {0};
+static bool g_popup_visible = false;
+
void windows_init(uint32_t height, uint32_t width,
struct buffer *initial_buffer, struct buffer *minibuffer) {
BINTREE_INIT(&g_windows.windows);
@@ -88,6 +91,12 @@ struct window *minibuffer_window() {
return &g_minibuffer_window;
}
+struct window *popup_window() {
+ return &g_popup_window;
+}
+
+bool popup_window_visible() { return g_popup_visible; }
+
static void window_tree_resize(struct window_node *root, uint32_t height,
uint32_t width) {
@@ -145,6 +154,21 @@ void windows_resize(uint32_t height, uint32_t width) {
}
void windows_update(void *(*frame_alloc)(size_t), uint64_t frame_time) {
+
+ struct window *w = &g_minibuffer_window;
+ w->commands = command_list_create(w->height * w->width, frame_alloc, w->x,
+ w->y, w->buffer_view.buffer->name);
+ buffer_update(&w->buffer_view, -1, w->width, w->height, w->commands,
+ frame_time, &w->relline, &w->relcol);
+
+ if (g_popup_visible) {
+ w = &g_popup_window;
+ w->commands = command_list_create(w->height * w->width, frame_alloc, w->x,
+ w->y, w->buffer_view.buffer->name);
+ buffer_update(&w->buffer_view, -1, w->width, w->height, w->commands,
+ frame_time, &w->relline, &w->relcol);
+ }
+
struct window_node *n = BINTREE_ROOT(&g_windows.windows);
BINTREE_FIRST(n);
uint32_t window_id = 0;
@@ -163,11 +187,23 @@ void windows_update(void *(*frame_alloc)(size_t), uint64_t frame_time) {
BINTREE_NEXT(n);
}
- struct window *w = &g_minibuffer_window;
- w->commands = command_list_create(w->height * w->width, frame_alloc, w->x,
- w->y, w->buffer_view.buffer->name);
- buffer_update(&w->buffer_view, -1, w->width, w->height, w->commands,
- frame_time, &w->relline, &w->relcol);
+ // clear text props for next frame
+ n = BINTREE_ROOT(&g_windows.windows);
+ BINTREE_FIRST(n);
+
+ while (n != NULL) {
+ struct window *w = &BINTREE_VALUE(n);
+ if (w->type == Window_Buffer) {
+ buffer_clear_text_properties(w->buffer_view.buffer);
+ }
+
+ BINTREE_NEXT(n);
+ }
+
+ buffer_clear_text_properties(g_minibuffer_window.buffer_view.buffer);
+ if (g_popup_visible) {
+ buffer_clear_text_properties(g_popup_window.buffer_view.buffer);
+ }
}
void windows_render(struct display *display) {
@@ -182,6 +218,9 @@ void windows_render(struct display *display) {
}
display_render(display, g_minibuffer_window.commands);
+ if (g_popup_visible) {
+ display_render(display, g_popup_window.commands);
+ }
}
struct window_node *find_window(struct window *window) {
@@ -224,9 +263,16 @@ struct window *windows_get_active() {
}
void window_set_buffer(struct window *window, struct buffer *buffer) {
- window->prev_buffer = window->buffer_view.buffer;
- buffer_view_destroy(&window->buffer_view);
- window->buffer_view = buffer_view_create(buffer, true, true);
+ window_set_buffer_e(window, buffer, true, true);
+}
+
+void window_set_buffer_e(struct window *window, struct buffer *buffer,
+ bool modeline, bool line_numbers) {
+ if (buffer != window->buffer_view.buffer) {
+ window->prev_buffer = window->buffer_view.buffer;
+ buffer_view_destroy(&window->buffer_view);
+ window->buffer_view = buffer_view_create(buffer, modeline, line_numbers);
+ }
}
struct buffer *window_buffer(struct window *window) {
@@ -464,3 +510,14 @@ struct window *windows_focus(uint32_t id) {
uint32_t window_width(struct window *window) { return window->width; }
uint32_t window_height(struct window *window) { return window->height; }
+
+void windows_show_popup(uint32_t row, uint32_t col, uint32_t width,
+ uint32_t height) {
+ g_popup_window.x = col;
+ g_popup_window.y = row;
+ g_popup_window.width = width;
+ g_popup_window.height = height;
+ g_popup_visible = true;
+}
+
+void windows_close_popup() { g_popup_visible = false; }
diff --git a/src/dged/window.h b/src/dged/window.h
index be9b952..30c1061 100644
--- a/src/dged/window.h
+++ b/src/dged/window.h
@@ -22,6 +22,8 @@ void windows_render(struct display *display);
struct window *root_window();
struct window *minibuffer_window();
+struct window *popup_window();
+bool popup_window_visible();
void windows_set_active(struct window *window);
struct window *windows_focus(uint32_t id);
@@ -30,6 +32,8 @@ struct window *windows_focus_next();
struct window *window_find_by_buffer(struct buffer *b);
void window_set_buffer(struct window *window, struct buffer *buffer);
+void window_set_buffer_e(struct window *window, struct buffer *buffer,
+ bool modeline, bool line_numbers);
struct buffer *window_buffer(struct window *window);
struct buffer_view *window_buffer_view(struct window *window);
struct buffer *window_prev_buffer(struct window *window);
@@ -47,3 +51,7 @@ void window_hsplit(struct window *window, struct window **new_window_a,
struct window **new_window_b);
void window_vsplit(struct window *window, struct window **new_window_a,
struct window **new_window_b);
+
+void windows_show_popup(uint32_t row, uint32_t col, uint32_t width,
+ uint32_t height);
+void windows_close_popup();
diff --git a/src/main/cmds.c b/src/main/cmds.c
index 82fddf0..ecce343 100644
--- a/src/main/cmds.c
+++ b/src/main/cmds.c
@@ -1,21 +1,32 @@
+#define _DEFAULT_SOURCE
+#include <dirent.h>
#include <errno.h>
+#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include "dged/binding.h"
#include "dged/buffer.h"
#include "dged/buffers.h"
#include "dged/command.h"
+#include "dged/display.h"
#include "dged/minibuffer.h"
#include "dged/path.h"
#include "dged/settings.h"
#include "bindings.h"
+#include "search-replace.h"
+
+static void abort_completion();
int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) {
+ abort_replace();
+ abort_completion();
minibuffer_abort_prompt();
buffer_clear_mark(window_buffer_view(ctx.active_window));
+ reset_minibuffer_keys(minibuffer_buffer());
minibuffer_echo_timeout(4, "💣 aborted");
return 0;
}
@@ -31,12 +42,279 @@ int32_t exit_editor(struct command_ctx ctx, int argc, const char *argv[]) {
return 0;
}
+struct completion {
+ const char *display;
+ const char *insert;
+ bool complete;
+};
+
+uint32_t g_ncompletions = 0;
+struct completion g_completions[50] = {0};
+
+static void abort_completion() {
+ if (!minibuffer_focused()) {
+ reset_buffer_keys(window_buffer(windows_get_active()));
+ } else {
+ reset_minibuffer_keys(minibuffer_buffer());
+ }
+ windows_close_popup();
+
+ for (uint32_t compi = 0; compi < g_ncompletions; ++compi) {
+ free((void *)g_completions[compi].display);
+ free((void *)g_completions[compi].insert);
+ }
+ g_ncompletions = 0;
+}
+
+int cmp_completions(const void *comp_a, const void *comp_b) {
+ struct completion *a = (struct completion *)comp_a;
+ struct completion *b = (struct completion *)comp_b;
+ return strcmp(a->display, b->display);
+}
+
+static bool is_hidden(const char *filename) {
+ return filename[0] == '.' && filename[1] != '\0' && filename[1] != '.';
+}
+
+static void complete_path(const char *path, struct completion results[],
+ uint32_t nresults_max, uint32_t *nresults) {
+ uint32_t n = 0;
+ char *p1 = to_abspath(path);
+ size_t len = strlen(p1);
+ char *p2 = strdup(p1);
+
+ if (len == 0) {
+ goto done;
+ }
+
+ if (nresults_max == 0) {
+ goto done;
+ }
+
+ const char *dir = p1;
+ const char *file = "";
+
+ if (dir[len - 1] != '/') {
+ dir = dirname(p1);
+ file = basename(p2);
+ }
+
+ DIR *d = opendir(dir);
+ if (d == NULL) {
+ goto done;
+ }
+
+ errno = 0;
+ while (n < nresults_max) {
+ struct dirent *de = readdir(d);
+ if (de == NULL && errno != 0) {
+ // skip the erroring entry
+ errno = 0;
+ continue;
+ } else if (de == NULL && errno == 0) {
+ break;
+ }
+
+ switch (de->d_type) {
+ case DT_DIR:
+ case DT_REG:
+ case DT_LNK:
+ if (!is_hidden(de->d_name) &&
+ (strncmp(file, de->d_name, strlen(file)) == 0 || strlen(file) == 0)) {
+ const char *disp = strdup(de->d_name);
+ results[n] = (struct completion){
+ .display = disp,
+ .insert = strdup(disp + strlen(file)),
+ .complete = de->d_type == DT_REG,
+ };
+ ++n;
+ }
+ break;
+ }
+ }
+
+ closedir(d);
+
+done:
+ free(p1);
+ free(p2);
+
+ qsort(results, n, sizeof(struct completion), cmp_completions);
+ *nresults = n;
+}
+
+void render_completion_line(struct text_chunk *line_data, uint32_t line,
+ struct command_list *commands, void *userdata) {
+ command_list_set_show_whitespace(commands, false);
+ command_list_draw_repeated(commands, 0, line, ' ', 1);
+}
+
+struct update_hook_result
+update_completion_buffer(struct buffer_view *view,
+ struct command_list *commands, uint32_t width,
+ uint32_t height, uint64_t frame_time, void *userdata) {
+ struct text_chunk line = buffer_get_line(view->buffer, view->dot.line);
+ buffer_add_text_property(
+ view->buffer, (struct buffer_location){.line = view->dot.line, .col = 0},
+ (struct buffer_location){.line = view->dot.line, .col = line.nchars},
+ (struct text_property){.type = TextProperty_Colors,
+ .colors = (struct text_property_colors){
+ .set_bg = false,
+ .bg = 0,
+ .set_fg = true,
+ .fg = 4,
+ }});
+
+ if (line.allocated) {
+ free(line.text);
+ }
+
+ struct update_hook_result res = {0};
+ res.margins.left = 1;
+ res.margins.right = 1;
+ res.line_render_hook = (struct line_render_hook){
+ .callback = render_completion_line,
+ .empty_callback = NULL,
+ .userdata = NULL,
+ };
+
+ return res;
+}
+
+static int32_t goto_completion(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ void (*movement_fn)(struct buffer_view *) =
+ (void (*)(struct buffer_view *))ctx.userdata;
+ struct buffer *b = buffers_find(ctx.buffers, "*completions*");
+
+ // is it in the popup?
+ if (b != NULL && window_buffer(popup_window()) == b) {
+ struct buffer_view *v = window_buffer_view(popup_window());
+ movement_fn(v);
+
+ if (v->dot.line >= text_num_lines(b->text)) {
+ buffer_backward_line(v);
+ }
+ }
+
+ return 0;
+}
+
+static int32_t insert_completion(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ struct buffer *b = buffers_find(ctx.buffers, "*completions*");
+ // is it in the popup?
+ if (b != NULL && window_buffer(popup_window()) == b) {
+ struct buffer_view *cv = window_buffer_view(popup_window());
+
+ if (cv->dot.line < g_ncompletions) {
+ char *ins = (char *)g_completions[cv->dot.line].insert;
+ bool complete = g_completions[cv->dot.line].complete;
+ size_t inslen = strlen(ins);
+ if (minibuffer_focused()) {
+ buffer_add_text(window_buffer_view(minibuffer_window()), ins, inslen);
+ } else {
+ buffer_add_text(window_buffer_view(windows_get_active()), ins, inslen);
+ }
+
+ if (complete) {
+ minibuffer_execute();
+ }
+ }
+ }
+
+ return 0;
+}
+
+COMMAND_FN("next-completion", next_completion, goto_completion,
+ buffer_forward_line);
+COMMAND_FN("prev-completion", prev_completion, goto_completion,
+ buffer_backward_line);
+COMMAND_FN("insert-completion", insert_completion, insert_completion, NULL);
+
+static void on_find_file_input(void *userdata) {
+ struct buffers *buffers = (struct buffers *)userdata;
+ struct text_chunk txt = minibuffer_content();
+
+ struct window *mb = minibuffer_window();
+ struct buffer_location mb_dot = window_absolute_cursor_location(mb);
+
+ struct buffer *b = buffers_find(buffers, "*completions*");
+ if (b == NULL) {
+ b = buffers_add(buffers, buffer_create("*completions*"));
+ buffer_add_update_hook(b, update_completion_buffer, NULL);
+ window_set_buffer_e(popup_window(), b, false, false);
+ }
+
+ struct buffer_view *v = window_buffer_view(popup_window());
+
+ char path[1024];
+ strncpy(path, txt.text, txt.nbytes);
+ path[(txt.nbytes >= 1024 ? 1023 : txt.nbytes)] = '\0';
+
+ for (uint32_t compi = 0; compi < g_ncompletions; ++compi) {
+ free((void *)g_completions[compi].display);
+ free((void *)g_completions[compi].insert);
+ }
+
+ g_ncompletions = 0;
+ complete_path(path, g_completions, 50, &g_ncompletions);
+
+ size_t max_width = 0;
+ struct buffer_location prev_dot = v->dot;
+
+ buffer_clear(v);
+ if (g_ncompletions > 0) {
+ for (uint32_t compi = 0; compi < g_ncompletions; ++compi) {
+ const char *disp = g_completions[compi].display;
+ size_t width = strlen(disp);
+ if (width > max_width) {
+ max_width = width;
+ }
+ buffer_add_text(v, (uint8_t *)disp, width);
+
+ // the extra newline feels weird in navigation
+ if (compi != g_ncompletions - 1) {
+ buffer_add_text(v, (uint8_t *)"\n", 1);
+ }
+ }
+
+ buffer_goto(v, prev_dot.line, prev_dot.col);
+ if (prev_dot.line >= text_num_lines(b->text)) {
+ buffer_backward_line(v);
+ }
+
+ if (!popup_window_visible()) {
+ struct binding bindings[] = {
+ ANONYMOUS_BINDING(Ctrl, 'N', &next_completion_command),
+ ANONYMOUS_BINDING(Ctrl, 'P', &prev_completion_command),
+ ANONYMOUS_BINDING(ENTER, &insert_completion_command),
+ };
+ buffer_bind_keys(minibuffer_buffer(), bindings,
+ sizeof(bindings) / sizeof(bindings[0]));
+ }
+
+ uint32_t width = max_width > 2 ? max_width + 2 : 4,
+ height = g_ncompletions > 10 ? 10 : g_ncompletions;
+ windows_show_popup(mb_dot.line - height, mb_dot.col, width, height);
+ } else {
+ windows_close_popup();
+ }
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+}
+
int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) {
const char *pth = NULL;
if (argc == 0) {
- return minibuffer_prompt(ctx, "find file: ");
+ return minibuffer_prompt_interactive(ctx, on_find_file_input, ctx.buffers,
+ "find file: ");
}
+ abort_completion();
+
pth = argv[0];
struct stat sb = {0};
if (stat(pth, &sb) < 0 && errno != ENOENT) {
@@ -132,124 +410,6 @@ int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) {
ctx.active_window, ctx.buffers, argc, argv);
}
-static char *g_last_search = NULL;
-
-int64_t matchdist(struct match *match, struct buffer_location loc) {
- struct buffer_location begin = match->begin;
-
- int64_t linedist = (int64_t)begin.line - (int64_t)loc.line;
- int64_t coldist = (int64_t)begin.col - (int64_t)loc.col;
-
- return linedist * linedist + coldist * coldist;
-}
-
-int buffer_loc_cmp(struct buffer_location loc1, struct buffer_location loc2) {
- if (loc1.line < loc2.line) {
- return -1;
- } else if (loc1.line > loc2.line) {
- return 1;
- } else {
- if (loc1.col < loc2.col) {
- return -1;
- } else if (loc1.col > loc2.col) {
- return 1;
- } else {
- return 0;
- }
- }
-}
-
-const char *search_prompt(bool reverse) {
- const char *txt = "search (down): ";
- if (reverse) {
- txt = "search (up): ";
- }
-
- return txt;
-}
-
-void do_search(struct buffer_view *view, const char *pattern, bool reverse) {
- struct match *matches = NULL;
- uint32_t nmatches = 0;
-
- g_last_search = strdup(pattern);
-
- struct buffer_view *buffer_view = window_buffer_view(windows_get_active());
- buffer_find(buffer_view->buffer, pattern, &matches, &nmatches);
-
- // find the "nearest" match
- if (nmatches > 0) {
- struct match *closest = reverse ? &matches[nmatches - 1] : &matches[0];
- int64_t closest_dist = INT64_MAX;
- for (uint32_t matchi = 0; matchi < nmatches; ++matchi) {
- struct match *m = &matches[matchi];
- int res = buffer_loc_cmp(m->begin, view->dot);
- int64_t dist = matchdist(m, view->dot);
- if (((res < 0 && reverse) || (res > 0 && !reverse)) &&
- dist < closest_dist) {
- closest_dist = dist;
- closest = m;
- }
- }
- buffer_goto(buffer_view, closest->begin.line, closest->begin.col);
- }
-}
-
-int32_t search_interactive(struct command_ctx ctx, int argc,
- const char *argv[]) {
- const char *pattern = NULL;
- if (minibuffer_content().nbytes == 0) {
- // recall the last search, if any
- if (g_last_search != NULL) {
- struct buffer_view *view = window_buffer_view(minibuffer_window());
- buffer_clear(view);
- buffer_add_text(view, (uint8_t *)g_last_search, strlen(g_last_search));
- pattern = g_last_search;
- }
- } else {
- struct text_chunk content = minibuffer_content();
- char *p = malloc(content.nbytes + 1);
- memcpy(p, content.text, content.nbytes);
- p[content.nbytes] = '\0';
- pattern = p;
- }
-
- minibuffer_set_prompt(search_prompt(*(bool *)ctx.userdata));
-
- if (pattern != NULL) {
- // ctx.active_window would be the minibuffer window
- do_search(window_buffer_view(windows_get_active()), pattern,
- *(bool *)ctx.userdata);
- }
- return 0;
-}
-
-static bool search_dir_backward = true;
-static bool search_dir_forward = false;
-
-COMMAND_FN("search-forward", search_forward, search_interactive,
- &search_dir_forward);
-COMMAND_FN("search-backward", search_backward, search_interactive,
- &search_dir_backward);
-
-int32_t find(struct command_ctx ctx, int argc, const char *argv[]) {
- bool reverse = strcmp((char *)ctx.userdata, "backward") == 0;
- if (argc == 0) {
- struct binding bindings[] = {
- ANONYMOUS_BINDING(Ctrl, 'S', &search_forward_command),
- ANONYMOUS_BINDING(Ctrl, 'R', &search_backward_command),
- };
- buffer_bind_keys(minibuffer_buffer(), bindings,
- sizeof(bindings) / sizeof(bindings[0]));
- return minibuffer_prompt(ctx, search_prompt(reverse));
- }
-
- reset_minibuffer_keys(minibuffer_buffer());
- do_search(window_buffer_view(ctx.active_window), argv[0], reverse);
-
- return 0;
-}
-
int32_t timers(struct command_ctx ctx, int argc, const char *argv[]) {
struct buffer *b = buffers_add(ctx.buffers, buffer_create("timers"));
buffer_set_readonly(b, true);
@@ -289,6 +449,7 @@ int32_t buflist_visit_cmd(struct command_ctx ctx, int argc,
uint32_t len = end - (char *)text.text;
char *bufname = (char *)malloc(len + 1);
strncpy(bufname, text.text, len);
+ bufname[len] = '\0';
struct buffer *target = buffers_find(ctx.buffers, bufname);
free(bufname);
@@ -364,14 +525,14 @@ void register_global_commands(struct commands *commands,
{.name = "run-command-interactive", .fn = run_interactive},
{.name = "switch-buffer", .fn = switch_buffer},
{.name = "abort", .fn = _abort},
- {.name = "find-next", .fn = find, .userdata = "forward"},
- {.name = "find-prev", .fn = find, .userdata = "backward"},
{.name = "timers", .fn = timers},
{.name = "buffer-list", .fn = buffer_list},
{.name = "exit", .fn = exit_editor, .userdata = terminate_cb}};
register_commands(commands, global_commands,
sizeof(global_commands) / sizeof(global_commands[0]));
+
+ register_search_replace_commands(commands);
}
#define BUFFER_WRAPCMD_POS(fn) \
diff --git a/src/main/search-replace.c b/src/main/search-replace.c
new file mode 100644
index 0000000..828ce32
--- /dev/null
+++ b/src/main/search-replace.c
@@ -0,0 +1,365 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "dged/binding.h"
+#include "dged/buffer.h"
+#include "dged/command.h"
+#include "dged/minibuffer.h"
+#include "dged/window.h"
+
+#include "bindings.h"
+#include "search-replace.h"
+
+static struct replace {
+ char *replace;
+ struct match *matches;
+ uint32_t nmatches;
+ uint32_t current_match;
+} g_current_replace = {0};
+
+void abort_replace() {
+ reset_minibuffer_keys(minibuffer_buffer());
+ free(g_current_replace.matches);
+ free(g_current_replace.replace);
+ g_current_replace.matches = NULL;
+ g_current_replace.replace = NULL;
+ g_current_replace.nmatches = 0;
+ minibuffer_abort_prompt();
+}
+
+uint64_t matchdist(struct match *match, struct buffer_location loc) {
+ struct buffer_location begin = match->begin;
+
+ int64_t linedist = (int64_t)begin.line - (int64_t)loc.line;
+ int64_t coldist = (int64_t)begin.col - (int64_t)loc.col;
+
+ // arbitrary row scaling, best effort to avoid counting line length
+ return (linedist * linedist) * 1e6 + coldist * coldist;
+}
+
+int buffer_loc_cmp(struct buffer_location loc1, struct buffer_location loc2) {
+ if (loc1.line < loc2.line) {
+ return -1;
+ } else if (loc1.line > loc2.line) {
+ return 1;
+ } else {
+ if (loc1.col < loc2.col) {
+ return -1;
+ } else if (loc1.col > loc2.col) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
+
+static void highlight_matches(struct buffer *buffer, struct match *matches,
+ uint32_t nmatches, uint32_t current) {
+ for (uint32_t matchi = 0; matchi < nmatches; ++matchi) {
+ struct match *m = &matches[matchi];
+ if (matchi == current) {
+ buffer_add_text_property(
+ buffer, m->begin, m->end,
+ (struct text_property){.type = TextProperty_Colors,
+ .colors = (struct text_property_colors){
+ .set_bg = true,
+ .bg = 3,
+ .set_fg = true,
+ .fg = 0,
+ }});
+
+ } else {
+ buffer_add_text_property(
+ buffer, m->begin, m->end,
+ (struct text_property){.type = TextProperty_Colors,
+ .colors = (struct text_property_colors){
+ .set_bg = true,
+ .bg = 6,
+ .set_fg = true,
+ .fg = 0,
+ }});
+ }
+ }
+}
+
+static int32_t replace_next(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ struct replace *state = &g_current_replace;
+ struct buffer_view *buffer_view = window_buffer_view(windows_get_active());
+
+ struct match *m = &state->matches[state->current_match];
+ buffer_set_mark_at(buffer_view, m->begin.line, m->begin.col);
+ buffer_goto(buffer_view, m->end.line, m->end.col + 1);
+ buffer_add_text(buffer_view, state->replace, strlen(state->replace));
+
+ ++state->current_match;
+
+ if (state->current_match == state->nmatches) {
+ abort_replace();
+ } else {
+ m = &state->matches[state->current_match];
+ buffer_goto(buffer_view, m->begin.line, m->begin.col);
+ highlight_matches(buffer_view->buffer, state->matches, state->nmatches,
+ state->current_match);
+ }
+
+ return 0;
+}
+
+static int32_t skip_next(struct command_ctx ctx, int argc, const char *argv[]) {
+ struct replace *state = &g_current_replace;
+
+ struct buffer_view *buffer_view = window_buffer_view(windows_get_active());
+ struct match *m = &state->matches[state->current_match];
+ buffer_goto(buffer_view, m->end.line, m->end.col + 1);
+
+ ++state->current_match;
+
+ if (state->current_match == state->nmatches) {
+ abort_replace();
+ } else {
+ m = &state->matches[state->current_match];
+ buffer_goto(buffer_view, m->begin.line, m->begin.col);
+ highlight_matches(buffer_view->buffer, state->matches, state->nmatches,
+ state->current_match);
+ }
+
+ return 0;
+}
+
+COMMAND_FN("replace-next", replace_next, replace_next, NULL);
+COMMAND_FN("skip-next", skip_next, skip_next, NULL);
+
+static int cmp_matches(const void *m1, const void *m2) {
+ struct match *match1 = (struct match *)m1;
+ struct match *match2 = (struct match *)m2;
+ struct buffer_location dot = window_buffer_view(windows_get_active())->dot;
+ uint64_t dist1 = matchdist(match1, dot);
+ uint64_t dist2 = matchdist(match2, dot);
+
+ int loc1 = buffer_loc_cmp(match1->begin, dot);
+ int loc2 = buffer_loc_cmp(match2->begin, dot);
+
+ int64_t score1 = dist1 * loc1;
+ int64_t score2 = dist2 * loc2;
+
+ if (score1 > 0 && score2 > 0) {
+ return score1 < score2 ? -1 : score1 > score2 ? 1 : 0;
+ } else if (score1 < 0 && score2 > 0) {
+ return 1;
+ } else if (score1 > 0 && score2 < 0) {
+ return -1;
+ } else { // both matches are behind dot
+ return score1 < score2 ? 1 : score1 > score2 ? -1 : 0;
+ }
+}
+
+static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) {
+ if (argc == 0) {
+ return minibuffer_prompt(ctx, "find: ");
+ }
+
+ if (argc == 1) {
+ command_ctx_push_arg(&ctx, argv[0]);
+ return minibuffer_prompt(ctx, "replace with: ");
+ }
+
+ struct buffer_view *buffer_view = window_buffer_view(windows_get_active());
+ struct match *matches = NULL;
+ uint32_t nmatches = 0;
+ buffer_find(buffer_view->buffer, argv[0], &matches, &nmatches);
+
+ if (nmatches == 0) {
+ minibuffer_echo_timeout(4, "%s not found", argv[0]);
+ free(matches);
+ return 0;
+ }
+
+ // sort matches
+ qsort(matches, nmatches, sizeof(struct match), cmp_matches);
+
+ g_current_replace = (struct replace){
+ .replace = strdup(argv[1]),
+ .matches = matches,
+ .nmatches = nmatches,
+ .current_match = 0,
+ };
+
+ struct match *m = &g_current_replace.matches[0];
+ buffer_goto(buffer_view, m->begin.line, m->begin.col);
+ highlight_matches(buffer_view->buffer, g_current_replace.matches,
+ g_current_replace.nmatches, 0);
+
+ struct binding bindings[] = {
+ ANONYMOUS_BINDING(None, 'y', &replace_next_command),
+ ANONYMOUS_BINDING(None, 'n', &skip_next_command),
+ ANONYMOUS_BINDING(Ctrl, 'M', &replace_next_command),
+ };
+ buffer_bind_keys(minibuffer_buffer(), bindings,
+ sizeof(bindings) / sizeof(bindings[0]));
+
+ return minibuffer_prompt(ctx, "replace? [yn] ");
+}
+
+static char *g_last_search = NULL;
+
+const char *search_prompt(bool reverse) {
+ const char *txt = "search (down): ";
+ if (reverse) {
+ txt = "search (up): ";
+ }
+
+ return txt;
+}
+
+struct closest_match {
+ bool found;
+ struct match closest;
+};
+
+static struct closest_match find_closest(struct buffer_view *view,
+ const char *pattern, bool highlight,
+ bool reverse) {
+ struct match *matches = NULL;
+ uint32_t nmatches = 0;
+ struct closest_match res = {
+ .found = false,
+ .closest = {0},
+ };
+
+ buffer_find(view->buffer, pattern, &matches, &nmatches);
+
+ // find the "nearest" match
+ if (nmatches > 0) {
+ res.found = true;
+ struct match *closest = &matches[0];
+ int64_t closest_dist = INT64_MAX;
+ for (uint32_t matchi = 0; matchi < nmatches; ++matchi) {
+ struct match *m = &matches[matchi];
+
+ if (highlight) {
+ buffer_add_text_property(
+ view->buffer, m->begin, m->end,
+ (struct text_property){.type = TextProperty_Colors,
+ .colors = (struct text_property_colors){
+ .set_bg = true,
+ .bg = 6,
+ .set_fg = true,
+ .fg = 0,
+ }});
+ }
+ int res = buffer_loc_cmp(m->begin, view->dot);
+ uint64_t dist = matchdist(m, view->dot);
+ if (((res < 0 && reverse) || (res > 0 && !reverse)) &&
+ dist < closest_dist) {
+ closest_dist = dist;
+ closest = m;
+ }
+ }
+
+ if (highlight) {
+ buffer_add_text_property(
+ view->buffer, closest->begin, closest->end,
+ (struct text_property){.type = TextProperty_Colors,
+ .colors = (struct text_property_colors){
+ .set_bg = true,
+ .bg = 3,
+ .set_fg = true,
+ .fg = 0,
+ }});
+ }
+ res.closest = *closest;
+ }
+
+ free(matches);
+ return res;
+}
+
+void do_search(struct buffer_view *view, const char *pattern, bool reverse) {
+ g_last_search = strdup(pattern);
+
+ struct buffer_view *buffer_view = window_buffer_view(windows_get_active());
+ struct closest_match m = find_closest(buffer_view, pattern, true, reverse);
+
+ // find the "nearest" match
+ if (m.found) {
+ buffer_goto(buffer_view, m.closest.begin.line, m.closest.begin.col);
+ } else {
+ minibuffer_echo_timeout(4, "%s not found", pattern);
+ }
+}
+
+int32_t search_interactive(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ const char *pattern = NULL;
+ if (minibuffer_content().nbytes == 0) {
+ // recall the last search, if any
+ if (g_last_search != NULL) {
+ struct buffer_view *view = window_buffer_view(minibuffer_window());
+ buffer_clear(view);
+ buffer_add_text(view, (uint8_t *)g_last_search, strlen(g_last_search));
+ pattern = g_last_search;
+ }
+ } else {
+ struct text_chunk content = minibuffer_content();
+ char *p = malloc(content.nbytes + 1);
+ strncpy(p, content.text, content.nbytes);
+ p[content.nbytes] = '\0';
+ pattern = p;
+ }
+
+ minibuffer_set_prompt(search_prompt(*(bool *)ctx.userdata));
+
+ if (pattern != NULL) {
+ // ctx.active_window would be the minibuffer window
+ do_search(window_buffer_view(windows_get_active()), pattern,
+ *(bool *)ctx.userdata);
+ free((char *)pattern);
+ }
+ return 0;
+}
+
+static bool search_dir_backward = true;
+static bool search_dir_forward = false;
+
+COMMAND_FN("search-forward", search_forward, search_interactive,
+ &search_dir_forward);
+COMMAND_FN("search-backward", search_backward, search_interactive,
+ &search_dir_backward);
+
+int32_t find(struct command_ctx ctx, int argc, const char *argv[]) {
+ bool reverse = strcmp((char *)ctx.userdata, "backward") == 0;
+ if (argc == 0) {
+ struct binding bindings[] = {
+ ANONYMOUS_BINDING(Ctrl, 'S', &search_forward_command),
+ ANONYMOUS_BINDING(Ctrl, 'R', &search_backward_command),
+ };
+ buffer_bind_keys(minibuffer_buffer(), bindings,
+ sizeof(bindings) / sizeof(bindings[0]));
+ return minibuffer_prompt(ctx, search_prompt(reverse));
+ }
+
+ reset_minibuffer_keys(minibuffer_buffer());
+ struct text_chunk content = minibuffer_content();
+ char *pattern = malloc(content.nbytes + 1);
+ strncpy(pattern, content.text, content.nbytes);
+ pattern[content.nbytes] = '\0';
+
+ do_search(window_buffer_view(ctx.active_window), pattern, reverse);
+ free(pattern);
+
+ return 0;
+}
+
+void register_search_replace_commands(struct commands *commands) {
+ struct command search_replace_commands[] = {
+ {.name = "find-next", .fn = find, .userdata = "forward"},
+ {.name = "find-prev", .fn = find, .userdata = "backward"},
+ {.name = "replace", .fn = replace},
+ };
+
+ register_commands(commands, search_replace_commands,
+ sizeof(search_replace_commands) /
+ sizeof(search_replace_commands[0]));
+}
diff --git a/src/main/search-replace.h b/src/main/search-replace.h
new file mode 100644
index 0000000..d0b2012
--- /dev/null
+++ b/src/main/search-replace.h
@@ -0,0 +1,14 @@
+struct commands;
+
+/**
+ * Abort a replace currently in progress.
+ */
+void abort_replace();
+
+/**
+ * Register search and replace commands
+ *
+ * @param [in] commands Command registry to register search and
+ * replace commands in.
+ */
+void register_search_replace_commands(struct commands *commands);