bc66e673fa
# Conflicts: # include/builtins.h # include/data.h # include/editor_op.h # include/file_io.h # include/input.h # include/row_op.h # install.sh # main.c # meson.build # src/builtins.c # src/editor_op.c # src/file_io.c # src/init.c # src/input.c # src/output.c # src/row_op.c
161 lines
4.6 KiB
C
161 lines
4.6 KiB
C
/**
|
|
* @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 <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
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);
|
|
}
|
|
|