summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--src/dged/buffer.c1270
-rw-r--r--src/dged/buffer.h550
-rw-r--r--src/dged/buffer_view.c418
-rw-r--r--src/dged/buffer_view.h100
-rw-r--r--src/dged/display.c91
-rw-r--r--src/dged/display.h7
-rw-r--r--src/dged/location.c68
-rw-r--r--src/dged/location.h81
-rw-r--r--src/dged/minibuffer.c40
-rw-r--r--src/dged/minibuffer.h4
-rw-r--r--src/dged/text.c40
-rw-r--r--src/dged/text.h36
-rw-r--r--src/dged/window.c216
-rw-r--r--src/dged/window.h17
-rw-r--r--src/main/cmds.c241
-rw-r--r--src/main/main.c26
-rw-r--r--src/main/search-replace.c105
-rw-r--r--test/buffer.c45
-rw-r--r--test/minibuffer.c18
20 files changed, 2004 insertions, 1374 deletions
diff --git a/Makefile b/Makefile
index b094db3..1c157de 100644
--- a/Makefile
+++ b/Makefile
@@ -12,12 +12,13 @@ HEADERS = src/dged/settings.h src/dged/minibuffer.h src/dged/keyboard.h src/dged
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/main/search-replace.h
+ src/main/search-replace.h src/dged/location.h src/dged/buffer_view.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
+ src/dged/settings.c src/dged/lang.c src/dged/settings-parse.c src/dged/location.c \
+ src/dged/buffer_view.c
MAIN_SOURCES = src/main/main.c src/main/cmds.c src/main/bindings.c src/main/search-replace.c
diff --git a/src/dged/buffer.c b/src/dged/buffer.c
index c826401..117fab5 100644
--- a/src/dged/buffer.c
+++ b/src/dged/buffer.c
@@ -29,7 +29,7 @@ struct modeline {
#define KILL_RING_SZ 64
static struct kill_ring {
struct text_chunk buffer[KILL_RING_SZ];
- struct buffer_location last_paste;
+ struct location last_paste;
bool paste_up_to_date;
uint32_t curr_idx;
uint32_t paste_idx;
@@ -46,67 +46,6 @@ static struct create_hook {
} g_create_hooks[MAX_CREATE_HOOKS];
static uint32_t g_num_create_hooks = 0;
-struct update_hook_result buffer_linenum_hook(struct buffer_view *view,
- struct command_list *commands,
- uint32_t width, uint32_t height,
- uint64_t frame_time,
- void *userdata);
-
-struct update_hook_result buffer_modeline_hook(struct buffer_view *view,
- struct command_list *commands,
- uint32_t width, uint32_t height,
- uint64_t frame_time,
- void *userdata);
-
-struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline,
- bool line_numbers) {
- struct buffer_view view = {
- .dot = {0},
- .mark = {0},
- .mark_set = false,
- .scroll = {0},
- .buffer = buffer,
- .modeline = NULL,
- .line_numbers = line_numbers,
- };
-
- if (modeline) {
- view.modeline = calloc(1, sizeof(struct modeline));
- view.modeline->buffer = malloc(1024);
- view.modeline->sz = 1024;
- view.modeline->buffer[0] = '\0';
- }
-
- return view;
-}
-
-struct buffer_view buffer_view_clone(struct buffer_view *view) {
- struct buffer_view c = {
- .dot = view->dot,
- .mark = view->mark,
- .mark_set = view->mark_set,
- .scroll = view->scroll,
- .buffer = view->buffer,
- .modeline = NULL,
- .line_numbers = view->line_numbers,
- };
-
- if (view->modeline) {
- c.modeline = calloc(1, sizeof(struct modeline));
- c.modeline->buffer = malloc(view->modeline->sz);
- memcpy(c.modeline->buffer, view->modeline->buffer, view->modeline->sz);
- }
-
- return c;
-}
-
-void buffer_view_destroy(struct buffer_view *view) {
- if (view->modeline != NULL) {
- free(view->modeline->buffer);
- free(view->modeline);
- }
-}
-
uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata) {
if (g_num_create_hooks < MAX_CREATE_HOOKS) {
g_create_hooks[g_num_create_hooks] = (struct create_hook){
@@ -119,56 +58,6 @@ uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata) {
return g_num_create_hooks - 1;
}
-struct buffer create_internal(char *name, char *filename) {
- struct buffer b = (struct buffer){
- .filename = filename,
- .name = strdup(name),
- .text = text_create(10),
- .modified = false,
- .readonly = false,
- .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;
-}
-
-struct buffer buffer_create(char *name) {
-
- struct buffer b = create_internal(name, NULL);
-
- for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) {
- g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata);
- }
-
- return b;
-}
-
-void buffer_destroy(struct buffer *buffer) {
- VEC_DESTROY(&buffer->text_properties);
- text_destroy(buffer->text);
- buffer->text = NULL;
-
- free(buffer->name);
- buffer->name = NULL;
-
- free(buffer->filename);
- buffer->filename = NULL;
-
- undo_destroy(&buffer->undo);
-}
-
-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() {
settings_register_setting(
"editor.tab-width",
@@ -187,335 +76,95 @@ void buffer_static_teardown() {
}
}
-bool buffer_is_empty(struct buffer *buffer) {
- return text_num_lines(buffer->text) == 0;
-}
-
-bool buffer_is_modified(struct buffer *buffer) { return buffer->modified; }
-
-bool buffer_is_readonly(struct buffer *buffer) { return buffer->readonly; }
-
-void buffer_set_readonly(struct buffer *buffer, bool readonly) {
- buffer->readonly = readonly;
-}
-
-bool buffer_is_backed(struct buffer *buffer) {
- return buffer->filename != NULL;
-}
-
-void delete_with_undo(struct buffer *buffer, struct buffer_location start,
- struct buffer_location end) {
- if (buffer->readonly) {
- minibuffer_echo_timeout(4, "buffer is read-only");
- return;
- }
-
- struct text_chunk txt =
- text_get_region(buffer->text, start.line, start.col, end.line, end.col);
-
- undo_push_delete(
- &buffer->undo,
- (struct undo_delete){.data = txt.text,
- .nbytes = txt.nbytes,
- .pos = {.row = start.line, .col = start.col}});
- undo_push_boundary(&buffer->undo,
- (struct undo_boundary){.save_point = false});
+static struct buffer create_internal(const char *name, char *filename) {
+ struct buffer b = (struct buffer){
+ .filename = filename,
+ .name = strdup(name),
+ .text = text_create(10),
+ .modified = false,
+ .readonly = false,
+ .lang =
+ filename != NULL ? lang_from_filename(filename) : lang_from_id("fnd"),
+ .last_write = {0},
+ };
- text_delete(buffer->text, start.line, start.col, end.line, end.col);
- buffer->modified = true;
-}
+ VEC_INIT(&b.update_hooks, 32);
-void buffer_goto_beginning(struct buffer_view *view) {
- view->dot.col = 0;
- view->dot.line = 0;
-}
+ undo_init(&b.undo, 100);
-void buffer_goto_end(struct buffer_view *view) {
- view->dot.line = text_num_lines(view->buffer->text);
- view->dot.col = 0;
+ return b;
}
-bool movev(struct buffer_view *view, int rowdelta) {
- int64_t new_line = (int64_t)view->dot.line + rowdelta;
-
+static bool movev(struct buffer *buffer, int64_t linedelta,
+ struct location *location) {
+ int64_t new_line = (int64_t)location->line + linedelta;
if (new_line < 0) {
- view->dot.line = 0;
+ location->line = 0;
return false;
- } else if (new_line > text_num_lines(view->buffer->text)) {
- view->dot.line = text_num_lines(view->buffer->text);
+ } else if (new_line > text_num_lines(buffer->text)) {
+ // allow addition of an extra line by going past the bottom
+ location->line = text_num_lines(buffer->text);
return false;
} else {
- view->dot.line = (uint32_t)new_line;
+ location->line = (uint32_t)new_line;
// make sure column stays on the line
- uint32_t linelen = text_line_length(view->buffer->text, view->dot.line);
- view->dot.col = view->dot.col > linelen ? linelen : view->dot.col;
+ uint32_t linelen = text_line_length(buffer->text, location->line);
+ location->col = location->col > linelen ? linelen : location->col;
return true;
}
}
// move dot `coldelta` chars
-bool moveh(struct buffer_view *view, int coldelta) {
- int64_t new_col = (int64_t)view->dot.col + coldelta;
-
- if (new_col > (int64_t)text_line_length(view->buffer->text, view->dot.line)) {
- if (movev(view, 1)) {
- view->dot.col = 0;
+static bool moveh(struct buffer *buffer, int64_t coldelta,
+ struct location *location) {
+ int64_t new_col = (int64_t)location->col + coldelta;
+ if (new_col > (int64_t)text_line_length(buffer->text, location->line)) {
+ if (movev(buffer, 1, location)) {
+ location->col = 0;
}
} else if (new_col < 0) {
- if (movev(view, -1)) {
- view->dot.col = text_line_length(view->buffer->text, view->dot.line);
+ if (movev(buffer, -1, location)) {
+ location->col = text_line_length(buffer->text, location->line);
} else {
return false;
}
} else {
- view->dot.col = new_col;
+ location->col = new_col;
}
return true;
}
-void buffer_goto(struct buffer_view *view, uint32_t line, uint32_t col) {
- int64_t linedelta = (int64_t)line - (int64_t)view->dot.line;
- movev(view, linedelta);
-
- int64_t coldelta = (int64_t)col - (int64_t)view->dot.col;
- moveh(view, coldelta);
-}
-
-struct region {
- struct buffer_location begin;
- struct buffer_location end;
-};
-
-struct region to_region(struct buffer_location dot,
- struct buffer_location mark) {
- struct region reg = {.begin = mark, .end = dot};
-
- if (dot.line < mark.line || (dot.line == mark.line && dot.col < mark.col)) {
- reg.begin = dot;
- reg.end = mark;
- }
-
- return reg;
-}
-
-struct region buffer_get_region(struct buffer_view *view) {
- return to_region(view->dot, view->mark);
-}
-
-bool buffer_region_has_size(struct buffer_view *view) {
- return view->mark_set &&
- (labs((int64_t)view->mark.line - (int64_t)view->dot.line) +
- labs((int64_t)view->mark.col - (int64_t)view->dot.col)) > 0;
-}
-
-struct text_chunk *copy_region(struct buffer *buffer, struct region region) {
- struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx];
- g_kill_ring.curr_idx = (g_kill_ring.curr_idx + 1) % KILL_RING_SZ;
-
- if (curr->allocated) {
- free(curr->text);
- }
-
- struct text_chunk txt =
- text_get_region(buffer->text, region.begin.line, region.begin.col,
- region.end.line, region.end.col);
- *curr = txt;
- return curr;
-}
-
-void buffer_copy(struct buffer_view *view) {
- if (buffer_region_has_size(view)) {
- struct region reg = buffer_get_region(view);
- struct text_chunk *curr = copy_region(view->buffer, reg);
- buffer_clear_mark(view);
- }
-}
-
-void paste(struct buffer_view *view, uint32_t ring_idx) {
- if (ring_idx > 0) {
- struct text_chunk *curr = &g_kill_ring.buffer[ring_idx - 1];
- if (curr->text != NULL) {
- g_kill_ring.last_paste = view->mark_set ? view->mark : view->dot;
- buffer_add_text(view, curr->text, curr->nbytes);
- g_kill_ring.paste_up_to_date = true;
- }
- }
-}
-
-void buffer_paste(struct buffer_view *view) {
- g_kill_ring.paste_idx = g_kill_ring.curr_idx;
- paste(view, g_kill_ring.curr_idx);
-}
-
-void buffer_paste_older(struct buffer_view *view) {
- if (g_kill_ring.paste_up_to_date) {
-
- // remove previous paste
- struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx];
- delete_with_undo(view->buffer, g_kill_ring.last_paste, view->dot);
-
- // place ourselves right
- view->dot = g_kill_ring.last_paste;
-
- // paste older
- if (g_kill_ring.paste_idx - 1 > 0) {
- --g_kill_ring.paste_idx;
- } else {
- g_kill_ring.paste_idx = g_kill_ring.curr_idx;
- }
-
- paste(view, g_kill_ring.paste_idx);
-
- } else {
- buffer_paste(view);
- }
-}
-
-void buffer_cut(struct buffer_view *view) {
- if (buffer_region_has_size(view)) {
- struct region reg = buffer_get_region(view);
- copy_region(view->buffer, reg);
- delete_with_undo(view->buffer, reg.begin, reg.end);
- buffer_clear_mark(view);
- view->dot = reg.begin;
- }
-}
-
-bool maybe_delete_region(struct buffer_view *view) {
- if (buffer_region_has_size(view)) {
- struct region reg = buffer_get_region(view);
- delete_with_undo(view->buffer, reg.begin, reg.end);
- buffer_clear_mark(view);
- view->dot = reg.begin;
- return true;
- }
-
- return false;
-}
-
-void buffer_kill_line(struct buffer_view *view) {
- uint32_t nchars =
- text_line_length(view->buffer->text, view->dot.line) - view->dot.col;
- if (nchars == 0) {
- nchars = 1;
- }
-
- struct region reg = {
- .begin = view->dot,
- .end =
- {
- .line = view->dot.line,
- .col = view->dot.col + nchars,
- },
- };
-
- copy_region(view->buffer, reg);
- delete_with_undo(view->buffer, view->dot,
- (struct buffer_location){
- .line = view->dot.line,
- .col = view->dot.col + nchars,
- });
-}
-
-void buffer_forward_delete_char(struct buffer_view *view) {
- if (maybe_delete_region(view)) {
- return;
- }
-
- delete_with_undo(view->buffer, view->dot,
- (struct buffer_location){
- .line = view->dot.line,
- .col = view->dot.col + 1,
- });
-}
-
-void buffer_backward_delete_char(struct buffer_view *view) {
- if (maybe_delete_region(view)) {
- return;
- }
-
- if (moveh(view, -1)) {
- buffer_forward_delete_char(view);
- }
-}
-
-void buffer_forward_delete_word(struct buffer_view *view) {
- if (maybe_delete_region(view)) {
- return;
- }
-
- struct buffer_location start = view->dot;
- buffer_forward_word(view);
- struct buffer_location end = view->dot;
-
- buffer_goto(view, start.line, start.col);
-
- delete_with_undo(view->buffer, start, end);
-}
-
-void buffer_backward_delete_word(struct buffer_view *view) {
- if (maybe_delete_region(view)) {
+static void delete_with_undo(struct buffer *buffer, struct location start,
+ struct location end) {
+ if (buffer->readonly) {
+ minibuffer_echo_timeout(4, "buffer is read-only");
return;
}
- struct buffer_location end = view->dot;
- buffer_backward_word(view);
- struct buffer_location start = view->dot;
+ struct text_chunk txt =
+ text_get_region(buffer->text, start.line, start.col, end.line, end.col);
- buffer_goto(view, start.line, start.col);
+ undo_push_delete(
+ &buffer->undo,
+ (struct undo_delete){.data = txt.text,
+ .nbytes = txt.nbytes,
+ .pos = {.row = start.line, .col = start.col}});
+ undo_push_boundary(&buffer->undo,
+ (struct undo_boundary){.save_point = false});
- delete_with_undo(view->buffer, start, end);
+ text_delete(buffer->text, start.line, start.col, end.line, end.col);
+ buffer->modified = true;
}
-void buffer_backward_char(struct buffer_view *view) { moveh(view, -1); }
-void buffer_forward_char(struct buffer_view *view) { moveh(view, 1); }
-
-struct buffer_location find_next(struct buffer_view *view, uint8_t chars[],
- uint32_t nchars, int direction) {
- struct text_chunk line = text_get_line(view->buffer->text, view->dot.line);
- int64_t bytei =
- text_col_to_byteindex(view->buffer->text, view->dot.line, view->dot.col);
- while (bytei < line.nbytes && bytei > 0 &&
- (line.text[bytei] == ' ' || line.text[bytei] == '.')) {
- bytei += direction;
- }
-
- for (; bytei < line.nbytes && bytei > 0; bytei += direction) {
- uint8_t b = line.text[bytei];
- if (b == ' ' || b == '.') {
- break;
- }
+static void maybe_delete_region(struct buffer *buffer, struct region region) {
+ if (region_has_size(region)) {
+ delete_with_undo(buffer, region.begin, region.end);
}
-
- uint32_t target_col =
- text_byteindex_to_col(view->buffer->text, view->dot.line, bytei);
- return (struct buffer_location){.line = view->dot.line, .col = target_col};
}
-void buffer_forward_word(struct buffer_view *view) {
- moveh(view, 1);
- uint8_t chars[] = {' ', '.'};
- view->dot = find_next(view, chars, 2, 1);
-}
-
-void buffer_backward_word(struct buffer_view *view) {
- moveh(view, -1);
- uint8_t chars[] = {' ', '.'};
- view->dot = find_next(view, chars, 2, -1);
-}
-
-void buffer_backward_line(struct buffer_view *view) { movev(view, -1); }
-void buffer_forward_line(struct buffer_view *view) { movev(view, 1); }
-
-void buffer_end_of_line(struct buffer_view *view) {
- view->dot.col = text_line_length(view->buffer->text, view->dot.line);
-}
-
-void buffer_beginning_of_line(struct buffer_view *view) { view->dot.col = 0; }
-
-void buffer_read_from_file(struct buffer *b) {
+static void buffer_read_from_file(struct buffer *b) {
struct stat sb;
char *fullname = to_abspath(b->filename);
if (stat(fullname, &sb) == 0) {
@@ -554,10 +203,56 @@ void buffer_read_from_file(struct buffer *b) {
undo_push_boundary(&b->undo, (struct undo_boundary){.save_point = true});
}
-struct buffer buffer_from_file(char *filename) {
- char *full_filename = to_abspath(filename);
- struct buffer b = create_internal(basename((char *)filename), full_filename);
- buffer_read_from_file(&b);
+static void write_line(struct text_chunk *chunk, void *userdata) {
+ FILE *file = (FILE *)userdata;
+ fwrite(chunk->text, 1, chunk->nbytes, file);
+
+ // final newline is not optional!
+ fputc('\n', file);
+}
+
+static struct location find_next(struct buffer *buffer, struct location from,
+ uint8_t chars[], uint32_t nchars,
+ int direction) {
+ struct text_chunk line = text_get_line(buffer->text, from.line);
+ int64_t bytei = text_col_to_byteindex(buffer->text, from.line, from.col);
+ while (bytei < line.nbytes && bytei > 0 &&
+ (line.text[bytei] == ' ' || line.text[bytei] == '.')) {
+ bytei += direction;
+ }
+
+ for (; bytei < line.nbytes && bytei > 0; bytei += direction) {
+ uint8_t b = line.text[bytei];
+ if (b == ' ' || b == '.') {
+ break;
+ }
+ }
+
+ uint32_t target_col = text_byteindex_to_col(buffer->text, from.line, bytei);
+ return (struct location){.line = from.line, .col = target_col};
+}
+
+static struct text_chunk *copy_region(struct buffer *buffer,
+ struct region region) {
+ struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx];
+ g_kill_ring.curr_idx = (g_kill_ring.curr_idx + 1) % KILL_RING_SZ;
+
+ if (curr->allocated) {
+ free(curr->text);
+ }
+
+ struct text_chunk txt =
+ text_get_region(buffer->text, region.begin.line, region.begin.col,
+ region.end.line, region.end.col);
+ *curr = txt;
+ return curr;
+}
+
+/* --------------------- buffer methods -------------------- */
+
+struct buffer buffer_create(const char *name) {
+
+ struct buffer b = create_internal(name, NULL);
for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) {
g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata);
@@ -566,12 +261,16 @@ struct buffer buffer_from_file(char *filename) {
return b;
}
-void write_line(struct text_chunk *chunk, void *userdata) {
- FILE *file = (FILE *)userdata;
- fwrite(chunk->text, 1, chunk->nbytes, file);
+struct buffer buffer_from_file(const char *path) {
+ char *full_path = to_abspath(path);
+ struct buffer b = create_internal(basename((char *)path), full_path);
+ buffer_read_from_file(&b);
- // final newline is not optional!
- fputc('\n', file);
+ for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) {
+ g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata);
+ }
+
+ return b;
}
void buffer_to_file(struct buffer *buffer) {
@@ -609,10 +308,9 @@ void buffer_to_file(struct buffer *buffer) {
undo_push_boundary(&buffer->undo, (struct undo_boundary){.save_point = true});
}
-void buffer_write_to(struct buffer *buffer, const char *filename) {
+void buffer_set_filename(struct buffer *buffer, const char *filename) {
buffer->filename = to_abspath(filename);
buffer->modified = true;
- buffer_to_file(buffer);
}
void buffer_reload(struct buffer *buffer) {
@@ -635,141 +333,152 @@ void buffer_reload(struct buffer *buffer) {
}
}
-struct search_data {
- VEC(struct match) matches;
- const char *pattern;
-};
-
-// TODO: maybe should live in text
-void search_line(struct text_chunk *chunk, void *userdata) {
- struct search_data *data = (struct search_data *)userdata;
- size_t pattern_len = strlen(data->pattern);
- uint32_t pattern_nchars = utf8_nchars((uint8_t *)data->pattern, pattern_len);
-
- char *line = malloc(chunk->nbytes + 1);
- strncpy(line, chunk->text, chunk->nbytes);
- line[chunk->nbytes] = '\0';
- char *hit = NULL;
- uint32_t byteidx = 0;
- while ((hit = strstr(line + byteidx, data->pattern)) != NULL) {
- byteidx = hit - line;
- uint32_t begin = utf8_nchars(chunk->text, byteidx);
- struct match match = (struct match){
- .begin = {.col = begin, .line = chunk->line},
- .end = {.col = begin + pattern_nchars - 1, .line = chunk->line},
- };
-
- VEC_PUSH(&data->matches, match);
-
- // proceed to after match
- byteidx += pattern_len;
- }
-
- free(line);
-}
-
-void buffer_find(struct buffer *buffer, const char *pattern,
- struct match **matches, uint32_t *nmatches) {
+void buffer_destroy(struct buffer *buffer) {
+ text_destroy(buffer->text);
+ buffer->text = NULL;
- struct search_data data = (struct search_data){.pattern = pattern};
- VEC_INIT(&data.matches, 16);
- text_for_each_line(buffer->text, 0, text_num_lines(buffer->text), search_line,
- &data);
+ free(buffer->name);
+ buffer->name = NULL;
- *matches = VEC_ENTRIES(&data.matches);
- *nmatches = VEC_SIZE(&data.matches);
-}
+ free(buffer->filename);
+ buffer->filename = NULL;
-void buffer_set_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) {
- text_clear(buffer->text);
- uint32_t lines, cols;
- text_append(buffer->text, text, nbytes, &lines, &cols);
+ undo_destroy(&buffer->undo);
}
-int buffer_add_text(struct buffer_view *view, uint8_t *text, uint32_t nbytes) {
- if (view->buffer->readonly) {
+struct location buffer_add(struct buffer *buffer, struct location at,
+ uint8_t *text, uint32_t nbytes) {
+ if (buffer->readonly) {
minibuffer_echo_timeout(4, "buffer is read-only");
- return 0;
+ return at;
}
// invalidate last paste
g_kill_ring.paste_up_to_date = false;
- /* If we currently have a selection active,
- * replace it with the text to insert. */
- maybe_delete_region(view);
-
- struct buffer_location initial = view->dot;
+ struct location initial = at;
+ struct location final = at;
uint32_t lines_added, cols_added;
- text_insert_at(view->buffer->text, initial.line, initial.col, text, nbytes,
+ text_insert_at(buffer->text, initial.line, initial.col, text, nbytes,
&lines_added, &cols_added);
// move to after inserted text
- movev(view, lines_added);
+ movev(buffer, lines_added, &final);
if (lines_added > 0) {
// does not make sense to use position from another line
- view->dot.col = 0;
+ final.col = 0;
}
- moveh(view, cols_added);
+ moveh(buffer, cols_added, &final);
- struct buffer_location final = view->dot;
undo_push_add(
- &view->buffer->undo,
+ &buffer->undo,
(struct undo_add){.begin = {.row = initial.line, .col = initial.col},
.end = {.row = final.line, .col = final.col}});
if (lines_added > 0) {
- undo_push_boundary(&view->buffer->undo,
+ undo_push_boundary(&buffer->undo,
(struct undo_boundary){.save_point = false});
}
- view->buffer->modified = true;
- return lines_added;
+ buffer->modified = true;
+ return final;
}
-void buffer_newline(struct buffer_view *view) {
- buffer_add_text(view, (uint8_t *)"\n", 1);
+struct location buffer_set_text(struct buffer *buffer, uint8_t *text,
+ uint32_t nbytes) {
+ uint32_t lines, cols;
+
+ text_clear(buffer->text);
+ text_append(buffer->text, text, nbytes, &lines, &cols);
+ return buffer_clamp(buffer, lines, cols);
}
-void buffer_indent(struct buffer_view *view) {
- uint32_t tab_width = view->buffer->lang.tab_width;
- buffer_add_text(view, (uint8_t *)" ",
- tab_width > 16 ? 16 : tab_width);
+void buffer_clear(struct buffer *buffer) { text_clear(buffer->text); }
+
+bool buffer_is_empty(struct buffer *buffer) {
+ return text_num_lines(buffer->text) == 0;
}
-uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook,
- void *userdata) {
- struct update_hook *h =
- &buffer->update_hooks.hooks[buffer->update_hooks.nhooks];
- h->callback = hook;
- h->userdata = userdata;
+bool buffer_is_modified(struct buffer *buffer) { return buffer->modified; }
+bool buffer_is_readonly(struct buffer *buffer) { return buffer->readonly; }
- ++buffer->update_hooks.nhooks;
+void buffer_set_readonly(struct buffer *buffer, bool readonly) {
+ buffer->readonly = readonly;
+}
- // TODO: cant really have this if we actually want to remove a hook
- return buffer->update_hooks.nhooks - 1;
+bool buffer_is_backed(struct buffer *buffer) {
+ return buffer->filename != NULL;
+}
+
+struct location buffer_previous_char(struct buffer *buffer,
+ struct location dot) {
+ moveh(buffer, -1, &dot);
+ return dot;
+}
+
+struct location buffer_previous_word(struct buffer *buffer,
+ struct location dot) {
+ moveh(buffer, -1, &dot);
+ uint8_t chars[] = {' ', '.'};
+ return find_next(buffer, dot, chars, 2, -1);
}
-void buffer_set_mark(struct buffer_view *view) {
- view->mark_set ? buffer_clear_mark(view)
- : buffer_set_mark_at(view, view->dot.line, view->dot.col);
+struct location buffer_previous_line(struct buffer *buffer,
+ struct location dot) {
+ movev(buffer, -1, &dot);
+ return dot;
}
-void buffer_clear_mark(struct buffer_view *view) {
- view->mark_set = false;
- minibuffer_echo_timeout(2, "mark cleared");
+struct location buffer_next_char(struct buffer *buffer, struct location dot) {
+ moveh(buffer, 1, &dot);
+ return dot;
+}
+
+struct location buffer_next_word(struct buffer *buffer, struct location dot) {
+ moveh(buffer, 1, &dot);
+ uint8_t chars[] = {' ', '.'};
+ return find_next(buffer, dot, chars, 2, 1);
}
-void buffer_set_mark_at(struct buffer_view *view, uint32_t line, uint32_t col) {
- view->mark_set = true;
- view->mark.line = line;
- view->mark.col = col;
- minibuffer_echo_timeout(2, "mark set");
+struct location buffer_next_line(struct buffer *buffer, struct location dot) {
+ movev(buffer, 1, &dot);
+ return dot;
}
-void buffer_undo(struct buffer_view *view) {
- struct undo_stack *undo = &view->buffer->undo;
+struct location buffer_clamp(struct buffer *buffer, int64_t line, int64_t col) {
+ struct location location = {.line = 0, .col = 0};
+ movev(buffer, line, &location);
+ moveh(buffer, col, &location);
+
+ return location;
+}
+
+struct location buffer_end(struct buffer *buffer) {
+ uint32_t nlines = buffer_num_lines(buffer);
+ return (struct location){nlines, buffer_num_chars(buffer, nlines)};
+}
+
+uint32_t buffer_num_lines(struct buffer *buffer) {
+ return text_num_lines(buffer->text);
+}
+
+uint32_t buffer_num_chars(struct buffer *buffer, uint32_t line) {
+ return text_line_length(buffer->text, line);
+}
+
+struct location buffer_newline(struct buffer *buffer, struct location at) {
+ return buffer_add(buffer, at, (uint8_t *)"\n", 1);
+}
+
+struct location buffer_indent(struct buffer *buffer, struct location at) {
+ uint32_t tab_width = buffer->lang.tab_width;
+ buffer_add(buffer, at, (uint8_t *)" ",
+ tab_width > 16 ? 16 : tab_width);
+}
+
+struct location buffer_undo(struct buffer *buffer, struct location dot) {
+ struct undo_stack *undo = &buffer->undo;
undo_begin(undo);
// fetch and handle records
@@ -783,62 +492,213 @@ void buffer_undo(struct buffer_view *view) {
undo_next(undo, &records, &nrecords);
+ struct location pos = dot;
undo_push_boundary(undo, (struct undo_boundary){.save_point = false});
for (uint32_t reci = 0; reci < nrecords; ++reci) {
struct undo_record *rec = &records[reci];
switch (rec->type) {
+
case Undo_Boundary: {
struct undo_boundary *b = &rec->boundary;
if (b->save_point) {
- view->buffer->modified = false;
+ buffer->modified = false;
}
break;
}
+
case Undo_Add: {
struct undo_add *add = &rec->add;
- delete_with_undo(view->buffer,
- (struct buffer_location){
- .line = add->begin.row,
- .col = add->begin.col,
- },
- (struct buffer_location){
- .line = add->end.row,
- .col = add->end.col,
- });
+ pos =
+ buffer_delete(buffer, (struct region){.begin =
+ (struct location){
+ .line = add->begin.row,
+ .col = add->begin.col,
+ },
+ .end = (struct location){
+ .line = add->end.row,
+ .col = add->end.col,
+ }});
- buffer_goto(view, add->begin.row, add->begin.col);
break;
}
+
case Undo_Delete: {
struct undo_delete *del = &rec->delete;
- buffer_goto(view, del->pos.row, del->pos.col);
- buffer_add_text(view, del->data, del->nbytes);
+ pos = buffer_add(buffer,
+ (struct location){
+ .line = del->pos.row,
+ .col = del->pos.col,
+ },
+ del->data, del->nbytes);
break;
}
}
}
+
undo_push_boundary(undo, (struct undo_boundary){.save_point = false});
free(records);
undo_end(undo);
+
+ return pos;
+}
+
+/* --------------- searching and supporting types ---------------- */
+struct search_data {
+ VEC(struct region) matches;
+ const char *pattern;
+};
+
+// TODO: maybe should live in text
+static void search_line(struct text_chunk *chunk, void *userdata) {
+ struct search_data *data = (struct search_data *)userdata;
+ size_t pattern_len = strlen(data->pattern);
+ uint32_t pattern_nchars = utf8_nchars((uint8_t *)data->pattern, pattern_len);
+
+ char *line = malloc(chunk->nbytes + 1);
+ strncpy(line, chunk->text, chunk->nbytes);
+ line[chunk->nbytes] = '\0';
+ char *hit = NULL;
+ uint32_t byteidx = 0;
+ while ((hit = strstr(line + byteidx, data->pattern)) != NULL) {
+ byteidx = hit - line;
+ uint32_t begin = utf8_nchars(chunk->text, byteidx);
+ struct region match =
+ region_new((struct location){.col = begin, .line = chunk->line},
+ (struct location){.col = begin + pattern_nchars - 1,
+ .line = chunk->line});
+ VEC_PUSH(&data->matches, match);
+
+ // proceed to after match
+ byteidx += pattern_len;
+ }
+
+ free(line);
+}
+
+void buffer_find(struct buffer *buffer, const char *pattern,
+ struct region **matches, uint32_t *nmatches) {
+
+ struct search_data data = (struct search_data){.pattern = pattern};
+ VEC_INIT(&data.matches, 16);
+ text_for_each_line(buffer->text, 0, text_num_lines(buffer->text), search_line,
+ &data);
+
+ *matches = VEC_ENTRIES(&data.matches);
+ *nmatches = VEC_SIZE(&data.matches);
+}
+
+struct location buffer_copy(struct buffer *buffer, struct region region) {
+ if (region_has_size(region)) {
+ struct text_chunk *curr = copy_region(buffer, region);
+ }
+
+ return region.begin;
+}
+
+struct location buffer_cut(struct buffer *buffer, struct region region) {
+ if (region_has_size(region)) {
+ copy_region(buffer, region);
+ buffer_delete(buffer, region);
+ }
+
+ return region.begin;
+}
+
+struct location buffer_delete(struct buffer *buffer, struct region region) {
+ if (buffer->readonly) {
+ minibuffer_echo_timeout(4, "buffer is read-only");
+ return region.end;
+ }
+
+ if (!region_has_size(region)) {
+ return region.begin;
+ }
+
+ struct text_chunk txt =
+ text_get_region(buffer->text, region.begin.line, region.begin.col,
+ region.end.line, region.end.col);
+
+ undo_push_delete(&buffer->undo,
+ (struct undo_delete){.data = txt.text,
+ .nbytes = txt.nbytes,
+ .pos = {.row = region.begin.line,
+ .col = region.begin.col}});
+ undo_push_boundary(&buffer->undo,
+ (struct undo_boundary){.save_point = false});
+
+ text_delete(buffer->text, region.begin.line, region.begin.col,
+ region.end.line, region.end.col);
+ buffer->modified = true;
+
+ return region.begin;
+}
+
+static struct location paste(struct buffer *buffer, struct location at,
+ uint32_t ring_idx) {
+ struct location new_loc = at;
+ if (ring_idx > 0) {
+ struct text_chunk *curr = &g_kill_ring.buffer[ring_idx - 1];
+ if (curr->text != NULL) {
+ g_kill_ring.last_paste = at;
+ new_loc = buffer_add(buffer, at, curr->text, curr->nbytes);
+ g_kill_ring.paste_up_to_date = true;
+ }
+ }
+
+ return new_loc;
+}
+
+struct location buffer_paste(struct buffer *buffer, struct location at) {
+ g_kill_ring.paste_idx = g_kill_ring.curr_idx;
+ return paste(buffer, at, g_kill_ring.curr_idx);
+}
+
+struct location buffer_paste_older(struct buffer *buffer, struct location at) {
+ if (g_kill_ring.paste_up_to_date) {
+
+ // remove previous paste
+ struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx];
+ delete_with_undo(buffer, g_kill_ring.last_paste, at);
+
+ // paste older
+ if (g_kill_ring.paste_idx - 1 > 0) {
+ --g_kill_ring.paste_idx;
+ } else {
+ g_kill_ring.paste_idx = g_kill_ring.curr_idx;
+ }
+
+ paste(buffer, g_kill_ring.last_paste, g_kill_ring.paste_idx);
+
+ } else {
+ buffer_paste(buffer, at);
+ }
+}
+
+struct text_chunk buffer_line(struct buffer *buffer, uint32_t line) {
+ return text_get_line(buffer->text, line);
+}
+
+uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook,
+ void *userdata) {
+ VEC_APPEND(&buffer->update_hooks, struct update_hook_entry * e);
+ struct update_hook *h = &e->hook;
+ h->callback = hook;
+ h->userdata = userdata;
+
+ // TODO: cant really have this if we actually want to remove a hook
+ return VEC_SIZE(&buffer->update_hooks) - 1;
}
struct cmdbuf {
struct command_list *cmds;
- struct buffer_location scroll;
- uint32_t line_offset;
- uint32_t left_margin;
+ struct location origin;
uint32_t width;
-
- struct region region;
- bool mark_set;
+ uint32_t height;
bool show_ws;
- struct line_render_hook *line_render_hooks;
- uint32_t nlinerender_hooks;
-
struct buffer *buffer;
};
@@ -865,33 +725,26 @@ static uint32_t visual_string_width(uint8_t *txt, uint32_t len,
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;
-
- for (uint32_t hooki = 0; hooki < cmdbuf->nlinerender_hooks; ++hooki) {
- struct line_render_hook *hook = &cmdbuf->line_render_hooks[hooki];
- hook->callback(line, visual_line, cmdbuf->cmds, hook->userdata);
- }
+ uint32_t visual_line = line->line - cmdbuf->origin.line;
command_list_set_show_whitespace(cmdbuf->cmds, cmdbuf->show_ws);
- struct buffer_location *begin = &cmdbuf->region.begin,
- *end = &cmdbuf->region.end;
// calculate scroll offsets
uint32_t scroll_bytes =
- utf8_nbytes(line->text, line->nbytes, cmdbuf->scroll.col);
+ utf8_nbytes(line->text, line->nbytes, cmdbuf->origin.col);
uint32_t text_nbytes_scroll =
scroll_bytes > line->nbytes ? 0 : line->nbytes - scroll_bytes;
uint8_t *text = line->text + scroll_bytes;
- uint32_t visual_col_start = cmdbuf->left_margin;
- uint32_t cur_visual_col = visual_col_start;
+ uint32_t visual_col_start = 0;
+ uint32_t cur_visual_col = 0;
uint32_t start_byte = 0, text_nbytes = 0;
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;
+ coli < line->nchars - cmdbuf->origin.col;
++coli) {
uint32_t bytes_remaining = text_nbytes_scroll - cur_byte;
@@ -900,10 +753,9 @@ void render_line(struct text_chunk *line, void *userdata) {
// calculate character properties
uint32_t nproperties = 0;
- buffer_get_text_properties(
- cmdbuf->buffer,
- (struct buffer_location){.line = line->line, .col = coli}, properties,
- 16, &nproperties);
+ text_get_properties(cmdbuf->buffer->text,
+ (struct location){.line = line->line, .col = coli + cmdbuf->origin.col},
+ properties, 16, &nproperties);
// handle changes to properties
uint32_t nnew_props = 0;
@@ -967,327 +819,55 @@ void render_line(struct text_chunk *line, void *userdata) {
}
}
-void scroll(struct buffer_view *view, int line_delta, int col_delta) {
- uint32_t nlines = text_num_lines(view->buffer->text);
- int64_t new_line = (int64_t)view->scroll.line + line_delta;
- if (new_line >= 0 && new_line < nlines) {
- view->scroll.line = (uint32_t)new_line;
- } else if (new_line < 0) {
- view->scroll.line = 0;
- }
-
- int64_t new_col = (int64_t)view->scroll.col + col_delta;
- if (new_col >= 0 &&
- new_col < text_line_length(view->buffer->text, view->dot.line)) {
- view->scroll.col = (uint32_t)new_col;
- } else if (new_col < 0) {
- view->scroll.col = 0;
- }
-}
-
-void to_relative(struct buffer_view *view, uint32_t line, uint32_t col,
- int64_t *rel_line, int64_t *rel_col) {
- *rel_col = (int64_t)col - (int64_t)view->scroll.col;
- *rel_line = (int64_t)line - (int64_t)view->scroll.line;
-}
-
-uint32_t visual_dot_col(struct buffer_view *view, uint32_t dot_col) {
- struct text_chunk line = text_get_line(view->buffer->text, view->dot.line);
- return visual_string_width(line.text, line.nbytes, view->scroll.col, dot_col);
-}
-
-void render_modeline(struct modeline *modeline, struct buffer_view *view,
- struct command_list *commands, uint32_t window_id,
- uint32_t width, uint32_t height, uint64_t frame_time) {
- char buf[width * 4];
-
- static uint64_t samples[10] = {0};
- static uint32_t samplei = 0;
- static uint64_t avg = 0;
-
- // calc a moving average with a window of the last 10 frames
- ++samplei;
- samplei %= 10;
- avg += 0.1 * (frame_time - samples[samplei]);
- samples[samplei] = frame_time;
-
- time_t now = time(NULL);
- struct tm *lt = localtime(&now);
- char left[128], right[128];
-
- snprintf(left, 128, " %c%c %d:%-16s (%d, %d) (%s)",
- view->buffer->modified ? '*' : '-',
- view->buffer->readonly ? '%' : '-', window_id, view->buffer->name,
- view->dot.line + 1, visual_dot_col(view, view->dot.col),
- view->buffer->lang.name);
- snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour,
- lt->tm_min);
-
- snprintf(buf, width * 4, "%s%*s%s", left,
- (int)(width - (strlen(left) + strlen(right))), "", right);
-
- if (strcmp(buf, (char *)modeline->buffer) != 0) {
- modeline->buffer = realloc(modeline->buffer, width * 4);
- modeline->sz = width * 4;
- strcpy((char *)modeline->buffer, buf);
- }
-
- command_list_set_index_color_bg(commands, 8);
- command_list_draw_text(commands, 0, height - 1, modeline->buffer,
- strlen((char *)modeline->buffer));
- command_list_reset_color(commands);
-}
-
-struct linenumdata {
- uint32_t longest_nchars;
- uint32_t dot_line;
-} linenum_data;
-
-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, 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);
- command_list_reset_color(commands);
- command_list_draw_text(commands, data->longest_nchars + 1, line,
- (uint8_t *)" ", 1);
-}
-
-void clear_empty_linenum_lines(uint32_t line, struct command_list *commands,
- void *userdata) {
- struct linenumdata *data = (struct linenumdata *)userdata;
- uint32_t longest_nchars = data->longest_nchars;
- command_list_draw_repeated(commands, 0, line, ' ', longest_nchars + 2);
-}
-
-uint32_t longest_linenum(struct buffer *buffer) {
- uint32_t total_lines = text_num_lines(buffer->text);
- uint32_t longest_nchars = 10;
- if (total_lines < 10) {
- longest_nchars = 1;
- } else if (total_lines < 100) {
- longest_nchars = 2;
- } else if (total_lines < 1000) {
- longest_nchars = 3;
- } else if (total_lines < 10000) {
- longest_nchars = 4;
- } else if (total_lines < 100000) {
- longest_nchars = 5;
- } else if (total_lines < 1000000) {
- longest_nchars = 6;
- } else if (total_lines < 10000000) {
- longest_nchars = 7;
- } else if (total_lines < 100000000) {
- longest_nchars = 8;
- } else if (total_lines < 1000000000) {
- longest_nchars = 9;
- }
-
- return longest_nchars;
-}
-
-void buffer_update(struct buffer_view *view, uint32_t window_id, uint32_t width,
- uint32_t height, struct command_list *commands,
- uint64_t frame_time, uint32_t *relline, uint32_t *relcol) {
- if (width == 0 || height == 0) {
+void buffer_update(struct buffer *buffer, struct buffer_update_params *params) {
+ if (params->width == 0 || params->height == 0) {
return;
}
- uint32_t total_width = width, total_height = height;
- struct margin total_margins = {0};
- struct line_render_hook line_hooks[16 + 1];
- uint32_t nlinehooks = 0;
- for (uint32_t hooki = 0; hooki < view->buffer->update_hooks.nhooks; ++hooki) {
- struct update_hook *h = &view->buffer->update_hooks.hooks[hooki];
- struct update_hook_result res =
- h->callback(view, commands, width, height, frame_time, h->userdata);
-
- if (res.line_render_hook.callback != NULL) {
- line_hooks[nlinehooks] = res.line_render_hook;
- ++nlinehooks;
- }
-
- total_margins.left += res.margins.left;
- total_margins.right += res.margins.right;
- total_margins.bottom += res.margins.bottom;
- total_margins.top += res.margins.top;
-
- height -= total_margins.top + total_margins.bottom;
- width -= total_margins.left + total_margins.right;
- }
-
- if (view->line_numbers) {
- linenum_data.longest_nchars = longest_linenum(view->buffer);
- linenum_data.dot_line = view->dot.line;
- line_hooks[nlinehooks].callback = linenum_render_hook;
- line_hooks[nlinehooks].empty_callback = clear_empty_linenum_lines;
- line_hooks[nlinehooks].userdata = &linenum_data;
- ++nlinehooks;
-
- total_margins.left += linenum_data.longest_nchars + 2;
- }
-
- if (view->modeline != NULL) {
- render_modeline(view->modeline, view, commands, window_id, width, height,
- frame_time);
- total_margins.bottom += 1;
- }
-
- height -= total_margins.top + total_margins.bottom;
- width -= total_margins.left + total_margins.right;
-
- int64_t rel_line, rel_col;
- to_relative(view, view->dot.line, view->dot.col, &rel_line, &rel_col);
- int line_delta = 0, col_delta = 0;
- if (rel_line < 0) {
- line_delta = rel_line - ((int)height / 2);
- } else if (rel_line >= height) {
- line_delta = (rel_line - height) + height / 2;
+ VEC_FOR_EACH(&buffer->update_hooks, struct update_hook_entry * entry) {
+ struct update_hook *h = &entry->hook;
+ h->callback(buffer, params->width, params->height, h->userdata);
}
- if (rel_col < 0) {
- col_delta = rel_col - ((int)width / 2);
- } else if (rel_col >= width) {
- col_delta = (rel_col - width) + width / 2;
- }
-
- scroll(view, line_delta, col_delta);
-
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,
- .left_margin = total_margins.left,
- .width = total_width,
- .line_offset = total_margins.top,
- .line_render_hooks = line_hooks,
- .nlinerender_hooks = nlinehooks,
- .mark_set = view->mark_set,
- .region = to_region(view->dot, view->mark),
+ .cmds = params->commands,
+ .origin = params->origin,
+ .width = params->width,
+ .height = params->height,
.show_ws = show_ws != NULL ? show_ws->value.bool_value : true,
- .buffer = view->buffer,
+ .buffer = buffer,
};
- text_for_each_line(view->buffer->text, view->scroll.line, height, render_line,
- &cmdbuf);
+ text_for_each_line(buffer->text, params->origin.line, params->height,
+ render_line, &cmdbuf);
// draw empty lines
- uint32_t nlines = text_num_lines(view->buffer->text);
- for (uint32_t linei = nlines - view->scroll.line + total_margins.top;
- linei < height; ++linei) {
-
- for (uint32_t hooki = 0; hooki < nlinehooks; ++hooki) {
- struct line_render_hook *hook = &line_hooks[hooki];
- if (hook->empty_callback != NULL) {
- hook->empty_callback(linei, commands, hook->userdata);
- }
- }
-
- command_list_draw_repeated(commands, total_margins.left, linei, ' ',
- total_width - total_margins.left);
+ uint32_t nlines = text_num_lines(buffer->text);
+ for (uint32_t linei = nlines - params->origin.line; linei < params->height;
+ ++linei) {
+ command_list_draw_repeated(params->commands, 0, linei, ' ', params->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;
-}
-
-struct text_chunk buffer_get_line(struct buffer *buffer, uint32_t line) {
- return text_get_line(buffer->text, line);
-}
-
-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,
+void buffer_add_text_property(struct buffer *buffer, struct location start,
+ struct location end,
struct text_property property) {
- struct text_property_entry entry = {
- .start = start,
- .end = end,
- .property = property,
- };
- VEC_PUSH(&buffer->text_properties, entry);
+ text_add_property(
+ buffer->text, (struct location){.line = start.line, .col = start.col},
+ (struct location){.line = end.line, .col = end.col}, property);
}
-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,
+void buffer_get_text_properties(struct buffer *buffer, struct 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;
+ text_get_properties(
+ buffer->text,
+ (struct location){.line = location.line, .col = location.col}, properties,
+ max_nproperties, nproperties);
+}
- if (nres == max_nproperties) {
- break;
- }
- }
- }
- *nproperties = nres;
+void buffer_clear_text_properties(struct buffer *buffer) {
+ text_clear_properties(buffer->text);
}
diff --git a/src/dged/buffer.h b/src/dged/buffer.h
index 28d9797..e29e3e1 100644
--- a/src/dged/buffer.h
+++ b/src/dged/buffer.h
@@ -1,3 +1,6 @@
+#ifndef _BUFFER_H
+#define _BUFFER_H
+
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
@@ -5,81 +8,22 @@
#include "command.h"
#include "lang.h"
+#include "location.h"
#include "text.h"
#include "undo.h"
#include "window.h"
-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
- */
-struct margin {
- uint32_t left;
- uint32_t right;
- uint32_t top;
- uint32_t bottom;
-};
-
-/** Callback for line rendering hooks */
-typedef void (*line_render_cb)(struct text_chunk *line_data, uint32_t line,
- struct command_list *commands, void *userdata);
-
-typedef void (*line_render_empty_cb)(uint32_t line,
- struct command_list *commands,
- void *userdata);
-
-/**
- * A line render hook
- *
- * A callback paired with userdata
- */
-struct line_render_hook {
- line_render_cb callback;
- line_render_empty_cb empty_callback;
- void *userdata;
-};
-
-/**
- * Result of updating a buffer hook
- */
-struct update_hook_result {
- /** Desired margins for this hook */
- struct margin margins;
-
- /** Hook to be added to rendering of buffer lines */
- struct line_render_hook line_render_hook;
-};
-
/** Buffer update hook callback function */
-typedef struct update_hook_result (*update_hook_cb)(
- struct buffer_view *view, struct command_list *commands, uint32_t width,
- uint32_t height, uint64_t frame_time, void *userdata);
+typedef void (*update_hook_cb)(struct buffer *buffer, uint32_t width,
+ uint32_t height, void *userdata);
/**
* A buffer update hook.
*
* Can be used to implement custom behavior on top of a buffer. Used for
- * minibuffer, line numbers, modeline etc.
+ * minibuffer.
*/
struct update_hook {
/** Callback function */
@@ -89,67 +33,9 @@ struct update_hook {
void *userdata;
};
-typedef void (*create_hook_cb)(struct buffer *buffer, void *userdata);
-
-/**
- * A set of update hooks
- */
-struct update_hooks {
- /** The update hooks */
- struct update_hook hooks[32];
-
- /** The number of update hooks */
- uint32_t nhooks;
-};
-
-struct buffer_location {
- uint32_t line;
- 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;
-};
-
-struct buffer_view {
- /** Location of dot (cursor) */
- struct buffer_location dot;
-
- /** Location of mark (where a selection starts) */
- struct buffer_location mark;
-
- /** Current buffer scroll position */
- struct buffer_location scroll;
-
- /** True if the start of a selection has been set */
- bool mark_set;
-
- /** Modeline buffer (may be NULL) */
- struct modeline *modeline;
-
- bool line_numbers;
-
- struct buffer *buffer;
-};
-
-struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline,
- bool line_numbers);
-struct buffer_view buffer_view_clone(struct buffer_view *view);
-
-void buffer_view_scroll_down(struct buffer_view *view, uint32_t height);
-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;
+struct update_hook_entry {
+ uint32_t id;
+ struct update_hook hook;
};
/**
@@ -166,103 +52,411 @@ struct buffer {
/** Associated filename, this is where the buffer will be saved to */
char *filename;
+ /** Time when buffer was last written to disk */
struct timespec last_write;
/** Text data structure */
struct text *text;
/** Buffer update hooks */
- struct update_hooks update_hooks;
+ VEC(struct update_hook_entry) update_hooks;
/** Buffer undo stack */
struct undo_stack undo;
+ /** Buffer programming language */
+ struct language lang;
+
/** Has this buffer been modified from when it was last saved */
bool modified;
/** Can this buffer be changed */
bool readonly;
+};
- /** Buffer programming language */
- struct language lang;
+void buffer_static_init();
+void buffer_static_teardown();
- VEC(struct text_property_entry) text_properties;
-};
+/**
+ * Create a new buffer.
+ *
+ * @param [in] name The buffer name.
+ * @returns A new buffer
+ */
+struct buffer buffer_create(const char *name);
+
+/**
+ * Create a new buffer from a file path.
+ *
+ * @param [in] path Path to the file to load into the new buffer.
+ * @returns A new buffer with @ref path loaded.
+ */
+struct buffer buffer_from_file(const char *path);
+
+/**
+ * Save buffer to the backing file.
+ *
+ * @param [in] buffer Buffer to save.
+ */
+void buffer_to_file(struct buffer *buffer);
-struct buffer buffer_create(char *name);
+/**
+ * Set path to backing file for buffer.
+ *
+ * The backing file is used when writing the buffer to a file.
+ * @param [in] buffer The buffer to set filename for.
+ * @param [in] filename The filename to use. It is required that this is a full
+ * path.
+ */
+void buffer_set_filename(struct buffer *buffer, const char *filename);
+
+/**
+ * Reload the buffer from disk.
+ *
+ * Reload the buffer from the backing file.
+ * @param [in] buffer The buffer to reload.
+ */
+void buffer_reload(struct buffer *buffer);
+
+/**
+ * Destroy the buffer.
+ *
+ * Destroy the buffer, releasing all associated resources.
+ * @param [in] buffer The buffer to destroy.
+ */
void buffer_destroy(struct buffer *buffer);
-void buffer_static_init();
-void buffer_static_teardown();
+/**
+ * Add text to the buffer at the specified location.
+ *
+ * @param [in] buffer The buffer to add text to.
+ * @param [in] at The location to add text at.
+ * @param [in] text Pointer to the text bytes, not NULL-terminated.
+ * @param [in] nbytes Number of bytes in @ref text.
+ *
+ * @returns The location at the end of the inserted text.
+ */
+struct location buffer_add(struct buffer *buffer, struct location at,
+ uint8_t *text, uint32_t nbytes);
+
+/**
+ * Set the entire text contents of the buffer.
+ *
+ * @param [in] buffer The buffer to set text for.
+ * @param [in] text Pointer to the text bytes, not NULL-terminated.
+ * @param [in] nbytes Number of bytes in @ref text.
+ *
+ * @returns The location at the end of the inserted text
+ */
+struct location buffer_set_text(struct buffer *buffer, uint8_t *text,
+ uint32_t nbytes);
+
+/**
+ * Clear all text in the buffer
+ *
+ * @param [in] buffer The buffer to clear.
+ */
+void buffer_clear(struct buffer *buffer);
-int buffer_add_text(struct buffer_view *view, uint8_t *text, uint32_t nbytes);
-void buffer_set_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes);
-void buffer_clear(struct buffer_view *view);
+/**
+ * Does buffer contain any text?
+ *
+ * @param [in] buffer The buffer to check.
+ * @returns True if the buffer is empty (has no text in it), false otherwise.
+ */
bool buffer_is_empty(struct buffer *buffer);
+
+/**
+ * Has the buffer been modified since it was last retrieved from/saved to disk?
+ *
+ * @param [in] buffer The buffer to examine.
+ * @returns True if the buffer has been modified, false otherwise.
+ */
bool buffer_is_modified(struct buffer *buffer);
+
+/**
+ * Is this buffer read-only?
+ *
+ * @param [in] buffer The buffer to examine.
+ * @returns True if the buffer is read-only (cannot be modified), false
+ * otherwise.
+ */
bool buffer_is_readonly(struct buffer *buffer);
+
+/**
+ * Set the read-only status for the buffer.
+ *
+ * @param [in] buffer The buffer to set read-only for.
+ * @param [in] readonly If true, the buffer is set to read-only, otherwise it is
+ * set to writable.
+ */
void buffer_set_readonly(struct buffer *buffer, bool readonly);
+
+/**
+ * Is the buffer backed by a file on disk?
+ *
+ * @param [in] buffer The buffer to examine.
+ * @returns True if the buffer has a path to a file on disk to use as backing
+ * file, false otherwise. Note that this function returns true even if the
+ * buffer has never been written to the backing file.
+ */
bool buffer_is_backed(struct buffer *buffer);
-void buffer_kill_line(struct buffer_view *view);
-void buffer_forward_delete_char(struct buffer_view *view);
-void buffer_forward_delete_word(struct buffer_view *view);
-void buffer_backward_delete_char(struct buffer_view *view);
-void buffer_backward_delete_word(struct buffer_view *view);
-void buffer_backward_char(struct buffer_view *view);
-void buffer_backward_word(struct buffer_view *view);
-void buffer_forward_char(struct buffer_view *view);
-void buffer_forward_word(struct buffer_view *view);
-void buffer_backward_line(struct buffer_view *view);
-void buffer_forward_line(struct buffer_view *view);
-void buffer_end_of_line(struct buffer_view *view);
-void buffer_beginning_of_line(struct buffer_view *view);
-void buffer_newline(struct buffer_view *view);
-void buffer_indent(struct buffer_view *view);
-
-void buffer_undo(struct buffer_view *view);
-
-void buffer_goto_beginning(struct buffer_view *view);
-void buffer_goto_end(struct buffer_view *view);
-void buffer_goto(struct buffer_view *view, uint32_t line, uint32_t col);
+/**
+ * Get location of previous character in buffer.
+ *
+ * @param [in] buffer The buffer to use.
+ * @param [in] dot The location to start from.
+ * @returns The location in front of the previous char given @ref dot.
+ */
+struct location buffer_previous_char(struct buffer *buffer,
+ struct location dot);
+
+/**
+ * Get location of previous word in buffer.
+ *
+ * @param [in] buffer The buffer to look in.
+ * @param [in] dot The location to start from.
+ * @returns The location at the start of the previous word, given @ref dot.
+ */
+struct location buffer_previous_word(struct buffer *buffer,
+ struct location dot);
+
+/**
+ * Get location of previous line.
+ *
+ * @param [in] buffer The buffer to look in.
+ * @param [in] dot The location to start from.
+ * @returns The location at the start of the line above the current one (the one
+ * @ref dot is on). If @ref dot is on the first line, the location (0, 0) is
+ * returned.
+ */
+struct location buffer_previous_line(struct buffer *buffer,
+ struct location dot);
+
+/**
+ * Get location of next character in buffer.
+ *
+ * @param [in] buffer The buffer to use.
+ * @param [in] dot The location to start from.
+ * @returns The location in front of the next char given @ref dot.
+ */
+struct location buffer_next_char(struct buffer *buffer, struct location dot);
+
+/**
+ * Get location of next word in buffer.
+ *
+ * @param [in] buffer The buffer to look in.
+ * @param [in] dot The location to start from.
+ * @returns The location at the start of the next word, given @ref dot.
+ */
+struct location buffer_next_word(struct buffer *buffer, struct location dot);
+
+/**
+ * Get location of next line.
+ *
+ * @param [in] buffer The buffer to look in.
+ * @param [in] dot The location to start from.
+ * @returns The location at the start of the line above the current one (the one
+ * @ref dot is on). If @ref dot is on the last line, the last location in the
+ * buffer is returned.
+ */
+struct location buffer_next_line(struct buffer *buffer, struct location dot);
+
+/**
+ * Clamp a buffer position to the boundaries of the buffer.
+ *
+ * Note that both @ref line and @ref col can be negative or bigger than the
+ * buffer.
+ *
+ * @param [in] buffer The buffer to use for clamping.
+ * @param [in] line The line position to clamp.
+ * @param [in] col The column position to clamp.
+ * @returns The closest position inside the buffer matching (line, col).
+ */
+struct location buffer_clamp(struct buffer *buffer, int64_t line, int64_t col);
+
+/**
+ * Get location of buffer end.
+ *
+ * @param [in] buffer The buffer to use.
+ * @returns the position after the last character in @ref buffer.
+ */
+struct location buffer_end(struct buffer *buffer);
+
+/**
+ * Get the number of lines in the buffer
+ *
+ * @param [in] buffer The buffer to use.
+ * @returns The number of lines in @ref buffer.
+ */
+uint32_t buffer_num_lines(struct buffer *buffer);
+
+/**
+ * Get the number of chars in a given line in buffer.
+ *
+ * @param [in] buffer The buffer to use.
+ * @param [in] line The line to get number of chars for.
+ * @returns The number of chars in @ref line.
+ */
+uint32_t buffer_num_chars(struct buffer *buffer, uint32_t line);
+
+/**
+ * Insert a newline in the buffer.
+ *
+ * @param [in] buffer The buffer to insert in.
+ * @param [in] at The point to insert at.
+ * @returns The location at the start of the new line.
+ */
+struct location buffer_newline(struct buffer *buffer, struct location at);
+
+/**
+ * Insert indentation in the buffer.
+ *
+ * @param [in] buffer The buffer to indent in.
+ * @param [in] at The location to insert indentation at.
+ * @returns The position after indenting.
+ */
+struct location buffer_indent(struct buffer *buffer, struct location at);
+
+/**
+ * Undo the last operation in the buffer.
+ *
+ * @param [in] buffer The buffer to undo in.
+ * @param [in] dot The point to undo at.
+ * @returns The position in the buffer after undo.
+ */
+struct location buffer_undo(struct buffer *buffer, struct location dot);
+/**
+ * Search for a substring in the buffer.
+ *
+ * @param [in] buffer The buffer to search in.
+ * @param [in] pattern The substring to search for.
+ * @param [out] matches The pointer passed in is modified to point at the
+ * resulting matches. This pointer should be freed using @ref free.
+ * @param [nmatches] nmatches The pointer passed in is modified to point at the
+ * number of resulting matches.
+ */
void buffer_find(struct buffer *buffer, const char *pattern,
- struct match **matches, uint32_t *nmatches);
+ struct region **matches, uint32_t *nmatches);
-void buffer_set_mark(struct buffer_view *view);
-void buffer_clear_mark(struct buffer_view *view);
-void buffer_set_mark_at(struct buffer_view *view, uint32_t line, uint32_t col);
+/**
+ * Copy a region in the buffer into the kill ring.
+ *
+ * @param [in] buffer The buffer to copy from.
+ * @param [in] region The region to copy.
+ * @returns The position in the buffer after the copy.
+ */
+struct location buffer_copy(struct buffer *buffer, struct region region);
-void buffer_copy(struct buffer_view *view);
-void buffer_paste(struct buffer_view *view);
-void buffer_paste_older(struct buffer_view *view);
-void buffer_cut(struct buffer_view *view);
+/**
+ * Cut a region in the buffer into the kill ring.
+ *
+ * @param [in] buffer The buffer to cut from.
+ * @param [in] region The region to cut.
+ * @returns The position in the buffer after the cut.
+ */
+struct location buffer_cut(struct buffer *buffer, struct region region);
-void buffer_clear_text_properties(struct buffer *buffer);
+/**
+ * Delete a region in the buffer without putting it into the kill ring.
+ *
+ * @param [in] buffer The buffer to delete from.
+ * @param [in] region The region to delete.
+ * @returns The position in the buffer after the delete.
+ */
+struct location buffer_delete(struct buffer *buffer, struct region region);
+
+/**
+ * Paste from the kill ring into the buffer.
+ *
+ * @param [in] buffer Buffer to paste in.
+ * @param [in] at Location to paste at.
+ * @returns The location in the buffer after the paste.
+ */
+struct location buffer_paste(struct buffer *buffer, struct location at);
+
+/**
+ * Paste the next older entry from the kill ring into the buffer.
+ *
+ * @param [in] buffer Buffer to paste in.
+ * @param [in] at Location to paste at.
+ * @returns The location in the buffer after the paste.
+ */
+struct location buffer_paste_older(struct buffer *buffer, struct location at);
-void buffer_add_text_property(struct buffer *buffer,
- struct buffer_location start,
- struct buffer_location end,
+/**
+ * Get one line from the buffer.
+ *
+ * @param buffer The buffer to get the line from.
+ * @param line Line number in the buffer.
+ * @returns A text chunk describing the line. Note that if the line number is
+ * greater than the number of lines, the @ref text_chunk will be empty.
+ */
+struct text_chunk buffer_line(struct buffer *buffer, uint32_t line);
+
+void buffer_add_text_property(struct buffer *buffer, struct location start,
+ struct location end,
struct text_property property);
-void buffer_get_text_properties(struct buffer *buffer,
- struct buffer_location location,
+void buffer_get_text_properties(struct buffer *buffer, struct 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);
+void buffer_clear_text_properties(struct buffer *buffer);
+/**
+ * Add a buffer update hook.
+ *
+ * @param [in] buffer The buffer to add the hook to.
+ * @param [in] hook The update hook callback.
+ * @param [in] userdata Data that is passed unmodified to the update hook.
+ * @returns The hook id.
+ */
uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook,
void *userdata);
+/** Buffer create hook callback function */
+typedef void (*create_hook_cb)(struct buffer *buffer, void *userdata);
+
+/**
+ * Add a buffer create hook.
+ *
+ * @param [in] hook Create hook callback.
+ * @param [in] userdata Pointer to data that is passed unmodified to the update
+ * hook.
+ * @returns The hook id.
+ */
uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata);
-struct buffer buffer_from_file(char *filename);
-void buffer_to_file(struct buffer *buffer);
-void buffer_write_to(struct buffer *buffer, const char *filename);
-void buffer_reload(struct buffer *buffer);
+/**
+ * Parameters for updating a buffer.
+ */
+struct buffer_update_params {
+
+ /** Command list to add rendering commands for the buffer */
+ struct command_list *commands;
+
+ /** Where is the upper left corner of the buffer */
+ struct location origin;
+
+ /** Window width for this buffer, -1 if it is not in a window */
+ uint32_t width;
+
+ /** Window height for this buffer, -1 if it is not in a window */
+ uint32_t height;
+};
+
+/**
+ * Update a buffer.
+ *
+ * @param [in] buffer The buffer to update.
+ * @param [inout] params The parameters for the update. The @ref commands field
+ * in @ref params will be modified with the rendering commands needed for this
+ * buffer.
+ */
+void buffer_update(struct buffer *buffer, struct buffer_update_params *params);
-void buffer_update(struct buffer_view *view, uint32_t window_id, uint32_t width,
- uint32_t height, struct command_list *commands,
- uint64_t frame_time, uint32_t *relline, uint32_t *relcol);
+#endif
diff --git a/src/dged/buffer_view.c b/src/dged/buffer_view.c
new file mode 100644
index 0000000..2c69161
--- /dev/null
+++ b/src/dged/buffer_view.c
@@ -0,0 +1,418 @@
+#include <string.h>
+
+#include "buffer.h"
+#include "buffer_view.h"
+#include "display.h"
+
+struct modeline {
+ uint8_t *buffer;
+ uint32_t sz;
+};
+
+static bool maybe_delete_region(struct buffer_view *view) {
+ struct region reg = region_new(view->dot, view->mark);
+ if (view->mark_set && region_has_size(reg)) {
+ buffer_delete(view->buffer, reg);
+ buffer_view_clear_mark(view);
+ view->dot = reg.begin;
+ return true;
+ }
+
+ return false;
+}
+
+struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline,
+ bool line_numbers) {
+ struct buffer_view v = (struct buffer_view){
+ .dot = (struct location){.line = 0, .col = 0},
+ .mark = (struct location){.line = 0, .col = 0},
+ .mark_set = false,
+ .scroll = (struct location){.line = 0, .col = 0},
+ .buffer = buffer,
+ .modeline = NULL,
+ .line_numbers = line_numbers,
+ .fringe_width = 0,
+ };
+
+ if (modeline) {
+ v.modeline = calloc(1, sizeof(struct modeline));
+ v.modeline->buffer = malloc(1024);
+ v.modeline->sz = 1024;
+ v.modeline->buffer[0] = '\0';
+ }
+
+ return v;
+}
+
+struct buffer_view buffer_view_clone(const struct buffer_view *view) {
+ struct buffer_view c = {
+ .dot = view->dot,
+ .mark = view->mark,
+ .mark_set = view->mark_set,
+ .scroll = view->scroll,
+ .buffer = view->buffer,
+ .modeline = NULL,
+ .line_numbers = view->line_numbers,
+ };
+
+ if (view->modeline) {
+ c.modeline = calloc(1, sizeof(struct modeline));
+ c.modeline->buffer = malloc(view->modeline->sz);
+ memcpy(c.modeline->buffer, view->modeline->buffer, view->modeline->sz);
+ }
+
+ return c;
+}
+
+void buffer_view_destroy(struct buffer_view *view) {
+ if (view->modeline != NULL) {
+ free(view->modeline->buffer);
+ free(view->modeline);
+ }
+}
+
+void buffer_view_add(struct buffer_view *view, uint8_t *txt, uint32_t nbytes) {
+ maybe_delete_region(view);
+ view->dot = buffer_add(view->buffer, view->dot, txt, nbytes);
+}
+
+void buffer_view_goto_beginning(struct buffer_view *view) {
+ view->dot = (struct location){.line = 1, .col = 0};
+}
+
+void buffer_view_goto_end(struct buffer_view *view) {
+ view->dot = buffer_end(view->buffer);
+}
+
+void buffer_view_goto(struct buffer_view *view, struct location to) {
+ view->dot = buffer_clamp(view->buffer, (int64_t)to.line, (int64_t)to.col);
+}
+
+void buffer_view_forward_char(struct buffer_view *view) {
+ view->dot = buffer_next_char(view->buffer, view->dot);
+}
+
+void buffer_view_backward_char(struct buffer_view *view) {
+ view->dot = buffer_previous_char(view->buffer, view->dot);
+}
+
+void buffer_view_forward_word(struct buffer_view *view) {
+ view->dot = buffer_next_word(view->buffer, view->dot);
+}
+
+void buffer_view_backward_word(struct buffer_view *view) {
+ view->dot = buffer_previous_word(view->buffer, view->dot);
+}
+
+void buffer_view_forward_line(struct buffer_view *view) {
+ view->dot = buffer_next_line(view->buffer, view->dot);
+}
+
+void buffer_view_backward_line(struct buffer_view *view) {
+ view->dot = buffer_previous_line(view->buffer, view->dot);
+}
+
+void buffer_view_forward_nlines(struct buffer_view *view, uint32_t nlines) {
+ view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line + nlines,
+ (int64_t)view->dot.col);
+}
+
+void buffer_view_backward_nlines(struct buffer_view *view, uint32_t nlines) {
+ view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line - nlines,
+ (int64_t)view->dot.col);
+}
+
+void buffer_view_goto_end_of_line(struct buffer_view *view) {
+ view->dot.col = buffer_num_chars(view->buffer, view->dot.line);
+}
+
+void buffer_view_goto_beginning_of_line(struct buffer_view *view) {
+ view->dot.col = 0;
+}
+
+void buffer_view_newline(struct buffer_view *view) {
+ view->dot = buffer_newline(view->buffer, view->dot);
+}
+
+void buffer_view_indent(struct buffer_view *view) {
+ view->dot = buffer_indent(view->buffer, view->dot);
+}
+
+void buffer_view_copy(struct buffer_view *view) {
+ if (!view->mark_set) {
+ return;
+ }
+
+ view->dot = buffer_copy(view->buffer, region_new(view->dot, view->mark));
+ buffer_view_clear_mark(view);
+}
+
+void buffer_view_cut(struct buffer_view *view) {
+ if (!view->mark_set) {
+ return;
+ }
+
+ view->dot = buffer_cut(view->buffer, region_new(view->dot, view->mark));
+ buffer_view_clear_mark(view);
+}
+
+void buffer_view_paste(struct buffer_view *view) {
+ maybe_delete_region(view);
+ view->dot = buffer_paste(view->buffer, view->dot);
+}
+
+void buffer_view_paste_older(struct buffer_view *view) {
+ view->dot = buffer_paste_older(view->buffer, view->dot);
+}
+
+void buffer_view_forward_delete_char(struct buffer_view *view) {
+ if (maybe_delete_region(view)) {
+ return;
+ }
+
+ view->dot = buffer_delete(
+ view->buffer,
+ region_new(view->dot, buffer_next_char(view->buffer, view->dot)));
+}
+
+void buffer_view_backward_delete_char(struct buffer_view *view) {
+ if (maybe_delete_region(view)) {
+ return;
+ }
+
+ view->dot = buffer_delete(
+ view->buffer,
+ region_new(buffer_previous_char(view->buffer, view->dot), view->dot));
+}
+
+void buffer_view_forward_delete_word(struct buffer_view *view) {
+ if (maybe_delete_region(view)) {
+ return;
+ }
+
+ view->dot = buffer_delete(
+ view->buffer,
+ region_new(view->dot, buffer_next_word(view->buffer, view->dot)));
+}
+
+void buffer_view_backward_delete_word(struct buffer_view *view) {
+ if (maybe_delete_region(view)) {
+ return;
+ }
+
+ view->dot = buffer_delete(
+ view->buffer,
+ region_new(buffer_previous_word(view->buffer, view->dot), view->dot));
+}
+
+void buffer_view_kill_line(struct buffer_view *view) {
+ uint32_t nchars =
+ buffer_num_chars(view->buffer, view->dot.line) - view->dot.col;
+ if (nchars == 0) {
+ nchars = 1;
+ }
+
+ struct region reg = region_new(view->dot, (struct location){
+ .line = view->dot.line,
+ .col = view->dot.col + nchars,
+ });
+
+ buffer_cut(view->buffer, reg);
+}
+
+void buffer_view_set_mark(struct buffer_view *view) {
+ buffer_view_set_mark_at(view, view->dot);
+}
+
+void buffer_view_clear_mark(struct buffer_view *view) {
+ view->mark_set = false;
+}
+
+void buffer_view_set_mark_at(struct buffer_view *view, struct location mark) {
+ view->mark = mark;
+ view->mark_set = true;
+}
+
+struct location buffer_view_dot_to_relative(struct buffer_view *view) {
+ return (struct location){
+ .line = view->dot.line - view->scroll.line,
+ .col = view->dot.col - view->scroll.col + view->fringe_width,
+ };
+}
+
+void buffer_view_undo(struct buffer_view *view) {
+ view->dot = buffer_undo(view->buffer, view->dot);
+}
+
+static uint32_t longest_linenum(struct buffer_view *view) {
+ uint32_t total_lines = buffer_num_lines(view->buffer);
+ uint32_t longest_nchars = 10;
+ if (total_lines < 10) {
+ longest_nchars = 1;
+ } else if (total_lines < 100) {
+ longest_nchars = 2;
+ } else if (total_lines < 1000) {
+ longest_nchars = 3;
+ } else if (total_lines < 10000) {
+ longest_nchars = 4;
+ } else if (total_lines < 100000) {
+ longest_nchars = 5;
+ } else if (total_lines < 1000000) {
+ longest_nchars = 6;
+ } else if (total_lines < 10000000) {
+ longest_nchars = 7;
+ } else if (total_lines < 100000000) {
+ longest_nchars = 8;
+ } else if (total_lines < 1000000000) {
+ longest_nchars = 9;
+ }
+
+ return longest_nchars;
+}
+
+static uint32_t render_line_numbers(struct buffer_view *view,
+ struct command_list *commands,
+ uint32_t height) {
+ uint32_t longest_nchars = longest_linenum(view);
+ static char buf[16];
+
+ uint32_t nlines_buf = buffer_num_lines(view->buffer);
+ uint32_t line = view->scroll.line;
+ uint32_t relline = 0;
+
+ for (; relline < height && line < nlines_buf; ++line, ++relline) {
+ command_list_set_index_color_bg(commands, 8);
+ command_list_set_index_color_fg(commands, line == view->dot.line ? 15 : 7);
+ uint32_t chars = snprintf(buf, 16, "%*d", longest_nchars + 1, line + 1);
+ command_list_draw_text_copy(commands, 0, relline, (uint8_t *)buf, chars);
+ command_list_reset_color(commands);
+ command_list_draw_repeated(commands, longest_nchars + 1, relline, ' ', 1);
+ }
+
+ for (; relline < height; ++relline) {
+ command_list_set_index_color_bg(commands, 8);
+ command_list_set_index_color_fg(commands, 7);
+ command_list_draw_repeated(commands, 0, relline, ' ', longest_nchars + 1);
+ command_list_reset_color(commands);
+ command_list_draw_repeated(commands, longest_nchars + 1, relline, ' ', 1);
+ }
+
+ return longest_nchars + 2;
+}
+
+static void render_modeline(struct modeline *modeline, struct buffer_view *view,
+ struct command_list *commands, uint32_t window_id,
+ uint32_t width, uint32_t height,
+ uint64_t frame_time) {
+ char buf[width * 4];
+
+ static uint64_t samples[10] = {0};
+ static uint32_t samplei = 0;
+ static uint64_t avg = 0;
+
+ // calc a moving average with a window of the last 10 frames
+ ++samplei;
+ samplei %= 10;
+ avg += 0.1 * (frame_time - samples[samplei]);
+ samples[samplei] = frame_time;
+
+ time_t now = time(NULL);
+ struct tm *lt = localtime(&now);
+ char left[128], right[128];
+
+ snprintf(left, 128, " %c%c %d:%-16s (%d, %d) (%s)",
+ view->buffer->modified ? '*' : '-',
+ view->buffer->readonly ? '%' : '-', window_id, view->buffer->name,
+ view->dot.line + 1, view->dot.col, view->buffer->lang.name);
+ snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour,
+ lt->tm_min);
+
+ snprintf(buf, width * 4, "%s%*s%s", left,
+ (int)(width - (strlen(left) + strlen(right))), "", right);
+
+ if (strcmp(buf, (char *)modeline->buffer) != 0) {
+ modeline->buffer = realloc(modeline->buffer, width * 4);
+ modeline->sz = width * 4;
+ strcpy((char *)modeline->buffer, buf);
+ }
+
+ command_list_set_index_color_bg(commands, 8);
+ command_list_draw_text(commands, 0, height - 1, modeline->buffer,
+ strlen((char *)modeline->buffer));
+ command_list_reset_color(commands);
+}
+
+void buffer_view_update(struct buffer_view *view,
+ struct buffer_view_update_params *params) {
+
+ uint32_t height = params->height;
+ uint32_t width = params->width;
+
+ // render modeline
+ uint32_t modeline_height = 0;
+ if (view->modeline != NULL) {
+ modeline_height = 1;
+ render_modeline(view->modeline, view, params->commands, params->window_id,
+ params->width, params->height, params->frame_time);
+ }
+
+ height -= modeline_height;
+
+ // render line numbers
+ uint32_t linum_width = 0;
+ if (view->line_numbers) {
+ linum_width = render_line_numbers(view, params->commands, height);
+ }
+
+ width -= linum_width;
+ view->fringe_width = linum_width;
+
+ /* Make sure the dot is always inside buffer limits.
+ * It can be outside for example if the text is changed elsewhere. */
+ view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line,
+ (int64_t)view->dot.col);
+
+ // update scroll position if needed
+ if (view->dot.line >= view->scroll.line + height || view->dot.line < view->scroll.line) {
+ // put dot in the middle, height-wise
+ view->scroll.line = buffer_clamp(view->buffer, (int64_t)view->dot.line - params->height / 2,
+ 0).line;
+ }
+
+ if (view->dot.col >= view->scroll.col + width || view->dot.col < view->scroll.col) {
+ view->scroll.col = buffer_clamp(view->buffer, view->dot.line, view->dot.col).col;
+ }
+
+ // color region
+ if (view->mark_set) {
+ struct region reg = region_new(view->dot, view->mark);
+ if (region_has_size(reg)) {
+ 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,
+ },
+ });
+ }
+ }
+
+ // update buffer
+ struct command_list *buf_cmds = command_list_create(
+ width * height, params->frame_alloc, params->window_x + linum_width,
+ params->window_y, view->buffer->name);
+ struct buffer_update_params bufparams = {
+ .commands = buf_cmds,
+ .origin = view->scroll,
+ .width = width,
+ .height = height,
+ };
+ buffer_update(view->buffer, &bufparams);
+
+ // draw buffer commands nested inside this command list
+ command_list_draw_command_list(params->commands, buf_cmds);
+ buffer_clear_text_properties(view->buffer);
+}
diff --git a/src/dged/buffer_view.h b/src/dged/buffer_view.h
new file mode 100644
index 0000000..0002b95
--- /dev/null
+++ b/src/dged/buffer_view.h
@@ -0,0 +1,100 @@
+#ifndef _BUFFER_VIEW_H
+#define _BUFFER_VIEW_H
+
+#include <stddef.h>
+
+#include "location.h"
+
+struct buffer;
+
+/**
+ * A view of a buffer.
+ *
+ * This contains the mark and dot as well as the scroll position for a buffer.
+ */
+struct buffer_view {
+ /** Location of dot (cursor) */
+ struct location dot;
+
+ /** Location of mark (where a selection starts) */
+ struct location mark;
+
+ /** Current buffer scroll position */
+ struct location scroll;
+
+ /** Pointer to the actual buffer */
+ struct buffer *buffer;
+
+ /** Modeline buffer (may be NULL) */
+ struct modeline *modeline;
+
+ /** Current left fringe size */
+ uint32_t fringe_width;
+
+ /** Does the buffer show line numbers */
+ bool line_numbers;
+
+ /** True if the start of a selection has been set */
+ bool mark_set;
+};
+
+struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline,
+ bool line_numbers);
+struct buffer_view buffer_view_clone(const struct buffer_view *view);
+void buffer_view_destroy(struct buffer_view *view);
+
+void buffer_view_add(struct buffer_view *view, uint8_t *txt, uint32_t nbytes);
+
+void buffer_view_goto_beginning(struct buffer_view *view);
+void buffer_view_goto_end(struct buffer_view *view);
+void buffer_view_goto(struct buffer_view *view, struct location to);
+void buffer_view_goto_end_of_line(struct buffer_view *view);
+void buffer_view_goto_beginning_of_line(struct buffer_view *view);
+
+void buffer_view_forward_char(struct buffer_view *view);
+void buffer_view_backward_char(struct buffer_view *view);
+void buffer_view_forward_word(struct buffer_view *view);
+void buffer_view_backward_word(struct buffer_view *view);
+void buffer_view_forward_line(struct buffer_view *view);
+void buffer_view_backward_line(struct buffer_view *view);
+void buffer_view_forward_nlines(struct buffer_view *view, uint32_t nlines);
+void buffer_view_backward_nlines(struct buffer_view *view, uint32_t nlines);
+
+void buffer_view_forward_delete_char(struct buffer_view *view);
+void buffer_view_backward_delete_char(struct buffer_view *view);
+void buffer_view_forward_delete_word(struct buffer_view *view);
+void buffer_view_backward_delete_word(struct buffer_view *view);
+
+void buffer_view_kill_line(struct buffer_view *view);
+
+void buffer_view_newline(struct buffer_view *view);
+void buffer_view_indent(struct buffer_view *view);
+
+void buffer_view_copy(struct buffer_view *view);
+void buffer_view_cut(struct buffer_view *view);
+void buffer_view_paste(struct buffer_view *view);
+void buffer_view_paste_older(struct buffer_view *view);
+
+void buffer_view_set_mark(struct buffer_view *view);
+void buffer_view_clear_mark(struct buffer_view *view);
+void buffer_view_set_mark_at(struct buffer_view *view, struct location mark);
+
+struct location buffer_view_dot_to_relative(struct buffer_view *view);
+
+void buffer_view_undo(struct buffer_view *view);
+
+struct buffer_view_update_params {
+ struct command_list *commands;
+ void *(*frame_alloc)(size_t);
+ uint32_t window_id;
+ int64_t frame_time;
+ uint32_t width;
+ uint32_t height;
+ uint32_t window_x;
+ uint32_t window_y;
+};
+
+void buffer_view_update(struct buffer_view *view,
+ struct buffer_view_update_params *params);
+
+#endif
diff --git a/src/dged/display.c b/src/dged/display.c
index cf2e5d5..b779d5f 100644
--- a/src/dged/display.c
+++ b/src/dged/display.c
@@ -2,6 +2,7 @@
#include "display.h"
#include "buffer.h"
+#include "utf8.h"
#include <assert.h>
#include <stdarg.h>
@@ -27,6 +28,7 @@ enum render_cmd_type {
RenderCommand_Repeat = 2,
RenderCommand_ClearFormat = 3,
RenderCommand_SetShowWhitespace = 4,
+ RenderCommand_DrawList = 5,
};
struct render_command {
@@ -36,6 +38,7 @@ struct render_command {
struct push_fmt_cmd *push_fmt;
struct repeat_cmd *repeat;
struct show_ws_cmd *show_ws;
+ struct draw_list_cmd *draw_list;
};
};
@@ -55,7 +58,7 @@ struct push_fmt_cmd {
struct repeat_cmd {
uint32_t col;
uint32_t row;
- uint8_t c;
+ int32_t c;
uint32_t nrepeat;
};
@@ -63,6 +66,10 @@ struct show_ws_cmd {
bool show;
};
+struct draw_list_cmd {
+ struct command_list *list;
+};
+
struct command_list {
struct render_command *cmds;
uint64_t ncmds;
@@ -122,26 +129,25 @@ void display_destroy(struct display *display) {
uint32_t display_width(struct display *display) { return display->width; }
uint32_t display_height(struct display *display) { return display->height; }
-void putbyte(uint8_t c) {
+void putch(uint8_t c) {
if (c != '\r') {
putc(c, stdout);
}
}
-void putbyte_ws(uint8_t c, bool show_whitespace) {
+void putch_ws(uint8_t c, bool show_whitespace) {
if (show_whitespace && c == '\t') {
fputs("\x1b[90m → \x1b[39m", stdout);
} else if (show_whitespace && c == ' ') {
fputs("\x1b[90m·\x1b[39m", stdout);
} else {
- putbyte(c);
+ putch(c);
}
}
void putbytes(uint8_t *line_bytes, uint32_t line_length, bool show_whitespace) {
for (uint32_t bytei = 0; bytei < line_length; ++bytei) {
- uint8_t byte = line_bytes[bytei];
- putbyte_ws(byte, show_whitespace);
+ putch_ws(line_bytes[bytei], show_whitespace);
}
}
@@ -150,20 +156,20 @@ void put_ansiparm(int n) {
if (q != 0) {
int r = q / 10;
if (r != 0) {
- putbyte((r % 10) + '0');
+ putch((r % 10) + '0');
}
- putbyte((q % 10) + '0');
+ putch((q % 10) + '0');
}
- putbyte((n % 10) + '0');
+ putch((n % 10) + '0');
}
void display_move_cursor(struct display *display, uint32_t row, uint32_t col) {
- putbyte(ESC);
- putbyte('[');
+ putch(ESC);
+ putch('[');
put_ansiparm(row + 1);
- putbyte(';');
+ putch(';');
put_ansiparm(col + 1);
- putbyte('H');
+ putch('H');
}
void display_clear(struct display *display) {
@@ -226,6 +232,11 @@ struct render_command *add_command(struct command_list *list,
break;
case RenderCommand_ClearFormat:
break;
+ case RenderCommand_DrawList:
+ cmd->draw_list = l->allocator(sizeof(struct draw_list_cmd));
+ break;
+ default:
+ assert(false);
}
++l->ncmds;
@@ -251,7 +262,7 @@ void command_list_draw_text_copy(struct command_list *list, uint32_t col,
}
void command_list_draw_repeated(struct command_list *list, uint32_t col,
- uint32_t row, uint8_t c, uint32_t nrepeat) {
+ uint32_t row, int32_t c, uint32_t nrepeat) {
struct repeat_cmd *cmd = add_command(list, RenderCommand_Repeat)->repeat;
cmd->col = col;
cmd->row = row;
@@ -259,6 +270,13 @@ void command_list_draw_repeated(struct command_list *list, uint32_t col,
cmd->nrepeat = nrepeat;
}
+void command_list_draw_command_list(struct command_list *list,
+ struct command_list *to_draw) {
+ struct draw_list_cmd *cmd =
+ add_command(list, RenderCommand_DrawList)->draw_list;
+ cmd->list = to_draw;
+}
+
void command_list_set_index_color_fg(struct command_list *list,
uint8_t color_idx) {
struct push_fmt_cmd *cmd =
@@ -329,7 +347,7 @@ void display_render(struct display *display,
display_move_cursor(display, txt_cmd->row + cl->yoffset,
txt_cmd->col + cl->xoffset);
putbytes(fmt_stack, fmt_stack_len, false);
- putbyte('m');
+ putch('m');
putbytes(txt_cmd->data, txt_cmd->len, show_whitespace_state);
break;
}
@@ -339,17 +357,10 @@ void display_render(struct display *display,
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);
+ putch('m');
+ uint32_t nbytes = utf8_nbytes((uint8_t *)&repeat_cmd->c, 4, 1);
+ for (uint32_t i = 0; i < repeat_cmd->nrepeat; ++i) {
+ putbytes((uint8_t *)&repeat_cmd->c, nbytes, show_whitespace_state);
}
break;
}
@@ -372,6 +383,10 @@ void display_render(struct display *display,
case RenderCommand_SetShowWhitespace:
show_whitespace_state = cmd->show_ws->show;
break;
+
+ case RenderCommand_DrawList:
+ display_render(display, cmd->draw_list->list);
+ break;
}
}
cl = cl->next_list;
@@ -379,21 +394,21 @@ void display_render(struct display *display,
}
void hide_cursor() {
- putbyte(ESC);
- putbyte('[');
- putbyte('?');
- putbyte('2');
- putbyte('5');
- putbyte('l');
+ putch(ESC);
+ putch('[');
+ putch('?');
+ putch('2');
+ putch('5');
+ putch('l');
}
void show_cursor() {
- putbyte(ESC);
- putbyte('[');
- putbyte('?');
- putbyte('2');
- putbyte('5');
- putbyte('h');
+ putch(ESC);
+ putch('[');
+ putch('?');
+ putch('2');
+ putch('5');
+ putch('h');
}
void display_begin_render(struct display *display) { hide_cursor(); }
diff --git a/src/dged/display.h b/src/dged/display.h
index 14dd246..2fc807b 100644
--- a/src/dged/display.h
+++ b/src/dged/display.h
@@ -205,8 +205,11 @@ void command_list_draw_text_copy(struct command_list *list, uint32_t col,
* @param list Command list to record draw command in.
* @param col Column to start text at.
* @param row Row to start text at.
- * @param c Byte to repeat.
+ * @param c Character to repeat.
* @param nrepeat Number of times to repeat byte.
*/
void command_list_draw_repeated(struct command_list *list, uint32_t col,
- uint32_t row, uint8_t c, uint32_t nrepeat);
+ uint32_t row, int32_t c, uint32_t nrepeat);
+
+void command_list_draw_command_list(struct command_list *list,
+ struct command_list *to_draw);
diff --git a/src/dged/location.c b/src/dged/location.c
new file mode 100644
index 0000000..cac0333
--- /dev/null
+++ b/src/dged/location.c
@@ -0,0 +1,68 @@
+#include "location.h"
+
+bool location_is_between(struct location location, struct location start,
+ struct 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;
+}
+
+int location_compare(struct location l1, struct location l2) {
+ if (l1.line < l2.line) {
+ return -1;
+ } else if (l1.line > l2.line) {
+ return 1;
+ } else {
+ if (l1.col < l2.col) {
+ return -1;
+ } else if (l1.col > l2.col) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
+
+struct region region_new(struct location begin, struct location end) {
+ struct region reg = {.begin = begin, .end = end};
+
+ if (end.line < begin.line ||
+ (end.line == begin.line && end.col < begin.col)) {
+ reg.begin = end;
+ reg.end = begin;
+ }
+
+ return reg;
+}
+
+bool region_has_size(struct region region) {
+ return region.end.line != region.begin.line ||
+ (region.end.line == region.begin.line &&
+ region.begin.col != region.end.col);
+}
+
+bool region_is_inside(struct region region, struct location location) {
+ return location_is_between(location, region.begin, region.end);
+}
+
+bool region_is_inside_rect(struct region region, struct location location) {
+ return location.line >= region.begin.line &&
+ location.line <= region.end.line && location.col >= region.begin.col &&
+ location.col <= region.end.col;
+}
diff --git a/src/dged/location.h b/src/dged/location.h
new file mode 100644
index 0000000..d11dd94
--- /dev/null
+++ b/src/dged/location.h
@@ -0,0 +1,81 @@
+#ifndef _LOCATION_H
+#define _LOCATION_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * A location inside text.
+ */
+struct location {
+ /** The line in the text (0..) */
+ uint32_t line;
+
+ /** The column in the text (0..) */
+ uint32_t col;
+};
+
+/**
+ * Is the location between two other locations.
+ *
+ * @param [in] location The location to test.
+ * @param [in] l1 The first location.
+ * @param [in] l2 The other location.
+ * @returns True if @ref location is between @ref l1 and @ref l2.
+ */
+bool location_is_between(struct location location, struct location l1,
+ struct location l2);
+
+/**
+ * Compare two locations.
+ *
+ * @param [in] l1 The first location.
+ * @param [in] l2 The second location.
+ *
+ * @returns -1 if @ref l1 is before @ref l2, 0 if @ref l1 is equal to @ref l2
+ * and +1 if @ref l1 is after @ref l2.
+ */
+int location_compare(struct location l1, struct location l2);
+
+/**
+ * A region (area) in text.
+ */
+struct region {
+ /** The top left corner of the region. */
+ struct location begin;
+
+ /** The bottom right corner of the region. */
+ struct location end;
+};
+
+/**
+ * Create a new region.
+ *
+ * Note that if begin is after end, their order will be reversed.
+ *
+ * @param [in] begin The point in the text where this region begins.
+ * @param [in] end The point in the text where this region ends.
+ * @returns a new region.
+ */
+struct region region_new(struct location begin, struct location end);
+
+/**
+ * Is this region covering anything?
+ *
+ * @param [in] region The region to check.
+ * @returns True if the region has a size > 0.
+ */
+bool region_has_size(struct region region);
+
+/**
+ * Is the location inside the region?
+ *
+ * @param [in] region The region to test.
+ * @param [in] location The location to test.
+ * @returns True if @ref location is inside @ref region.
+ */
+bool region_is_inside(struct region region, struct location location);
+
+bool region_is_inside_rect(struct region region, struct location location);
+
+#endif
diff --git a/src/dged/minibuffer.c b/src/dged/minibuffer.c
index 3c5a291..d31d634 100644
--- a/src/dged/minibuffer.c
+++ b/src/dged/minibuffer.c
@@ -17,17 +17,24 @@ static struct minibuffer {
struct command_ctx prompt_command_ctx;
bool prompt_active;
bool clear;
+ struct window *prev_window;
void (*update_callback)(void *);
void *update_callback_userdata;
} g_minibuffer = {0};
-void draw_prompt(struct command_list *commands, void *userdata) {
+uint32_t minibuffer_draw_prompt(struct command_list *commands) {
+ if (!g_minibuffer.prompt_active) {
+ return 0;
+ }
+
uint32_t len = strlen(g_minibuffer.prompt);
command_list_set_index_color_fg(commands, 4);
command_list_draw_text(commands, 0, 0, (uint8_t *)g_minibuffer.prompt, len);
command_list_reset_color(commands);
+
+ return len;
}
int32_t minibuffer_execute() {
@@ -69,30 +76,20 @@ int32_t minibuffer_execute() {
}
}
-struct update_hook_result update(struct buffer_view *view,
- struct command_list *commands, uint32_t width,
- uint32_t height, uint64_t frame_time,
- void *userdata) {
+void update(struct buffer *buffer, uint32_t width, uint32_t height,
+ void *userdata) {
struct timespec current;
struct minibuffer *mb = (struct minibuffer *)userdata;
clock_gettime(CLOCK_MONOTONIC, &current);
if ((!mb->prompt_active && current.tv_sec >= mb->expires.tv_sec) ||
mb->clear) {
- buffer_clear(view);
+ buffer_clear(buffer);
mb->clear = false;
}
- struct update_hook_result res = {0};
- if (mb->prompt_active) {
- res.margins.left = strlen(mb->prompt);
- draw_prompt(commands, NULL);
- }
-
if (mb->update_callback != NULL) {
mb->update_callback(mb->update_callback_userdata);
}
-
- return res;
}
void minibuffer_init(struct buffer *buffer) {
@@ -128,7 +125,7 @@ void minibuffer_destroy() {
}
struct text_chunk minibuffer_content() {
- return buffer_get_line(g_minibuffer.buffer, 0);
+ return buffer_line(g_minibuffer.buffer, 0);
}
struct buffer *minibuffer_buffer() {
@@ -174,6 +171,11 @@ int32_t minibuffer_prompt_internal(struct command_ctx command_ctx,
g_minibuffer.update_callback_userdata = userdata;
}
+ if (windows_get_active() != minibuffer_window()) {
+ g_minibuffer.prev_window = windows_get_active();
+ windows_set_active(minibuffer_window());
+ }
+
return 0;
}
@@ -210,6 +212,10 @@ void minibuffer_abort_prompt() {
minibuffer_clear();
g_minibuffer.update_callback = NULL;
g_minibuffer.prompt_active = false;
+
+ if (g_minibuffer.prev_window != NULL) {
+ windows_set_active(g_minibuffer.prev_window);
+ }
}
bool minibuffer_empty() { return !minibuffer_displaying(); }
@@ -224,3 +230,7 @@ void minibuffer_clear() {
}
bool minibuffer_focused() { return g_minibuffer.prompt_active; }
+
+struct window *minibuffer_target_window() {
+ return g_minibuffer.prev_window;
+}
diff --git a/src/dged/minibuffer.h b/src/dged/minibuffer.h
index 98a4db8..fb6eae4 100644
--- a/src/dged/minibuffer.h
+++ b/src/dged/minibuffer.h
@@ -5,6 +5,7 @@
struct buffer;
struct command_ctx;
+struct command_list;
struct keymap;
/**
@@ -63,6 +64,7 @@ int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx,
void *userdata, const char *fmt, ...);
void minibuffer_set_prompt(const char *fmt, ...);
+uint32_t minibuffer_draw_prompt(struct command_list *commands);
/**
* Evaluate the current contents of the minibuffer
@@ -106,3 +108,5 @@ bool minibuffer_displaying();
* @returns True if the minibuffer is currently focused, receiving user input.
*/
bool minibuffer_focused();
+
+struct window *minibuffer_target_window();
diff --git a/src/dged/text.c b/src/dged/text.c
index f8ba72d..b3eb4ad 100644
--- a/src/dged/text.c
+++ b/src/dged/text.c
@@ -8,6 +8,7 @@
#include "display.h"
#include "signal.h"
#include "utf8.h"
+#include "vec.h"
enum flags {
LineChanged = 1 << 0,
@@ -20,11 +21,18 @@ struct line {
uint32_t nchars;
};
+struct text_property_entry {
+ struct location start;
+ struct location end;
+ struct text_property property;
+};
+
struct text {
// raw bytes without any null terminators
struct line *lines;
uint32_t nlines;
uint32_t capacity;
+ VEC(struct text_property_entry) properties;
};
struct text *text_create(uint32_t initial_capacity) {
@@ -33,6 +41,8 @@ struct text *text_create(uint32_t initial_capacity) {
txt->capacity = initial_capacity;
txt->nlines = 0;
+ VEC_INIT(&txt->properties, 32);
+
return txt;
}
@@ -60,6 +70,7 @@ void text_clear(struct text *text) {
}
text->nlines = 0;
+ text_clear_properties(text);
}
// given `char_idx` as a character index, return the byte index
@@ -494,3 +505,32 @@ struct text_chunk text_get_region(struct text *text, uint32_t start_line,
bool text_line_contains_unicode(struct text *text, uint32_t line) {
return text->lines[line].nbytes != text->lines[line].nchars;
}
+
+void text_add_property(struct text *text, struct location start,
+ struct location end, struct text_property property) {
+ struct text_property_entry entry = {
+ .start = start,
+ .end = end,
+ .property = property,
+ };
+ VEC_PUSH(&text->properties, entry);
+}
+
+void text_get_properties(struct text *text, struct location location,
+ struct text_property **properties,
+ uint32_t max_nproperties, uint32_t *nproperties) {
+ uint32_t nres = 0;
+ VEC_FOR_EACH(&text->properties, struct text_property_entry * prop) {
+ if (location_is_between(location, prop->start, prop->end)) {
+ properties[nres] = &prop->property;
+ ++nres;
+
+ if (nres == max_nproperties) {
+ break;
+ }
+ }
+ }
+ *nproperties = nres;
+}
+
+void text_clear_properties(struct text *text) { VEC_CLEAR(&text->properties); }
diff --git a/src/dged/text.h b/src/dged/text.h
index fbee89b..e3bb3e4 100644
--- a/src/dged/text.h
+++ b/src/dged/text.h
@@ -1,10 +1,13 @@
+#ifndef _TEXT_H
+#define _TEXT_H
+
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-// opaque so it is easier to change representation to gap, rope etc.
-struct text;
+#include "location.h"
+struct text;
struct render_command;
struct text *text_create(uint32_t initial_capacity);
@@ -52,3 +55,32 @@ struct text_chunk text_get_region(struct text *text, uint32_t start_line,
uint32_t end_col);
bool text_line_contains_unicode(struct text *text, uint32_t line);
+
+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;
+ };
+};
+
+void text_add_property(struct text *text, struct location start,
+ struct location end, struct text_property property);
+
+void text_get_properties(struct text *text, struct location location,
+ struct text_property **properties,
+ uint32_t max_nproperties, uint32_t *nproperties);
+
+void text_clear_properties(struct text *text);
+
+#endif
diff --git a/src/dged/window.c b/src/dged/window.c
index e928e42..91b1735 100644
--- a/src/dged/window.c
+++ b/src/dged/window.c
@@ -1,6 +1,7 @@
#include "binding.h"
#include "btree.h"
#include "buffer.h"
+#include "buffer_view.h"
#include "command.h"
#include "display.h"
#include "minibuffer.h"
@@ -30,7 +31,7 @@ BINTREE_ENTRY_TYPE(window_node, struct window);
static struct windows {
BINTREE(window_node) windows;
- struct window_node *active;
+ struct window *active;
struct keymap keymap;
} g_windows;
@@ -61,7 +62,7 @@ void windows_init(uint32_t height, uint32_t width,
.y = 0,
};
BINTREE_SET_ROOT(&g_windows.windows, root_window);
- g_windows.active = BINTREE_ROOT(&g_windows.windows);
+ g_windows.active = &BINTREE_VALUE(BINTREE_ROOT(&g_windows.windows));
}
static void window_tree_clear_sub(struct window_node *root_node) {
@@ -156,54 +157,160 @@ 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);
+ w->x = 0;
+ w->commands = command_list_create(10, frame_alloc, w->x, w->y, "mb-prompt");
+
+ // draw the prompt here to make it off-limits for the buffer/buffer view
+ uint32_t prompt_len = minibuffer_draw_prompt(w->commands);
+ w->x += prompt_len;
+ uint32_t width = prompt_len < w->width ? w->width - prompt_len : 1;
+
+ struct command_list *inner_commands = command_list_create(
+ w->height * width, frame_alloc, w->x, w->y, "bufview-mb");
+
+ struct buffer_view_update_params p = {
+ .commands = inner_commands,
+ .window_id = -1,
+ .frame_time = frame_time,
+ .width = width,
+ .height = w->height,
+ .window_x = w->x,
+ .window_y = w->y,
+ .frame_alloc = frame_alloc,
+ };
+ buffer_view_update(&w->buffer_view, &p);
+ command_list_draw_command_list(w->commands, inner_commands);
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;
- while (n != NULL) {
- struct window *w = &BINTREE_VALUE(n);
- if (w->type == Window_Buffer) {
- w->commands = command_list_create(w->height * w->width, frame_alloc, w->x,
- w->y, w->buffer_view.buffer->name);
+ const uint32_t hpadding = 1;
+ const uint32_t border_width = 1;
+ const uint32_t shadow_width = 1;
- buffer_update(&w->buffer_view, window_id, w->width, w->height,
- w->commands, frame_time, &w->relline, &w->relcol);
+ bool draw_padding = false, draw_borders = false;
- ++window_id;
+ // first, make sure window fits
+ struct window *rw = root_window();
+ uint32_t w_x = w->x;
+ uint32_t w_y = w->y;
+ uint32_t width = w_x + w->width > rw->width ? rw->width - w_x : w->width;
+ uint32_t height =
+ w_y + w->height > rw->height ? rw->height - w_y : w->height;
+
+ // is there space for padding?
+ if (w_x > 1 && w_x + width + hpadding <= rw->width) {
+ draw_padding = true;
+
+ --w_x;
+ width += hpadding * 2;
}
- BINTREE_NEXT(n);
+ // is there space for borders?
+ if (w_y > 1 && w_y + height <= rw->height && w_x > 1 &&
+ w_x + width + border_width <= rw->width) {
+ draw_borders = true;
+
+ w_x -= border_width;
+ // shift text upward
+ w_y -= border_width * 2;
+ height += border_width * 2;
+ width += border_width * 2;
+ }
+
+ w->commands = command_list_create(height * width, frame_alloc, w_x, w_y,
+ "popup-decor");
+ uint32_t x = 0, y = 0;
+ if (draw_borders) {
+ // top
+ command_list_draw_repeated(w->commands, x, y, 0x8c94e2, 1);
+ command_list_draw_repeated(w->commands, x + 1, y, 0x8094e2,
+ width - (border_width * 2));
+ command_list_draw_repeated(w->commands, x + width - 1, y, 0x9094e2, 1);
+
+ // sides
+ for (uint32_t line = y + 1; line < (height + y - border_width); ++line) {
+ command_list_draw_repeated(w->commands, x, line, 0x8294e2,
+ border_width);
+ command_list_draw_repeated(w->commands, x + width - border_width, line,
+ 0x8294e2, border_width);
+ }
+
+ // bottom
+ command_list_draw_repeated(w->commands, x, y + height - border_width,
+ 0x9494e2, 1);
+ command_list_draw_repeated(w->commands, x + 1, y + height - border_width,
+ 0x8094e2, width - (border_width * 2));
+ command_list_draw_repeated(w->commands, x + width - 1,
+ y + height - border_width, 0x9894e2, 1);
+
+ x += border_width;
+ y += border_width;
+ }
+
+ if (draw_padding) {
+ for (uint32_t line = y; line < w->height + y; ++line) {
+ command_list_draw_repeated(w->commands, x, line, ' ', hpadding);
+ command_list_draw_repeated(w->commands, w->width + x + 1, line, ' ',
+ hpadding);
+ }
+ x += border_width;
+ }
+
+ // shadow
+ /*command_list_set_index_color_bg(w->commands, 236);
+ command_list_draw_repeated(w->commands, 1, w->height + vmargins, ' ',
+ w->width + margins * 2); for (uint32_t line = 1; line < w->height; ++line) {
+ command_list_draw_repeated(w->commands, w->width + margins * 2, line, ' ',
+ shadow_width);
+ }*/
+
+ struct command_list *inner = command_list_create(
+ w->height * w->width, frame_alloc, w_x + x, w_y + y, "bufview-popup");
+
+ struct buffer_view_update_params p = {
+ .commands = inner,
+ .window_id = -1,
+ .frame_time = frame_time,
+ .width = w->width,
+ .height = w->height,
+ .window_x = w_x + x,
+ .window_y = w_y + y,
+ .frame_alloc = frame_alloc,
+ };
+
+ buffer_view_update(&w->buffer_view, &p);
+ command_list_draw_command_list(w->commands, inner);
}
- // clear text props for next frame
- n = BINTREE_ROOT(&g_windows.windows);
+ struct window_node *n = BINTREE_ROOT(&g_windows.windows);
BINTREE_FIRST(n);
-
+ uint32_t window_id = 0;
while (n != NULL) {
struct window *w = &BINTREE_VALUE(n);
if (w->type == Window_Buffer) {
- buffer_clear_text_properties(w->buffer_view.buffer);
+ char name[16];
+ snprintf(name, 16, "bufview-%s", w->buffer_view.buffer->name);
+ w->commands = command_list_create(w->height * w->width, frame_alloc, w->x,
+ w->y, name);
+
+ struct buffer_view_update_params p = {
+ .commands = w->commands,
+ .window_id = window_id,
+ .frame_time = frame_time,
+ .width = w->width,
+ .height = w->height,
+ .window_x = w->x,
+ .window_y = w->y,
+ .frame_alloc = frame_alloc,
+ };
+
+ buffer_view_update(&w->buffer_view, &p);
+ ++window_id;
}
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) {
@@ -238,13 +345,22 @@ struct window_node *find_window(struct window *window) {
}
void windows_set_active(struct window *window) {
+ if (window == minibuffer_window()) {
+ g_windows.active = minibuffer_window();
+ return;
+ }
+
struct window_node *n = find_window(window);
if (n != NULL) {
- g_windows.active = n;
+ g_windows.active = &BINTREE_VALUE(n);
}
}
struct window *window_find_by_buffer(struct buffer *b) {
+ if (b == window_buffer(minibuffer_window())) {
+ return minibuffer_window();
+ }
+
struct window_node *n = BINTREE_ROOT(&g_windows.windows);
BINTREE_FIRST(n);
while (n != NULL) {
@@ -259,7 +375,7 @@ struct window *window_find_by_buffer(struct buffer *b) {
}
struct window *windows_get_active() {
- return &BINTREE_VALUE(g_windows.active);
+ return g_windows.active;
}
void window_set_buffer(struct window *window, struct buffer *buffer) {
@@ -291,19 +407,6 @@ bool window_has_prev_buffer(struct window *window) {
return window->prev_buffer != NULL;
}
-struct buffer_location window_cursor_location(struct window *window) {
- return (struct buffer_location){
- .col = window->relcol,
- .line = window->relline,
- };
-}
-struct buffer_location window_absolute_cursor_location(struct window *window) {
- return (struct buffer_location){
- .col = window->x + window->relcol,
- .line = window->y + window->relline,
- };
-}
-
void window_close(struct window *window) {
// do not want to delete last window
if (window == root_window()) {
@@ -397,8 +500,9 @@ void window_hsplit(struct window *window, struct window **new_window_a,
new_window.type = Window_Buffer;
new_window.buffer_view =
buffer_view_create(w.buffer_view.buffer, true, true);
- buffer_goto(&new_window.buffer_view, w.buffer_view.dot.line,
- w.buffer_view.dot.col);
+ buffer_view_goto(&new_window.buffer_view,
+ (struct location){.line = w.buffer_view.dot.line,
+ .col = w.buffer_view.dot.col});
new_window.prev_buffer = w.prev_buffer;
new_window.x = w.x;
new_window.y = w.y + w.height;
@@ -439,8 +543,10 @@ void window_vsplit(struct window *window, struct window **new_window_a,
new_window.type = Window_Buffer;
new_window.buffer_view =
buffer_view_create(w.buffer_view.buffer, true, true);
- buffer_goto(&new_window.buffer_view, w.buffer_view.dot.line,
- w.buffer_view.dot.col);
+ buffer_view_goto(&new_window.buffer_view,
+ (struct location){.line = w.buffer_view.dot.line,
+ .col = w.buffer_view.dot.col});
+
new_window.prev_buffer = w.prev_buffer;
new_window.x = w.x + w.width;
new_window.y = w.y;
@@ -507,9 +613,13 @@ struct window *windows_focus(uint32_t id) {
return NULL;
}
-uint32_t window_width(struct window *window) { return window->width; }
+uint32_t window_width(const struct window *window) { return window->width; }
+
+uint32_t window_height(const struct window *window) { return window->height; }
-uint32_t window_height(struct window *window) { return window->height; }
+struct window_position window_position(const struct window *window) {
+ return (struct window_position){.x = window->x, .y = window->y};
+}
void windows_show_popup(uint32_t row, uint32_t col, uint32_t width,
uint32_t height) {
diff --git a/src/dged/window.h b/src/dged/window.h
index 30c1061..e9f90aa 100644
--- a/src/dged/window.h
+++ b/src/dged/window.h
@@ -1,3 +1,6 @@
+#ifndef _WINDOW_H
+#define _WINDOW_H
+
#include <stdbool.h>
#include <stdint.h>
@@ -12,6 +15,11 @@ struct buffer;
struct window;
struct windows;
+struct window_position {
+ uint32_t x;
+ uint32_t y;
+};
+
void windows_init(uint32_t height, uint32_t width,
struct buffer *initial_buffer, struct buffer *minibuffer);
@@ -38,10 +46,9 @@ struct buffer *window_buffer(struct window *window);
struct buffer_view *window_buffer_view(struct window *window);
struct buffer *window_prev_buffer(struct window *window);
bool window_has_prev_buffer(struct window *window);
-struct buffer_location window_cursor_location(struct window *window);
-struct buffer_location window_absolute_cursor_location(struct window *window);
-uint32_t window_width(struct window *window);
-uint32_t window_height(struct window *window);
+uint32_t window_width(const struct window *window);
+uint32_t window_height(const struct window *window);
+struct window_position window_position(const struct window *window);
void window_close(struct window *window);
void window_close_others(struct window *window);
@@ -55,3 +62,5 @@ void window_vsplit(struct window *window, struct window **new_window_a,
void windows_show_popup(uint32_t row, uint32_t col, uint32_t width,
uint32_t height);
void windows_close_popup();
+
+#endif
diff --git a/src/main/cmds.c b/src/main/cmds.c
index ecce343..26e2628 100644
--- a/src/main/cmds.c
+++ b/src/main/cmds.c
@@ -9,6 +9,7 @@
#include "dged/binding.h"
#include "dged/buffer.h"
+#include "dged/buffer_view.h"
#include "dged/buffers.h"
#include "dged/command.h"
#include "dged/display.h"
@@ -25,7 +26,7 @@ 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));
+ buffer_view_clear_mark(window_buffer_view(ctx.active_window));
reset_minibuffer_keys(minibuffer_buffer());
minibuffer_echo_timeout(4, "💣 aborted");
return 0;
@@ -52,11 +53,7 @@ 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());
- }
+ reset_minibuffer_keys(minibuffer_buffer());
windows_close_popup();
for (uint32_t compi = 0; compi < g_ncompletions; ++compi) {
@@ -83,6 +80,8 @@ static void complete_path(const char *path, struct completion results[],
size_t len = strlen(p1);
char *p2 = strdup(p1);
+ size_t inlen = strlen(path);
+
if (len == 0) {
goto done;
}
@@ -94,7 +93,9 @@ static void complete_path(const char *path, struct completion results[],
const char *dir = p1;
const char *file = "";
- if (dir[len - 1] != '/') {
+ // check the input path here since
+ // to_abspath removes trailing slashes
+ if (path[inlen - 1] != '/') {
dir = dirname(p1);
file = basename(p2);
}
@@ -143,20 +144,13 @@ done:
*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);
+void update_completion_buffer(struct buffer *buffer, uint32_t width,
+ uint32_t height, void *userdata) {
+ struct buffer_view *view = (struct buffer_view *)userdata;
+ struct text_chunk line = buffer_line(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},
+ view->buffer, (struct location){.line = view->dot.line, .col = 0},
+ (struct location){.line = view->dot.line, .col = line.nchars},
(struct text_property){.type = TextProperty_Colors,
.colors = (struct text_property_colors){
.set_bg = false,
@@ -168,17 +162,6 @@ update_completion_buffer(struct buffer_view *view,
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,
@@ -193,7 +176,7 @@ static int32_t goto_completion(struct command_ctx ctx, int argc,
movement_fn(v);
if (v->dot.line >= text_num_lines(b->text)) {
- buffer_backward_line(v);
+ buffer_view_backward_line(v);
}
}
@@ -211,15 +194,13 @@ static int32_t insert_completion(struct command_ctx ctx, int argc,
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);
- }
+ buffer_view_add(window_buffer_view(windows_get_active()), ins, inslen);
- if (complete) {
+ if (minibuffer_focused() && complete) {
minibuffer_execute();
}
+
+ abort_completion();
}
}
@@ -227,9 +208,9 @@ static int32_t insert_completion(struct command_ctx ctx, int argc,
}
COMMAND_FN("next-completion", next_completion, goto_completion,
- buffer_forward_line);
+ buffer_view_forward_line);
COMMAND_FN("prev-completion", prev_completion, goto_completion,
- buffer_backward_line);
+ buffer_view_backward_line);
COMMAND_FN("insert-completion", insert_completion, insert_completion, NULL);
static void on_find_file_input(void *userdata) {
@@ -237,12 +218,14 @@ static void on_find_file_input(void *userdata) {
struct text_chunk txt = minibuffer_content();
struct window *mb = minibuffer_window();
- struct buffer_location mb_dot = window_absolute_cursor_location(mb);
+ struct location mb_dot = window_buffer_view(mb)->dot;
+ struct window_position mbpos = window_position(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);
+ buffer_add_update_hook(b, update_completion_buffer,
+ (void *)window_buffer_view(popup_window()));
window_set_buffer_e(popup_window(), b, false, false);
}
@@ -261,9 +244,10 @@ static void on_find_file_input(void *userdata) {
complete_path(path, g_completions, 50, &g_ncompletions);
size_t max_width = 0;
- struct buffer_location prev_dot = v->dot;
+ struct location prev_dot = v->dot;
- buffer_clear(v);
+ buffer_clear(v->buffer);
+ buffer_view_goto(v, (struct location){.line = 0, .col = 0});
if (g_ncompletions > 0) {
for (uint32_t compi = 0; compi < g_ncompletions; ++compi) {
const char *disp = g_completions[compi].display;
@@ -271,17 +255,18 @@ static void on_find_file_input(void *userdata) {
if (width > max_width) {
max_width = width;
}
- buffer_add_text(v, (uint8_t *)disp, width);
+ buffer_view_add(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_view_add(v, (uint8_t *)"\n", 1);
}
}
- buffer_goto(v, prev_dot.line, prev_dot.col);
+ buffer_view_goto(
+ v, (struct location){.line = prev_dot.line, .col = prev_dot.col});
if (prev_dot.line >= text_num_lines(b->text)) {
- buffer_backward_line(v);
+ buffer_view_backward_line(v);
}
if (!popup_window_visible()) {
@@ -294,11 +279,12 @@ static void on_find_file_input(void *userdata) {
sizeof(bindings) / sizeof(bindings[0]));
}
- uint32_t width = max_width > 2 ? max_width + 2 : 4,
+ uint32_t width = max_width > 2 ? max_width : 4,
height = g_ncompletions > 10 ? 10 : g_ncompletions;
- windows_show_popup(mb_dot.line - height, mb_dot.col, width, height);
+ windows_show_popup(mbpos.y + mb_dot.line - height, mbpos.x + mb_dot.col,
+ width, height);
} else {
- windows_close_popup();
+ abort_completion();
}
if (txt.allocated) {
@@ -313,8 +299,6 @@ int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) {
"find file: ");
}
- abort_completion();
-
pth = argv[0];
struct stat sb = {0};
if (stat(pth, &sb) < 0 && errno != ENOENT) {
@@ -351,7 +335,8 @@ int32_t write_file(struct command_ctx ctx, int argc, const char *argv[]) {
}
pth = argv[0];
- buffer_write_to(window_buffer(ctx.active_window), pth);
+ buffer_set_filename(window_buffer(ctx.active_window), pth);
+ buffer_to_file(window_buffer(ctx.active_window));
return 0;
}
@@ -432,7 +417,7 @@ void buffer_to_list_line(struct buffer *buffer, void *userdata) {
buffer->filename != NULL ? buffer->filename : "<no-file>");
if (written > 0) {
- buffer_add_text(listbuf, (uint8_t *)buf, written);
+ buffer_view_add(listbuf, (uint8_t *)buf, written);
}
}
@@ -441,7 +426,7 @@ int32_t buflist_visit_cmd(struct command_ctx ctx, int argc,
struct window *w = ctx.active_window;
struct buffer_view *bv = window_buffer_view(w);
- struct text_chunk text = buffer_get_line(bv->buffer, bv->dot.line);
+ struct text_chunk text = buffer_line(bv->buffer, bv->dot.line);
char *end = (char *)memchr(text.text, ' ', text.nbytes);
@@ -468,9 +453,9 @@ int32_t buflist_close_cmd(struct command_ctx ctx, int argc,
void buflist_refresh(struct buffers *buffers, struct buffer_view *target) {
buffer_set_readonly(target->buffer, false);
- buffer_clear(target);
+ buffer_clear(target->buffer);
buffers_for_each(buffers, buffer_to_list_line, target);
- buffer_goto_beginning(target);
+ buffer_view_goto_beginning(target);
buffer_set_readonly(target->buffer, true);
}
@@ -535,102 +520,102 @@ void register_global_commands(struct commands *commands,
register_search_replace_commands(commands);
}
-#define BUFFER_WRAPCMD_POS(fn) \
+#define BUFFER_VIEW_WRAPCMD(fn) \
static int32_t fn##_cmd(struct command_ctx ctx, int argc, \
const char *argv[]) { \
- fn(window_buffer_view(ctx.active_window)); \
+ buffer_view_##fn(window_buffer_view(ctx.active_window)); \
return 0; \
}
#define BUFFER_WRAPCMD(fn) \
static int32_t fn##_cmd(struct command_ctx ctx, int argc, \
const char *argv[]) { \
- fn(window_buffer(ctx.active_window)); \
+ buffer_##fn(window_buffer(ctx.active_window)); \
return 0; \
}
-BUFFER_WRAPCMD_POS(buffer_kill_line);
-BUFFER_WRAPCMD_POS(buffer_forward_delete_char);
-BUFFER_WRAPCMD_POS(buffer_backward_delete_char);
-BUFFER_WRAPCMD_POS(buffer_forward_delete_word);
-BUFFER_WRAPCMD_POS(buffer_backward_delete_word);
-BUFFER_WRAPCMD_POS(buffer_backward_char);
-BUFFER_WRAPCMD_POS(buffer_backward_word);
-BUFFER_WRAPCMD_POS(buffer_forward_char);
-BUFFER_WRAPCMD_POS(buffer_forward_word);
-BUFFER_WRAPCMD_POS(buffer_backward_line);
-BUFFER_WRAPCMD_POS(buffer_forward_line);
-BUFFER_WRAPCMD_POS(buffer_end_of_line);
-BUFFER_WRAPCMD_POS(buffer_beginning_of_line);
-BUFFER_WRAPCMD_POS(buffer_newline);
-BUFFER_WRAPCMD_POS(buffer_indent);
-BUFFER_WRAPCMD(buffer_to_file);
-BUFFER_WRAPCMD(buffer_reload);
-BUFFER_WRAPCMD_POS(buffer_set_mark);
-BUFFER_WRAPCMD_POS(buffer_clear_mark);
-BUFFER_WRAPCMD_POS(buffer_copy);
-BUFFER_WRAPCMD_POS(buffer_cut);
-BUFFER_WRAPCMD_POS(buffer_paste);
-BUFFER_WRAPCMD_POS(buffer_paste_older);
-BUFFER_WRAPCMD_POS(buffer_goto_beginning);
-BUFFER_WRAPCMD_POS(buffer_goto_end);
-BUFFER_WRAPCMD_POS(buffer_undo);
-
-static int32_t buffer_view_scroll_up_cmd(struct command_ctx ctx, int argc,
- const char *argv[]) {
- buffer_view_scroll_up(window_buffer_view(ctx.active_window),
- window_height(ctx.active_window));
+BUFFER_WRAPCMD(to_file);
+BUFFER_WRAPCMD(reload);
+BUFFER_VIEW_WRAPCMD(kill_line);
+BUFFER_VIEW_WRAPCMD(forward_delete_char);
+BUFFER_VIEW_WRAPCMD(backward_delete_char);
+BUFFER_VIEW_WRAPCMD(forward_delete_word);
+BUFFER_VIEW_WRAPCMD(backward_delete_word);
+BUFFER_VIEW_WRAPCMD(backward_char);
+BUFFER_VIEW_WRAPCMD(backward_word);
+BUFFER_VIEW_WRAPCMD(forward_char);
+BUFFER_VIEW_WRAPCMD(forward_word);
+BUFFER_VIEW_WRAPCMD(backward_line);
+BUFFER_VIEW_WRAPCMD(forward_line);
+BUFFER_VIEW_WRAPCMD(goto_end_of_line);
+BUFFER_VIEW_WRAPCMD(goto_beginning_of_line);
+BUFFER_VIEW_WRAPCMD(newline);
+BUFFER_VIEW_WRAPCMD(indent);
+BUFFER_VIEW_WRAPCMD(set_mark);
+BUFFER_VIEW_WRAPCMD(clear_mark);
+BUFFER_VIEW_WRAPCMD(copy);
+BUFFER_VIEW_WRAPCMD(cut);
+BUFFER_VIEW_WRAPCMD(paste);
+BUFFER_VIEW_WRAPCMD(paste_older);
+BUFFER_VIEW_WRAPCMD(goto_beginning);
+BUFFER_VIEW_WRAPCMD(goto_end);
+BUFFER_VIEW_WRAPCMD(undo);
+
+static int32_t scroll_up_cmd(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ buffer_view_backward_nlines(window_buffer_view(ctx.active_window),
+ window_height(ctx.active_window) - 1);
return 0;
};
-static int32_t buffer_view_scroll_down_cmd(struct command_ctx ctx, int argc,
- const char *argv[]) {
- buffer_view_scroll_down(window_buffer_view(ctx.active_window),
- window_height(ctx.active_window));
+static int32_t scroll_down_cmd(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ buffer_view_forward_nlines(window_buffer_view(ctx.active_window),
+ window_height(ctx.active_window) - 1);
return 0;
};
-static int32_t buffer_goto_line(struct command_ctx ctx, int argc,
- const char *argv[]) {
+static int32_t goto_line(struct command_ctx ctx, int argc, const char *argv[]) {
if (argc == 0) {
return minibuffer_prompt(ctx, "line: ");
}
uint32_t line = atoi(argv[0]);
- buffer_goto(window_buffer_view(ctx.active_window), line - 1, 0);
+ buffer_view_goto(window_buffer_view(ctx.active_window),
+ (struct location){.line = line, .col = 0});
}
void register_buffer_commands(struct commands *commands) {
static struct command buffer_commands[] = {
- {.name = "kill-line", .fn = buffer_kill_line_cmd},
- {.name = "delete-word", .fn = buffer_forward_delete_word_cmd},
- {.name = "backward-delete-word", .fn = buffer_backward_delete_word_cmd},
- {.name = "delete-char", .fn = buffer_forward_delete_char_cmd},
- {.name = "backward-delete-char", .fn = buffer_backward_delete_char_cmd},
- {.name = "backward-char", .fn = buffer_backward_char_cmd},
- {.name = "backward-word", .fn = buffer_backward_word_cmd},
- {.name = "forward-char", .fn = buffer_forward_char_cmd},
- {.name = "forward-word", .fn = buffer_forward_word_cmd},
- {.name = "backward-line", .fn = buffer_backward_line_cmd},
- {.name = "forward-line", .fn = buffer_forward_line_cmd},
- {.name = "end-of-line", .fn = buffer_end_of_line_cmd},
- {.name = "beginning-of-line", .fn = buffer_beginning_of_line_cmd},
- {.name = "newline", .fn = buffer_newline_cmd},
- {.name = "indent", .fn = buffer_indent_cmd},
- {.name = "buffer-write-to-file", .fn = buffer_to_file_cmd},
- {.name = "set-mark", .fn = buffer_set_mark_cmd},
- {.name = "clear-mark", .fn = buffer_clear_mark_cmd},
- {.name = "copy", .fn = buffer_copy_cmd},
- {.name = "cut", .fn = buffer_cut_cmd},
- {.name = "paste", .fn = buffer_paste_cmd},
- {.name = "paste-older", .fn = buffer_paste_older_cmd},
- {.name = "goto-beginning", .fn = buffer_goto_beginning_cmd},
- {.name = "goto-end", .fn = buffer_goto_end_cmd},
- {.name = "undo", .fn = buffer_undo_cmd},
- {.name = "scroll-down", .fn = buffer_view_scroll_down_cmd},
- {.name = "scroll-up", .fn = buffer_view_scroll_up_cmd},
- {.name = "reload", .fn = buffer_reload_cmd},
- {.name = "goto-line", .fn = buffer_goto_line},
+ {.name = "kill-line", .fn = kill_line_cmd},
+ {.name = "delete-word", .fn = forward_delete_word_cmd},
+ {.name = "backward-delete-word", .fn = backward_delete_word_cmd},
+ {.name = "delete-char", .fn = forward_delete_char_cmd},
+ {.name = "backward-delete-char", .fn = backward_delete_char_cmd},
+ {.name = "backward-char", .fn = backward_char_cmd},
+ {.name = "backward-word", .fn = backward_word_cmd},
+ {.name = "forward-char", .fn = forward_char_cmd},
+ {.name = "forward-word", .fn = forward_word_cmd},
+ {.name = "backward-line", .fn = backward_line_cmd},
+ {.name = "forward-line", .fn = forward_line_cmd},
+ {.name = "end-of-line", .fn = goto_end_of_line_cmd},
+ {.name = "beginning-of-line", .fn = goto_beginning_of_line_cmd},
+ {.name = "newline", .fn = newline_cmd},
+ {.name = "indent", .fn = indent_cmd},
+ {.name = "buffer-write-to-file", .fn = to_file_cmd},
+ {.name = "set-mark", .fn = set_mark_cmd},
+ {.name = "clear-mark", .fn = clear_mark_cmd},
+ {.name = "copy", .fn = copy_cmd},
+ {.name = "cut", .fn = cut_cmd},
+ {.name = "paste", .fn = paste_cmd},
+ {.name = "paste-older", .fn = paste_older_cmd},
+ {.name = "goto-beginning", .fn = goto_beginning_cmd},
+ {.name = "goto-end", .fn = goto_end_cmd},
+ {.name = "undo", .fn = undo_cmd},
+ {.name = "scroll-down", .fn = scroll_down_cmd},
+ {.name = "scroll-up", .fn = scroll_up_cmd},
+ {.name = "reload", .fn = reload_cmd},
+ {.name = "goto-line", .fn = goto_line},
};
register_commands(commands, buffer_commands,
diff --git a/src/main/main.c b/src/main/main.c
index b6ee3c0..fd69cff 100644
--- a/src/main/main.c
+++ b/src/main/main.c
@@ -11,6 +11,7 @@
#include "dged/allocator.h"
#include "dged/binding.h"
#include "dged/buffer.h"
+#include "dged/buffer_view.h"
#include "dged/buffers.h"
#include "dged/display.h"
#include "dged/lang.h"
@@ -127,7 +128,7 @@ int main(int argc, char *argv[]) {
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}};
- char *filename = NULL;
+ const char *filename = NULL;
uint32_t jumpline = 1;
bool goto_end = false;
char ch;
@@ -239,9 +240,13 @@ int main(int argc, char *argv[]) {
&minibuffer);
struct window *active = windows_get_active();
if (goto_end) {
- buffer_goto_end(window_buffer_view(active));
+ buffer_view_goto_end(window_buffer_view(active));
} else {
- buffer_goto(window_buffer_view(active), jumpline > 0 ? jumpline - 1 : 0, 0);
+ struct location to = {
+ .line = jumpline > 0 ? jumpline - 1 : 0,
+ .col = 0,
+ };
+ buffer_view_goto(window_buffer_view(active), to);
}
DECLARE_TIMER(buffer);
@@ -253,7 +258,6 @@ int main(int argc, char *argv[]) {
static uint32_t nkeychars = 0;
while (running) {
-
if (display_resized) {
windows_resize(display_height(display), display_width(display));
display_resized = false;
@@ -265,9 +269,6 @@ int main(int argc, char *argv[]) {
TIMED_SCOPE_END(buffer);
struct window *active_window = windows_get_active();
- if (minibuffer_focused()) {
- active_window = minibuffer_window();
- }
/* Update the screen by flushing command lists collected from updating the
* buffers.
@@ -275,14 +276,15 @@ int main(int argc, char *argv[]) {
TIMED_SCOPE_BEGIN(display);
display_begin_render(display);
windows_render(display);
- struct buffer_location cursor =
- window_absolute_cursor_location(active_window);
- display_move_cursor(display, cursor.line, cursor.col);
+ struct buffer_view *view = window_buffer_view(active_window);
+ struct location cursor = buffer_view_dot_to_relative(view);
+ struct window_position winpos = window_position(active_window);
+ display_move_cursor(display, winpos.y + cursor.line, winpos.x + cursor.col);
display_end_render(display);
TIMED_SCOPE_END(display);
/* This blocks for events, so if nothing has happened we block here and let
- * the CPU do something more useful than updating this narcissistic editor.
+ * the CPU do something more useful than updating this editor for no reason.
* This is also the reason that there is no timed scope around this, it
* simply makes no sense.
*/
@@ -343,7 +345,7 @@ int main(int argc, char *argv[]) {
}
}
} else if (k->mod == 0) {
- buffer_add_text(window_buffer_view(active_window),
+ buffer_view_add(window_buffer_view(active_window),
&kbd_upd.raw[k->start], k->end - k->start);
} else {
char keyname[16];
diff --git a/src/main/search-replace.c b/src/main/search-replace.c
index 828ce32..cd12d5d 100644
--- a/src/main/search-replace.c
+++ b/src/main/search-replace.c
@@ -4,6 +4,7 @@
#include "dged/binding.h"
#include "dged/buffer.h"
+#include "dged/buffer_view.h"
#include "dged/command.h"
#include "dged/minibuffer.h"
#include "dged/window.h"
@@ -13,7 +14,7 @@
static struct replace {
char *replace;
- struct match *matches;
+ struct region *matches;
uint32_t nmatches;
uint32_t current_match;
} g_current_replace = {0};
@@ -28,8 +29,8 @@ void abort_replace() {
minibuffer_abort_prompt();
}
-uint64_t matchdist(struct match *match, struct buffer_location loc) {
- struct buffer_location begin = match->begin;
+uint64_t matchdist(struct region *match, struct location loc) {
+ struct 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;
@@ -38,26 +39,10 @@ uint64_t matchdist(struct match *match, struct buffer_location loc) {
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,
+static void highlight_matches(struct buffer *buffer, struct region *matches,
uint32_t nmatches, uint32_t current) {
for (uint32_t matchi = 0; matchi < nmatches; ++matchi) {
- struct match *m = &matches[matchi];
+ struct region *m = &matches[matchi];
if (matchi == current) {
buffer_add_text_property(
buffer, m->begin, m->end,
@@ -88,10 +73,12 @@ static int32_t replace_next(struct command_ctx ctx, int argc,
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));
+ struct region *m = &state->matches[state->current_match];
+ buffer_view_set_mark_at(buffer_view, (struct location){.line = m->begin.line,
+ .col = m->begin.col});
+ buffer_view_goto(buffer_view, (struct location){.line = m->end.line,
+ .col = m->end.col + 1});
+ buffer_view_add(buffer_view, state->replace, strlen(state->replace));
++state->current_match;
@@ -99,7 +86,8 @@ static int32_t replace_next(struct command_ctx ctx, int argc,
abort_replace();
} else {
m = &state->matches[state->current_match];
- buffer_goto(buffer_view, m->begin.line, m->begin.col);
+ buffer_view_goto(buffer_view, (struct location){.line = m->begin.line,
+ .col = m->begin.col});
highlight_matches(buffer_view->buffer, state->matches, state->nmatches,
state->current_match);
}
@@ -111,8 +99,9 @@ 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);
+ struct region *m = &state->matches[state->current_match];
+ buffer_view_goto(buffer_view, (struct location){.line = m->end.line,
+ .col = m->end.col + 1});
++state->current_match;
@@ -120,7 +109,8 @@ static int32_t skip_next(struct command_ctx ctx, int argc, const char *argv[]) {
abort_replace();
} else {
m = &state->matches[state->current_match];
- buffer_goto(buffer_view, m->begin.line, m->begin.col);
+ buffer_view_goto(buffer_view, (struct location){.line = m->begin.line,
+ .col = m->begin.col});
highlight_matches(buffer_view->buffer, state->matches, state->nmatches,
state->current_match);
}
@@ -132,14 +122,14 @@ 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;
+ struct region *match1 = (struct region *)m1;
+ struct region *match2 = (struct region *)m2;
+ struct 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);
+ int loc1 = location_compare(match1->begin, dot);
+ int loc2 = location_compare(match2->begin, dot);
int64_t score1 = dist1 * loc1;
int64_t score2 = dist2 * loc2;
@@ -166,7 +156,7 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) {
}
struct buffer_view *buffer_view = window_buffer_view(windows_get_active());
- struct match *matches = NULL;
+ struct region *matches = NULL;
uint32_t nmatches = 0;
buffer_find(buffer_view->buffer, argv[0], &matches, &nmatches);
@@ -177,7 +167,7 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) {
}
// sort matches
- qsort(matches, nmatches, sizeof(struct match), cmp_matches);
+ qsort(matches, nmatches, sizeof(struct region), cmp_matches);
g_current_replace = (struct replace){
.replace = strdup(argv[1]),
@@ -186,8 +176,9 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) {
.current_match = 0,
};
- struct match *m = &g_current_replace.matches[0];
- buffer_goto(buffer_view, m->begin.line, m->begin.col);
+ struct region *m = &g_current_replace.matches[0];
+ buffer_view_goto(buffer_view, (struct location){.line = m->begin.line,
+ .col = m->begin.col});
highlight_matches(buffer_view->buffer, g_current_replace.matches,
g_current_replace.nmatches, 0);
@@ -203,6 +194,7 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) {
}
static char *g_last_search = NULL;
+static bool g_last_search_interactive = false;
const char *search_prompt(bool reverse) {
const char *txt = "search (down): ";
@@ -215,13 +207,13 @@ const char *search_prompt(bool reverse) {
struct closest_match {
bool found;
- struct match closest;
+ struct region closest;
};
static struct closest_match find_closest(struct buffer_view *view,
const char *pattern, bool highlight,
bool reverse) {
- struct match *matches = NULL;
+ struct region *matches = NULL;
uint32_t nmatches = 0;
struct closest_match res = {
.found = false,
@@ -233,10 +225,10 @@ static struct closest_match find_closest(struct buffer_view *view,
// find the "nearest" match
if (nmatches > 0) {
res.found = true;
- struct match *closest = &matches[0];
+ struct region *closest = &matches[0];
int64_t closest_dist = INT64_MAX;
for (uint32_t matchi = 0; matchi < nmatches; ++matchi) {
- struct match *m = &matches[matchi];
+ struct region *m = &matches[matchi];
if (highlight) {
buffer_add_text_property(
@@ -249,7 +241,7 @@ static struct closest_match find_closest(struct buffer_view *view,
.fg = 0,
}});
}
- int res = buffer_loc_cmp(m->begin, view->dot);
+ int res = location_compare(m->begin, view->dot);
uint64_t dist = matchdist(m, view->dot);
if (((res < 0 && reverse) || (res > 0 && !reverse)) &&
dist < closest_dist) {
@@ -279,12 +271,13 @@ static struct closest_match find_closest(struct buffer_view *view,
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);
+ struct closest_match m = find_closest(view, pattern, true, reverse);
// find the "nearest" match
if (m.found) {
- buffer_goto(buffer_view, m.closest.begin.line, m.closest.begin.col);
+ buffer_view_goto(view,
+ (struct location){.line = m.closest.begin.line,
+ .col = m.closest.begin.col});
} else {
minibuffer_echo_timeout(4, "%s not found", pattern);
}
@@ -292,13 +285,13 @@ void do_search(struct buffer_view *view, const char *pattern, bool reverse) {
int32_t search_interactive(struct command_ctx ctx, int argc,
const char *argv[]) {
+ g_last_search_interactive = true;
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));
+ buffer_set_text(view->buffer, (uint8_t *)g_last_search, strlen(g_last_search));
pattern = g_last_search;
}
} else {
@@ -309,11 +302,10 @@ int32_t search_interactive(struct command_ctx ctx, int argc,
pattern = p;
}
- minibuffer_set_prompt(search_prompt(*(bool *)ctx.userdata));
+ 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,
+ do_search(window_buffer_view(minibuffer_target_window()), pattern,
*(bool *)ctx.userdata);
free((char *)pattern);
}
@@ -329,7 +321,7 @@ 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;
+ bool reverse = *(bool *)ctx.userdata;
if (argc == 0) {
struct binding bindings[] = {
ANONYMOUS_BINDING(Ctrl, 'S', &search_forward_command),
@@ -341,6 +333,11 @@ int32_t find(struct command_ctx ctx, int argc, const char *argv[]) {
}
reset_minibuffer_keys(minibuffer_buffer());
+ if (g_last_search_interactive) {
+ g_last_search_interactive = false;
+ return 0;
+ }
+
struct text_chunk content = minibuffer_content();
char *pattern = malloc(content.nbytes + 1);
strncpy(pattern, content.text, content.nbytes);
@@ -354,8 +351,8 @@ int32_t find(struct command_ctx ctx, int argc, const char *argv[]) {
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 = "find-next", .fn = find, .userdata = &search_dir_forward},
+ {.name = "find-prev", .fn = find, .userdata = &search_dir_backward},
{.name = "replace", .fn = replace},
};
diff --git a/test/buffer.c b/test/buffer.c
index a4ac754..19fca8c 100644
--- a/test/buffer.c
+++ b/test/buffer.c
@@ -5,45 +5,16 @@
#include "assert.h"
#include "test.h"
-void test_move() {
+void test_add() {
struct buffer b = buffer_create("test-buffer");
- struct buffer_view v = buffer_view_create(&b, false, false);
- ASSERT(v.dot.col == 0 && v.dot.line == 0,
- "Expected dot to be at buffer start");
+ ASSERT(buffer_num_lines(&b) == 0, "Expected buffer to have zero lines");
- // make sure we cannot move now
- buffer_backward_char(&v);
- buffer_backward_line(&v);
- ASSERT(v.dot.col == 0 && v.dot.line == 0,
- "Expected to not be able to move backward in empty buffer");
+ const char *txt = "we are adding some text";
+ struct location loc = buffer_add(&b, (struct location){.line = 1, .col = 0},
+ (uint8_t *)txt, strlen(txt));
- buffer_forward_char(&v);
- buffer_forward_line(&v);
- ASSERT(v.dot.col == 0 && v.dot.line == 0,
- "Expected to not be able to move forward in empty buffer");
-
- // add some text and try again
- const char *txt = "testing movement";
- int lineindex = buffer_add_text(&v, (uint8_t *)txt, strlen(txt));
- ASSERT(lineindex + 1 == 1, "Expected buffer to have one line");
-
- buffer_beginning_of_line(&v);
- buffer_forward_char(&v);
- ASSERT(v.dot.col == 1 && v.dot.line == 0,
- "Expected to be able to move forward by one char");
-
- // now we have two lines
- const char *txt2 = "\n";
- int lineindex2 = buffer_add_text(&v, (uint8_t *)txt2, strlen(txt2));
- ASSERT(lineindex2 + 1 == 2, "Expected buffer to have two lines");
- buffer_backward_line(&v);
- buffer_beginning_of_line(&v);
- buffer_backward_char(&v);
- ASSERT(
- v.dot.col == 0 && v.dot.line == 0,
- "Expected to not be able to move backwards when at beginning of buffer");
-
- buffer_destroy(&b);
+ ASSERT(loc.line == 1 && loc.col == strlen(txt),
+ "Expected buffer to have one line with characters");
}
-void run_buffer_tests() { run_test(test_move); }
+void run_buffer_tests() { run_test(test_add); }
diff --git a/test/minibuffer.c b/test/minibuffer.c
index 5c5ae63..7ac6c9b 100644
--- a/test/minibuffer.c
+++ b/test/minibuffer.c
@@ -4,6 +4,7 @@
#include "dged/allocator.h"
#include "dged/buffer.h"
+#include "dged/buffer_view.h"
#include "dged/command.h"
#include "dged/display.h"
#include "dged/minibuffer.h"
@@ -32,11 +33,10 @@ void destroy() {
}
void test_minibuffer_echo() {
- uint32_t relline, relcol;
struct buffer_view view = buffer_view_create(&b, false, false);
// TODO: how to clear this?
- struct frame_allocator alloc = frame_allocator_create(1024);
+ struct frame_allocator alloc = frame_allocator_create(1024 * 1024);
g_alloc = &alloc;
struct command_list *list =
@@ -50,13 +50,23 @@ void test_minibuffer_echo() {
ASSERT(minibuffer_displaying(), "Minibuffer should now have text to display");
minibuffer_clear();
- buffer_update(&view, -1, 100, 1, list, 0, &relline, &relcol);
+ struct buffer_view_update_params p = {
+ .commands = list,
+ .frame_alloc = alloc_fn,
+ .window_id = -1,
+ .frame_time = 0,
+ .width = 100,
+ .height = 1,
+ .window_x = 0,
+ .window_y = 25,
+ };
+ buffer_view_update(&view, &p);
ASSERT(!minibuffer_displaying(),
"Minibuffer should have nothing to display after clearing");
minibuffer_echo_timeout(0, "You will not see me");
- buffer_update(&view, -1, 100, 1, list, 0, &relline, &relcol);
+ buffer_view_update(&view, &p);
ASSERT(!minibuffer_displaying(),
"A zero timeout echo should be cleared after first update");