304 lines
7.6 KiB
C
304 lines
7.6 KiB
C
/**
|
|
* @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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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);
|
|
}
|