#include #include #include #include #include #include #include #include #include "allocator.h" #include "binding.h" #include "bits/getopt_core.h" #include "bits/getopt_ext.h" #include "buffer.h" #include "buffers.h" #include "display.h" #include "lang.h" #include "minibuffer.h" #include "reactor.h" #include "settings.h" struct frame_allocator frame_allocator; void *frame_alloc(size_t sz) { return frame_allocator_alloc(&frame_allocator, sz); } bool running = true; void terminate() { running = false; } static struct display *display = NULL; static bool display_resized = false; void resized() { if (display != NULL) { display_resize(display); } display_resized = true; signal(SIGWINCH, resized); } int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) { minibuffer_abort_prompt(); minibuffer_echo_timeout(4, "💣 aborted"); return 0; } int32_t unimplemented_command(struct command_ctx ctx, int argc, const char *argv[]) { minibuffer_echo("TODO: %s is not implemented", (const char *)ctx.userdata); return 0; } int32_t exit_editor(struct command_ctx ctx, int argc, const char *argv[]) { terminate(); return 0; } static struct command GLOBAL_COMMANDS[] = { {.name = "find-file", .fn = find_file}, {.name = "write-file", .fn = write_file}, {.name = "run-command-interactive", .fn = run_interactive}, {.name = "switch-buffer", .fn = switch_buffer}, {.name = "abort", .fn = _abort}, {.name = "exit", .fn = exit_editor}}; uint64_t calc_frame_time_ns(struct timespec *timers, uint32_t num_timer_pairs) { uint64_t total = 0; for (uint32_t ti = 0; ti < num_timer_pairs * 2; ti += 2) { struct timespec *start_timer = &timers[ti]; struct timespec *end_timer = &timers[ti + 1]; total += ((uint64_t)end_timer->tv_sec * 1e9 + (uint64_t)end_timer->tv_nsec) - ((uint64_t)start_timer->tv_sec * 1e9 + (uint64_t)start_timer->tv_nsec); } return total; } void usage() { printf("TODO: print usage\n"); } int main(int argc, char *argv[]) { static struct option longopts[] = {{"line", required_argument, NULL, 'l'}, {"end", no_argument, NULL, 'e'}, {NULL, 0, NULL, 0}}; char *filename = NULL; uint32_t jumpline = 1; bool goto_end = false; char ch; while ((ch = getopt_long(argc, argv, "el:", longopts, NULL)) != -1) { switch (ch) { case 'l': jumpline = atoi(optarg); break; case 'e': goto_end = true; break; default: usage(); return 1; } } argc -= optind; argv += optind; if (argc > 1) { fprintf(stderr, "More than one file to open is not supported\n"); return 2; } else if (argc == 1) { filename = strdup(argv[0]); } setlocale(LC_ALL, ""); signal(SIGTERM, terminate); struct commands commands = command_registry_create(32); settings_init(64, &commands); languages_init(true); buffer_static_init(&commands); frame_allocator = frame_allocator_create(16 * 1024 * 1024); // create reactor struct reactor *reactor = reactor_create(); // initialize display display = display_create(); display_clear(display); signal(SIGWINCH, resized); // init keyboard struct keyboard kbd = keyboard_create(reactor); // global commands, TODO: move these, they should exist even if main does not register_commands(&commands, GLOBAL_COMMANDS, sizeof(GLOBAL_COMMANDS) / sizeof(GLOBAL_COMMANDS[0])); // keymaps struct keymap *current_keymap = NULL; struct keymap global_keymap = keymap_create("global", 32); struct keymap ctrlx_map = keymap_create("c-x", 32); struct binding global_binds[] = { PREFIX(Ctrl, 'X', &ctrlx_map), BINDING(Ctrl, 'G', "abort"), BINDING(Meta, 'x', "run-command-interactive"), }; struct binding ctrlx_bindings[] = { BINDING(Ctrl, 'C', "exit"), BINDING(Ctrl, 'S', "buffer-write-to-file"), BINDING(Ctrl, 'F', "find-file"), BINDING(Ctrl, 'W', "write-file"), BINDING(None, 'b', "switch-buffer"), }; keymap_bind_keys(&global_keymap, global_binds, sizeof(global_binds) / sizeof(global_binds[0])); keymap_bind_keys(&ctrlx_map, ctrlx_bindings, sizeof(ctrlx_bindings) / sizeof(ctrlx_bindings[0])); struct buffers buflist = {0}; buffers_init(&buflist, 32); struct buffer initial_buffer = buffer_create("welcome", true); if (filename != NULL) { buffer_destroy(&initial_buffer); initial_buffer = buffer_from_file(filename); if (goto_end) { buffer_goto_end(&initial_buffer); } else buffer_goto(&initial_buffer, jumpline > 0 ? jumpline - 1 : 0, 0); } else { const char *welcome_txt = "Welcome to the editor for datagubbar 👴\n"; buffer_add_text(&initial_buffer, (uint8_t *)welcome_txt, strlen(welcome_txt)); } // one main window struct window main_window = (struct window){ .buffer = buffers_add(&buflist, initial_buffer), .prev_buffer = NULL, .height = display_height(display) - 1, .width = display_width(display), .x = 0, .y = 0, }; // and one for the minibuffer struct buffer minibuffer = buffer_create("minibuffer", false); minibuffer_init(&minibuffer); struct window minibuffer_window = (struct window){ .buffer = &minibuffer, .prev_buffer = NULL, .x = 0, .y = display_height(display) - 1, .height = 1, .width = display_width(display), }; struct timespec buffer_begin, buffer_end, display_begin, display_end, keyboard_begin, keyboard_end; uint64_t frame_time = 0; struct window *windows[2] = { &minibuffer_window, &main_window, }; struct command_list *command_lists[2] = {0}; // TODO: not always struct window *active_window = &main_window; while (running) { clock_gettime(CLOCK_MONOTONIC, &buffer_begin); if (display_resized) { minibuffer_window.width = display_width(display); minibuffer_window.y = display_height(display) - 1; main_window.height = display_height(display) - 1; main_window.width = display_width(display); display_resized = false; } // update windows uint32_t dot_line = 0, dot_col = 0; for (uint32_t windowi = 0; windowi < sizeof(windows) / sizeof(windows[0]); ++windowi) { struct window *win = windows[windowi]; // TODO: better capacity command_lists[windowi] = command_list_create(win->height * win->width, frame_alloc, win->x, win->y, win->buffer->name); uint32_t relline, relcol; window_update_buffer(win, command_lists[windowi], frame_time, &relline, &relcol); if (win == active_window) { dot_line = relline; dot_col = relcol; } } clock_gettime(CLOCK_MONOTONIC, &buffer_end); // update screen clock_gettime(CLOCK_MONOTONIC, &display_begin); uint32_t relline, relcol; display_begin_render(display); for (uint32_t windowi = 0; windowi < sizeof(windows) / sizeof(windows[0]); ++windowi) { display_render(display, command_lists[windowi]); } display_move_cursor(display, dot_line + active_window->y, dot_col + active_window->x); display_end_render(display); clock_gettime(CLOCK_MONOTONIC, &display_end); // this blocks for events, so if nothing has happened we block here. reactor_update(reactor); clock_gettime(CLOCK_MONOTONIC, &keyboard_begin); struct keymap *local_keymaps = NULL; uint32_t nbuffer_keymaps = buffer_keymaps(active_window->buffer, &local_keymaps); struct keyboard_update kbd_upd = keyboard_update(&kbd, reactor, frame_alloc); uint32_t input_data_idx = 0; for (uint32_t ki = 0; ki < kbd_upd.nkeys; ++ki) { struct key *k = &kbd_upd.keys[ki]; struct lookup_result res = {.found = false}; if (current_keymap != NULL) { res = lookup_key(current_keymap, 1, k, &commands); } else { // check first the global keymap, then the buffer ones res = lookup_key(&global_keymap, 1, k, &commands); if (!res.found) { res = lookup_key(local_keymaps, nbuffer_keymaps, k, &commands); } } if (res.found) { switch (res.type) { case BindingType_Command: { if (res.command == NULL) { minibuffer_echo_timeout( 4, "binding found for key %s but not command", k); } else { int32_t ec = execute_command(res.command, &commands, active_window, &buflist, 0, NULL); if (ec != 0 && !minibuffer_displaying()) { minibuffer_echo_timeout(4, "command %s failed with exit code %d", res.command->name, ec); } } current_keymap = NULL; break; } case BindingType_Keymap: { char keyname[16]; key_name(k, keyname, 16); current_keymap = res.keymap; minibuffer_echo("%s", current_keymap->name); break; } } } else if (k->mod == 0) { buffer_add_text(active_window->buffer, &kbd_upd.raw[k->start], k->end - k->start); } else { char keyname[16]; key_name(k, keyname, 16); if (current_keymap == NULL) { minibuffer_echo_timeout(4, "key \"%s\" is not bound!", keyname); } else { minibuffer_echo_timeout(4, "key \"%s %s\" is not bound!", current_keymap->name, keyname); } current_keymap = NULL; } } clock_gettime(CLOCK_MONOTONIC, &keyboard_end); // calculate frame time struct timespec timers[] = {buffer_begin, buffer_end, display_begin, display_end, keyboard_begin, keyboard_end}; frame_time = calc_frame_time_ns(timers, 3); if (minibuffer_focused()) { active_window = &minibuffer_window; } else { // TODO: not this active_window = &main_window; } frame_allocator_clear(&frame_allocator); } minibuffer_destroy(); buffer_destroy(&minibuffer); buffers_destroy(&buflist); display_clear(display); display_destroy(display); keymap_destroy(&global_keymap); keymap_destroy(&ctrlx_map); command_registry_destroy(&commands); reactor_destroy(reactor); frame_allocator_destroy(&frame_allocator); buffer_static_teardown(); settings_destroy(); return 0; }