/** * @file builtins.c * @brief Built-in Lisp functions for editor operations * @details Provides Lisp bindings for core editor functionality including * cursor movement, file operations, keybinding management, and text manipulation */ #include "../include/builtins.h" #include "../include/data.h" #include "../include/define.h" #include "../include/editor_op.h" #include "../include/file_io.h" #include "../include/input.h" #include "../include/row_op.h" #include "../include/buffer.h" #include "../include/split_screen.h" #include #include #include /** * @brief Finds a prefix configuration by name * @details Searches the prefix array for a prefix matching the given name. * Returns the first prefix (default) if no match is found. * @param prefix_name Name of the prefix to search for (max 64 chars) * @return Matching prefix_t structure, or E.prefix[0] if not found * @note Updates global editor state E */ struct prefix_t find_prefix(const char prefix_name[64]) { int i = E.number_of_prefix + 1; while (i--) { if (!strcmp(prefix_name, E.prefix[i].prefix_name)) { return E.prefix[i]; } } return E.prefix[0]; } /** * @brief Lisp function to bind a command to a key sequence * @details Registers a keybinding with an associated Lisp command and optional * prefix modifier. Dynamically extends the keybind array. * @param args Lisp list of 3 arguments: (key-sequence command prefix-name) * - key_sequence (string): The key or key combination to bind * - command (Lisp): The Lisp command/function to execute * - prefix_name (string): Prefix modifier name * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Updates global editor state E (key_binds array) */ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) { /* * 3 arguments keybind command prefix */ const char *key_sequence = lisp_string(lisp_car(args)); args = lisp_cdr(args); // second argument Lisp func = lisp_car(args); E.key_binds = (struct keyBind_t *)realloc( E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t)); E.key_binds[E.number_of_keybinds - 1].key_sequence = (char *)malloc(50 * sizeof(char)); strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50); E.key_binds[E.number_of_keybinds - 1].command = func; // Third argument args = lisp_cdr(args); const char *prefix_name = lisp_string(lisp_car(args)); struct prefix_t prefix = find_prefix(prefix_name); E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id; return lisp_null(); } /** * @brief Lisp function to create a new terminal buffer * @details Creates a new terminal buffer and switches to it. * @param args Lisp arguments (ignored) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Updates global editor state E */ Lisp editorCreateTerminal(Lisp args, LispError *e, LispContext ctx) { int buffer_id = bufferCreateTerminal(); if (buffer_id >= 0) { bufferSwitch(buffer_id); } return lisp_null(); } /** * @brief Lisp function to move cursor in a specified direction * @details Moves the editor cursor up, down, left, or right based on direction string. * @param args Lisp list with one argument: direction string * - "u": up, "d": down, "r": right, "l": left * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return Lisp boolean indicating whether movement was valid * @note Updates global editor state E * @see editorMoveCursor() */ Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) { const char *direction = lisp_string(lisp_car(args)); int is_in = 0; switch (direction[0]) { case 'u': is_in = editorMoveCursor(ARROW_UP); break; case 'd': is_in = editorMoveCursor(ARROW_DOWN); break; case 'r': is_in = editorMoveCursor(ARROW_RIGHT); break; case 'l': is_in = editorMoveCursor(ARROW_LEFT); break; } fprintf(stderr, "move lisp %d\n", is_in); return lisp_make_bool(is_in); } /** * @brief Frees all dynamically allocated editor structures * @details Releases memory for prefix table, keybinds, filename, and all rows. * Called during shutdown to prevent memory leaks. * @note Updates global editor state E */ void free_structs(void) { int i, j; free(E.prefix); for (i = 0; i < E.number_of_keybinds; ++i) { free(E.key_binds[i].key_sequence); } free(E.key_binds); // free layout free(E.layout.panes); // Free buffers for (i = 0; i < E.number_of_buffer; ++i) { free(E.buffers[i].filename); for (j = 0; j < E.buffers[i].numrows; ++j) { free(E.buffers[i].row[j].render); free(E.buffers[i].row[j].chars); } free(E.buffers[i].row); } free(E.init_file_path); fclose(E.fd_init_file); } /** * @brief Lisp function to quit the editor * @details Closes editor with unsaved changes protection. Prompts user to confirm * quit after multiple attempts if file is dirty. Cleans up resources and restores terminal. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() (never returns on successful exit) * @note Calls exit(0) to terminate program * @note Updates quit_times_buffer counter * @see free_structs() */ Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) { if (E.dirty && E.quit_times_buffer > 0) { editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q " "another time to quit."); --E.quit_times_buffer; return lisp_null(); } free_structs(); write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); disableRawMode(); lisp_shutdown(E.ctx); exit(0); return lisp_null(); } /** * @brief Lisp function to save the current file * @details Wrapper around editorSave() for use in Lisp keybindings. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @see editorSave() */ Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx) { if (E.number_of_buffer >= 2) { splitScreenVertical(E.buffers[0].buffer_id, E.buffers[1].buffer_id); } else { editorSetStatusMessage("Need at least 2 buffers open"); } return lisp_null(); } Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) { editorSave(); return lisp_null(); } /** * @brief Lisp function to insert a new line at cursor * @details Wrapper around editorInsertNewLine() for use in Lisp keybindings. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @see editorInsertNewLine() */ Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) { bufferInsertNewLine(); return lisp_null(); } /** * @brief Lisp function to insert a tab (spaces) at cursor * @details Inserts TAB_LENGTH spaces at the current cursor position. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Uses E.constantes.TAB_LENGTH for indentation width */ Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) { for (int i = 0; ibuffer_id); if (active->cursor_y < buf->numrows) { active->cursor_x = buf->row[active->cursor_y].size; } return lisp_null(); } /** * @brief Lisp function to delete character before cursor * @details Wrapper around editorDelChar() for use in Lisp keybindings. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @see editorDelChar() */ Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) { bufferDelChar(); return lisp_null(); } /** * @brief Lisp function to move cursor up by one screen * @details Scrolls up one full screen height, moving cursor to top of visible area. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Updates global editor state E * @see editorMoveCursor() */ Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) { EditorPane *active = splitScreenGetActivePane(); active->cursor_y = active->row_offset; int times = E.screenrows; while (--times) { editorMoveCursor(ARROW_UP); } return lisp_null(); } /** * @brief Lisp function to move cursor down by one screen * @details Scrolls down one full screen height, moving cursor to bottom of visible area. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Updates global editor state E * @see editorMoveCursor() */ Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) { EditorPane *active = splitScreenGetActivePane(); struct buffer_t *buffer = bufferFindById(active->buffer_id); active->cursor_y = active->row_offset + E.screenrows - 1; if (active->cursor_y > buffer->numrows) { active->cursor_y = buffer->numrows; } int times = E.screenrows; while (--times) { editorMoveCursor(ARROW_DOWN); } return lisp_null(); } /** * @brief Lisp function to open a file * @details Prompts user for filename and opens the file for editing. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Updates global editor state E * @see editorOpen() * @see editorPrompt() */ Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) { char *filename = editorPrompt("Open : %s", getenv("PWD"), 1); if (filename){ // editorOpen(filename); EditorPane *active = splitScreenGetActivePane(); active->buffer_id = bufferCreate(filename); } free(filename); return lisp_null(); } /** * @brief Lisp function to insert a character * @details Extracts a character from Lisp string argument and inserts it at cursor. * @param args Lisp list with one string argument * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Uses first character of the string argument */ Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) { char c = lisp_string(lisp_car(args))[0]; bufferInsertChar(c); return lisp_null(); } /** * @brief Lisp function to load and execute a package * @details Loads a Lisp package from the user's packages directory * (~/.beluga/packages//init.lisp) and evaluates it. * @param args Lisp list with one argument: package name (string) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Package files must be valid Lisp code * @note Package directory defaults to ~/.beluga/packages/ */ Lisp addPackage(Lisp args, LispError *e, LispContext ctx) { const char *package_name = lisp_string(lisp_car(args)); fprintf(stderr, "%s\n", package_name); char *package_dir = (char *)calloc(256, sizeof(char)); FILE *fd_package = NULL; strcat(package_dir, getenv("HOME")); strcat(package_dir, "/.beluga/packages/"); strcat(package_dir, package_name); strcat(package_dir, "/init.lisp"); fprintf(stderr, "%s\n", package_dir); fd_package = fopen(package_dir, "r"); lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error, E.ctx); fclose(fd_package); free(package_dir); return lisp_null(); } /** * @brief Lisp function to delete the current row * @details Removes the line at the current cursor position. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Updates global editor state E * @see editorDelRow() */ Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) { EditorPane *active = splitScreenGetActivePane(); struct buffer_t *buffer = bufferFindById(active->buffer_id); bufferDelRow(buffer, active->cursor_y); return lisp_null(); } Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) { fprintf(stderr, "switch buffer\n"); if (E.number_of_buffer > 0) { EditorPane *active = splitScreenGetActivePane(); int next_idx = (active->buffer_id + 1) % E.number_of_buffer; active->buffer_id = next_idx; } return lisp_null(); } Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx) { splitScreenSwitchPane(); return lisp_null(); } Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx) { if (E.layout.num_panes - 1) { splitScreenUnify(); } return lisp_null(); } /** * @brief Lisp function to search for text * @details Wrapper around editorFind() for use in Lisp keybindings. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @see editorFind() */ Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx) { fprintf(stderr, "LispFind\n"); EditorPane *active = splitScreenGetActivePane(); struct buffer_t *buffer = bufferFindById(active->buffer_id); bufferFind(buffer); return lisp_null(); } /** * @brief Lisp function to read character at cursor * @details Returns the character at the current cursor position as a Lisp character. * Returns 'a' if at end of line. * @param args Lisp arguments (unused) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return Lisp character object representing the character at cursor */ Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) { Lisp returned_char; EditorPane *active = splitScreenGetActivePane(); struct buffer_t *buffer = bufferFindById(active->buffer_id); if (buffer->row[active->cursor_y].render[active->cursor_x] == 0) { returned_char = lisp_make_char('a'); } else { returned_char = lisp_make_char(buffer->row[active->cursor_y].render[active->cursor_x]); } return returned_char; } /** * @brief Lisp function to set the current prefix mode * @details Changes the editor's prefix state to a named prefix, affecting which * keybindings are active. Updates status message to show active prefix. * @param args Lisp list with one argument: prefix name (string) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Updates global editor state E (prefix_state) * @see find_prefix() */ Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) { /* * Set the prefix state of editor to the prefix in argument */ const char *prefix_name = lisp_string(lisp_car(args)); struct prefix_t prefix = find_prefix(prefix_name); E.prefix_state = prefix.prefix_id; editorSetStatusMessage("prefix %s", prefix.prefix_name); fprintf(stderr, "%s set\n", prefix_name); return lisp_null(); } /** * @brief Lisp function to define a new prefix modifier * @details Registers a named prefix modifier that can be used with keybindings * to create context-aware command sequences (e.g., Ctrl-X as a prefix). * @param args Lisp list with one argument: prefix name (string, max 64 chars) * @param e Error pointer for Lisp error handling * @param ctx Lisp context * @return lisp_null() * @note Updates global editor state E (prefix array) */ Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx) { E.prefix = (struct prefix_t *)realloc(E.prefix, (++(E.number_of_prefix) + 1) * sizeof(struct prefix_t)); E.prefix[E.number_of_prefix].prefix_id = E.number_of_prefix; strncpy(E.prefix[E.number_of_prefix].prefix_name, lisp_string(lisp_car(args)), 64); return lisp_null(); }