#include "minibuffer.h" #include "binding.h" #include "buffer.h" #include "buffer_view.h" #include "buffers.h" #include "command.h" #include "display.h" #include #include #include #include struct prompt_key { char key[16]; char name[128]; }; static struct minibuffer { struct buffer *buffer; struct timespec expires; char prompt[128]; struct prompt_key prompt_keys[16]; uint32_t nprompt_keys; struct command_ctx prompt_command_ctx; bool prompt_active; struct window *prev_window; struct buffer *message_buffer; struct timespec created_at; } g_minibuffer = {0}; uint32_t minibuffer_draw_prompt(struct command_list *commands) { if (!g_minibuffer.prompt_active) { return 0; } uint32_t len = strlen(g_minibuffer.prompt); command_list_set_index_color_fg(commands, 4); command_list_draw_text(commands, 0, 0, (uint8_t *)g_minibuffer.prompt, len); command_list_reset_color(commands); uint32_t xoffset = len; for (uint32_t i = 0; i < g_minibuffer.nprompt_keys; ++i) { struct prompt_key *pk = &g_minibuffer.prompt_keys[i]; command_list_set_index_color_fg(commands, Color_Green); size_t keylen = strlen(pk->key); command_list_draw_text_copy(commands, xoffset, 0, (uint8_t *)pk->key, keylen); command_list_reset_color(commands); xoffset += keylen; command_list_draw_text(commands, xoffset, 0, (uint8_t *)" -> ", 4); xoffset += 4; command_list_set_index_color_fg(commands, Color_Magenta); size_t namelen = strlen(pk->name); command_list_draw_text_copy(commands, xoffset, 0, (uint8_t *)pk->name, namelen); command_list_reset_color(commands); xoffset += namelen + 1; } return xoffset; } static void minibuffer_abort_prompt_internal(bool clear); int32_t minibuffer_execute(void) { if (g_minibuffer.prompt_active) { struct command_ctx *c = &g_minibuffer.prompt_command_ctx; struct text_chunk line = minibuffer_content(); 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 < (uint32_t)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 ' ' for (uint32_t bytei = 0; bytei < line.nbytes; ++bytei) { uint8_t byte = line.text[bytei]; if (byte == ' ' && argc < 64) { l[bytei] = '\0'; argv[argc] = l + bytei + 1; ++argc; } } minibuffer_abort_prompt_internal(false); 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; } } void update(struct buffer *buffer, void *userdata) { (void)buffer; struct timespec current; struct minibuffer *mb = (struct minibuffer *)userdata; clock_gettime(CLOCK_MONOTONIC, ¤t); if ((!mb->prompt_active && current.tv_sec >= mb->expires.tv_sec)) { minibuffer_clear(); } } static void print_message(const char *buff, size_t len) { if (g_minibuffer.message_buffer == NULL) { return; } struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t elapsed = (((uint64_t)ts.tv_sec * 1e9 + (uint64_t)ts.tv_nsec) - ((uint64_t)g_minibuffer.created_at.tv_sec * 1e9 + (uint64_t)g_minibuffer.created_at.tv_nsec)) / 1e6; struct s8 timestamp = s8from_fmt("%d: ", elapsed); struct location at = buffer_add(g_minibuffer.message_buffer, buffer_end(g_minibuffer.message_buffer), timestamp.s, timestamp.l); s8delete(timestamp); buffer_add(g_minibuffer.message_buffer, at, (uint8_t *)buff, len); } void minibuffer_init(struct buffer *buffer, struct buffers *buffers) { if (g_minibuffer.buffer != NULL) { return; } g_minibuffer.buffer = buffer; g_minibuffer.expires.tv_sec = 0; g_minibuffer.expires.tv_nsec = 0; g_minibuffer.prompt_active = false; g_minibuffer.nprompt_keys = 0; buffer_add_update_hook(g_minibuffer.buffer, update, &g_minibuffer); g_minibuffer.message_buffer = buffers_add(buffers, buffer_create("*messages*")); clock_gettime(CLOCK_MONOTONIC, &g_minibuffer.created_at); } static void echo(uint32_t timeout, const char *fmt, va_list args, bool message) { if (g_minibuffer.prompt_active || g_minibuffer.buffer == NULL) { return; } clock_gettime(CLOCK_MONOTONIC, &g_minibuffer.expires); g_minibuffer.expires.tv_sec += timeout; static char buff[2048]; size_t nbytes = vsnprintf(buff, 2048, fmt, args); // vsnprintf returns how many characters it would have wanted to write in case // of overflow buffer_set_text(g_minibuffer.buffer, (uint8_t *)buff, nbytes > 2048 ? 2048 : nbytes); if (message) { print_message(buff, nbytes > 2048 ? 2048 : nbytes); } } void message(const char *fmt, ...) { // we can get messages before this is set up if (g_minibuffer.message_buffer == NULL) { return; } va_list args; va_start(args, fmt); static char buff[2048]; size_t nbytes = vsnprintf(buff, 2048, fmt, args); va_end(args); print_message(buff, nbytes > 2048 ? 2048 : nbytes); } void minibuffer_destroy(void) { command_ctx_free(&g_minibuffer.prompt_command_ctx); } struct text_chunk minibuffer_content(void) { return buffer_line(g_minibuffer.buffer, 0); } struct buffer *minibuffer_buffer(void) { return g_minibuffer.buffer; } void minibuffer_echo(const char *fmt, ...) { va_list args; va_start(args, fmt); echo(1000, fmt, args, true); va_end(args); } void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...) { va_list args; va_start(args, fmt); echo(timeout, fmt, args, true); va_end(args); } void minibuffer_display(const char *fmt, ...) { va_list args; va_start(args, fmt); echo(1000, fmt, args, false); va_end(args); } void minibuffer_display_timeout(uint32_t timeout, const char *fmt, ...) { va_list args; va_start(args, fmt); echo(timeout, fmt, args, false); va_end(args); } void minibuffer_set_prompt_internal(const char *fmt, va_list args) { vsnprintf(g_minibuffer.prompt, sizeof(g_minibuffer.prompt), fmt, args); } static void minibuffer_setup(struct command_ctx command_ctx, const char *initial) { g_minibuffer.prompt_active = true; command_ctx_free(&g_minibuffer.prompt_command_ctx); g_minibuffer.prompt_command_ctx = command_ctx; if (windows_get_active() != minibuffer_window()) { g_minibuffer.prev_window = windows_get_active(); windows_set_active(minibuffer_window()); } minibuffer_clear(); if (initial != NULL) { buffer_set_text(g_minibuffer.buffer, (uint8_t *)initial, strlen(initial)); // TODO: what to do with these buffer_view_goto_end_of_line(window_buffer_view(minibuffer_window())); } } int32_t minibuffer_prompt_initial(struct command_ctx command_ctx, const char *initial, const char *fmt, ...) { if (g_minibuffer.buffer == NULL) { return 1; } minibuffer_setup(command_ctx, initial); va_list args; va_start(args, fmt); minibuffer_set_prompt_internal(fmt, args); va_end(args); return 0; } int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...) { if (g_minibuffer.buffer == NULL) { return 1; } minibuffer_setup(command_ctx, NULL); va_list args; va_start(args, fmt); minibuffer_set_prompt_internal(fmt, args); va_end(args); return 0; } int32_t minibuffer_keymap_prompt(struct command_ctx command_ctx, const char *fmt, struct keymap *keys, ...) { if (g_minibuffer.buffer == NULL) { return 1; } for (uint32_t i = 0; i < keys->nbindings; ++i) { struct prompt_key *pk = &g_minibuffer.prompt_keys[i]; struct binding *bind = &keys->bindings[i]; key_name(&bind->key, pk->key, 16); switch (bind->type) { case BindingType_Command: // FIXME: this is not awesome memcpy(pk->name, "", 5); pk->name[5] = '\0'; break; case BindingType_Keymap: memcpy(pk->name, "", 5); pk->name[5] = '\0'; break; case BindingType_DirectCommand: { const char *n = bind->data.direct_command->name; size_t l = strlen(n); if (l > 0) { l = l > 127 ? 127 : l; memcpy(pk->name, n, l); pk->name[l] = '\0'; } } break; } } g_minibuffer.nprompt_keys = keys->nbindings; minibuffer_setup(command_ctx, NULL); va_list args; va_start(args, keys); minibuffer_set_prompt_internal(fmt, args); va_end(args); return 0; } void minibuffer_set_prompt(const char *fmt, ...) { va_list args; va_start(args, fmt); minibuffer_set_prompt_internal(fmt, args); va_end(args); } static void minibuffer_abort_prompt_internal(bool clear) { if (clear) { minibuffer_clear(); } if (g_minibuffer.prompt_active && g_minibuffer.prev_window != NULL) { windows_set_active(g_minibuffer.prev_window); } g_minibuffer.prompt_active = false; g_minibuffer.nprompt_keys = 0; } void minibuffer_abort_prompt(void) { minibuffer_abort_prompt_internal(true); } bool minibuffer_empty(void) { return !minibuffer_displaying(); } bool minibuffer_displaying(void) { return g_minibuffer.buffer != NULL && !buffer_is_empty(g_minibuffer.buffer); } void minibuffer_clear(void) { g_minibuffer.expires.tv_sec = 0; g_minibuffer.expires.tv_nsec = 0; buffer_clear(g_minibuffer.buffer); } bool minibuffer_focused(void) { return g_minibuffer.prompt_active; } struct window *minibuffer_target_window(void) { return g_minibuffer.prev_window; }