summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlbert Cervin <albert@acervin.com>2023-11-26 23:08:06 +0100
committerAlbert Cervin <albert@acervin.com>2024-01-15 10:39:56 +0100
commit64d6816a36567274551dd4f067fe4d05b1445cc0 (patch)
tree50f8dc895d363ab391d30226f665870d8ce263b5 /src
parentc87888f10dfb54590c5aae8311b7aff887193d9a (diff)
downloaddged-64d6816a36567274551dd4f067fe4d05b1445cc0.tar.gz
dged-64d6816a36567274551dd4f067fe4d05b1445cc0.tar.xz
dged-64d6816a36567274551dd4f067fe4d05b1445cc0.zip
Completion rework
- Add support for building with clang Also fix some annoying bugs: - Visual column was wrong when using tabs - Add shift-tab for inserting an actual tab - Fix minibuffer sometimes having dot above it
Diffstat (limited to 'src')
-rw-r--r--src/dged/buffer.c209
-rw-r--r--src/dged/buffer.h91
-rw-r--r--src/dged/buffer_view.c35
-rw-r--r--src/dged/buffer_view.h1
-rw-r--r--src/dged/display.c88
-rw-r--r--src/dged/keyboard.c2
-rw-r--r--src/dged/minibuffer.c49
-rw-r--r--src/dged/minibuffer.h4
-rw-r--r--src/dged/reactor-epoll.c2
-rw-r--r--src/dged/settings-parse.c3
-rw-r--r--src/dged/settings.c16
-rw-r--r--src/dged/text.c2
-rw-r--r--src/dged/utf8.c2
-rw-r--r--src/dged/vec.h17
-rw-r--r--src/dged/window.c1
-rw-r--r--src/main/bindings.c129
-rw-r--r--src/main/bindings.h14
-rw-r--r--src/main/cmds.c366
-rw-r--r--src/main/completion.c541
-rw-r--r--src/main/completion.h83
-rw-r--r--src/main/main.c40
-rw-r--r--src/main/search-replace.c23
22 files changed, 1135 insertions, 583 deletions
diff --git a/src/dged/buffer.c b/src/dged/buffer.c
index c95da91..f1ce2ce 100644
--- a/src/dged/buffer.c
+++ b/src/dged/buffer.c
@@ -21,11 +21,6 @@
#include <unistd.h>
#include <wchar.h>
-struct modeline {
- uint8_t *buffer;
- uint32_t sz;
-};
-
#define KILL_RING_SZ 64
static struct kill_ring {
struct text_chunk buffer[KILL_RING_SZ];
@@ -39,23 +34,78 @@ static struct kill_ring {
.paste_idx = 0,
.paste_up_to_date = false};
-#define MAX_CREATE_HOOKS 32
-static struct create_hook {
- create_hook_cb callback;
- void *userdata;
-} g_create_hooks[MAX_CREATE_HOOKS];
-static uint32_t g_num_create_hooks = 0;
+#define DECLARE_HOOK(name, callback_type, vec_type) \
+ struct name##_hook { \
+ uint32_t id; \
+ callback_type callback; \
+ void *userdata; \
+ }; \
+ \
+ static uint32_t insert_##name##_hook( \
+ vec_type *hooks, uint32_t *id, callback_type callback, void *userdata) { \
+ uint32_t iid = ++(*id); \
+ struct name##_hook hook = (struct name##_hook){ \
+ .id = iid, \
+ .callback = callback, \
+ .userdata = userdata, \
+ }; \
+ VEC_PUSH(hooks, hook); \
+ \
+ return iid; \
+ } \
+ \
+ static void remove_##name##_hook(vec_type *hooks, uint32_t id, \
+ remove_hook_cb callback) { \
+ uint64_t found_at = -1; \
+ VEC_FOR_EACH_INDEXED(hooks, struct name##_hook *h, idx) { \
+ if (h->id == id) { \
+ callback(h->userdata); \
+ found_at = idx; \
+ break; \
+ } \
+ } \
+ if (found_at != -1) { \
+ if (found_at < VEC_SIZE(hooks) - 1) { \
+ VEC_SWAP(hooks, found_at, VEC_SIZE(hooks) - 1); \
+ } \
+ VEC_POP(hooks, struct name##_hook removed); \
+ } \
+ }
+
+typedef VEC(struct create_hook) create_hook_vec;
+typedef VEC(struct insert_hook) insert_hook_vec;
+typedef VEC(struct update_hook) update_hook_vec;
+typedef VEC(struct delete_hook) delete_hook_vec;
+
+DECLARE_HOOK(create, create_hook_cb, create_hook_vec);
+DECLARE_HOOK(insert, insert_hook_cb, insert_hook_vec);
+DECLARE_HOOK(update, update_hook_cb, update_hook_vec);
+DECLARE_HOOK(delete, delete_hook_cb, delete_hook_vec);
+
+static create_hook_vec g_create_hooks;
+uint32_t g_create_hook_id;
+
+struct hooks {
+ create_hook_vec create_hooks;
+ uint32_t create_hook_id;
+
+ insert_hook_vec insert_hooks;
+ uint32_t insert_hook_id;
+
+ update_hook_vec update_hooks;
+ uint32_t update_hook_id;
+
+ delete_hook_vec delete_hooks;
+ uint32_t delete_hook_id;
+};
-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){
- .callback = hook,
- .userdata = userdata,
- };
- ++g_num_create_hooks;
- }
+uint32_t buffer_add_create_hook(create_hook_cb callback, void *userdata) {
+ return insert_create_hook(&g_create_hooks, &g_create_hook_id, callback,
+ userdata);
+}
- return g_num_create_hooks - 1;
+void buffer_remove_create_hook(uint32_t hook_id, remove_hook_cb callback) {
+ remove_create_hook(&g_create_hooks, hook_id, callback);
}
void buffer_static_init() {
@@ -88,7 +138,10 @@ static struct buffer create_internal(const char *name, char *filename) {
.last_write = {0},
};
- VEC_INIT(&b.update_hooks, 32);
+ b.hooks = calloc(1, sizeof(struct hooks));
+ VEC_INIT(&b.hooks->insert_hooks, 8);
+ VEC_INIT(&b.hooks->update_hooks, 8);
+ VEC_INIT(&b.hooks->delete_hooks, 8);
undo_init(&b.undo, 100);
@@ -136,31 +189,9 @@ static bool moveh(struct buffer *buffer, int64_t coldelta,
return true;
}
-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 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});
-
- text_delete(buffer->text, start.line, start.col, end.line, end.col);
- buffer->modified = true;
-}
-
static void maybe_delete_region(struct buffer *buffer, struct region region) {
if (region_has_size(region)) {
- delete_with_undo(buffer, region.begin, region.end);
+ buffer_delete(buffer, region);
}
}
@@ -254,8 +285,8 @@ 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);
+ VEC_FOR_EACH(&g_create_hooks, struct create_hook * h) {
+ h->callback(&b, h->userdata);
}
return b;
@@ -266,8 +297,8 @@ struct buffer buffer_from_file(const char *path) {
struct buffer b = create_internal(basename((char *)path), full_path);
buffer_read_from_file(&b);
- for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) {
- g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata);
+ VEC_FOR_EACH(&g_create_hooks, struct create_hook * h) {
+ h->callback(&b, h->userdata);
}
return b;
@@ -343,6 +374,11 @@ void buffer_destroy(struct buffer *buffer) {
free(buffer->filename);
buffer->filename = NULL;
+ VEC_DESTROY(&buffer->hooks->update_hooks);
+ VEC_DESTROY(&buffer->hooks->insert_hooks);
+ VEC_DESTROY(&buffer->hooks->delete_hooks);
+ free(buffer->hooks);
+
undo_destroy(&buffer->undo);
}
@@ -381,6 +417,10 @@ struct location buffer_add(struct buffer *buffer, struct location at,
(struct undo_boundary){.save_point = false});
}
+ VEC_FOR_EACH(&buffer->hooks->insert_hooks, struct insert_hook * h) {
+ h->callback(buffer, region_new(initial, final), h->userdata);
+ }
+
buffer->modified = true;
return final;
}
@@ -473,8 +513,8 @@ struct location buffer_newline(struct buffer *buffer, struct location at) {
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);
+ return buffer_add(buffer, at, (uint8_t *)" ",
+ tab_width > 16 ? 16 : tab_width);
}
struct location buffer_undo(struct buffer *buffer, struct location dot) {
@@ -557,7 +597,7 @@ static void search_line(struct text_chunk *chunk, void *userdata) {
uint32_t pattern_nchars = utf8_nchars((uint8_t *)data->pattern, pattern_len);
char *line = malloc(chunk->nbytes + 1);
- strncpy(line, chunk->text, chunk->nbytes);
+ strncpy(line, (const char *)chunk->text, chunk->nbytes);
line[chunk->nbytes] = '\0';
char *hit = NULL;
uint32_t byteidx = 0;
@@ -632,6 +672,10 @@ struct location buffer_delete(struct buffer *buffer, struct region region) {
region.end.line, region.end.col);
buffer->modified = true;
+ VEC_FOR_EACH(&buffer->hooks->delete_hooks, struct delete_hook * h) {
+ h->callback(buffer, region, h->userdata);
+ }
+
return region.begin;
}
@@ -660,7 +704,7 @@ struct location buffer_paste_older(struct buffer *buffer, struct location at) {
// 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);
+ buffer_delete(buffer, region_new(g_kill_ring.last_paste, at));
// paste older
if (g_kill_ring.paste_idx - 1 > 0) {
@@ -669,10 +713,10 @@ struct location buffer_paste_older(struct buffer *buffer, struct location at) {
g_kill_ring.paste_idx = g_kill_ring.curr_idx;
}
- paste(buffer, g_kill_ring.last_paste, g_kill_ring.paste_idx);
+ return paste(buffer, g_kill_ring.last_paste, g_kill_ring.paste_idx);
} else {
- buffer_paste(buffer, at);
+ return buffer_paste(buffer, at);
}
}
@@ -680,15 +724,42 @@ struct text_chunk buffer_line(struct buffer *buffer, uint32_t line) {
return text_get_line(buffer->text, line);
}
+struct text_chunk buffer_region(struct buffer *buffer, struct region region) {
+ return text_get_region(buffer->text, region.begin.line, region.begin.col,
+ region.end.line, region.end.col);
+}
+
+uint32_t buffer_add_insert_hook(struct buffer *buffer, insert_hook_cb hook,
+ void *userdata) {
+ return insert_insert_hook(&buffer->hooks->insert_hooks,
+ &buffer->hooks->insert_hook_id, hook, userdata);
+}
+
+void buffer_remove_insert_hook(struct buffer *buffer, uint32_t hook_id,
+ remove_hook_cb callback) {
+ remove_insert_hook(&buffer->hooks->insert_hooks, hook_id, callback);
+}
+
+uint32_t buffer_add_delete_hook(struct buffer *buffer, delete_hook_cb hook,
+ void *userdata) {
+ return insert_delete_hook(&buffer->hooks->delete_hooks,
+ &buffer->hooks->delete_hook_id, hook, userdata);
+}
+
+void buffer_remove_delete_hook(struct buffer *buffer, uint32_t hook_id,
+ remove_hook_cb callback) {
+ remove_delete_hook(&buffer->hooks->delete_hooks, hook_id, callback);
+}
+
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;
+ return insert_update_hook(&buffer->hooks->update_hooks,
+ &buffer->hooks->update_hook_id, hook, userdata);
+}
- // TODO: cant really have this if we actually want to remove a hook
- return VEC_SIZE(&buffer->update_hooks) - 1;
+void buffer_remove_update_hook(struct buffer *buffer, uint32_t hook_id,
+ remove_hook_cb callback) {
+ remove_update_hook(&buffer->hooks->update_hooks, hook_id, callback);
}
struct cmdbuf {
@@ -710,8 +781,8 @@ static uint32_t visual_char_width(uint8_t *byte, uint32_t maxlen) {
}
}
-static uint32_t visual_string_width(uint8_t *txt, uint32_t len,
- uint32_t start_col, uint32_t end_col) {
+uint32_t visual_string_width(uint8_t *txt, uint32_t len, uint32_t start_col,
+ uint32_t end_col) {
uint32_t start_byte = utf8_nbytes(txt, len, start_col);
uint32_t end_byte = utf8_nbytes(txt, len, end_col);
@@ -780,9 +851,10 @@ void render_line(struct text_chunk *line, void *userdata) {
// apply new properties
for (uint32_t propi = 0; propi < nnew_props; ++propi) {
struct text_property *prop = new_props[propi];
+ struct text_property_colors *colors = &prop->colors;
+
switch (prop->type) {
case TextProperty_Colors:
- struct text_property_colors *colors = &prop->colors;
if (colors->set_bg) {
command_list_set_index_color_bg(cmdbuf->cmds, colors->bg);
}
@@ -821,13 +893,14 @@ void render_line(struct text_chunk *line, void *userdata) {
}
void buffer_update(struct buffer *buffer, struct buffer_update_params *params) {
- if (params->width == 0 || params->height == 0) {
- return;
+ VEC_FOR_EACH(&buffer->hooks->update_hooks, struct update_hook * h) {
+ h->callback(buffer, h->userdata);
}
+}
- 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);
+void buffer_render(struct buffer *buffer, struct buffer_render_params *params) {
+ if (params->width == 0 || params->height == 0) {
+ return;
}
struct setting *show_ws = settings_get("editor.show-whitespace");
diff --git a/src/dged/buffer.h b/src/dged/buffer.h
index e29e3e1..7e4ef78 100644
--- a/src/dged/buffer.h
+++ b/src/dged/buffer.h
@@ -14,29 +14,7 @@
#include "window.h"
struct command_list;
-
-/** Buffer update hook callback function */
-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.
- */
-struct update_hook {
- /** Callback function */
- update_hook_cb callback;
-
- /** Optional userdata to pass to the callback function unmodified */
- void *userdata;
-};
-
-struct update_hook_entry {
- uint32_t id;
- struct update_hook hook;
-};
+struct hooks;
/**
* A buffer of text that can be modified, read from and written to disk.
@@ -55,12 +33,12 @@ struct buffer {
/** Time when buffer was last written to disk */
struct timespec last_write;
+ /** Hooks for this buffer */
+ struct hooks *hooks;
+
/** Text data structure */
struct text *text;
- /** Buffer update hooks */
- VEC(struct update_hook_entry) update_hooks;
-
/** Buffer undo stack */
struct undo_stack undo;
@@ -396,6 +374,16 @@ struct location buffer_paste_older(struct buffer *buffer, struct location at);
*/
struct text_chunk buffer_line(struct buffer *buffer, uint32_t line);
+/**
+ * Get a region of text from the buffer.
+ *
+ * @param buffer The buffer to get text from.
+ * @param region A region representing the buffer area to get text from.
+ *
+ * @returns A text chunk describing the region.
+ */
+struct text_chunk buffer_region(struct buffer *buffer, struct region region);
+
void buffer_add_text_property(struct buffer *buffer, struct location start,
struct location end,
struct text_property property);
@@ -407,6 +395,12 @@ void buffer_get_text_properties(struct buffer *buffer, struct location location,
void buffer_clear_text_properties(struct buffer *buffer);
+/** Callback when removing hooks to clean up userdata */
+typedef void (*remove_hook_cb)(void *userdata);
+
+/** Buffer update hook callback function */
+typedef void (*update_hook_cb)(struct buffer *buffer, void *userdata);
+
/**
* Add a buffer update hook.
*
@@ -418,23 +412,51 @@ void buffer_clear_text_properties(struct buffer *buffer);
uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook,
void *userdata);
+void buffer_remove_update_hook(struct buffer *buffer, uint32_t hook_id,
+ remove_hook_cb callback);
+
+/** Buffer insert hook callback function */
+typedef void (*insert_hook_cb)(struct buffer *buffer, struct region inserted,
+ void *userdata);
+
+uint32_t buffer_add_insert_hook(struct buffer *buffer, insert_hook_cb callback,
+ void *userdata);
+void buffer_remove_insert_hook(struct buffer *buffer, uint32_t hook_id,
+ remove_hook_cb callback);
+
+/** Buffer delete hook callback function */
+typedef void (*delete_hook_cb)(struct buffer *buffer, struct region removed,
+ void *userdata);
+
+uint32_t buffer_add_delete_hook(struct buffer *buffer, delete_hook_cb callback,
+ void *userdata);
+void buffer_remove_delete_hook(struct buffer *buffer, uint32_t hook_id,
+ remove_hook_cb callback);
+
/** 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] callback 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);
+uint32_t buffer_add_create_hook(create_hook_cb callback, void *userdata);
+
+void buffer_remove_create_hook(uint32_t hook_id, remove_hook_cb callback);
/**
* Parameters for updating a buffer.
*/
-struct buffer_update_params {
+struct buffer_update_params {};
+
+/**
+ * Parameters for rendering a buffer.
+ */
+struct buffer_render_params {
/** Command list to add rendering commands for the buffer */
struct command_list *commands;
@@ -459,4 +481,15 @@ struct buffer_update_params {
*/
void buffer_update(struct buffer *buffer, struct buffer_update_params *params);
+/**
+ * Render a buffer.
+ * @param [in] buffer The buffer to render.
+ * @param [inout] params The parameters for the rendering.
+ */
+void buffer_render(struct buffer *buffer, struct buffer_render_params *params);
+
+// TODO: move this to where it makes sense
+uint32_t visual_string_width(uint8_t *txt, uint32_t len, uint32_t start_col,
+ uint32_t end_col);
+
#endif
diff --git a/src/dged/buffer_view.c b/src/dged/buffer_view.c
index a95e3b0..05d3caa 100644
--- a/src/dged/buffer_view.c
+++ b/src/dged/buffer_view.c
@@ -3,6 +3,7 @@
#include "buffer.h"
#include "buffer_view.h"
#include "display.h"
+#include "utf8.h"
struct modeline {
uint8_t *buffer;
@@ -240,6 +241,24 @@ struct location buffer_view_dot_to_relative(struct buffer_view *view) {
};
}
+struct location buffer_view_dot_to_visual(struct buffer_view *view) {
+ // calculate visual column index for dot column
+ struct text_chunk c = buffer_line(view->buffer, view->dot.line);
+ uint32_t width = visual_string_width(c.text, c.nbytes, 0, view->dot.col);
+ if (view->scroll.col > 0) {
+ width -= visual_string_width(c.text, c.nbytes, 0, view->scroll.col);
+ }
+
+ struct location l = buffer_view_dot_to_relative(view);
+ l.col = width + view->fringe_width;
+
+ if (c.allocated) {
+ free(c.text);
+ }
+
+ return l;
+}
+
void buffer_view_undo(struct buffer_view *view) {
view->dot = buffer_undo(view->buffer, view->dot);
}
@@ -345,6 +364,9 @@ static void render_modeline(struct modeline *modeline, struct buffer_view *view,
void buffer_view_update(struct buffer_view *view,
struct buffer_view_update_params *params) {
+ struct buffer_update_params update_params = {};
+ buffer_update(view->buffer, &update_params);
+
uint32_t height = params->height;
uint32_t width = params->width;
@@ -409,22 +431,13 @@ void buffer_view_update(struct buffer_view *view,
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 = {
+ struct buffer_render_params render_params = {
.commands = buf_cmds,
.origin = view->scroll,
.width = width,
.height = height,
};
- buffer_update(view->buffer, &bufparams);
-
- /* Make sure the dot is always inside buffer limits.
- * Updating the buffer above could have removed text.
- * TODO: this is not really correct, since it may have caused
- * changes that would need a re-eval of scroll and redraw.
- * Hooks should prob not get width and height and be ran before rendering.
- */
- view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line,
- (int64_t)view->dot.col);
+ buffer_render(view->buffer, &render_params);
// draw buffer commands nested inside this command list
command_list_draw_command_list(params->commands, buf_cmds);
diff --git a/src/dged/buffer_view.h b/src/dged/buffer_view.h
index 0002b95..1c8fa6a 100644
--- a/src/dged/buffer_view.h
+++ b/src/dged/buffer_view.h
@@ -80,6 +80,7 @@ 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);
+struct location buffer_view_dot_to_visual(struct buffer_view *view);
void buffer_view_undo(struct buffer_view *view);
diff --git a/src/dged/display.c b/src/dged/display.c
index b779d5f..85a6e0b 100644
--- a/src/dged/display.c
+++ b/src/dged/display.c
@@ -5,6 +5,7 @@
#include "utf8.h"
#include <assert.h>
+#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -130,24 +131,49 @@ uint32_t display_width(struct display *display) { return display->width; }
uint32_t display_height(struct display *display) { return display->height; }
void putch(uint8_t c) {
- if (c != '\r') {
+ // TODO: move this to buffer rendering
+ if (c < ' ') {
+ fprintf(stdout, "^%c", c + 0x40);
+ } else if (c == 0x7f) {
+ fprintf(stdout, "^?");
+ } else if (utf8_byte_is_unicode_start(c) ||
+ utf8_byte_is_unicode_continuation(c)) {
putc(c, stdout);
+ } else if (c >= ' ' && c < 0x7f) {
+ putc(c, stdout);
+ } else {
+ fprintf(stdout, "|0x%02x|", c);
+ }
+}
+
+static void apply_fmt(uint8_t *fmt_stack, uint32_t fmt_stack_len) {
+ if (fmt_stack == NULL || fmt_stack_len == 0) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < fmt_stack_len; ++i) {
+ putc(fmt_stack[i], stdout);
}
+ putc('m', stdout);
}
-void putch_ws(uint8_t c, bool show_whitespace) {
+void putch_ws(uint8_t c, bool show_whitespace, uint8_t *fmt_stack,
+ uint32_t fmt_stack_len) {
if (show_whitespace && c == '\t') {
fputs("\x1b[90m → \x1b[39m", stdout);
+ apply_fmt(fmt_stack, fmt_stack_len);
} else if (show_whitespace && c == ' ') {
fputs("\x1b[90m·\x1b[39m", stdout);
+ apply_fmt(fmt_stack, fmt_stack_len);
} else {
putch(c);
}
}
-void putbytes(uint8_t *line_bytes, uint32_t line_length, bool show_whitespace) {
+void putbytes(uint8_t *line_bytes, uint32_t line_length, bool show_whitespace,
+ uint8_t *fmt_stack, uint32_t fmt_stack_len) {
for (uint32_t bytei = 0; bytei < line_length; ++bytei) {
- putch_ws(line_bytes[bytei], show_whitespace);
+ putch_ws(line_bytes[bytei], show_whitespace, fmt_stack, fmt_stack_len);
}
}
@@ -156,26 +182,28 @@ void put_ansiparm(int n) {
if (q != 0) {
int r = q / 10;
if (r != 0) {
- putch((r % 10) + '0');
+ putc((r % 10) + '0', stdout);
}
- putch((q % 10) + '0');
+ putc((q % 10) + '0', stdout);
}
- putch((n % 10) + '0');
+ putc((n % 10) + '0', stdout);
}
void display_move_cursor(struct display *display, uint32_t row, uint32_t col) {
- putch(ESC);
- putch('[');
+ putc(ESC, stdout);
+ putc('[', stdout);
put_ansiparm(row + 1);
- putch(';');
+ putc(';', stdout);
put_ansiparm(col + 1);
- putch('H');
+ putc('H', stdout);
}
void display_clear(struct display *display) {
display_move_cursor(display, 0, 0);
uint8_t bytes[] = {ESC, '[', 'J'};
- putbytes(bytes, 3, false);
+ putc(ESC, stdout);
+ putc('[', stdout);
+ putc('J', stdout);
}
struct command_list *command_list_create(uint32_t initial_capacity,
@@ -346,9 +374,9 @@ void display_render(struct display *display,
struct draw_text_cmd *txt_cmd = cmd->draw_txt;
display_move_cursor(display, txt_cmd->row + cl->yoffset,
txt_cmd->col + cl->xoffset);
- putbytes(fmt_stack, fmt_stack_len, false);
- putch('m');
- putbytes(txt_cmd->data, txt_cmd->len, show_whitespace_state);
+ apply_fmt(fmt_stack, fmt_stack_len);
+ putbytes(txt_cmd->data, txt_cmd->len, show_whitespace_state, fmt_stack,
+ fmt_stack_len);
break;
}
@@ -356,11 +384,11 @@ void display_render(struct display *display,
struct repeat_cmd *repeat_cmd = cmd->repeat;
display_move_cursor(display, repeat_cmd->row + cl->yoffset,
repeat_cmd->col + cl->xoffset);
- putbytes(fmt_stack, fmt_stack_len, false);
- putch('m');
+ apply_fmt(fmt_stack, fmt_stack_len);
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);
+ putbytes((uint8_t *)&repeat_cmd->c, nbytes, show_whitespace_state,
+ fmt_stack, fmt_stack_len);
}
break;
}
@@ -394,21 +422,21 @@ void display_render(struct display *display,
}
void hide_cursor() {
- putch(ESC);
- putch('[');
- putch('?');
- putch('2');
- putch('5');
- putch('l');
+ putc(ESC, stdout);
+ putc('[', stdout);
+ putc('?', stdout);
+ putc('2', stdout);
+ putc('5', stdout);
+ putc('l', stdout);
}
void show_cursor() {
- putch(ESC);
- putch('[');
- putch('?');
- putch('2');
- putch('5');
- putch('h');
+ putc(ESC, stdout);
+ putc('[', stdout);
+ putc('?', stdout);
+ putc('2', stdout);
+ putc('5', stdout);
+ putc('h', stdout);
}
void display_begin_render(struct display *display) { hide_cursor(); }
diff --git a/src/dged/keyboard.c b/src/dged/keyboard.c
index 63b7b6e..4d4526e 100644
--- a/src/dged/keyboard.c
+++ b/src/dged/keyboard.c
@@ -163,4 +163,6 @@ uint32_t key_name(struct key *key, char *buf, size_t capacity) {
}
snprintf(buf, capacity, "%s%c", mod, tolower(key->key));
+
+ return 0;
}
diff --git a/src/dged/minibuffer.c b/src/dged/minibuffer.c
index d31d634..2105c52 100644
--- a/src/dged/minibuffer.c
+++ b/src/dged/minibuffer.c
@@ -1,6 +1,7 @@
#include "minibuffer.h"
#include "binding.h"
#include "buffer.h"
+#include "buffer_view.h"
#include "command.h"
#include "display.h"
@@ -19,9 +20,6 @@ static struct minibuffer {
bool clear;
struct window *prev_window;
- void (*update_callback)(void *);
- void *update_callback_userdata;
-
} g_minibuffer = {0};
uint32_t minibuffer_draw_prompt(struct command_list *commands) {
@@ -76,8 +74,7 @@ int32_t minibuffer_execute() {
}
}
-void update(struct buffer *buffer, uint32_t width, uint32_t height,
- void *userdata) {
+void update(struct buffer *buffer, void *userdata) {
struct timespec current;
struct minibuffer *mb = (struct minibuffer *)userdata;
clock_gettime(CLOCK_MONOTONIC, &current);
@@ -86,10 +83,6 @@ void update(struct buffer *buffer, uint32_t width, uint32_t height,
buffer_clear(buffer);
mb->clear = false;
}
-
- if (mb->update_callback != NULL) {
- mb->update_callback(mb->update_callback_userdata);
- }
}
void minibuffer_init(struct buffer *buffer) {
@@ -150,14 +143,15 @@ void minibuffer_set_prompt_internal(const char *fmt, va_list args) {
vsnprintf(g_minibuffer.prompt, sizeof(g_minibuffer.prompt), fmt, args);
}
-int32_t minibuffer_prompt_internal(struct command_ctx command_ctx,
- void (*update_callback)(void *),
- void *userdata, const char *fmt,
- va_list args) {
+int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt,
+ ...) {
if (g_minibuffer.buffer == NULL) {
return 1;
}
+ va_list args;
+ va_start(args, fmt);
+
minibuffer_clear();
g_minibuffer.prompt_active = true;
@@ -166,39 +160,13 @@ int32_t minibuffer_prompt_internal(struct command_ctx command_ctx,
minibuffer_set_prompt_internal(fmt, args);
- if (update_callback != NULL) {
- g_minibuffer.update_callback = update_callback;
- 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;
-}
-
-int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt,
- ...) {
- va_list args;
- va_start(args, fmt);
- int32_t r = minibuffer_prompt_internal(command_ctx, NULL, NULL, fmt, args);
- va_end(args);
-
- return r;
-}
-
-int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx,
- void (*update_callback)(void *),
- void *userdata, const char *fmt, ...) {
- va_list args;
- va_start(args, fmt);
- int32_t r = minibuffer_prompt_internal(command_ctx, update_callback, userdata,
- fmt, args);
va_end(args);
- return r;
+ return 0;
}
void minibuffer_set_prompt(const char *fmt, ...) {
@@ -210,7 +178,6 @@ void minibuffer_set_prompt(const char *fmt, ...) {
void minibuffer_abort_prompt() {
minibuffer_clear();
- g_minibuffer.update_callback = NULL;
g_minibuffer.prompt_active = false;
if (g_minibuffer.prev_window != NULL) {
diff --git a/src/dged/minibuffer.h b/src/dged/minibuffer.h
index fb6eae4..727aac5 100644
--- a/src/dged/minibuffer.h
+++ b/src/dged/minibuffer.h
@@ -59,10 +59,6 @@ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...);
*/
int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...);
-int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx,
- void (*update_callback)(void *),
- void *userdata, const char *fmt, ...);
-
void minibuffer_set_prompt(const char *fmt, ...);
uint32_t minibuffer_draw_prompt(struct command_list *commands);
diff --git a/src/dged/reactor-epoll.c b/src/dged/reactor-epoll.c
index 96c6da4..22888f6 100644
--- a/src/dged/reactor-epoll.c
+++ b/src/dged/reactor-epoll.c
@@ -109,7 +109,7 @@ bool reactor_next_file_event(struct reactor *reactor, struct file_event *out) {
struct inotify_event *ev = (struct inotify_event *)buf;
// TODO: change when adding more of these
out->mask = FileWritten;
- if (ev->mask & IN_IGNORED != 0) {
+ if ((ev->mask & IN_IGNORED) != 0) {
out->mask |= LastEvent;
}
out->id = (uint32_t)ev->wd;
diff --git a/src/dged/settings-parse.c b/src/dged/settings-parse.c
index 6c1c482..c63a0cf 100644
--- a/src/dged/settings-parse.c
+++ b/src/dged/settings-parse.c
@@ -115,6 +115,7 @@ bool parser_next_token(struct parser *state, struct token *token_out) {
memset(token_out, 0, sizeof(struct token));
while (state->reader.getbytes(1, &byte, state->reader.userdata) > 0) {
+ bool multiline = false;
switch (classify(byte)) {
case Byte_Alphanumeric: // unquoted key / value
if (!parse_value) {
@@ -223,7 +224,7 @@ bool parser_next_token(struct parser *state, struct token *token_out) {
return true;
case '"': // quoted key or string value
- bool multiline = false;
+ multiline = false;
if (parse_value) {
token_out->type = Token_StringValue;
} else {
diff --git a/src/dged/settings.c b/src/dged/settings.c
index 4370aa9..7b5e4dc 100644
--- a/src/dged/settings.c
+++ b/src/dged/settings.c
@@ -116,6 +116,9 @@ static int32_t parse_toml(struct parser *state, char **errmsgs[]) {
VEC_INIT(&errs, 16);
struct token t = {0};
+ int64_t i = 0;
+ bool b = false;
+ char *v = NULL, *err = NULL;
while (parser_next_token(state, &t)) {
switch (t.type) {
case Token_Table:
@@ -145,26 +148,26 @@ static int32_t parse_toml(struct parser *state, char **errmsgs[]) {
curkey = calloc(len, 1);
if (curtbl != NULL) {
strcpy(curkey, curtbl);
- strncat(curkey, ".", 1);
+ curkey[strlen(curtbl)] = '.';
}
strncat(curkey, (char *)t.data, t.len);
break;
case Token_IntValue:
- int64_t i = *((int64_t *)t.data);
+ i = *((int64_t *)t.data);
settings_upsert(curkey, (struct setting_value){.type = Setting_Number,
.number_value = i});
break;
case Token_BoolValue:
- bool b = *((bool *)t.data);
+ b = *((bool *)t.data);
settings_upsert(curkey, (struct setting_value){.type = Setting_Bool,
.bool_value = b});
break;
case Token_StringValue:
- char *v = calloc(t.len + 1, 1);
+ v = calloc(t.len + 1, 1);
strncpy(v, (char *)t.data, t.len);
settings_upsert(curkey, (struct setting_value){.type = Setting_String,
.string_value = v});
@@ -172,11 +175,14 @@ static int32_t parse_toml(struct parser *state, char **errmsgs[]) {
break;
case Token_Error:
- char *err = malloc(t.len + 128);
+ err = malloc(t.len + 128);
snprintf(err, t.len + 128, "error (%d:%d): %.*s\n", t.row, t.col, t.len,
(char *)t.data);
VEC_PUSH(&errs, err);
break;
+
+ case Token_Comment:
+ break;
}
}
diff --git a/src/dged/text.c b/src/dged/text.c
index b3eb4ad..4d9a073 100644
--- a/src/dged/text.c
+++ b/src/dged/text.c
@@ -47,6 +47,8 @@ struct text *text_create(uint32_t initial_capacity) {
}
void text_destroy(struct text *text) {
+ VEC_DESTROY(&text->properties);
+
for (uint32_t li = 0; li < text->nlines; ++li) {
free(text->lines[li].data);
text->lines[li].data = NULL;
diff --git a/src/dged/utf8.c b/src/dged/utf8.c
index 30344be..b71a7e1 100644
--- a/src/dged/utf8.c
+++ b/src/dged/utf8.c
@@ -71,7 +71,7 @@ uint32_t utf8_visual_char_width(uint8_t *bytes, uint32_t len) {
if (utf8_byte_is_unicode_start(*bytes)) {
wchar_t wc;
size_t nbytes = 0;
- if (nbytes = mbrtowc(&wc, (char *)bytes, len, NULL) > 0) {
+ if ((nbytes = mbrtowc(&wc, (char *)bytes, len, NULL)) > 0) {
return wcwidth(wc);
} else {
return 0;
diff --git a/src/dged/vec.h b/src/dged/vec.h
index df5cd0e..8b17fa5 100644
--- a/src/dged/vec.h
+++ b/src/dged/vec.h
@@ -5,6 +5,7 @@
#define VEC(entry) \
struct { \
+ entry *temp; \
entry *entries; \
uint32_t nentries; \
uint32_t capacity; \
@@ -12,10 +13,12 @@
#define VEC_INIT(vec, initial_capacity) \
(vec)->entries = malloc(sizeof((vec)->entries[0]) * initial_capacity); \
+ (vec)->temp = calloc(1, sizeof((vec)->entries[0])); \
(vec)->capacity = initial_capacity; \
(vec)->nentries = 0;
#define VEC_DESTROY(vec) \
+ free((vec)->temp); \
free((vec)->entries); \
(vec)->entries = NULL; \
(vec)->capacity = 0; \
@@ -36,6 +39,20 @@
(vec)->entries[(vec)->nentries] = entry; \
++(vec)->nentries;
+#define VEC_POP(vec, var) \
+ var = (vec)->entries[(vec)->nentries - 1]; \
+ if ((vec)->nentries > 0) { \
+ --(vec)->nentries; \
+ }
+
+#define VEC_SWAP(vec, idx1, idx2) \
+ if (idx1 < (vec)->nentries && idx2 < (vec)->nentries && idx1 >= 0 && \
+ idx2 >= 0) { \
+ *((vec)->temp) = (vec)->entries[idx1]; \
+ (vec)->entries[idx1] = (vec)->entries[idx2]; \
+ (vec)->entries[idx2] = *((vec)->temp); \
+ }
+
#define VEC_APPEND(vec, var) \
if ((vec)->nentries + 1 >= (vec)->capacity) { \
VEC_GROW(vec, (vec)->capacity * 2); \
diff --git a/src/dged/window.c b/src/dged/window.c
index a25154a..da2cebe 100644
--- a/src/dged/window.c
+++ b/src/dged/window.c
@@ -178,6 +178,7 @@ void windows_update(void *(*frame_alloc)(size_t), uint64_t frame_time) {
.window_y = w->y,
.frame_alloc = frame_alloc,
};
+
buffer_view_update(&w->buffer_view, &p);
command_list_draw_command_list(w->commands, inner_commands);
diff --git a/src/main/bindings.c b/src/main/bindings.c
index 0e01306..32dcbdb 100644
--- a/src/main/bindings.c
+++ b/src/main/bindings.c
@@ -3,16 +3,19 @@
#include "dged/minibuffer.h"
#include "dged/vec.h"
+#include "bindings.h"
+
static struct keymap g_global_keymap, g_ctrlx_map, g_windows_keymap,
g_buffer_default_keymap;
struct buffer_keymap {
+ buffer_keymap_id id;
struct buffer *buffer;
- bool active;
struct keymap keymap;
};
static VEC(struct buffer_keymap) g_buffer_keymaps;
+static buffer_keymap_id g_current_keymap_id;
void set_default_buffer_bindings(struct keymap *keymap) {
struct binding buffer_bindings[] = {
@@ -46,6 +49,7 @@ void set_default_buffer_bindings(struct keymap *keymap) {
BINDING(ENTER, "newline"),
BINDING(TAB, "indent"),
+ BINDING(Spec, 'Z', "insert-tab"),
BINDING(Ctrl, 'K', "kill-line"),
BINDING(DELETE, "delete-char"),
@@ -68,7 +72,18 @@ void set_default_buffer_bindings(struct keymap *keymap) {
sizeof(buffer_bindings) / sizeof(buffer_bindings[0]));
}
-struct keymap *register_bindings() {
+int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) {
+ // TODO: this should be more lib-like
+ return minibuffer_execute();
+}
+
+static struct command execute_minibuffer_command = {
+ .fn = execute,
+ .name = "minibuffer-execute",
+ .userdata = NULL,
+};
+
+void init_bindings() {
g_global_keymap = keymap_create("global", 32);
g_ctrlx_map = keymap_create("c-x", 32);
g_windows_keymap = keymap_create("c-x w", 32);
@@ -122,93 +137,79 @@ struct keymap *register_bindings() {
sizeof(ctrlx_bindings) / sizeof(ctrlx_bindings[0]));
VEC_INIT(&g_buffer_keymaps, 32);
+ g_current_keymap_id = 0;
- return &g_global_keymap;
+ /* Minibuffer binds.
+ * This map is actually never removed so forget about the id.
+ */
+ struct binding minibuffer_binds[] = {
+ ANONYMOUS_BINDING(ENTER, &execute_minibuffer_command),
+ };
+ struct keymap minibuffer_map = keymap_create("minibuffer", 8);
+ keymap_bind_keys(&minibuffer_map, minibuffer_binds,
+ sizeof(minibuffer_binds) / sizeof(minibuffer_binds[0]));
+ buffer_add_keymap(minibuffer_buffer(), minibuffer_map);
}
-struct keymap *buffer_default_bindings() {
- return &g_buffer_default_keymap;
-}
+buffer_keymap_id buffer_add_keymap(struct buffer *buffer,
+ struct keymap keymap) {
+ buffer_keymap_id id = ++g_current_keymap_id;
+ VEC_PUSH(&g_buffer_keymaps, ((struct buffer_keymap){
+ .id = id,
+ .buffer = buffer,
+ .keymap = keymap,
+ }));
-int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) {
- // TODO: this should be more lib-like
- return minibuffer_execute();
+ return id;
}
-static struct command execute_minibuffer_command = {
- .fn = execute,
- .name = "minibuffer-execute",
- .userdata = NULL,
-};
-
-void buffer_bind_keys(struct buffer *buffer, struct binding *bindings,
- uint32_t nbindings) {
- struct buffer_keymap *target = NULL;
- VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) {
- if (buffer == km->buffer) {
- target = km;
+void buffer_remove_keymap(buffer_keymap_id id) {
+ VEC_FOR_EACH_INDEXED(&g_buffer_keymaps, struct buffer_keymap * km, i) {
+ if (km->id == id) {
+ VEC_SWAP(&g_buffer_keymaps, i, VEC_SIZE(&g_buffer_keymaps) - 1);
+ VEC_POP(&g_buffer_keymaps, struct buffer_keymap removed);
+ keymap_destroy(&removed.keymap);
}
}
+}
- if (target == NULL) {
- struct buffer_keymap new = (struct buffer_keymap){
- .buffer = buffer,
- .active = false,
- };
- VEC_PUSH(&g_buffer_keymaps, new);
- target = VEC_BACK(&g_buffer_keymaps);
- }
+uint32_t buffer_keymaps(struct buffer *buffer, struct keymap *keymaps[],
+ uint32_t max_nkeymaps) {
+ uint32_t nkeymaps = 0;
- if (!target->active) {
- target->keymap = keymap_create("buffer-overlay-keys", 32);
- target->active = true;
- set_default_buffer_bindings(&target->keymap);
+ // global keybinds
+ if (nkeymaps < max_nkeymaps) {
+ keymaps[nkeymaps] = &g_global_keymap;
+ ++nkeymaps;
}
- keymap_bind_keys(&target->keymap, bindings, nbindings);
-}
-
-// TODO: do something better
-void reset_buffer_keys(struct buffer *buffer) {
- VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) {
- if (buffer == km->buffer) {
- keymap_destroy(&km->keymap);
- km->active = false;
- }
+ // buffer keybinds
+ if (nkeymaps < max_nkeymaps) {
+ keymaps[nkeymaps] = &g_buffer_default_keymap;
+ ++nkeymaps;
}
-}
-struct keymap *buffer_keymap(struct buffer *buffer) {
- VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) {
- if (buffer == km->buffer && km->active) {
- return &km->keymap;
+ // keybinds specific to this buffer
+ if (nkeymaps < max_nkeymaps) {
+ VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) {
+ if (buffer == km->buffer && nkeymaps < max_nkeymaps) {
+ keymaps[nkeymaps] = &km->keymap;
+ ++nkeymaps;
+ }
}
}
- return &g_buffer_default_keymap;
+ return nkeymaps;
}
-void reset_minibuffer_keys(struct buffer *minibuffer) {
- reset_buffer_keys(minibuffer);
- struct binding bindings[] = {
- ANONYMOUS_BINDING(ENTER, &execute_minibuffer_command),
- };
-
- buffer_bind_keys(minibuffer, bindings,
- sizeof(bindings) / sizeof(bindings[0]));
-}
-
-void destroy_keymaps() {
+void destroy_bindings() {
keymap_destroy(&g_windows_keymap);
keymap_destroy(&g_global_keymap);
keymap_destroy(&g_ctrlx_map);
keymap_destroy(&g_buffer_default_keymap);
VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) {
- if (km->active) {
- keymap_destroy(&km->keymap);
- km->active = false;
- }
+ keymap_destroy(&km->keymap);
}
VEC_DESTROY(&g_buffer_keymaps);
diff --git a/src/main/bindings.h b/src/main/bindings.h
index d0ba27c..4fd760a 100644
--- a/src/main/bindings.h
+++ b/src/main/bindings.h
@@ -4,12 +4,12 @@ struct keymap;
struct buffer;
struct binding;
-struct keymap *register_bindings();
+void init_bindings();
-void buffer_bind_keys(struct buffer *buffer, struct binding *bindings,
- uint32_t nbindings);
-void reset_buffer_keys(struct buffer *buffer);
-void reset_minibuffer_keys(struct buffer *minibuffer);
-struct keymap *buffer_keymap(struct buffer *buffer);
+typedef uint64_t buffer_keymap_id;
+buffer_keymap_id buffer_add_keymap(struct buffer *buffer, struct keymap keymap);
+void buffer_remove_keymap(buffer_keymap_id id);
+uint32_t buffer_keymaps(struct buffer *buffer, struct keymap *keymaps[],
+ uint32_t max_nkeymaps);
-void destroy_keymaps();
+void destroy_bindings();
diff --git a/src/main/cmds.c b/src/main/cmds.c
index 5991f4a..bf465ed 100644
--- a/src/main/cmds.c
+++ b/src/main/cmds.c
@@ -1,11 +1,7 @@
-#define _DEFAULT_SOURCE
-#include <dirent.h>
#include <errno.h>
-#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
-#include <sys/types.h>
#include "dged/binding.h"
#include "dged/buffer.h"
@@ -18,16 +14,15 @@
#include "dged/settings.h"
#include "bindings.h"
+#include "completion.h"
#include "search-replace.h"
-static void abort_completion();
-
int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) {
abort_replace();
abort_completion();
+ disable_completion(minibuffer_buffer());
minibuffer_abort_prompt();
buffer_view_clear_mark(window_buffer_view(ctx.active_window));
- reset_minibuffer_keys(minibuffer_buffer());
minibuffer_echo_timeout(4, "💣 aborted");
return 0;
}
@@ -43,291 +38,6 @@ int32_t exit_editor(struct command_ctx ctx, int argc, const char *argv[]) {
return 0;
}
-struct completion {
- const char *display;
- const char *insert;
- bool complete;
-};
-
-uint32_t g_ncompletions = 0;
-struct completion g_completions[50] = {0};
-
-static void abort_completion() {
- reset_minibuffer_keys(minibuffer_buffer());
- windows_close_popup();
-
- for (uint32_t compi = 0; compi < g_ncompletions; ++compi) {
- free((void *)g_completions[compi].display);
- free((void *)g_completions[compi].insert);
- }
- g_ncompletions = 0;
-}
-
-int cmp_completions(const void *comp_a, const void *comp_b) {
- struct completion *a = (struct completion *)comp_a;
- struct completion *b = (struct completion *)comp_b;
- return strcmp(a->display, b->display);
-}
-
-static bool is_hidden(const char *filename) {
- return filename[0] == '.' && filename[1] != '\0' && filename[1] != '.';
-}
-
-static void complete_path(const char *path, struct completion results[],
- uint32_t nresults_max, uint32_t *nresults) {
- uint32_t n = 0;
- char *p1 = to_abspath(path);
- size_t len = strlen(p1);
- char *p2 = strdup(p1);
-
- size_t inlen = strlen(path);
-
- if (len == 0) {
- goto done;
- }
-
- if (nresults_max == 0) {
- goto done;
- }
-
- const char *dir = p1;
- const char *file = "";
-
- // check the input path here since
- // to_abspath removes trailing slashes
- if (path[inlen - 1] != '/') {
- dir = dirname(p1);
- file = basename(p2);
- }
-
- DIR *d = opendir(dir);
- if (d == NULL) {
- goto done;
- }
-
- errno = 0;
- while (n < nresults_max) {
- struct dirent *de = readdir(d);
- if (de == NULL && errno != 0) {
- // skip the erroring entry
- errno = 0;
- continue;
- } else if (de == NULL && errno == 0) {
- break;
- }
-
- switch (de->d_type) {
- case DT_DIR:
- case DT_REG:
- case DT_LNK:
- if (!is_hidden(de->d_name) &&
- (strncmp(file, de->d_name, strlen(file)) == 0 || strlen(file) == 0)) {
- const char *disp = strdup(de->d_name);
- results[n] = (struct completion){
- .display = disp,
- .insert = strdup(disp + strlen(file)),
- .complete = de->d_type == DT_REG,
- };
- ++n;
- }
- break;
- }
- }
-
- closedir(d);
-
-done:
- free(p1);
- free(p2);
-
- qsort(results, n, sizeof(struct completion), cmp_completions);
- *nresults = n;
-}
-
-void 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 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,
- .bg = 0,
- .set_fg = true,
- .fg = 4,
- }});
-
- if (line.allocated) {
- free(line.text);
- }
-}
-
-static int32_t goto_completion(struct command_ctx ctx, int argc,
- const char *argv[]) {
- void (*movement_fn)(struct buffer_view *) =
- (void (*)(struct buffer_view *))ctx.userdata;
- struct buffer *b = buffers_find(ctx.buffers, "*completions*");
-
- // is it in the popup?
- if (b != NULL && window_buffer(popup_window()) == b) {
- struct buffer_view *v = window_buffer_view(popup_window());
- movement_fn(v);
-
- if (v->dot.line >= text_num_lines(b->text)) {
- buffer_view_backward_line(v);
- }
- }
-
- return 0;
-}
-
-static int32_t insert_completion(struct command_ctx ctx, int argc,
- const char *argv[]) {
- struct buffer *b = buffers_find(ctx.buffers, "*completions*");
- // is it in the popup?
- if (b != NULL && window_buffer(popup_window()) == b) {
- struct buffer_view *cv = window_buffer_view(popup_window());
-
- if (cv->dot.line < g_ncompletions) {
- char *ins = (char *)g_completions[cv->dot.line].insert;
- bool complete = g_completions[cv->dot.line].complete;
- size_t inslen = strlen(ins);
- buffer_view_add(window_buffer_view(windows_get_active()), ins, inslen);
-
- if (minibuffer_focused() && complete) {
- minibuffer_execute();
- }
-
- abort_completion();
- }
- }
-
- return 0;
-}
-
-COMMAND_FN("next-completion", next_completion, goto_completion,
- buffer_view_forward_line);
-COMMAND_FN("prev-completion", prev_completion, goto_completion,
- buffer_view_backward_line);
-COMMAND_FN("insert-completion", insert_completion, insert_completion, NULL);
-
-static void on_find_file_input(void *userdata) {
- struct buffers *buffers = (struct buffers *)userdata;
- struct text_chunk txt = minibuffer_content();
-
- struct window *mb = minibuffer_window();
- struct 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,
- (void *)window_buffer_view(popup_window()));
- window_set_buffer_e(popup_window(), b, false, false);
- }
-
- struct buffer_view *v = window_buffer_view(popup_window());
-
- char path[1024];
- strncpy(path, txt.text, txt.nbytes);
- path[(txt.nbytes >= 1024 ? 1023 : txt.nbytes)] = '\0';
-
- for (uint32_t compi = 0; compi < g_ncompletions; ++compi) {
- free((void *)g_completions[compi].display);
- free((void *)g_completions[compi].insert);
- }
-
- g_ncompletions = 0;
- complete_path(path, g_completions, 50, &g_ncompletions);
-
- size_t max_width = 0;
- struct location prev_dot = v->dot;
-
- 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;
- size_t width = strlen(disp);
- if (width > max_width) {
- max_width = width;
- }
- buffer_view_add(v, (uint8_t *)disp, width);
-
- // the extra newline feels weird in navigation
- if (compi != g_ncompletions - 1) {
- buffer_view_add(v, (uint8_t *)"\n", 1);
- }
- }
-
- buffer_view_goto(
- v, (struct location){.line = prev_dot.line, .col = prev_dot.col});
- if (prev_dot.line >= text_num_lines(b->text)) {
- buffer_view_backward_line(v);
- }
-
- if (!popup_window_visible()) {
- struct binding bindings[] = {
- ANONYMOUS_BINDING(Ctrl, 'N', &next_completion_command),
- ANONYMOUS_BINDING(Ctrl, 'P', &prev_completion_command),
- ANONYMOUS_BINDING(ENTER, &insert_completion_command),
- };
- buffer_bind_keys(minibuffer_buffer(), bindings,
- sizeof(bindings) / sizeof(bindings[0]));
- }
-
- uint32_t width = max_width > 2 ? max_width : 4,
- height = g_ncompletions > 10 ? 10 : g_ncompletions;
- windows_show_popup(mbpos.y + mb_dot.line - height, mbpos.x + mb_dot.col,
- width, height);
- } else {
- abort_completion();
- }
-
- if (txt.allocated) {
- free(txt.text);
- }
-}
-
-int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) {
- const char *pth = NULL;
- if (argc == 0) {
- return minibuffer_prompt_interactive(ctx, on_find_file_input, ctx.buffers,
- "find file: ");
- }
-
- pth = argv[0];
- struct stat sb = {0};
- if (stat(pth, &sb) < 0 && errno != ENOENT) {
- minibuffer_echo("stat on %s failed: %s", pth, strerror(errno));
- return 1;
- }
-
- if (S_ISDIR(sb.st_mode) && errno != ENOENT) {
- minibuffer_echo("TODO: implement dired!");
- return 1;
- }
-
- const char *filename = to_abspath(pth);
- struct buffer *b = buffers_find_by_filename(ctx.buffers, filename);
- free((char *)filename);
-
- if (b == NULL) {
- b = buffers_add(ctx.buffers, buffer_from_file((char *)pth));
- } else {
- buffer_reload(b);
- }
-
- window_set_buffer(ctx.active_window, b);
- minibuffer_echo_timeout(4, "buffer \"%s\" loaded",
- window_buffer(ctx.active_window)->name);
-
- return 0;
-}
-
int32_t write_file(struct command_ctx ctx, int argc, const char *argv[]) {
const char *pth = NULL;
if (argc == 0) {
@@ -380,8 +90,16 @@ int32_t do_switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) {
COMMAND_FN("do-switch-buffer", do_switch_buffer, do_switch_buffer, NULL);
+static void switch_buffer_comp_inserted() { minibuffer_execute(); }
+
int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) {
if (argc == 0) {
+ struct completion_provider providers[] = {buffer_provider()};
+ enable_completion(minibuffer_buffer(),
+ ((struct completion_trigger){
+ .kind = CompletionTrigger_Input, .nchars = 0}),
+ providers, 1, switch_buffer_comp_inserted);
+
ctx.self = &do_switch_buffer_command;
if (window_has_prev_buffer(ctx.active_window)) {
return minibuffer_prompt(ctx, "buffer (default %s): ",
@@ -391,6 +109,8 @@ int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) {
}
}
+ disable_completion(minibuffer_buffer());
+
return execute_command(&do_switch_buffer_command, ctx.commands,
ctx.active_window, ctx.buffers, argc, argv);
}
@@ -433,7 +153,7 @@ int32_t buflist_visit_cmd(struct command_ctx ctx, int argc,
if (end != NULL) {
uint32_t len = end - (char *)text.text;
char *bufname = (char *)malloc(len + 1);
- strncpy(bufname, text.text, len);
+ strncpy(bufname, (const char *)text.text, len);
bufname[len] = '\0';
struct buffer *target = buffers_find(ctx.buffers, bufname);
@@ -454,8 +174,8 @@ 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);
- buffers_for_each(buffers, buffer_to_list_line, target);
buffer_view_goto_beginning(target);
+ buffers_for_each(buffers, buffer_to_list_line, target);
buffer_set_readonly(target->buffer, true);
}
@@ -496,12 +216,58 @@ int32_t buffer_list(struct command_ctx ctx, int argc, const char *argv[]) {
ANONYMOUS_BINDING(None, 'q', &buflist_close),
ANONYMOUS_BINDING(None, 'g', &buflist_refresh),
};
- buffer_bind_keys(b, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ struct keymap km = keymap_create("buflist", 8);
+ keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ buffer_add_keymap(b, km);
windows_set_active(w);
return 0;
}
+static void find_file_comp_inserted() { minibuffer_execute(); }
+
+int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) {
+ const char *pth = NULL;
+ if (argc == 0) {
+ struct completion_provider providers[] = {path_provider()};
+ enable_completion(minibuffer_buffer(),
+ ((struct completion_trigger){
+ .kind = CompletionTrigger_Input, .nchars = 0}),
+ providers, 1, find_file_comp_inserted);
+ return minibuffer_prompt(ctx, "find file: ");
+ }
+
+ disable_completion(minibuffer_buffer());
+
+ pth = argv[0];
+ struct stat sb = {0};
+ if (stat(pth, &sb) < 0 && errno != ENOENT) {
+ minibuffer_echo("stat on %s failed: %s", pth, strerror(errno));
+ return 1;
+ }
+
+ if (S_ISDIR(sb.st_mode) && errno != ENOENT) {
+ minibuffer_echo("TODO: implement dired!");
+ return 1;
+ }
+
+ const char *filename = to_abspath(pth);
+ struct buffer *b = buffers_find_by_filename(ctx.buffers, filename);
+ free((char *)filename);
+
+ if (b == NULL) {
+ b = buffers_add(ctx.buffers, buffer_from_file((char *)pth));
+ } else {
+ buffer_reload(b);
+ }
+
+ window_set_buffer(ctx.active_window, b);
+ minibuffer_echo_timeout(4, "buffer \"%s\" loaded",
+ window_buffer(ctx.active_window)->name);
+
+ return 0;
+}
+
void register_global_commands(struct commands *commands,
void (*terminate_cb)()) {
struct command global_commands[] = {
@@ -591,6 +357,15 @@ static int32_t goto_line(struct command_ctx ctx, int argc, const char *argv[]) {
line = line - 1;
}
buffer_view_goto(v, (struct location){.line = line, .col = 0});
+
+ return 0;
+}
+
+static int32_t insert_tab(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ struct buffer_view *v = window_buffer_view(ctx.active_window);
+ buffer_view_add(v, (uint8_t *)"\t", 1);
+ return 0;
}
void register_buffer_commands(struct commands *commands) {
@@ -610,6 +385,7 @@ void register_buffer_commands(struct commands *commands) {
{.name = "beginning-of-line", .fn = goto_beginning_of_line_cmd},
{.name = "newline", .fn = newline_cmd},
{.name = "indent", .fn = indent_cmd},
+ {.name = "insert-tab", .fn = insert_tab},
{.name = "buffer-write-to-file", .fn = to_file_cmd},
{.name = "set-mark", .fn = set_mark_cmd},
{.name = "clear-mark", .fn = clear_mark_cmd},
diff --git a/src/main/completion.c b/src/main/completion.c
new file mode 100644
index 0000000..7382805
--- /dev/null
+++ b/src/main/completion.c
@@ -0,0 +1,541 @@
+#define _DEFAULT_SOURCE
+#include "completion.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <libgen.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "dged/binding.h"
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/buffers.h"
+#include "dged/minibuffer.h"
+#include "dged/path.h"
+#include "dged/window.h"
+
+#include "bindings.h"
+
+struct active_completion_ctx {
+ struct completion_trigger trigger;
+ uint32_t trigger_current_nchars;
+ struct completion_provider *providers;
+ uint32_t nproviders;
+ insert_cb on_completion_inserted;
+};
+
+struct completion_state {
+ struct completion completions[50];
+ uint32_t ncompletions;
+ uint32_t current_completion;
+ bool active;
+ buffer_keymap_id keymap_id;
+ bool keymap_active;
+ struct active_completion_ctx *ctx;
+} g_state = {0};
+
+static struct buffer *g_target_buffer = NULL;
+
+static void hide_completion();
+
+static uint32_t complete_path(struct completion_context ctx, void *userdata);
+static struct completion_provider g_path_provider = {
+ .name = "path",
+ .complete = complete_path,
+ .userdata = NULL,
+};
+
+static uint32_t complete_buffers(struct completion_context ctx, void *userdata);
+static struct completion_provider g_buffer_provider = {
+ .name = "buffers",
+ .complete = complete_buffers,
+ .userdata = NULL,
+};
+
+struct completion_provider path_provider() {
+ return g_path_provider;
+}
+
+struct completion_provider buffer_provider() {
+ return g_buffer_provider;
+}
+
+struct active_completion {
+ struct buffer *buffer;
+ uint32_t insert_hook_id;
+ uint32_t remove_hook_id;
+};
+
+VEC(struct active_completion) g_active_completions;
+
+static int32_t goto_next_completion(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ if (g_state.current_completion < g_state.ncompletions - 1) {
+ ++g_state.current_completion;
+ }
+
+ if (completion_active()) {
+ buffer_view_goto(
+ window_buffer_view(popup_window()),
+ ((struct location){.line = g_state.current_completion, .col = 0}));
+ }
+
+ return 0;
+}
+
+static int32_t goto_prev_completion(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ if (g_state.current_completion > 0) {
+ --g_state.current_completion;
+ }
+
+ if (completion_active()) {
+ buffer_view_goto(
+ window_buffer_view(popup_window()),
+ ((struct location){.line = g_state.current_completion, .col = 0}));
+ }
+
+ return 0;
+}
+
+static int32_t insert_completion(struct command_ctx ctx, int argc,
+ const char *argv[]) {
+ // is it in the popup?
+ struct completion *comp = &g_state.completions[g_state.current_completion];
+ bool done = comp->complete;
+ const char *ins = comp->insert;
+ size_t inslen = strlen(ins);
+ buffer_view_add(window_buffer_view(windows_get_active()), (uint8_t *)ins,
+ inslen);
+
+ if (done) {
+ g_state.ctx->on_completion_inserted();
+ abort_completion();
+ }
+
+ return 0;
+}
+
+static void clear_completions() {
+ for (uint32_t ci = 0; ci < g_state.ncompletions; ++ci) {
+ free((void *)g_state.completions[ci].display);
+ free((void *)g_state.completions[ci].insert);
+ g_state.completions[ci].display = NULL;
+ g_state.completions[ci].insert = NULL;
+ g_state.completions[ci].complete = false;
+ }
+ g_state.ncompletions = 0;
+}
+
+COMMAND_FN("next-completion", next_completion, goto_next_completion, NULL);
+COMMAND_FN("prev-completion", prev_completion, goto_prev_completion, NULL);
+COMMAND_FN("insert-completion", insert_completion, insert_completion, NULL);
+
+static void update_completions(struct buffer *buffer,
+ struct active_completion_ctx *ctx,
+ struct location location) {
+ clear_completions();
+ for (uint32_t pi = 0; pi < ctx->nproviders; ++pi) {
+ struct completion_provider *provider = &ctx->providers[pi];
+
+ struct completion_context comp_ctx = (struct completion_context){
+ .buffer = buffer,
+ .location = location,
+ .max_ncompletions = 50 - g_state.ncompletions,
+ .completions = g_state.completions,
+ };
+
+ g_state.ncompletions += provider->complete(comp_ctx, provider->userdata);
+ }
+
+ window_set_buffer_e(popup_window(), g_target_buffer, false, false);
+ struct buffer_view *v = window_buffer_view(popup_window());
+
+ size_t max_width = 0;
+ uint32_t prev_selection = g_state.current_completion;
+
+ buffer_clear(v->buffer);
+ buffer_view_goto(v, (struct location){.line = 0, .col = 0});
+ if (g_state.ncompletions > 0) {
+ for (uint32_t compi = 0; compi < g_state.ncompletions; ++compi) {
+ const char *disp = g_state.completions[compi].display;
+ size_t width = strlen(disp);
+ if (width > max_width) {
+ max_width = width;
+ }
+ buffer_view_add(v, (uint8_t *)disp, width);
+ buffer_view_add(v, (uint8_t *)"\n", 1);
+ }
+
+ // select the closest one to previous selection
+ g_state.current_completion = prev_selection < g_state.ncompletions
+ ? prev_selection
+ : g_state.ncompletions - 1;
+
+ buffer_view_goto(
+ v, (struct location){.line = g_state.current_completion, .col = 0});
+
+ struct window *target_window = window_find_by_buffer(buffer);
+ struct window_position winpos = window_position(target_window);
+ struct buffer_view *view = window_buffer_view(target_window);
+ uint32_t height = g_state.ncompletions > 10 ? 10 : g_state.ncompletions;
+ windows_show_popup(winpos.y + location.line - height - 1,
+ winpos.x + view->fringe_width + location.col + 1,
+ max_width + 2, height);
+
+ if (!g_state.keymap_active) {
+ struct keymap km = keymap_create("completion", 8);
+ struct binding comp_bindings[] = {
+ ANONYMOUS_BINDING(Ctrl, 'N', &next_completion_command),
+ ANONYMOUS_BINDING(Ctrl, 'P', &prev_completion_command),
+ ANONYMOUS_BINDING(ENTER, &insert_completion_command),
+ };
+ keymap_bind_keys(&km, comp_bindings,
+ sizeof(comp_bindings) / sizeof(comp_bindings[0]));
+ g_state.keymap_id = buffer_add_keymap(buffer, km);
+ g_state.keymap_active = true;
+ }
+ } else {
+ hide_completion();
+ }
+}
+
+static void on_buffer_delete(struct buffer *buffer, struct region deleted,
+ void *userdata) {
+ struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata;
+
+ if (g_state.active) {
+ update_completions(buffer, ctx, deleted.begin);
+ }
+}
+
+static void on_buffer_insert(struct buffer *buffer, struct region inserted,
+ void *userdata) {
+ struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata;
+
+ if (!g_state.active) {
+ uint32_t nchars = 0;
+ switch (ctx->trigger.kind) {
+ case CompletionTrigger_Input:
+ for (uint32_t line = inserted.begin.line; line <= inserted.end.line;
+ ++line) {
+ nchars += buffer_num_chars(buffer, line);
+ }
+ nchars -=
+ inserted.begin.col +
+ (buffer_num_chars(buffer, inserted.end.line) - inserted.end.col);
+
+ ctx->trigger_current_nchars += nchars;
+
+ if (ctx->trigger_current_nchars < ctx->trigger.nchars) {
+ return;
+ }
+
+ ctx->trigger_current_nchars = 0;
+ break;
+
+ case CompletionTrigger_Char:
+ // TODO
+ break;
+ }
+
+ // activate completion
+ g_state.active = true;
+ g_state.ctx = ctx;
+ }
+
+ update_completions(buffer, ctx, inserted.end);
+}
+
+static void update_completion_buffer(struct buffer *buffer, void *userdata) {
+ buffer_add_text_property(
+ g_target_buffer,
+ (struct location){.line = g_state.current_completion, .col = 0},
+ (struct location){
+ .line = g_state.current_completion,
+ .col = buffer_num_chars(g_target_buffer, g_state.current_completion)},
+ (struct text_property){.type = TextProperty_Colors,
+ .colors = (struct text_property_colors){
+ .set_bg = false,
+ .bg = 0,
+ .set_fg = true,
+ .fg = 4,
+ }});
+}
+
+void init_completion(struct buffers *buffers) {
+ if (g_target_buffer == NULL) {
+ g_target_buffer = buffers_add(buffers, buffer_create("*completions*"));
+ buffer_add_update_hook(g_target_buffer, update_completion_buffer, NULL);
+ }
+
+ g_buffer_provider.userdata = buffers;
+ VEC_INIT(&g_active_completions, 32);
+}
+
+void enable_completion(struct buffer *source, struct completion_trigger trigger,
+ struct completion_provider *providers,
+ uint32_t nproviders, insert_cb on_completion_inserted) {
+ // check if we are already active
+ VEC_FOR_EACH(&g_active_completions, struct active_completion * c) {
+ if (c->buffer == source) {
+ disable_completion(source);
+ }
+ }
+
+ struct active_completion_ctx *ctx =
+ calloc(1, sizeof(struct active_completion_ctx));
+ ctx->trigger = trigger;
+ ctx->on_completion_inserted = on_completion_inserted;
+ ctx->nproviders = nproviders;
+ ctx->providers = calloc(nproviders, sizeof(struct completion_provider));
+ memcpy(ctx->providers, providers,
+ sizeof(struct completion_provider) * nproviders);
+
+ uint32_t insert_hook_id =
+ buffer_add_insert_hook(source, on_buffer_insert, ctx);
+ uint32_t remove_hook_id =
+ buffer_add_delete_hook(source, on_buffer_delete, ctx);
+
+ VEC_PUSH(&g_active_completions, ((struct active_completion){
+ .buffer = source,
+ .insert_hook_id = insert_hook_id,
+ .remove_hook_id = remove_hook_id,
+ }));
+}
+
+static void hide_completion() {
+ windows_close_popup();
+ if (g_state.active) {
+ buffer_remove_keymap(g_state.keymap_id);
+ g_state.keymap_active = false;
+ }
+}
+
+void abort_completion() {
+ hide_completion();
+ g_state.active = false;
+ clear_completions();
+}
+
+bool completion_active() {
+ return popup_window_visible() &&
+ window_buffer(popup_window()) == g_target_buffer && g_state.active;
+}
+
+static void cleanup_active_comp_ctx(void *userdata) {
+ struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata;
+ free(ctx->providers);
+ free(ctx);
+}
+
+static void do_nothing(void *userdata) {}
+
+static void cleanup_active_completion(struct active_completion *comp) {
+ buffer_remove_delete_hook(comp->buffer, comp->remove_hook_id, do_nothing);
+ buffer_remove_insert_hook(comp->buffer, comp->insert_hook_id,
+ cleanup_active_comp_ctx);
+}
+
+void disable_completion(struct buffer *buffer) {
+ VEC_FOR_EACH_INDEXED(&g_active_completions, struct active_completion * comp,
+ i) {
+ if (buffer == comp->buffer) {
+ VEC_SWAP(&g_active_completions, i, VEC_SIZE(&g_active_completions) - 1);
+ VEC_POP(&g_active_completions, struct active_completion removed);
+ cleanup_active_completion(&removed);
+ }
+ }
+}
+
+void destroy_completion() {
+ // clean up any active completions we might have
+ VEC_FOR_EACH(&g_active_completions, struct active_completion * comp) {
+ cleanup_active_completion(comp);
+ }
+ VEC_DESTROY(&g_active_completions);
+}
+
+static bool is_hidden(const char *filename) {
+ return filename[0] == '.' && filename[1] != '\0' && filename[1] != '.';
+}
+
+static int cmp_completions(const void *comp_a, const void *comp_b) {
+ struct completion *a = (struct completion *)comp_a;
+ struct completion *b = (struct completion *)comp_b;
+ return strcmp(a->display, b->display);
+}
+
+static uint32_t complete_path(struct completion_context ctx, void *userdata) {
+
+ // obtain path from the buffer
+ struct text_chunk txt = {0};
+ uint32_t start_idx = 0;
+ if (ctx.buffer == minibuffer_buffer()) {
+ txt = minibuffer_content();
+ } else {
+ txt = buffer_line(ctx.buffer, ctx.location.line);
+ uint32_t end_idx = text_col_to_byteindex(
+ ctx.buffer->text, ctx.location.line, ctx.location.col);
+
+ for (uint32_t bytei = end_idx; bytei > 0; --bytei) {
+ if (txt.text[bytei] == ' ') {
+ start_idx = bytei + 1;
+ break;
+ }
+ }
+
+ if (start_idx >= end_idx) {
+ return 0;
+ }
+
+ txt.nbytes = end_idx - start_idx;
+ }
+
+ char *path = calloc(txt.nbytes + 1, sizeof(uint8_t));
+ memcpy(path, txt.text + start_idx, txt.nbytes);
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+
+ uint32_t n = 0;
+ char *p1 = to_abspath(path);
+ size_t len = strlen(p1);
+ char *p2 = strdup(p1);
+
+ size_t inlen = strlen(path);
+
+ if (len == 0) {
+ goto done;
+ }
+
+ if (ctx.max_ncompletions == 0) {
+ goto done;
+ }
+
+ const char *dir = p1;
+ const char *file = "";
+
+ // check the input path here since
+ // to_abspath removes trailing slashes
+ if (path[inlen - 1] != '/') {
+ dir = dirname(p1);
+ file = basename(p2);
+ }
+
+ DIR *d = opendir(dir);
+ if (d == NULL) {
+ goto done;
+ }
+
+ errno = 0;
+ while (n < ctx.max_ncompletions) {
+ struct dirent *de = readdir(d);
+ if (de == NULL && errno != 0) {
+ // skip the erroring entry
+ errno = 0;
+ continue;
+ } else if (de == NULL && errno == 0) {
+ break;
+ }
+
+ switch (de->d_type) {
+ case DT_DIR:
+ case DT_REG:
+ case DT_LNK:
+ if (!is_hidden(de->d_name) &&
+ (strncmp(file, de->d_name, strlen(file)) == 0 || strlen(file) == 0)) {
+ const char *disp = strdup(de->d_name);
+ ctx.completions[n] = (struct completion){
+ .display = disp,
+ .insert = strdup(disp + strlen(file)),
+ .complete = de->d_type == DT_REG,
+ };
+ ++n;
+ }
+ break;
+ }
+ }
+
+ closedir(d);
+
+done:
+ free(path);
+ free(p1);
+ free(p2);
+
+ qsort(ctx.completions, n, sizeof(struct completion), cmp_completions);
+ return n;
+}
+
+struct buffer_match_ctx {
+ const char *needle;
+ struct completion *completions;
+ uint32_t max_ncompletions;
+ uint32_t ncompletions;
+};
+
+static void buffer_matches(struct buffer *buffer, void *userdata) {
+ struct buffer_match_ctx *ctx = (struct buffer_match_ctx *)userdata;
+
+ if (strncmp(ctx->needle, buffer->name, strlen(ctx->needle)) == 0 &&
+ ctx->ncompletions < ctx->max_ncompletions) {
+ ctx->completions[ctx->ncompletions] = (struct completion){
+ .display = strdup(buffer->name),
+ .insert = strdup(buffer->name + strlen(ctx->needle)),
+ .complete = true,
+ };
+ ++ctx->ncompletions;
+ }
+}
+
+static uint32_t complete_buffers(struct completion_context ctx,
+ void *userdata) {
+ struct buffers *buffers = (struct buffers *)userdata;
+ if (buffers == NULL) {
+ return 0;
+ }
+
+ struct text_chunk txt = {0};
+ uint32_t start_idx = 0;
+ if (ctx.buffer == minibuffer_buffer()) {
+ txt = minibuffer_content();
+ } else {
+ txt = buffer_line(ctx.buffer, ctx.location.line);
+ uint32_t end_idx = text_col_to_byteindex(
+ ctx.buffer->text, ctx.location.line, ctx.location.col);
+ for (uint32_t bytei = end_idx; bytei > 0; --bytei) {
+ if (txt.text[bytei] == ' ') {
+ start_idx = bytei + 1;
+ break;
+ }
+ }
+
+ if (start_idx >= end_idx) {
+ return 0;
+ }
+
+ txt.nbytes = end_idx - start_idx;
+ }
+
+ char *needle = calloc(txt.nbytes + 1, sizeof(uint8_t));
+ memcpy(needle, txt.text + start_idx, txt.nbytes);
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+
+ struct buffer_match_ctx match_ctx = (struct buffer_match_ctx){
+ .needle = needle,
+ .max_ncompletions = ctx.max_ncompletions,
+ .completions = ctx.completions,
+ .ncompletions = 0,
+ };
+ buffers_for_each(buffers, buffer_matches, &match_ctx);
+
+ free(needle);
+ return match_ctx.ncompletions;
+}
diff --git a/src/main/completion.h b/src/main/completion.h
new file mode 100644
index 0000000..751d152
--- /dev/null
+++ b/src/main/completion.h
@@ -0,0 +1,83 @@
+#ifndef _COMPLETION_H
+#define _COMPLETION_H
+
+#include "dged/location.h"
+
+struct buffer;
+struct buffers;
+
+struct completion {
+ const char *display;
+ const char *insert;
+ bool complete;
+};
+
+struct completion_context {
+ struct buffer *buffer;
+ const struct location location;
+ const uint32_t max_ncompletions;
+ struct completion *completions;
+};
+
+typedef uint32_t (*completion_fn)(struct completion_context ctx,
+ void *userdata);
+
+struct completion_provider {
+ char name[16];
+ completion_fn complete;
+ void *userdata;
+};
+
+enum completion_trigger_kind {
+ CompletionTrigger_Input = 0,
+ CompletionTrigger_Char = 1,
+};
+
+struct completion_trigger {
+ enum completion_trigger_kind kind;
+ union {
+ uint32_t c;
+ uint32_t nchars;
+ };
+};
+
+void init_completion(struct buffers *buffers);
+void destroy_completion();
+
+typedef void (*insert_cb)();
+
+/**
+ * Enable completions in the buffer @ref source.
+ *
+ * @param source [in] The buffer to provide completions for.
+ * @param trigger [in] The completion trigger to use for this completion.
+ * @param providers [in] The completion providers to use.
+ * @param nproviders [in] The number of providers in @ref providers.
+ */
+void enable_completion(struct buffer *source, struct completion_trigger trigger,
+ struct completion_provider *providers,
+ uint32_t nproviders, insert_cb on_completion_inserted);
+
+struct completion_provider path_provider();
+struct completion_provider buffer_provider();
+
+/**
+ * Abort any active completion.
+ */
+void abort_completion();
+
+/**
+ * Is a completion currently showing?
+ *
+ * @returns True if the completion window is showing completions.
+ */
+bool completion_active();
+
+/**
+ * Disable completion for @ref buffer.
+ *
+ * @param buffer [in] Buffer to disable completions for.
+ */
+void disable_completion(struct buffer *buffer);
+
+#endif
diff --git a/src/main/main.c b/src/main/main.c
index fd69cff..1bb3003 100644
--- a/src/main/main.c
+++ b/src/main/main.c
@@ -22,6 +22,7 @@
#include "bindings.h"
#include "cmds.h"
+#include "completion.h"
static struct frame_allocator frame_allocator;
@@ -103,7 +104,7 @@ void update_file_watches(struct reactor *reactor) {
// find the buffer we need to reload
VEC_FOR_EACH(&g_watched_files, struct watched_file * w) {
if (w->watch_id == ev.id) {
- if (ev.mask & LastEvent != 0) {
+ if ((ev.mask & LastEvent) != 0) {
w->watch_id = -1;
continue;
}
@@ -206,16 +207,8 @@ int main(int argc, char *argv[]) {
display_clear(display);
signal(SIGWINCH, resized);
- register_global_commands(&commands, terminate);
- register_buffer_commands(&commands);
- register_window_commands(&commands);
- register_settings_commands(&commands);
-
struct keyboard kbd = keyboard_create(reactor);
- struct keymap *current_keymap = NULL;
- struct keymap *global_keymap = register_bindings();
-
VEC_INIT(&g_watched_files, 32);
struct buffers buflist = {0};
@@ -234,7 +227,6 @@ int main(int argc, char *argv[]) {
struct buffer *ib = buffers_add(&buflist, initial_buffer);
struct buffer minibuffer = buffer_create("minibuffer");
minibuffer_init(&minibuffer);
- reset_minibuffer_keys(&minibuffer);
windows_init(display_height(display), display_width(display), ib,
&minibuffer);
@@ -249,6 +241,16 @@ int main(int argc, char *argv[]) {
buffer_view_goto(window_buffer_view(active), to);
}
+ register_global_commands(&commands, terminate);
+ register_buffer_commands(&commands);
+ register_window_commands(&commands);
+ register_settings_commands(&commands);
+
+ struct keymap *current_keymap = NULL;
+ init_bindings();
+
+ init_completion(&buflist);
+
DECLARE_TIMER(buffer);
DECLARE_TIMER(display);
DECLARE_TIMER(keyboard);
@@ -277,7 +279,7 @@ int main(int argc, char *argv[]) {
display_begin_render(display);
windows_render(display);
struct buffer_view *view = window_buffer_view(active_window);
- struct location cursor = buffer_view_dot_to_relative(view);
+ struct location cursor = buffer_view_dot_to_visual(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);
@@ -302,11 +304,14 @@ int main(int argc, char *argv[]) {
if (current_keymap != NULL) {
res = lookup_key(current_keymap, 1, k, &commands);
} else {
- // check the global keymap first, then the buffer one
- res = lookup_key(global_keymap, 1, k, &commands);
- if (!res.found) {
- res = lookup_key(buffer_keymap(window_buffer(active_window)), 1, k,
- &commands);
+ struct keymap *buffer_maps[128];
+ uint32_t nkeymaps =
+ buffer_keymaps(window_buffer(active_window), buffer_maps, 128);
+ for (uint32_t kmi = nkeymaps; kmi > 0; --kmi) {
+ res = lookup_key(buffer_maps[kmi - 1], 1, k, &commands);
+ if (res.found) {
+ break;
+ }
}
}
@@ -372,13 +377,14 @@ int main(int argc, char *argv[]) {
frame_allocator_clear(&frame_allocator);
}
+ destroy_completion();
windows_destroy();
minibuffer_destroy();
buffer_destroy(&minibuffer);
buffers_destroy(&buflist);
display_clear(display);
display_destroy(display);
- destroy_keymaps();
+ destroy_bindings();
command_registry_destroy(&commands);
reactor_destroy(reactor);
frame_allocator_destroy(&frame_allocator);
diff --git a/src/main/search-replace.c b/src/main/search-replace.c
index c1b812f..6467ee1 100644
--- a/src/main/search-replace.c
+++ b/src/main/search-replace.c
@@ -17,10 +17,11 @@ static struct replace {
struct region *matches;
uint32_t nmatches;
uint32_t current_match;
+ buffer_keymap_id keymap_id;
} g_current_replace = {0};
void abort_replace() {
- reset_minibuffer_keys(minibuffer_buffer());
+ buffer_remove_keymap(g_current_replace.keymap_id);
free(g_current_replace.matches);
free(g_current_replace.replace);
g_current_replace.matches = NULL;
@@ -84,7 +85,8 @@ static int32_t replace_next(struct command_ctx ctx, int argc,
.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));
+ buffer_view_add(buffer_view, (uint8_t *)state->replace,
+ strlen(state->replace));
++state->current_match;
@@ -193,14 +195,16 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) {
ANONYMOUS_BINDING(None, 'n', &skip_next_command),
ANONYMOUS_BINDING(Ctrl, 'M', &replace_next_command),
};
- buffer_bind_keys(minibuffer_buffer(), bindings,
- sizeof(bindings) / sizeof(bindings[0]));
+ struct keymap km = keymap_create("replace", 8);
+ keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ g_current_replace.keymap_id = buffer_add_keymap(minibuffer_buffer(), km);
return minibuffer_prompt(ctx, "replace? [yn] ");
}
static char *g_last_search = NULL;
static bool g_last_search_interactive = false;
+static buffer_keymap_id g_search_keymap;
const char *search_prompt(bool reverse) {
const char *txt = "search (down): ";
@@ -303,7 +307,7 @@ int32_t search_interactive(struct command_ctx ctx, int argc,
} else {
struct text_chunk content = minibuffer_content();
char *p = malloc(content.nbytes + 1);
- strncpy(p, content.text, content.nbytes);
+ strncpy(p, (const char *)content.text, content.nbytes);
p[content.nbytes] = '\0';
pattern = p;
}
@@ -334,12 +338,13 @@ int32_t find(struct command_ctx ctx, int argc, const char *argv[]) {
ANONYMOUS_BINDING(Ctrl, 'S', &search_forward_command),
ANONYMOUS_BINDING(Ctrl, 'R', &search_backward_command),
};
- buffer_bind_keys(minibuffer_buffer(), bindings,
- sizeof(bindings) / sizeof(bindings[0]));
+ struct keymap m = keymap_create("search", 8);
+ keymap_bind_keys(&m, bindings, sizeof(bindings) / sizeof(bindings[0]));
+ g_search_keymap = buffer_add_keymap(minibuffer_buffer(), m);
return minibuffer_prompt(ctx, search_prompt(reverse));
}
- reset_minibuffer_keys(minibuffer_buffer());
+ buffer_remove_keymap(g_search_keymap);
if (g_last_search_interactive) {
g_last_search_interactive = false;
return 0;
@@ -347,7 +352,7 @@ int32_t find(struct command_ctx ctx, int argc, const char *argv[]) {
struct text_chunk content = minibuffer_content();
char *pattern = malloc(content.nbytes + 1);
- strncpy(pattern, content.text, content.nbytes);
+ strncpy(pattern, (const char *)content.text, content.nbytes);
pattern[content.nbytes] = '\0';
do_search(window_buffer_view(ctx.active_window), pattern, reverse);