/** * @file file_io.c * @brief File I/O operations module for the Beluga text editor * @details Handles file loading, saving, searching, and buffer management. * Provides functionality for opening/closing files, persisting changes to disk, * and searching for text patterns within the document. */ #include "../include/file_io.h" #include "../include/editor_op.h" #include "../include/input.h" #include "../include/buffer.h" #include "../include/data.h" #include "../include/split_screen.h" #include #include #include #include #include #include #include extern struct editorConfig E; /** * @brief Closes the current file and resets editor state * @details Clears all rows, resets cursor position, scroll offsets, and file * metadata. Does not prompt to save unsaved changes. * @note Updates global editor state E */ void editorCloseFile(void) { EditorPane *active = splitScreenGetActivePane(); struct buffer_t *buf = bufferFindById(active->buffer_id); active->cursor_x = 0; active->cursor_y = 0; active->x_offset = 0; active->y_offset = 0; for (int i = 0; i < buf->numrows; ++i) { free(buf->row[i].chars); } buf->numrows = 0; free(buf->row); buf->row = NULL; buf->dirty = 0; free(buf->filename); buf->filename = NULL; E.status_msg[0] = '\0'; E.status_msg_time = 0; } /** * @brief Opens a file for editing * @details Loads file content into editor rows, one line per row. If another * file is already open, it is closed first (without saving). File is opened in * a+ (read/append) mode to allow both reading and modification. * @param filename Path to the file to open (relative or absolute) * @note Updates global editor state E * @note Calls die() on file open failure * @note Newline characters are stripped from loaded lines * @see editorInsertRow() */ void editorOpen(struct buffer_t* buffer) { FILE *fp; fp = fopen(buffer->filename, "a+"); if (!fp) die("fopen"); char *line = NULL; size_t line_cap; ssize_t line_len; rewind(fp); while ((line_len = getline(&line, &line_cap, fp)) != -1) { while (line_len > 0 && (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) { --line_len; } appDebug("line %s\n", line); bufferInsertRow(buffer, buffer->numrows, line, line_len); free(line); line = NULL; } free(line); fclose(fp); E.dirty = 0; } /** * @brief Saves the current file to disk * @details Prompts for filename if not set, converts all rows to a buffer, * writes to disk using open/ftruncate/write, and updates dirty flag. * Displays status messages on success or failure. * @note Updates global editor state E (dirty flag) * @note If no filename is set, prompts user via editorPrompt() * @note Uses O_RDWR | O_CREAT with mode 0644 * @see editorRowsToString() */ void editorSave() { EditorPane *active = splitScreenGetActivePane(); struct buffer_t *buffer = bufferFindById(active->buffer_id); int len; int fd; if (buffer->filename == NULL) { buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1); if (buffer->filename == NULL) { editorSetStatusMessage("Save aborted"); return; } } fd = open(buffer->filename, O_RDWR | O_CREAT, 0644); if (fd != -1) { for (int i = 0; i < buffer->numrows; ++i) { len = strlen(buffer->row[i].chars); if (write(fd, buffer->row[i].chars, len) != len) { close(fd); editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); return; } write(fd, "\n", 1); } buffer->dirty = 0; close(fd); } editorSetStatusMessage("File saved"); }