summaryrefslogtreecommitdiff
path: root/src/main/completion
diff options
context:
space:
mode:
authorAlbert Cervin <albert@acervin.com>2024-09-17 08:47:03 +0200
committerAlbert Cervin <albert@acervin.com>2025-11-01 22:11:14 +0100
commit4459b8b3aa9d73895391785a99dcc87134e80601 (patch)
treea5204f447a0b2b05f63504c7fe958ef9bbf1918a /src/main/completion
parent4689f3f38277bb64981fc960e8e384e2d065d659 (diff)
downloaddged-4459b8b3aa9d73895391785a99dcc87134e80601.tar.gz
dged-4459b8b3aa9d73895391785a99dcc87134e80601.tar.xz
dged-4459b8b3aa9d73895391785a99dcc87134e80601.zip
More lsp support
This makes the LSP support complete for now: - Completion - Diagnostics - Goto implementation/declaration - Rename - Documentation - Find references
Diffstat (limited to 'src/main/completion')
-rw-r--r--src/main/completion/buffer.c148
-rw-r--r--src/main/completion/buffer.h18
-rw-r--r--src/main/completion/command.c151
-rw-r--r--src/main/completion/command.h17
-rw-r--r--src/main/completion/path.c268
-rw-r--r--src/main/completion/path.h14
6 files changed, 616 insertions, 0 deletions
diff --git a/src/main/completion/buffer.c b/src/main/completion/buffer.c
new file mode 100644
index 0000000..8074414
--- /dev/null
+++ b/src/main/completion/buffer.c
@@ -0,0 +1,148 @@
+#include "buffer.h"
+
+#include <string.h>
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/buffers.h"
+#include "dged/minibuffer.h"
+
+#include "main/completion.h"
+
+static bool is_space(const struct codepoint *c) {
+ // TODO: utf8 whitespace and other whitespace
+ return c->codepoint == ' ';
+}
+
+typedef void (*on_buffer_selected_cb)(struct buffer *);
+
+struct buffer_completion {
+ struct buffer *buffer;
+ on_buffer_selected_cb on_buffer_selected;
+};
+
+struct buffer_provider_data {
+ struct buffers *buffers;
+ on_buffer_selected_cb on_buffer_selected;
+};
+
+static void buffer_comp_selected(void *data, struct buffer_view *target) {
+ struct buffer_completion *bc = (struct buffer_completion *)data;
+ buffer_set_text(target->buffer, (uint8_t *)bc->buffer->name,
+ strlen(bc->buffer->name));
+
+ abort_completion();
+ bc->on_buffer_selected(bc->buffer);
+}
+
+static struct region buffer_comp_render(void *data,
+ struct buffer *comp_buffer) {
+ struct buffer *buffer = ((struct buffer_completion *)data)->buffer;
+ struct location begin = buffer_end(comp_buffer);
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)buffer->name,
+ strlen(buffer->name));
+
+ struct location end = buffer_end(comp_buffer);
+ buffer_newline(comp_buffer, buffer_end(comp_buffer));
+ return region_new(begin, end);
+}
+
+static void buffer_comp_cleanup(void *data) {
+ struct buffer_completion *bc = (struct buffer_completion *)data;
+ free(bc);
+}
+
+struct needle_match_ctx {
+ const char *needle;
+ struct completion *completions;
+ uint32_t max_ncompletions;
+ uint32_t ncompletions;
+ on_buffer_selected_cb on_buffer_selected;
+};
+
+static void buffer_matches(struct buffer *buffer, void *userdata) {
+ struct needle_match_ctx *ctx = (struct needle_match_ctx *)userdata;
+
+ if (strncmp(ctx->needle, buffer->name, strlen(ctx->needle)) == 0 &&
+ ctx->ncompletions < ctx->max_ncompletions) {
+
+ struct buffer_completion *comp_data =
+ calloc(1, sizeof(struct buffer_completion));
+ comp_data->buffer = buffer;
+ comp_data->on_buffer_selected = ctx->on_buffer_selected;
+ ctx->completions[ctx->ncompletions] = (struct completion){
+ .render = buffer_comp_render,
+ .selected = buffer_comp_selected,
+ .cleanup = buffer_comp_cleanup,
+ .data = comp_data,
+ };
+ ++ctx->ncompletions;
+ }
+}
+
+static void buffer_complete(struct completion_context ctx, bool deletion,
+ void *userdata) {
+ (void)deletion;
+ struct buffer_provider_data *pd = (struct buffer_provider_data *)userdata;
+ struct buffers *buffers = pd->buffers;
+ if (buffers == NULL) {
+ return;
+ }
+
+ struct text_chunk txt = {0};
+ if (ctx.buffer == minibuffer_buffer()) {
+ txt = minibuffer_content();
+ } else {
+ struct match_result start =
+ buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
+ if (!start.found) {
+ start.at = (struct location){.line = ctx.location.line, .col = 0};
+ return;
+ }
+ txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
+ }
+
+ char *needle = calloc(txt.nbytes + 1, sizeof(char));
+ memcpy(needle, txt.text, txt.nbytes);
+ needle[txt.nbytes] = '\0';
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+
+ struct completion *completions = calloc(50, sizeof(struct completion));
+
+ struct needle_match_ctx match_ctx = (struct needle_match_ctx){
+ .needle = needle,
+ .max_ncompletions = 50,
+ .completions = completions,
+ .ncompletions = 0,
+ .on_buffer_selected = pd->on_buffer_selected,
+ };
+
+ buffers_for_each(buffers, buffer_matches, &match_ctx);
+ ctx.add_completions(match_ctx.completions, match_ctx.ncompletions);
+ free(completions);
+ free(needle);
+}
+
+static void cleanup_provider(void *data) {
+ struct buffer_provider_data *bpd = (struct buffer_provider_data *)data;
+ free(bpd);
+}
+
+struct completion_provider
+create_buffer_provider(struct buffers *buffers,
+ on_buffer_selected_cb on_buffer_selected) {
+ struct buffer_provider_data *data =
+ calloc(1, sizeof(struct buffer_provider_data));
+ data->buffers = buffers;
+ data->on_buffer_selected = on_buffer_selected;
+
+ return (struct completion_provider){
+ .name = "buffers",
+ .complete = buffer_complete,
+ .userdata = data,
+ .cleanup = cleanup_provider,
+ };
+}
diff --git a/src/main/completion/buffer.h b/src/main/completion/buffer.h
new file mode 100644
index 0000000..c2b6d42
--- /dev/null
+++ b/src/main/completion/buffer.h
@@ -0,0 +1,18 @@
+#ifndef _MAIN_COMPLETION_BUFFER_H
+#define _MAIN_COMPLETION_BUFFER_H
+
+struct buffer;
+struct buffers;
+
+/**
+ * Create a new buffer completion provider.
+ *
+ * This provider completes buffer names from the
+ * buffer list.
+ * @returns A buffer name @ref completion_provider.
+ */
+struct completion_provider
+create_buffer_provider(struct buffers *buffers,
+ void (*on_buffer_selected)(struct buffer *));
+
+#endif
diff --git a/src/main/completion/command.c b/src/main/completion/command.c
new file mode 100644
index 0000000..e4900ed
--- /dev/null
+++ b/src/main/completion/command.c
@@ -0,0 +1,151 @@
+#include "command.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/command.h"
+#include "dged/minibuffer.h"
+#include "dged/utf8.h"
+
+#include "main/completion.h"
+
+static bool is_space(const struct codepoint *c) {
+ // TODO: utf8 whitespace and other whitespace
+ return c->codepoint == ' ';
+}
+
+typedef void (*on_command_selected_cb)(struct command *);
+
+struct command_completion {
+ struct command *command;
+ on_command_selected_cb on_command_selected;
+};
+
+struct command_provider_data {
+ struct commands *commands;
+ on_command_selected_cb on_command_selected;
+};
+
+static void command_comp_selected(void *data, struct buffer_view *target) {
+ struct command_completion *cc = (struct command_completion *)data;
+ buffer_set_text(target->buffer, (uint8_t *)cc->command->name,
+ strlen(cc->command->name));
+
+ abort_completion();
+ cc->on_command_selected(cc->command);
+}
+
+static struct region command_comp_render(void *data,
+ struct buffer *comp_buffer) {
+ struct command *command = ((struct command_completion *)data)->command;
+ struct location begin = buffer_end(comp_buffer);
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)command->name,
+ strlen(command->name));
+
+ struct location end = buffer_end(comp_buffer);
+ buffer_newline(comp_buffer, buffer_end(comp_buffer));
+
+ return region_new(begin, end);
+}
+
+static void command_comp_cleanup(void *data) {
+ struct command_completion *cc = (struct command_completion *)data;
+ free(cc);
+}
+
+struct needle_match_ctx {
+ const char *needle;
+ struct completion *completions;
+ uint32_t max_ncompletions;
+ uint32_t ncompletions;
+ on_command_selected_cb on_command_selected;
+};
+
+static void command_matches(struct command *command, void *userdata) {
+ struct needle_match_ctx *ctx = (struct needle_match_ctx *)userdata;
+
+ if (strncmp(ctx->needle, command->name, strlen(ctx->needle)) == 0 &&
+ ctx->ncompletions < ctx->max_ncompletions) {
+
+ struct command_completion *comp_data =
+ calloc(1, sizeof(struct command_completion));
+ comp_data->command = command;
+ comp_data->on_command_selected = ctx->on_command_selected;
+ ctx->completions[ctx->ncompletions] = (struct completion){
+ .render = command_comp_render,
+ .selected = command_comp_selected,
+ .cleanup = command_comp_cleanup,
+ .data = comp_data,
+ };
+ ++ctx->ncompletions;
+ }
+}
+
+static void command_complete(struct completion_context ctx, bool deletion,
+ void *userdata) {
+ (void)deletion;
+ struct command_provider_data *pd = (struct command_provider_data *)userdata;
+ struct commands *commands = pd->commands;
+ if (commands == NULL) {
+ return;
+ }
+
+ struct text_chunk txt = {0};
+ if (ctx.buffer == minibuffer_buffer()) {
+ txt = minibuffer_content();
+ } else {
+ struct match_result start =
+ buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
+ if (!start.found) {
+ start.at = (struct location){.line = ctx.location.line, .col = 0};
+ return;
+ }
+ txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
+ }
+
+ char *needle = calloc(txt.nbytes + 1, sizeof(char));
+ memcpy(needle, txt.text, txt.nbytes);
+ needle[txt.nbytes] = '\0';
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+
+ struct completion *completions = calloc(50, sizeof(struct completion));
+
+ struct needle_match_ctx match_ctx = (struct needle_match_ctx){
+ .needle = needle,
+ .max_ncompletions = 50,
+ .completions = completions,
+ .ncompletions = 0,
+ .on_command_selected = pd->on_command_selected,
+ };
+
+ commands_for_each(commands, command_matches, &match_ctx);
+ ctx.add_completions(match_ctx.completions, match_ctx.ncompletions);
+ free(completions);
+ free(needle);
+}
+
+static void cleanup_provider(void *data) {
+ struct command_provider_data *cpd = (struct command_provider_data *)data;
+ free(cpd);
+}
+
+struct completion_provider
+create_commands_provider(struct commands *commands,
+ on_command_selected_cb on_command_selected) {
+ struct command_provider_data *data =
+ calloc(1, sizeof(struct command_provider_data));
+ data->commands = commands;
+ data->on_command_selected = on_command_selected;
+
+ return (struct completion_provider){
+ .name = "commands",
+ .complete = command_complete,
+ .userdata = data,
+ .cleanup = cleanup_provider,
+ };
+}
diff --git a/src/main/completion/command.h b/src/main/completion/command.h
new file mode 100644
index 0000000..c25df57
--- /dev/null
+++ b/src/main/completion/command.h
@@ -0,0 +1,17 @@
+#ifndef _MAIN_COMPLETION_COMMAND_H
+#define _MAIN_COMPLETION_COMMAND_H
+
+struct command;
+struct commands;
+
+/**
+ * Create a new command completion provider.
+ *
+ * This provider completes registered command names.
+ * @returns A command name @ref completion_provider.
+ */
+struct completion_provider
+create_commands_provider(struct commands *,
+ void (*on_command_selected)(struct command *));
+
+#endif
diff --git a/src/main/completion/path.c b/src/main/completion/path.c
new file mode 100644
index 0000000..708da3d
--- /dev/null
+++ b/src/main/completion/path.c
@@ -0,0 +1,268 @@
+#define _DEFAULT_SOURCE
+#include "path.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "dged/buffer.h"
+#include "dged/buffer_view.h"
+#include "dged/display.h"
+#include "dged/minibuffer.h"
+#include "dged/path.h"
+#include "dged/s8.h"
+#include "dged/utf8.h"
+
+static bool is_space(const struct codepoint *c) {
+ // TODO: utf8 whitespace and other whitespace
+ return c->codepoint == ' ';
+}
+
+typedef void (*on_complete_path_cb)(void);
+
+struct path_completion {
+ struct s8 name;
+ struct region replace;
+ unsigned char type;
+ on_complete_path_cb on_complete_path;
+};
+
+static void path_selected(void *data, struct buffer_view *target) {
+ struct path_completion *comp_path = (struct path_completion *)data;
+ struct location loc = buffer_delete(target->buffer, comp_path->replace);
+ loc = buffer_add(target->buffer, loc, (uint8_t *)comp_path->name.s,
+ comp_path->name.l);
+ buffer_view_goto(target, loc);
+ switch (comp_path->type) {
+ case DT_DIR:
+ if (s8eq(comp_path->name, s8("."))) {
+ // trigger "dired" in this case
+ abort_completion();
+ comp_path->on_complete_path();
+ return;
+ }
+
+ buffer_view_add(target, (uint8_t *)"/", 1);
+ break;
+ default:
+ break;
+ }
+
+ // if the user selected a "normal" file,
+ // the completion is finished
+ if (comp_path->type == DT_REG) {
+ abort_completion();
+ comp_path->on_complete_path();
+ } else {
+ complete(target->buffer, target->dot);
+ }
+}
+
+static struct region path_render(void *data, struct buffer *comp_buffer) {
+ struct path_completion *comp_path = (struct path_completion *)data;
+
+ struct location start = buffer_end(comp_buffer);
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)comp_path->name.s,
+ comp_path->name.l);
+ switch (comp_path->type) {
+ case DT_DIR:
+ if (!(s8eq(comp_path->name, s8(".")) || s8eq(comp_path->name, s8("..")))) {
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)"/", 1);
+ struct location end = buffer_end(comp_buffer);
+ buffer_add_text_property(comp_buffer, start, end,
+ (struct text_property){
+ .start = start,
+ .end = end,
+ .type = TextProperty_Colors,
+ .data.colors =
+ (struct text_property_colors){
+ .set_fg = true,
+ .fg = Color_Magenta,
+ },
+ });
+ }
+ break;
+ case DT_LNK: {
+ struct location end = buffer_end(comp_buffer);
+ buffer_add_text_property(comp_buffer, start, end,
+ (struct text_property){
+ .start = start,
+ .end = end,
+ .type = TextProperty_Colors,
+ .data.colors =
+ (struct text_property_colors){
+ .set_fg = true,
+ .fg = Color_Green,
+ },
+ });
+ } break;
+ default:
+ break;
+ }
+
+ struct location end = buffer_end(comp_buffer);
+ buffer_add(comp_buffer, buffer_end(comp_buffer), (uint8_t *)"\n", 1);
+
+ return region_new(start, end);
+}
+
+static void path_cleanup(void *data) {
+ struct path_completion *comp_path = (struct path_completion *)data;
+ s8delete(comp_path->name);
+ free(comp_path);
+}
+
+static int cmp_path_completions(const void *comp_a, const void *comp_b) {
+ struct completion *ca = (struct completion *)comp_a;
+ struct completion *cb = (struct completion *)comp_b;
+ struct path_completion *a = (struct path_completion *)ca->data;
+ struct path_completion *b = (struct path_completion *)cb->data;
+ return s8cmp(a->name, b->name);
+}
+
+static bool is_hidden(const char *filename) {
+ return filename[0] == '.' && filename[1] != '\0' && filename[1] != '.';
+}
+
+static bool fuzzy_match_filename(const char *haystack, const char *needle) {
+ for (; *haystack; ++haystack) {
+ const char *h = haystack;
+ const char *n = needle;
+
+ while (*h && *n && *h == *n) {
+ ++h;
+ ++n;
+ }
+
+ // if we reached the end of needle, we found a match
+ if (!*n) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void path_complete(struct completion_context ctx, bool deletion,
+ void *on_complete_path) {
+ (void)deletion;
+
+ // obtain path from the buffer
+ struct text_chunk txt = {0};
+ struct location needle_end = ctx.location;
+ if (ctx.buffer == minibuffer_buffer()) {
+ txt = minibuffer_content();
+ needle_end = buffer_end(minibuffer_buffer());
+ } else {
+ struct match_result start =
+ buffer_find_prev_in_line(ctx.buffer, ctx.location, is_space);
+ if (!start.found) {
+ start.at = (struct location){.line = ctx.location.line, .col = 0};
+ return;
+ }
+ txt = buffer_region(ctx.buffer, region_new(start.at, ctx.location));
+ }
+
+ char *path = calloc(txt.nbytes + 1, sizeof(char));
+ memcpy(path, txt.text, txt.nbytes);
+ path[txt.nbytes] = '\0';
+
+ if (txt.allocated) {
+ free(txt.text);
+ }
+
+ uint32_t n = 0;
+ char *p1 = to_abspath(path);
+ char *p2 = strdup(p1);
+
+ size_t inlen = strlen(path);
+
+ const char *dir = p1;
+ const char *file = "";
+
+ // check the input path here since
+ // to_abspath removes trailing slashes
+ if (inlen > 0 && path[inlen - 1] != '/') {
+ dir = dirname(p1);
+ file = basename(p2);
+ }
+
+ struct completion *completions = calloc(50, sizeof(struct completion));
+
+ DIR *d = opendir(dir);
+ if (d == NULL) {
+ goto done;
+ }
+
+ errno = 0;
+ size_t filelen = strlen(file);
+ size_t file_nchars = utf8_nchars((uint8_t *)file, filelen);
+ struct location needle_start = (struct location){
+ .line = needle_end.line,
+ .col = needle_end.col - file_nchars,
+ };
+
+ bool file_is_curdir = filelen == 1 && file[0] == '.';
+ while (n < 50) {
+ 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) && (filelen == 0 || file_is_curdir ||
+ fuzzy_match_filename(de->d_name, file))) {
+
+ struct path_completion *comp_data =
+ calloc(1, sizeof(struct path_completion));
+ comp_data->name = s8new(de->d_name, strlen(de->d_name));
+ comp_data->replace = region_new(needle_start, needle_end);
+ comp_data->type = de->d_type;
+ comp_data->on_complete_path = on_complete_path;
+
+ completions[n] = (struct completion){
+ .data = comp_data,
+ .render = path_render,
+ .selected = path_selected,
+ .cleanup = path_cleanup,
+ };
+
+ ++n;
+ }
+ break;
+ }
+ }
+
+ closedir(d);
+
+done:
+ free(path);
+ free(p1);
+ free(p2);
+
+ qsort(completions, n, sizeof(struct completion), cmp_path_completions);
+ ctx.add_completions(completions, n);
+
+ free(completions);
+}
+
+struct completion_provider
+create_path_provider(void (*on_complete_path)(void)) {
+ return (struct completion_provider){
+ .name = "path",
+ .complete = path_complete,
+ .userdata = on_complete_path,
+ };
+}
diff --git a/src/main/completion/path.h b/src/main/completion/path.h
new file mode 100644
index 0000000..407cae7
--- /dev/null
+++ b/src/main/completion/path.h
@@ -0,0 +1,14 @@
+#ifndef _MAIN_COMPLETION_PATH_H
+#define _MAIN_COMPLETION_PATH_H
+
+#include "main/completion.h"
+
+/**
+ * Create a new path completion provider.
+ *
+ * This provider completes filesystem paths.
+ * @returns A filesystem path @ref completion_provider.
+ */
+struct completion_provider create_path_provider(void (*on_complete_path)(void));
+
+#endif