/** * @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->rx = 0; active->row_offset = 0; active->col_offset = 0; for (int i = 0; i < buf->numrows; ++i) { free(buf->row[i].chars); free(buf->row[i].render); } 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 = 0; ssize_t line_len; 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; } 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; char *buf; int fd; if (buffer->filename == NULL) { buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1); if (buffer->filename == NULL) { editorSetStatusMessage("Save aborted"); return; } } buf = bufferRowsToString(buffer, &len); fd = open(buffer->filename, O_RDWR | O_CREAT, 0644); if (fd != -1) { if (ftruncate(fd, len) != -1) { if (write(fd, buf, len) == len) { close(fd); free(buf); E.dirty = 0; editorSetStatusMessage("%d bytes written to disk", len); return; } } close(fd); } free(buf); editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); } /** * @brief Searches for a string in the document * @details Prompts user for a search query, then searches forward from current * cursor position. Updates cursor position to the first match found. * @note Updates global editor state E (cursor position, row_offset) * @note Search is case-sensitive and operates on rendered line content * @note Searches begin from the line after current cursor position * @see editorPrompt() * @see editorRowRxToCx() */ void bufferFind(struct buffer_t *buf) { fprintf(stderr, "searching\n"); char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0); EditorPane *active = splitScreenGetActivePane(); if (query == NULL) return; int i; for (i = active->cursor_y + 1; i < buf->numrows; i++) { frow *row = &buf->row[i]; char *match = strstr(row->render, query); if (match) { active->cursor_y = i; active->cursor_x = bufferRowRxToCx(row, match - row->render); buf->row_offset = buf->numrows; break; } } free(query); }