/** * @file buffer.c * @brief Buffer management implementation for multiple open files */ #include "../include/buffer.h" #include "../include/file_io.h" #include "../include/editor_op.h" #include "../include/data.h" #include "include/split_screen.h" #include #include #include #include "include/input.h" extern struct editorConfig E; /** * @brief Finds a buffer by filename * @param filename The filename to search for * @return Buffer ID if found, -1 if not found */ static int bufferFindByFilename(const char* filename) { for (int i = 0; i < E.number_of_buffer; i++) { if (E.buffers[i].filename != NULL && strcmp(E.buffers[i].filename, filename) == 0) { return E.buffers[i].buffer_id; } } return -1; } /** * @brief Finds a buffer by ID * @param buffer_id The buffer ID to find * @return Pointer to buffer, NULL if not found */ struct buffer_t* bufferFindById(int buffer_id) { for (int i = 0; i < E.number_of_buffer; i++) { if (E.buffers[i].buffer_id == buffer_id) { return &E.buffers[i]; } } return NULL; } /** * @brief Creates a new buffer for a file * @details Allocates buffer slot, loads file content, saves buffer metadata. * If file is already open, switches to existing buffer instead. * @param filename Path to the file * @return Buffer ID on success, -1 on failure */ int bufferCreate(const char* filename) { // Check if file is already open const int existing_id = bufferFindByFilename(filename); if (existing_id != -1) { return bufferSwitch(existing_id); } // Check if we have space for more buffers if (E.number_of_buffer >= 64) { editorSetStatusMessage("Error: maximum buffers reached (64)"); return -1; } // Initialize new buffer struct buffer_t* new_buf = &E.buffers[E.number_of_buffer]; new_buf->buffer_id = E.number_of_buffer; new_buf->filename = strdup(filename); new_buf->type = FILE_BUFF; new_buf->state = READ_AND_WRITE; new_buf->x = 0; new_buf->y = 0; new_buf->dirty = 0; // New file starts clean // Load file content using existing editorOpen editorOpen(new_buf); E.number_of_buffer++; editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id); return new_buf->buffer_id; } /** * @brief Switches to a specific buffer by ID * @details Saves current buffer state, loads target buffer content and state. * @param buffer_id The buffer ID to switch to * @return 0 on success, -1 on failure */ int bufferSwitch(int buffer_id) { struct buffer_t* target = bufferFindById(buffer_id); if (target == NULL) { E.layout.panes[E.layout.active_pane].buffer_id = buffer_id; editorSetStatusMessage("Error: buffer not found"); return -1; } editorSetStatusMessage("Switched to: %s (buffer %d)", target->filename, buffer_id); return 0; } /** * @brief Closes a buffer by ID * @details Frees buffer resources and removes from buffer list. * Prompts to save unsaved changes. If closing current buffer, * switches to next available buffer. * @param buffer_id The buffer ID to close * @return 0 on success, -1 on failure */ int bufferClose(int buffer_id) { struct buffer_t* buf = bufferFindById(buffer_id); if (buf == NULL) { editorSetStatusMessage("Error: buffer not found"); return -1; } EditorPane* active = splitScreenGetActivePane(); // If this is the current buffer, find next buffer to switch to if (active->buffer_id == buffer_id) { int next_id = -1; // Try to switch to next buffer for (int i = 0; i < E.number_of_buffer; i++) { if (E.buffers[i].buffer_id != buffer_id) { next_id = E.buffers[i].buffer_id; break; } } if (next_id != -1) { bufferSwitch(next_id); } else { // No other buffers, clear editor editorCloseFile(); } } // Free buffer resources free(buf->filename); buf->filename = NULL; buf->buffer_id = -1; // Remove from buffer list by shifting for (int i = 0; i < E.number_of_buffer; i++) { if (E.buffers[i].buffer_id == buffer_id) { for (int j = i; j < E.number_of_buffer - 1; j++) { E.buffers[j] = E.buffers[j + 1]; } E.number_of_buffer--; break; } } editorSetStatusMessage("Closed buffer %d", buffer_id); return 0; } /** * @brief Gets the current active buffer * @return Pointer to current buffer_t, NULL if none */ struct buffer_t* bufferGetCurrent(void) { EditorPane* active = splitScreenGetActivePane(); return bufferFindById(active->buffer_id); } /** * @brief Lists all open buffers * @details Prints buffer information to status message * @return Number of open buffers */ int bufferListAll(void) { if (E.number_of_buffer == 0) { editorSetStatusMessage("No buffers open"); return 0; } char buf[256] = ""; int offset = 0; EditorPane* active = splitScreenGetActivePane(); for (int i = 0; i < E.number_of_buffer; i++) { struct buffer_t* b = &E.buffers[i]; char marker = (b->buffer_id == active->buffer_id) ? '*' : ' '; offset += snprintf(&buf[offset], sizeof(buf) - offset, "%c%d:%s ", marker, b->buffer_id, b->filename ? b->filename : "[No Name]"); } editorSetStatusMessage("Buffers: %s", buf); return E.number_of_buffer; } /** * @brief Saves current buffer to disk * @return 0 on success, -1 on failure */ int bufferSave(void) { EditorPane* active = splitScreenGetActivePane(); if (active->buffer_id == -1) { editorSetStatusMessage("Error: no buffer active"); return -1; } editorSave(); return 0; } /** * @brief Saves all buffers to disk * @details Iterates through all buffers, saves each one * @return 0 on success, -1 on failure */ int bufferSaveAll(void) { int saved = 0; int failed = 0; for (int i = 0; i < E.number_of_buffer; i++) { if (bufferSwitch(E.buffers[i].buffer_id) == 0) { if (E.dirty && bufferSave() == 0) { saved++; } } else { failed++; } } editorSetStatusMessage("Saved %d buffers (%d failed)", saved, failed); return (failed == 0) ? 0 : -1; } /** * @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) { appDebug("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++) { row_t* row = &buf->row[i]; char* match = strstr(row->chars, query); if (match) { active->cursor_y = i; buf->y = buf->numrows; break; } } free(query); }