diff options
| author | Albert Cervin <albert@acervin.com> | 2023-02-21 22:26:36 +0100 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2023-02-21 22:26:36 +0100 |
| commit | 44fd8cde61e3e89e5f83c98900a403e922073727 (patch) | |
| tree | 22ed65a8b3c766fa21c35fe4d567399e3810454a | |
| parent | d7bf8702bf32720d93c4e690937bc8b683926be1 (diff) | |
| download | dged-44fd8cde61e3e89e5f83c98900a403e922073727.tar.gz dged-44fd8cde61e3e89e5f83c98900a403e922073727.tar.xz dged-44fd8cde61e3e89e5f83c98900a403e922073727.zip | |
Implement support for settings
Settings are a flat "dictionary" containing
paths to settings on the format:
<category>.<sub-category>.<setting>.
| -rw-r--r-- | common.mk | 5 | ||||
| -rw-r--r-- | src/binding.h | 3 | ||||
| -rw-r--r-- | src/buffer.c | 13 | ||||
| -rw-r--r-- | src/buffer.h | 2 | ||||
| -rw-r--r-- | src/command.c | 86 | ||||
| -rw-r--r-- | src/command.h | 11 | ||||
| -rw-r--r-- | src/hash.h | 11 | ||||
| -rw-r--r-- | src/main.c | 8 | ||||
| -rw-r--r-- | src/minibuffer.c | 48 | ||||
| -rw-r--r-- | src/minibuffer.h | 19 | ||||
| -rw-r--r-- | src/settings.c | 168 | ||||
| -rw-r--r-- | src/settings.h | 133 | ||||
| -rw-r--r-- | test/command.c | 4 | ||||
| -rw-r--r-- | test/main.c | 3 | ||||
| -rw-r--r-- | test/settings.c | 73 | ||||
| -rw-r--r-- | test/test.h | 1 |
16 files changed, 520 insertions, 68 deletions
@@ -5,12 +5,13 @@ default: dged SOURCES = src/binding.c src/buffer.c src/command.c src/display.c \ src/keyboard.c src/minibuffer.c src/text.c \ - src/utf8.c src/buffers.c src/window.c src/allocator.c src/undo.c + src/utf8.c src/buffers.c src/window.c src/allocator.c src/undo.c \ + src/settings.c DGED_SOURCES = $(SOURCES) src/main.c TEST_SOURCES = test/assert.c test/buffer.c test/text.c test/utf8.c test/main.c \ test/command.c test/keyboard.c test/fake-reactor.c test/allocator.c \ - test/minibuffer.c test/undo.c + test/minibuffer.c test/undo.c test/settings.c prefix != if [ -n "$$prefix" ]; then echo "$$prefix"; else echo "/usr"; fi diff --git a/src/binding.h b/src/binding.h index db4867b..f2a531d 100644 --- a/src/binding.h +++ b/src/binding.h @@ -1,3 +1,4 @@ +#include "hash.h" #include "keyboard.h" /** @@ -36,7 +37,7 @@ enum binding_type { #define BINDING_INNER(mod_, c_, command_) \ (struct binding) { \ .key = {.mod = mod_, .key = c_}, .type = BindingType_Command, \ - .command = hash_command_name(command_) \ + .command = hash_name(command_) \ } #define ANONYMOUS_BINDING_INNER(mod_, c_, command_) \ diff --git a/src/buffer.c b/src/buffer.c index 60ef0f4..2decdea 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -4,6 +4,7 @@ #include "errno.h" #include "minibuffer.h" #include "reactor.h" +#include "settings.h" #include "utf8.h" #include <fcntl.h> @@ -149,6 +150,12 @@ void buffer_clear(struct buffer *buffer) { buffer->dot.col = buffer->dot.line = 0; } +void buffer_static_init() { + settings_register_setting( + "editor.tab-width", + (struct setting_value){.type = Setting_Number, .number_value = 4}); +} + void buffer_static_teardown() { for (uint32_t i = 0; i < KILL_RING_SZ; ++i) { if (g_kill_ring.buffer[i].allocated) { @@ -585,8 +592,10 @@ void buffer_newline(struct buffer *buffer) { } void buffer_indent(struct buffer *buffer) { - // TODO: config - buffer_add_text(buffer, (uint8_t *)" ", 4); + struct setting *setting = settings_get("editor.tab-width"); + buffer_add_text( + buffer, (uint8_t *)" ", + setting->value.number_value > 16 ? 16 : setting->value.number_value); } uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, diff --git a/src/buffer.h b/src/buffer.h index 57f7d4e..9a312c0 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -140,6 +140,8 @@ struct buffer { struct buffer buffer_create(char *name, bool modeline); void buffer_destroy(struct buffer *buffer); + +void buffer_static_init(); void buffer_static_teardown(); uint32_t buffer_keymaps(struct buffer *buffer, struct keymap **keymaps_out); diff --git a/src/command.c b/src/command.c index be021fe..55b5bb3 100644 --- a/src/command.c +++ b/src/command.c @@ -1,5 +1,7 @@ +#include "command.h" #include "buffer.h" #include "buffers.h" +#include "hash.h" #include "minibuffer.h" #include <errno.h> @@ -26,16 +28,6 @@ void command_registry_destroy(struct commands *commands) { commands->capacity = 0; } -uint32_t hash_command_name(const char *name) { - unsigned long hash = 5381; - int c; - - while ((c = *name++)) - hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ - - return hash; -} - uint32_t register_command(struct commands *commands, struct command command) { if (commands->ncommands == commands->capacity) { commands->capacity *= 2; @@ -43,7 +35,7 @@ uint32_t register_command(struct commands *commands, struct command command) { commands->commands, sizeof(struct hashed_command) * commands->capacity); } - uint32_t hash = hash_command_name(command.name); + uint32_t hash = hash_name(command.name); commands->commands[commands->ncommands] = (struct hashed_command){.command = command, .hash = hash}; @@ -60,7 +52,7 @@ void register_commands(struct commands *command_list, struct command *commands, struct command *lookup_command(struct commands *command_list, const char *name) { - uint32_t needle = hash_command_name(name); + uint32_t needle = hash_name(name); return lookup_command_by_hash(command_list, needle); } @@ -86,53 +78,68 @@ int32_t execute_command(struct command *command, struct commands *commands, .userdata = command->userdata, .commands = commands, .self = command, + .saved_argv = {0}, + .saved_argc = 0, }, argc, argv); } +void command_ctx_push_arg(struct command_ctx *ctx, const char *argv) { + if (ctx->saved_argc < 64) { + ctx->saved_argv[ctx->saved_argc] = strdup(argv); + ++ctx->saved_argc; + } +} + +void command_ctx_free(struct command_ctx *ctx) { + for (uint32_t i = 0; i < ctx->saved_argc; ++i) { + free((char *)ctx->saved_argv[i]); + } + + ctx->saved_argc = 0; +} + int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) { const char *pth = NULL; - if (argc == 1) { - pth = argv[0]; - struct stat sb; - 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; - } + if (argc == 0) { + return minibuffer_prompt(ctx, "find file: "); + } - window_set_buffer(ctx.active_window, - buffers_add(ctx.buffers, buffer_from_file((char *)pth))); - minibuffer_echo_timeout(4, "buffer \"%s\" loaded", - ctx.active_window->buffer->name); + pth = argv[0]; + struct stat sb; + if (stat(pth, &sb) < 0 && errno != ENOENT) { + minibuffer_echo("stat on %s failed: %s", pth, strerror(errno)); + return 1; + } - } else { - minibuffer_prompt(ctx, "find file: "); + if (S_ISDIR(sb.st_mode) && errno != ENOENT) { + minibuffer_echo("TODO: implement dired!"); + return 1; } + window_set_buffer(ctx.active_window, + buffers_add(ctx.buffers, buffer_from_file((char *)pth))); + minibuffer_echo_timeout(4, "buffer \"%s\" loaded", + ctx.active_window->buffer->name); + return 0; } int32_t write_file(struct command_ctx ctx, int argc, const char *argv[]) { const char *pth = NULL; - if (argc == 1) { - pth = argv[0]; - buffer_write_to(ctx.active_window->buffer, pth); - } else { - minibuffer_prompt(ctx, "write to file: "); + if (argc == 0) { + return minibuffer_prompt(ctx, "write to file: "); } + pth = argv[0]; + buffer_write_to(ctx.active_window->buffer, pth); + return 0; } int32_t run_interactive(struct command_ctx ctx, int argc, const char *argv[]) { if (argc == 0) { - minibuffer_prompt(ctx, "execute: "); - return 0; + return minibuffer_prompt(ctx, "execute: "); } struct command *cmd = lookup_command(ctx.commands, argv[0]); @@ -174,12 +181,11 @@ int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { if (argc == 0) { ctx.self = &do_switch_buffer_cmd; if (ctx.active_window->prev_buffer != NULL) { - minibuffer_prompt( + return minibuffer_prompt( ctx, "buffer (default %s): ", ctx.active_window->prev_buffer->name); } else { - minibuffer_prompt(ctx, "buffer: "); + return minibuffer_prompt(ctx, "buffer: "); } - return 0; } return execute_command(&do_switch_buffer_cmd, ctx.commands, ctx.active_window, diff --git a/src/command.h b/src/command.h index 35467e7..b151eb8 100644 --- a/src/command.h +++ b/src/command.h @@ -1,3 +1,6 @@ +#ifndef _COMMAND_H +#define _COMMAND_H + /** @file command.h * Commands and command registries */ @@ -40,6 +43,10 @@ struct command_ctx { * User data set up by the command currently being executed. */ void *userdata; + + const char *saved_argv[64]; + + int saved_argc; }; /** A command function callback which holds the implementation of a command */ @@ -165,6 +172,9 @@ struct command *lookup_command(struct commands *commands, const char *name); struct command *lookup_command_by_hash(struct commands *commands, uint32_t hash); +void command_ctx_push_arg(struct command_ctx *ctx, const char *argv); +void command_ctx_free(struct command_ctx *ctx); + /** * @defgroup common-commands Implementation of common commands * @{ @@ -191,3 +201,4 @@ int32_t run_interactive(struct command_ctx ctx, int argc, const char *argv[]); int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]); /**@}*/ +#endif diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 0000000..0fd689b --- /dev/null +++ b/src/hash.h @@ -0,0 +1,11 @@ +#include <stdint.h> + +static uint32_t hash_name(const char *s) { + unsigned long hash = 5381; + int c; + + while ((c = *s++)) + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + + return hash; +} @@ -13,6 +13,7 @@ #include "display.h" #include "minibuffer.h" #include "reactor.h" +#include "settings.h" struct frame_allocator frame_allocator; @@ -84,6 +85,9 @@ int main(int argc, char *argv[]) { signal(SIGTERM, terminate); + settings_init(64); + buffer_static_init(); + frame_allocator = frame_allocator_create(16 * 1024 * 1024); // create reactor @@ -103,6 +107,8 @@ int main(int argc, char *argv[]) { sizeof(GLOBAL_COMMANDS) / sizeof(GLOBAL_COMMANDS[0])); register_commands(&commands, BUFFER_COMMANDS, sizeof(BUFFER_COMMANDS) / sizeof(BUFFER_COMMANDS[0])); + register_commands(&commands, SETTINGS_COMMANDS, + sizeof(SETTINGS_COMMANDS) / sizeof(SETTINGS_COMMANDS[0])); // keymaps struct keymap *current_keymap = NULL; @@ -307,6 +313,7 @@ int main(int argc, char *argv[]) { frame_allocator_clear(&frame_allocator); } + minibuffer_destroy(); buffer_destroy(&minibuffer); buffers_destroy(&buflist); display_clear(display); @@ -317,6 +324,7 @@ int main(int argc, char *argv[]) { reactor_destroy(reactor); frame_allocator_destroy(&frame_allocator); buffer_static_teardown(); + settings_destroy(); return 0; } diff --git a/src/minibuffer.c b/src/minibuffer.c index 63cc5a8..3fa311c 100644 --- a/src/minibuffer.c +++ b/src/minibuffer.c @@ -1,6 +1,7 @@ #include "minibuffer.h" #include "binding.h" #include "buffer.h" +#include "command.h" #include "display.h" #include <stdarg.h> @@ -11,9 +12,11 @@ static struct minibuffer { struct buffer *buffer; struct timespec expires; + char prompt[128]; struct command_ctx prompt_command_ctx; bool prompt_active; + struct keymap keymap; } g_minibuffer = {0}; @@ -26,17 +29,25 @@ void draw_prompt(struct command_list *commands, void *userdata) { int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) { if (g_minibuffer.prompt_active) { + struct command_ctx *c = &g_minibuffer.prompt_command_ctx; + struct text_chunk line = buffer_get_line(g_minibuffer.buffer, 0); char *l = (char *)malloc(line.nbytes + 1); memcpy(l, line.text, line.nbytes); l[line.nbytes] = '\0'; + // propagate any saved arguments + char *argv[64]; + for (uint32_t i = 0; i < c->saved_argc; ++i) { + argv[i] = (char *)c->saved_argv[i]; + } + argv[c->saved_argc] = l; + uint32_t argc = c->saved_argc + (line.nbytes > 0 ? 1 : 0); + // split on ' ' - const char *argv[128] = {l}; - argc = line.nbytes > 0 ? 1 : 0; for (uint32_t bytei = 0; bytei < line.nbytes; ++bytei) { uint8_t byte = line.text[bytei]; - if (byte == ' ') { + if (byte == ' ' && argc < 64) { l[bytei] = '\0'; argv[argc] = l + bytei + 1; ++argc; @@ -44,11 +55,11 @@ int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) { } minibuffer_abort_prompt(); - struct command_ctx *ctx = &g_minibuffer.prompt_command_ctx; - uint32_t res = execute_command(ctx->self, ctx->commands, ctx->active_window, - ctx->buffers, argc, argv); + int32_t res = execute_command(c->self, c->commands, c->active_window, + c->buffers, argc, (const char **)argv); free(l); + return res; } else { return 0; @@ -61,16 +72,6 @@ struct command execute_minibuffer_command = { .userdata = NULL, }; -int32_t complete(struct command_ctx ctx, int argc, const char *argv[]) { - return 0; -} - -struct command complete_minibuffer_command = { - .fn = complete, - .name = "minibuffer-complete", - .userdata = NULL, -}; - struct update_hook_result update(struct buffer *buffer, struct command_list *commands, uint32_t width, uint32_t height, uint64_t frame_time, @@ -101,7 +102,6 @@ void minibuffer_init(struct buffer *buffer) { struct binding bindings[] = { ANONYMOUS_BINDING(Ctrl, 'M', &execute_minibuffer_command), - ANONYMOUS_BINDING(Ctrl, 'I', &complete_minibuffer_command), }; keymap_bind_keys(&g_minibuffer.keymap, bindings, sizeof(bindings) / sizeof(bindings[0])); @@ -128,6 +128,10 @@ void echo(uint32_t timeout, const char *fmt, va_list args) { g_minibuffer.expires.tv_sec += timeout; } +void minibuffer_destroy() { + command_ctx_free(&g_minibuffer.prompt_command_ctx); +} + void minibuffer_echo(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -142,20 +146,24 @@ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...) { va_end(args); } -void minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...) { +int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, + ...) { if (g_minibuffer.buffer == NULL) { - return; + return 1; } minibuffer_clear(); - g_minibuffer.prompt_active = true; + + command_ctx_free(&g_minibuffer.prompt_command_ctx); g_minibuffer.prompt_command_ctx = command_ctx; va_list args; va_start(args, fmt); vsnprintf(g_minibuffer.prompt, sizeof(g_minibuffer.prompt), fmt, args); va_end(args); + + return 0; } void minibuffer_abort_prompt() { diff --git a/src/minibuffer.h b/src/minibuffer.h index 71885ec..6845b07 100644 --- a/src/minibuffer.h +++ b/src/minibuffer.h @@ -16,6 +16,13 @@ struct command_ctx; void minibuffer_init(struct buffer *buffer); /** + * Destroy the minibuffer + * + * Note that this does not release the buffer used. + */ +void minibuffer_destroy(); + +/** * Echo a message to the minibuffer. * * @param fmt Format string for the message. @@ -42,8 +49,10 @@ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...); * command (or other command) when the user confirms the input. * @param fmt Format string for the prompt. * @param ... Format arguments. + * @returns a return code suitable to return from a command to signal more input + * is needed. */ -void minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...); +int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...); /** * Abort the current minibuffer prompt. @@ -53,6 +62,14 @@ void minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...); void minibuffer_abort_prompt(); /** + * Minibuffer prompt args + */ +struct minibuffer_prompt_args { + int argc; + const char **argv; +}; + +/** * Clear the current text in the minibuffer. */ void minibuffer_clear(); diff --git a/src/settings.c b/src/settings.c new file mode 100644 index 0000000..08e31d4 --- /dev/null +++ b/src/settings.c @@ -0,0 +1,168 @@ +#include "settings.h" +#include "command.h" +#include "hash.h" +#include "minibuffer.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static struct settings g_settings = {0}; + +void settings_resize(uint32_t new_capacity) { + if (new_capacity > g_settings.capacity) { + g_settings.settings = + realloc(g_settings.settings, sizeof(struct setting) * new_capacity); + } +} + +void settings_init(uint32_t initial_capacity) { + settings_resize(initial_capacity); + g_settings.capacity = initial_capacity; + g_settings.nsettings = 0; +} + +void settings_destroy() { + for (uint32_t i = 0; i < g_settings.nsettings; ++i) { + struct setting *setting = &g_settings.settings[i]; + if (setting->value.type == Setting_String) { + free(setting->value.string_value); + } + } + + free(g_settings.settings); + g_settings.settings = NULL; + g_settings.capacity = 0; + g_settings.nsettings = 0; +} + +void settings_register_setting(const char *path, + struct setting_value default_value) { + if (g_settings.nsettings + 1 == g_settings.capacity) { + g_settings.capacity *= 2; + settings_resize(g_settings.capacity); + } + + struct setting *s = &g_settings.settings[g_settings.nsettings]; + s->value = default_value; + s->hash = hash_name(path); + strncpy(s->path, path, 128); + s->path[127] = '\0'; + + ++g_settings.nsettings; +} + +struct setting *settings_get(const char *path) { + uint32_t needle = hash_name(path); + + for (uint32_t i = 0; i < g_settings.nsettings; ++i) { + struct setting *setting = &g_settings.settings[i]; + if (setting->hash == needle) { + return setting; + } + } + + return NULL; +} + +void settings_get_prefix(const char *prefix, struct setting **settings_out[], + uint32_t *nsettings_out) { + + uint32_t capacity = 16; + struct setting **res = malloc(sizeof(struct setting *) * capacity); + uint32_t nsettings = 0; + for (uint32_t i = 0; i < g_settings.nsettings; ++i) { + struct setting *setting = &g_settings.settings[i]; + if (strncmp(prefix, setting->path, strlen(prefix)) == 0) { + if (nsettings + 1 == capacity) { + capacity *= 2; + res = realloc(res, sizeof(struct setting *) * capacity); + } + + res[nsettings] = setting; + ++nsettings; + } + } + + *nsettings_out = nsettings; + *settings_out = res; +} + +void settings_set(const char *path, struct setting_value value) { + struct setting *setting = settings_get(path); + if (setting != NULL && setting->value.type == value.type) { + setting->value = value; + } +} + +void setting_to_string(struct setting *setting, char *buf, size_t n) { + switch (setting->value.type) { + case Setting_Bool: + snprintf(buf, n, "%s", setting->value.bool_value ? "true" : false); + break; + case Setting_Number: + snprintf(buf, n, "%ld", setting->value.number_value); + break; + case Setting_String: + snprintf(buf, n, "%s", setting->value.string_value); + break; + } +} + +int32_t settings_get_cmd(struct command_ctx ctx, int argc, const char *argv[]) { + if (argc == 0) { + return minibuffer_prompt(ctx, "setting: "); + } + + struct setting *setting = settings_get(argv[0]); + if (setting == NULL) { + minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); + return 1; + } else { + char buf[128]; + setting_to_string(setting, buf, 128); + minibuffer_echo("%s = %s", argv[0], buf); + } + + return 0; +} + +int32_t settings_set_cmd(struct command_ctx ctx, int argc, const char *argv[]) { + if (argc == 0) { + return minibuffer_prompt(ctx, "setting: "); + } else if (argc == 1) { + // validate setting here as well + struct setting *setting = settings_get(argv[0]); + if (setting == NULL) { + minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); + return 1; + } + + command_ctx_push_arg(&ctx, argv[0]); + return minibuffer_prompt(ctx, "value: "); + } + + struct setting *setting = settings_get(argv[0]); + if (setting == NULL) { + minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); + return 1; + } else { + const char *value = argv[1]; + struct setting_value new_value = {.type = setting->value.type}; + switch (setting->value.type) { + case Setting_Bool: + new_value.bool_value = strncmp("true", value, 4) == 0; + break; + case Setting_Number: + new_value.number_value = atol(value); + break; + case Setting_String: + new_value.string_value = strdup(value); + break; + } + + setting->value = new_value; + } + + return 0; +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..8d6f1f2 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,133 @@ +#include "command.h" + +#include <stdbool.h> +#include <stdint.h> + +/** + * The type of setting value. + */ +enum setting_type { + /** String setting. */ + Setting_String = 0, + + /** Number setting (a signed 64 bit integer). */ + Setting_Number, + + /** Boolean setting. */ + Setting_Bool, +}; + +/** + * Value for a setting. + */ +struct setting_value { + /** Type of setting. */ + enum setting_type type; + + union { + /** String setting. */ + char *string_value; + + /** Real number setting. */ + int64_t number_value; + + /** Boolean setting value. */ + bool bool_value; + }; +}; + +/** + * A single setting. + * + * A setting has a "path", denoted by a string + * containing a number (0-) of dots. + * Example: editor.tab-width. + */ +struct setting { + + /** Path of the setting. */ + char path[128]; + + /** Hashed path that can be used for equality checks. */ + uint32_t hash; + + /** Value of the setting. */ + struct setting_value value; +}; + +/** + * A collection of settings. + */ +struct settings { + /** Settings */ + struct setting *settings; + + /** Number of settings currently in collection. */ + uint32_t nsettings; + + /** Current capacity of collection. */ + uint32_t capacity; +}; + +/** + * Initialize the global collection of settings. + * + * @param initial_capacity Initial capacity of the settings collection. + * @returns Nothing, the settings collection is a global instance. + */ +void settings_init(uint32_t initial_capacity); + +/** + * Destroy the global collection of settings. + */ +void settings_destroy(); + +/** + * Register a new setting. + * + * @param path The path of the new setting on + * the form <category>.<sub-category>.<setting-name>. + * @param default_value The default value for the setting. + * All settings are required to declare a default value. + */ +void settings_register_setting(const char *path, + struct setting_value default_value); + +/** + * Retrieve a single setting by path. + * + * @param path The exact path of the setting on + * the form <category>.<sub-category>.<setting-name>. + * @returns A pointer to the setting if found, NULL otherwise. + */ +struct setting *settings_get(const char *path); + +/** + * Retrieve a collection of settings by prefix + * + * @param prefix Path prefix for the settings to retrieve. + * @param settings_out Pointer to an array that will be modified to point to the + * result. + * @param nsettings_out Pointer to an integer that will be set to the number of + * settings pointers in the result. + */ +void settings_get_prefix(const char *prefix, struct setting **settings_out[], + uint32_t *nsettings_out); + +/** + * Set a value for a setting. + * + * @param path The exact path of the setting on + * the form <category>.<sub-category>.<setting-name>. + * @param value The new value of the setting. The type has to match the declared + * type for the setting. If not, the new value is ignored. + */ +void settings_set(const char *path, struct setting_value value); + +int32_t settings_get_cmd(struct command_ctx ctx, int argc, const char *argv[]); +int32_t settings_set_cmd(struct command_ctx ctx, int argc, const char *argv[]); + +static struct command SETTINGS_COMMANDS[] = { + {.name = "set", .fn = settings_set_cmd}, + {.name = "get", .fn = settings_get_cmd}, +}; diff --git a/test/command.c b/test/command.c index 09de7f4..52a67ac 100644 --- a/test/command.c +++ b/test/command.c @@ -2,6 +2,7 @@ #include "test.h" #include "command.h" +#include "hash.h" void test_command_registry_create() { struct commands cmds = command_registry_create(10); @@ -66,8 +67,7 @@ void test_lookup_command() { ASSERT_STR_EQ(cmd->name, "fake", "Expected the found function to have the correct name"); - struct command *also_cmd = - lookup_command_by_hash(&cmds, hash_command_name("fake")); + struct command *also_cmd = lookup_command_by_hash(&cmds, hash_name("fake")); ASSERT(cmd != NULL, "Expected to be able to look up inserted command by hash"); ASSERT_STR_EQ(cmd->name, "fake", diff --git a/test/main.c b/test/main.c index a999b43..c4b50d0 100644 --- a/test/main.c +++ b/test/main.c @@ -39,6 +39,9 @@ int main() { printf("\nš \x1b[1;36mRunning minibuffer tests...\x1b[0m\n"); run_minibuffer_tests(); + printf("\n š \x1b[1;36mRunning settings tests...\x1b[0m\n"); + run_settings_tests(); + struct timespec elapsed; clock_gettime(CLOCK_MONOTONIC, &elapsed); uint64_t elapsed_nanos = diff --git a/test/settings.c b/test/settings.c new file mode 100644 index 0000000..181cd73 --- /dev/null +++ b/test/settings.c @@ -0,0 +1,73 @@ +#include "assert.h" +#include "test.h" + +#include "settings.h" +#include <stdlib.h> + +void test_get() { + settings_init(10); + settings_register_setting( + "my.setting", + (struct setting_value){.type = Setting_Bool, .bool_value = false}); + + struct setting *s = settings_get("my.setting"); + ASSERT(s != NULL, "Expected setting to exist after being inserted"); + ASSERT(s->value.type == Setting_Bool, + "Expected inserted setting to have the same type when retrieved"); + ASSERT(!s->value.bool_value, + "Expected inserted setting to have the same value when retrieved"); + + settings_register_setting( + "other.setting", + (struct setting_value){.type = Setting_Number, .number_value = 28}); + + struct setting **res = NULL; + uint32_t nres = 0; + settings_get_prefix("my", &res, &nres); + + ASSERT(nres == 1, "Expected to get one result back"); + ASSERT(s->value.type == Setting_Bool, "Expected inserted setting to have the " + "same type when retrieved by prefix"); + ASSERT(!s->value.bool_value, "Expected inserted setting to have the same " + "value when retrieved by prefix"); + + free(res); + + settings_destroy(); +} + +void test_set() { + settings_init(10); + settings_register_setting( + "my.setting", + (struct setting_value){.type = Setting_Bool, .bool_value = false}); + + // test that wrong type is ignored; + settings_set("my.setting", (struct setting_value){.type = Setting_String, + .string_value = "bonan"}); + + struct setting *s = settings_get("my.setting"); + ASSERT(s != NULL, "Expected setting to exist after being inserted"); + ASSERT(s->value.type == Setting_Bool, + "Expected inserted setting type to not have been changed"); + ASSERT(!s->value.bool_value, + "Expected inserted setting value to not have been changed"); + + // test that correct type is indeed changed + settings_set("my.setting", (struct setting_value){.type = Setting_Bool, + .bool_value = true}); + + s = settings_get("my.setting"); + ASSERT(s != NULL, "Expected setting to exist"); + ASSERT(s->value.type == Setting_Bool, + "Expected inserted setting type to not have been changed"); + ASSERT(s->value.bool_value, + "Expected inserted setting value to _have_ been changed"); + + settings_destroy(); +} + +void run_settings_tests() { + run_test(test_get); + run_test(test_set); +} diff --git a/test/test.h b/test/test.h index 4dab62e..047bbfb 100644 --- a/test/test.h +++ b/test/test.h @@ -17,5 +17,6 @@ void run_command_tests(); void run_keyboard_tests(); void run_allocator_tests(); void run_minibuffer_tests(); +void run_settings_tests(); #endif |
