From 557fc8894ae291aff156360314256f0a1cc9c24d Mon Sep 17 00:00:00 2001 From: Arthur Barraux Date: Sat, 24 Jan 2026 19:29:46 +0100 Subject: [PATCH] Adding splitting screen and buffer switching --- assets/beluga.txt | 2 +- config/init.lisp | 6 +- include/buffer.h | 52 ++++++++- include/builtins.h | 60 +++++----- include/data.h | 64 ++++++++--- include/editor_op.h | 6 +- include/file_io.h | 6 +- include/output.h | 4 - include/row_op.h | 18 +-- include/split_screen.h | 62 +++++++++++ install.sh | 3 +- main.c | 13 ++- meson.build | 4 +- src/buffer.c | 246 ++++++++++++++++++++++++++++++++++++++--- src/builtins.c | 101 +++++++++++++---- src/editor_op.c | 65 ++++++----- src/file_io.c | 200 ++++++++++++++++----------------- src/init.c | 49 ++++---- src/input.c | 61 +++++----- src/output.c | 239 +++++++++++++++++++++++++++++---------- src/row_op.c | 75 ++++++------- src/split_screen.c | 218 ++++++++++++++++++++++++++++++++++++ 22 files changed, 1170 insertions(+), 384 deletions(-) create mode 100644 include/split_screen.h create mode 100644 src/split_screen.c diff --git a/assets/beluga.txt b/assets/beluga.txt index 4eee748..0d2a3fe 100644 --- a/assets/beluga.txt +++ b/assets/beluga.txt @@ -9,7 +9,7 @@ #----------------------------------------------------------## #=--------------------------------------------------------------## +--------------------------+@#-%*-----------------------------------#* - +--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.2.1 + +--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.3 *-=-------------------------#@@*---------------------------------------=% %*#==--------------------------------------------------------------------+# ----- KEY-BINDS ----- *%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave diff --git a/config/init.lisp b/config/init.lisp index b314c55..8670304 100644 --- a/config/init.lisp +++ b/config/init.lisp @@ -81,5 +81,9 @@ (map-key "f" editor-open-file "user") (map-key "TAB" editor-insert-tab "no-prefix") (map-key "CTRL-k" editor-del-row "no-prefix") -(map-key "CTRL-s" editor-find "no-prefix") +(map-key "CTRL-s" buffer-find "no-prefix") (map-key "CTRL-r" editor-move-to-end-of-word "no-prefix") +(map-key "\"" editor-split-screen-vertical "user") +(map-key "o" editor-switch-next-pane "user") +(map-key "*" editor-unify-panes "user") + diff --git a/include/buffer.h b/include/buffer.h index 6951e13..02a4600 100644 --- a/include/buffer.h +++ b/include/buffer.h @@ -1,8 +1,58 @@ +/** + * @file buffer.h + * @brief Buffer management for handling multiple open files + */ + #ifndef BUFFER_H_ #define BUFFER_H_ #include "data.h" -int new_buffer(enum buffer_type type, char * filename, int width, int height); +/** + * @brief Creates a new buffer for a file + * @param filename Path to the file + * @return Buffer ID on success, -1 on failure + */ +int bufferCreate(const char *filename); + +/** + * @brief Switches to a specific buffer by ID + * @param buffer_id The buffer ID to switch to + * @return 0 on success, -1 on failure + */ +int bufferSwitch(int buffer_id); + +struct buffer_t *bufferFindById(int buffer_id); + +/** + * @brief Closes a buffer by ID + * @param buffer_id The buffer ID to close + * @return 0 on success, -1 on failure + */ +int bufferClose(int buffer_id); + +/** + * @brief Gets the current active buffer + * @return Pointer to current buffer_t, NULL if none + */ +struct buffer_t *bufferGetCurrent(void); + +/** + * @brief Lists all open buffers + * @return Number of open buffers + */ +int bufferListAll(void); + +/** + * @brief Saves current buffer to disk + * @return 0 on success, -1 on failure + */ +int bufferSave(void); + +/** + * @brief Saves all buffers to disk + * @return 0 on success, -1 on failure + */ +int bufferSaveAll(void); #endif diff --git a/include/builtins.h b/include/builtins.h index aea4fd5..099ba1c 100644 --- a/include/builtins.h +++ b/include/builtins.h @@ -3,45 +3,53 @@ #include "lisp.h" +// Mouvement + Lisp moveCursor(Lisp args, LispError *e, LispContext ctx); - -Lisp mapKey(Lisp args, LispError *e, LispContext ctx); - -void registerBuiltin(char * key_sequence, LispCFunc f); - -Lisp editorQuit(Lisp args, LispError *e, LispContext ctx); - -Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx); - -Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx); - Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx); - Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx); - -Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx); - -Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx); - Lisp editorMoveCursorPageUp(Lisp args, LispError* e, LispContext ctx); - Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx); -Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx); +// Text editing +Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx); +Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx); +Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx); Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx); - -Lisp addPackage(Lisp args, LispError *e, LispContext ctx); - Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx); - -Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx); - Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx); -Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx); +// Editor +Lisp editorQuit(Lisp args, LispError *e, LispContext ctx); +Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx); +Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx); Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx); +Lisp mapKey(Lisp args, LispError *e, LispContext ctx); +void registerBuiltin(char * key_sequence, LispCFunc f); +Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx); +Lisp addPackage(Lisp args, LispError *e, LispContext ctx); + + +// Buffer + +Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx); +Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx); + + +// Pane + +Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx); +Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx); +Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx); + + + + + + +// Other void free_structs(void); diff --git a/include/data.h b/include/data.h index 0accf9d..9379214 100644 --- a/include/data.h +++ b/include/data.h @@ -13,12 +13,50 @@ * \param * */ -typedef struct erow { +typedef struct frow { int size; /**< Size of the line */ int rsize; /**< Size of the render line */ char *chars; /**< Characters of the line */ char *render; /**< The actual line we will print */ -} erow; +} frow; + + +/** + * @brief Split modes for screen layout + */ +typedef enum { + SPLIT_NONE = 0, // Single buffer fullscreen + SPLIT_VERTICAL, // Left-right split + SPLIT_HORIZONTAL // Top-bottom split +} SplitMode; + +/** + * @brief Represents an editor viewport/pane + */ +typedef struct { + int buffer_id; // Which buffer this pane displays + int start_row; // Starting row on screen + int start_col; // Starting column on screen + int height; // Height of this pane + int width; // Width of this pane + int cursor_x; // Local cursor x in this pane + int cursor_y; // Local cursor y in this pane + int rx, ry; + int row_offset; // Scroll offset for rows + int col_offset; // Scroll offset for columns + int is_active; // Is this pane currently active +} EditorPane; + +/** + * @brief Screen layout manager + */ +typedef struct { + SplitMode mode; + EditorPane *panes; + int num_panes; + int active_pane; // Index of active pane +} ScreenLayout; + typedef struct theme { char *BACKGROUND_COLOR; @@ -31,7 +69,7 @@ typedef struct theme { } theme_t; -enum editorStatus_e { +enum bufferStatus_e { IDLE, READ_ONLY, READ_AND_WRITE, @@ -60,8 +98,14 @@ struct buffer_t { enum buffer_type type; int buffer_id; int width, height; - int x, y; /**< Position of the buffer*/ + int x, y; /**< Position in the buffer (cursor) */ + frow *row; + int numrows; char *filename; + enum bufferStatus_e state; + int dirty; /**< Has this buffer been modified since last save */ + int row_offset; /**< Scroll offset for rows in this buffer */ + int col_offset; /**< Scroll offset for columns in this buffer */ }; /** @@ -69,17 +113,12 @@ struct buffer_t { * \brief Containing our editor state. */ struct editorConfig { - int cursor_x, cursor_y; /**< Cursor position */ - int rx; /**< Position in the render*/ - int row_offset; /**< Position scroll of lines */ - int col_offset; /**< Position scroll of colomns*/ int screenrows; /**< Terminal height*/ int screencols; /**< Terminal width*/ - int numrows; /**< Number of rows contained */ - erow *row; /**< Store all the rows printed */ + + ScreenLayout layout; + int dirty; - char *filename; - enum editorStatus_e state; int prefix_state; char status_msg[80]; time_t status_msg_time; @@ -102,7 +141,6 @@ struct editorConfig { int number_of_prefix; struct buffer_t buffers[64]; - struct buffer_t ***screen_layout; /**< Which buffer is the current cell*/ int number_of_buffer; theme_t theme; diff --git a/include/editor_op.h b/include/editor_op.h index cfe88e0..db6863b 100644 --- a/include/editor_op.h +++ b/include/editor_op.h @@ -2,11 +2,11 @@ #define EDITOR_OP_H_ #include "data.h" -void editorInsertChar(int c); +void bufferInsertChar(int c); -void editorInsertNewLine(); +void bufferInsertNewLine(); -void editorDelChar(); +void bufferDelChar(); void editorSetStatusMessage(const char *fmt, ...); diff --git a/include/file_io.h b/include/file_io.h index f8ee453..7095570 100644 --- a/include/file_io.h +++ b/include/file_io.h @@ -8,15 +8,15 @@ #include #include -char *editorRowsToString(int *buffer_len); +char *bufferRowsToString(struct buffer_t *buf,int *buffer_len); void editorCloseFile(void); -void editorOpen(char *filename); +void editorOpen(struct buffer_t *buffer); void editorSave(); -void editorFind(); +void bufferFind(struct buffer_t *buf); #endif // FILE_IO_H_ diff --git a/include/output.h b/include/output.h index 6085b84..5539a8c 100644 --- a/include/output.h +++ b/include/output.h @@ -1,11 +1,7 @@ #ifndef OUTPUT_H_ #define OUTPUT_H_ -#include "append_buffer.h" #include "data.h" -#include "define.h" -#include "row_op.h" -#include #include /** diff --git a/include/row_op.h b/include/row_op.h index 80973ad..859af50 100644 --- a/include/row_op.h +++ b/include/row_op.h @@ -8,22 +8,22 @@ #include #include -int editorRowCxToRx(erow *row, int cursor_x); +int bufferRowCxToRx(frow *row, int cursor_x); -int editorRowRxToCx(erow *row, int rx); +int bufferRowRxToCx(frow *row, int rx); -void editorUpdateRow(erow *row); +void bufferUpdatfrow(frow *row); -void editorInsertRow(int at, char *s, size_t len); +void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len); -void editorFreeRow(erow *row); +void bufferFrefrow(frow *row); -void editorDelRow(int at); +void bufferDelRow(struct buffer_t *buffer, int at); -void editorRowInsertChar(erow *row, int at, int c); +void bufferRowInsertChar(struct buffer_t *buffer, frow *row, int at, int c); -void editorRowAppendString(erow *row, char *s, size_t len); +void bufferRowAppendString(struct buffer_t *buffer, frow *row, char *s, size_t len); -void editorRowDelchar(erow *row, int at); +void bufferRowDelchar(struct buffer_t *buffer, frow *row, int at); #endif // ROW_OP_H_ diff --git a/include/split_screen.h b/include/split_screen.h new file mode 100644 index 0000000..da7e555 --- /dev/null +++ b/include/split_screen.h @@ -0,0 +1,62 @@ +/** + * @file split_screen.h + * @brief Split screen management for displaying multiple buffers + */ + +#ifndef SPLIT_SCREEN_H_ +#define SPLIT_SCREEN_H_ + +#include "data.h" + +/** + * @brief Initializes split screen system + */ +void splitScreenInit(void); + +/** + * @brief Splits screen vertically (left-right) + * @param buffer_id_left Buffer ID for left pane + * @param buffer_id_right Buffer ID for right pane + * @return 0 on success, -1 on failure + */ +int splitScreenVertical(int buffer_id_left, int buffer_id_right); + +/** + * @brief Splits screen horizontally (top-bottom) + * @param buffer_id_top Buffer ID for top pane + * @param buffer_id_bottom Buffer ID for bottom pane + * @return 0 on success, -1 on failure + */ +int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom); + +/** + * @brief Returns to single buffer fullscreen + */ +void splitScreenUnify(void); + +/** + * @brief Switches active pane (focus moves between splits) + * @return 0 on success, -1 on failure + */ +int splitScreenSwitchPane(void); + +/** + * @brief Updates the active pane's buffer + * @param buffer_id New buffer ID for active pane + * @return 0 on success, -1 on failure + */ +int splitScreenSetPaneBuffer(int buffer_id); + +/** + * @brief Gets current screen layout + * @return Pointer to current ScreenLayout + */ +ScreenLayout *splitScreenGetLayout(void); + +/** + * @brief Gets active pane + * @return Pointer to active EditorPane + */ +EditorPane *splitScreenGetActivePane(void); + +#endif diff --git a/install.sh b/install.sh index e4c3c4c..dc4d838 100755 --- a/install.sh +++ b/install.sh @@ -16,7 +16,8 @@ fi echo "Create config files ..." mkdir -pv ~/.beluga/ -cp -rv ./assets/ ~/.beluga/ +mkdir -pv ~/.beluga/assets/ +cp -rv ./assets/beluga.txt ~/.beluga/assets/ mkdir -pv ~/.beluga/packages/ mkdir -pv ~/.beluga/config/ diff --git a/main.c b/main.c index 997b076..9bd5683 100644 --- a/main.c +++ b/main.c @@ -5,6 +5,8 @@ * interactions. \version 0.1 \date 21 septembre 2024 */ +#include "include/buffer.h" +#include "include/split_screen.h" #include #include #include @@ -30,17 +32,18 @@ int main(int argc, char *argv[]) { enableRawMode(); initEditor(); if (argc >= 2) { - E.state = READ_AND_WRITE; - editorOpen(argv[1]); + EditorPane *active = splitScreenGetActivePane(); + active->buffer_id = bufferCreate(argv[1]); } else { strcat(splash_screen, getenv("HOME")); strcat(splash_screen, "/.beluga/assets/beluga.txt"); fprintf(stderr, "%s\n", splash_screen); - editorOpen(splash_screen); + EditorPane *active = splitScreenGetActivePane(); + active->buffer_id = bufferCreate(splash_screen); } - free(splash_screen); + //free(splash_screen); - editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-q = quit"); + editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit"); while (1) { diff --git a/meson.build b/meson.build index 89781e3..cdc101d 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('beluga', 'c', - version : '1.1', + version : '2.3', default_options : [ 'c_std=none', ] @@ -21,6 +21,8 @@ src_files = files( 'src/row_op.c', 'src/terminal.c', 'src/builtins.c', + 'src/buffer.c', + 'src/split_screen.c' ) # Executable diff --git a/src/buffer.c b/src/buffer.c index 573a9c9..0392256 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,20 +1,236 @@ +/** + * @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 -int new_buffer(enum buffer_type type, char *filename, int x, int y, int width, - int height) { - // Create a new buffer +extern struct editorConfig E; - struct buffer_t *buffer = &E.buffers[E.number_of_buffer]; - buffer->type = type; - - if (type == FILE_BUFF) { - strcpy(buffer->filename, filename); - } - buffer->width = width, buffer->height = height; - buffer->x = x, buffer->y = y; - - - - return 1; +/** + * @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 + 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->row_offset = 0; + new_buf->col_offset = 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; } diff --git a/src/builtins.c b/src/builtins.c index e4abc96..f4222ee 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -12,6 +12,8 @@ #include "../include/file_io.h" #include "../include/input.h" #include "../include/row_op.h" +#include "../include/buffer.h" +#include "../include/split_screen.h" #include #include @@ -115,18 +117,25 @@ Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) { * @note Updates global editor state E */ void free_structs(void) { - int i; + int i, j; free(E.prefix); for (i = 0; i < E.number_of_keybinds; ++i) { free(E.key_binds[i].key_sequence); } free(E.key_binds); - free(E.filename); - for (i = 0; i < E.numrows; ++i) { - free(E.row[i].render); - free(E.row[i].chars); + // free layout + free(E.layout.panes); + + // Free buffers + for (i = 0; i < E.number_of_buffer; ++i) { + free(E.buffers[i].filename); + for (j = 0; j < E.buffers[i].numrows; ++j) { + free(E.buffers[i].row[j].render); + free(E.buffers[i].row[j].chars); + } + free(E.buffers[i].row); } - free(E.row); + free(E.init_file_path); fclose(E.fd_init_file); @@ -170,6 +179,16 @@ Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) { * @return lisp_null() * @see editorSave() */ + +Lisp l_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx) { + if (E.number_of_buffer >= 2) { + splitScreenVertical(E.buffers[0].buffer_id, E.buffers[1].buffer_id); + } else { + editorSetStatusMessage("Need at least 2 buffers open"); + } + return lisp_null(); +} + Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) { editorSave(); @@ -188,7 +207,7 @@ Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) { */ Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) { - editorInsertNewLine(); + bufferInsertNewLine(); return lisp_null(); } @@ -205,7 +224,7 @@ Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) { Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) { for (int i = 0; ibuffer_id); + if (active->cursor_y < buf->numrows) { + active->cursor_x = buf->row[active->cursor_y].size; } return lisp_null(); } @@ -251,7 +272,7 @@ Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) { * @see editorDelChar() */ Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) { - editorDelChar(); + bufferDelChar(); return lisp_null(); } @@ -266,7 +287,8 @@ Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) { * @see editorMoveCursor() */ Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) { - E.cursor_y = E.row_offset; + EditorPane *active = splitScreenGetActivePane(); + active->cursor_y = active->row_offset; int times = E.screenrows; while (--times) { editorMoveCursor(ARROW_UP); @@ -285,9 +307,11 @@ Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) { * @see editorMoveCursor() */ Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) { - E.cursor_y = E.row_offset + E.screenrows - 1; - if (E.cursor_y > E.numrows) { - E.cursor_y = E.numrows; + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buffer = bufferFindById(active->buffer_id); + active->cursor_y = active->row_offset + E.screenrows - 1; + if (active->cursor_y > buffer->numrows) { + active->cursor_y = buffer->numrows; } int times = E.screenrows; while (--times) { @@ -311,7 +335,9 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) { Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) { char *filename = editorPrompt("Open : %s", getenv("PWD"), 1); if (filename){ - editorOpen(filename); + // editorOpen(filename); + EditorPane *active = splitScreenGetActivePane(); + active->buffer_id = bufferCreate(filename); } free(filename); @@ -330,7 +356,7 @@ Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) { */ Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) { char c = lisp_string(lisp_car(args))[0]; - editorInsertChar(c); + bufferInsertChar(c); return lisp_null(); } @@ -375,7 +401,32 @@ Lisp addPackage(Lisp args, LispError *e, LispContext ctx) { * @see editorDelRow() */ Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) { - editorDelRow(E.cursor_y); + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buffer = bufferFindById(active->buffer_id); + bufferDelRow(buffer, active->cursor_y); + return lisp_null(); +} + +Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) { + fprintf(stderr, "switch buffer\n"); + if (E.number_of_buffer > 0) { + EditorPane *active = splitScreenGetActivePane(); + int next_idx = (active->buffer_id + 1) % E.number_of_buffer; + active->buffer_id = next_idx; + } + + return lisp_null(); +} + +Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx) { + splitScreenSwitchPane(); + return lisp_null(); +} + +Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx) { + if (E.layout.num_panes - 1) { + splitScreenUnify(); + } return lisp_null(); } @@ -388,9 +439,11 @@ Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) { * @return lisp_null() * @see editorFind() */ -Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) { +Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx) { fprintf(stderr, "LispFind\n"); - editorFind(); + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buffer = bufferFindById(active->buffer_id); + bufferFind(buffer); return lisp_null(); } @@ -405,11 +458,13 @@ Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) { */ Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) { Lisp returned_char; - if (E.row[E.cursor_y].render[E.cursor_x] == 0) { + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buffer = bufferFindById(active->buffer_id); + if (buffer->row[active->cursor_y].render[active->cursor_x] == 0) { returned_char = lisp_make_char('a'); } else { - returned_char = lisp_make_char(E.row[E.cursor_y].render[E.cursor_x]); + returned_char = lisp_make_char(buffer->row[active->cursor_y].render[active->cursor_x]); } return returned_char; } diff --git a/src/editor_op.c b/src/editor_op.c index 2a35642..875e5b1 100644 --- a/src/editor_op.c +++ b/src/editor_op.c @@ -2,6 +2,9 @@ #include "../include/editor_op.h" #include "../include/row_op.h" +#include "include/buffer.h" +#include "include/data.h" +#include "include/split_screen.h" extern struct editorConfig E; @@ -25,49 +28,55 @@ void editorSetStatusMessage(const char *fmt, ...) { } -void editorInsertChar(int c) { - if (E.cursor_y == E.numrows) { - editorInsertRow(E.numrows, "", 0); +void bufferInsertChar(int c) { + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buf = bufferFindById(active->buffer_id); + if (active->cursor_y == buf->numrows) { + bufferInsertRow(buf, buf->numrows, "", 0); } - editorRowInsertChar(&E.row[E.cursor_y], E.cursor_x, c); - E.cursor_x++; + bufferRowInsertChar(buf, &buf->row[active->cursor_y], active->cursor_x, c); + active->cursor_x++; } -void editorInsertNewLine() { +void bufferInsertNewLine() { /* * Add new line and place the cursor at the beginning of it */ fprintf(stderr, "Inserting new line\n"); - erow *row; - if (!E.cursor_x) { - editorInsertRow(E.cursor_y, "", 0); + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buf = bufferFindById(active->buffer_id); + frow *row; + if (!active->cursor_x) { + bufferInsertRow(buf, active->cursor_y, "", 0); } else { - row = &E.row[E.cursor_y]; - editorInsertRow(E.cursor_y + 1, &row->chars[E.cursor_x], - row->size - E.cursor_x); - row = &E.row[E.cursor_y]; - row->size = E.cursor_x; + row = &buf->row[active->cursor_y]; + bufferInsertRow(buf, active->cursor_y + 1, &row->chars[active->cursor_x], + row->size - active->cursor_x); + row = &buf->row[active->cursor_y]; + row->size = active->cursor_x; row->chars[row->size] = '\0'; - editorUpdateRow(row); + bufferUpdatfrow(row); } - ++E.cursor_y; - E.cursor_x = 0; + ++active->cursor_y; + active->cursor_x = 0; fprintf(stderr, "Insert new line done\n"); } -void editorDelChar() { - erow *row; - if (E.cursor_y == E.numrows || !(E.cursor_x || E.cursor_y)) { +void bufferDelChar() { + frow *row; + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buf = bufferFindById(active->buffer_id); + if (active->cursor_y == buf->numrows || !(active->cursor_x || active->cursor_y)) { return; } - row = &E.row[E.cursor_y]; - if (E.cursor_x > 0) { - editorRowDelchar(row, E.cursor_x - 1); - --E.cursor_x; + row = &buf->row[active->cursor_y]; + if (active->cursor_x > 0) { + bufferRowDelchar(buf, row, active->cursor_x - 1); + --active->cursor_x; } else { - E.cursor_x = E.row[E.cursor_y - 1].size; - editorRowAppendString(&E.row[E.cursor_y - 1], row->chars, row->size); - editorDelRow(E.cursor_y); - --E.cursor_y; + active->cursor_x = buf->row[active->cursor_y - 1].size; + bufferRowAppendString(buf, &buf->row[active->cursor_y - 1], row->chars, row->size); + bufferDelRow(buf, active->cursor_y); + --active->cursor_y; } } diff --git a/src/file_io.c b/src/file_io.c index 50de430..cefdf97 100644 --- a/src/file_io.c +++ b/src/file_io.c @@ -7,8 +7,11 @@ */ #include "../include/file_io.h" -#include "../include/input.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 @@ -31,96 +34,90 @@ extern struct editorConfig E; * Rows are separated by newline characters. * @note Caller is responsible for freeing the returned buffer */ -char *editorRowsToString(int *buffer_len) { - int tot_len = 0; - int j; - char *buf; - char *p; +char *bufferRowsToString(struct buffer_t *buf, int *buffer_len) { + int tot_len = 0; + int j; + char *buffer; + char *p; - for (j = 0; j < E.numrows; ++j) { - tot_len += E.row[j].size + 1; - } - *buffer_len = tot_len; - buf = malloc(tot_len); - p = buf; - for (j = 0; j < E.numrows; ++j) { - memcpy(p, E.row[j].chars, E.row[j].size); - p += E.row[j].size; - *p = '\n'; - p++; - } + for (j = 0; j < buf->numrows; ++j) { + tot_len += buf->row[j].size + 1; + } + *buffer_len = tot_len; + buffer = malloc(tot_len); + p = buffer; + for (j = 0; j < buf->numrows; ++j) { + memcpy(p, buf->row[j].chars, buf->row[j].size); + p += buf->row[j].size; + *p = '\n'; + p++; + } - return buf; + return buffer; } /** * @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. + * @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) { - E.cursor_x = 0; - E.cursor_y = 0; - E.rx = 0; - E.row_offset = 0; - E.col_offset = 0; - for (int i = 0; i < E.numrows; ++i) { - free(E.row[i].chars); - free(E.row[i].render); - } - E.numrows = 0; - free(E.row); - E.row = NULL; - E.dirty = 0; - free(E.filename); - E.filename = NULL; - E.status_msg[0] = '\0'; - E.status_msg_time = 0; + 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. + * @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(char *filename) { - FILE *fp; +void editorOpen(struct buffer_t* buffer) { + FILE *fp; - // Test if a file is already open - if (E.filename != NULL) { - editorCloseFile(); - E.state = READ_AND_WRITE; - } - - E.filename = strdup(filename); - - fp = fopen(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; - } - editorInsertRow(E.numrows, line, line_len); - free(line); - line = NULL; + 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); - fclose(fp); - E.dirty = 0; + line = NULL; + } + free(line); + fclose(fp); + E.dirty = 0; } /** @@ -134,32 +131,34 @@ void editorOpen(char *filename) { * @see editorRowsToString() */ void editorSave() { - int len; - char *buf; - int fd; - if (E.filename == NULL) { - E.filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1); - if (E.filename == NULL) { - editorSetStatusMessage("Save aborted"); - return; - } + 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 = editorRowsToString(&len); - fd = open(E.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; - } - } + } + 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; + } } - free(buf); - editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); + close(fd); + } + free(buf); + editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); } /** @@ -172,18 +171,21 @@ void editorSave() { * @see editorPrompt() * @see editorRowRxToCx() */ -void editorFind() { +void bufferFind(struct buffer_t *buf) { fprintf(stderr, "searching\n"); char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0); - if (query == NULL) return; + EditorPane *active = splitScreenGetActivePane(); + + if (query == NULL) + return; int i; - for (i = E.cursor_y + 1; i < E.numrows; i++) { - erow *row = &E.row[i]; + for (i = active->cursor_y + 1; i < buf->numrows; i++) { + frow *row = &buf->row[i]; char *match = strstr(row->render, query); if (match) { - E.cursor_y = i; - E.cursor_x = editorRowRxToCx(row, match - row->render); - E.row_offset = E.numrows; + active->cursor_y = i; + active->cursor_x = bufferRowRxToCx(row, match - row->render); + buf->row_offset = buf->numrows; break; } } diff --git a/src/init.c b/src/init.c index ac71e59..eb33325 100644 --- a/src/init.c +++ b/src/init.c @@ -3,6 +3,7 @@ #include "../include/color.h" #include "../include/data.h" #include "../include/terminal.h" +#include "include/split_screen.h" #include #include #include @@ -34,11 +35,15 @@ void initBuiltins() { registerBuiltin("editor-insert-char", editorPrintC); registerBuiltin("add-package", addPackage); registerBuiltin("editor-del-row", editorDelRow_L); - registerBuiltin("editor-find", editorFind_L); + registerBuiltin("buffer-find", bufferFind_L); registerBuiltin("editor-read-char", editorReadChar_L); registerBuiltin("add-prefix", editorPrefix); registerBuiltin("editor-set-prefix", editorSetPrefix); registerBuiltin("editor-insert-tab", l_editorInserTab); + registerBuiltin("editor-switch-next-buffer", editorSwitchNextBuffer); + registerBuiltin("editor-split-screen-vertical", l_editorSplitScreenVertical); + registerBuiltin("editor-switch-next-pane", editorSwitchNextPane); + registerBuiltin("editor-unify-panes", editorUnifiedPanes); } void initConfig() { @@ -80,37 +85,29 @@ void init_theme() { } void initEditor() { - // Init graphics variables - E.cursor_x = 0; - E.cursor_y = 0; - E.rx = 0; - E.row_offset = 0; - E.col_offset = 0; - E.numrows = 0; - E.row = NULL; - // File relative variables - E.dirty = 0; - E.filename = NULL; - E.init_file_path = (char *)calloc(256, sizeof(char)); - strcat(E.init_file_path, getenv("HOME")); - strcat(E.init_file_path, "/.beluga/config/init.lisp"); - E.fd_init_file = fopen(E.init_file_path, "r"); - E.state = READ_ONLY; - // Status bar - E.status_msg[0] = '\0'; - E.status_msg_time = 0; if (getWindowSize(&E.screenrows, &E.screencols) == -1) { die("getWindowSize"); } E.screenrows -= 2; - E.screen_layout = - (struct buffer_t ***)malloc(E.screenrows * sizeof(struct buffer_t **)); - for (int i = 0; i < E.screenrows; ++i) { - E.screen_layout[i] = - (struct buffer_t **)malloc(E.screencols * sizeof(struct buffer_t *)); - } + + // Init graphics variables + splitScreenInit(); + EditorPane *active = splitScreenGetActivePane(); + active->cursor_x = 0; + active->cursor_y = 0; + active->rx = 0; + active->row_offset = 0; + active->col_offset = 0; + E.init_file_path = (char *)calloc(256, sizeof(char)); + strcat(E.init_file_path, getenv("HOME")); + strcat(E.init_file_path, "/.beluga/config/init.lisp"); + E.fd_init_file = fopen(E.init_file_path, "r"); + + // Status bar + E.status_msg[0] = '\0'; + E.status_msg_time = 0; // Key binds E.number_of_keybinds = 0; diff --git a/src/input.c b/src/input.c index 8843394..69decfb 100644 --- a/src/input.c +++ b/src/input.c @@ -2,6 +2,9 @@ #include "../include/define.h" #include "../include/editor_op.h" #include "../include/output.h" +#include "include/buffer.h" +#include "include/data.h" +#include "include/split_screen.h" #include #include #include @@ -42,7 +45,8 @@ const char *file_completion(const char *path) { // path is a directory if (path[strlen(path) - 1] == '/') { - return path; + fprintf(stderr, "[FILE COMP] is dir\n"); + strncpy(directory, path, 128); } // Find dir name @@ -56,12 +60,13 @@ const char *file_completion(const char *path) { predict[predict_len] = '\0'; fprintf(stderr, "%s %s\n", directory, predict); } else { - return NULL; + fprintf(stderr, "[FILE COMP] dir not found\n"); + return strdup(path); } dir = opendir(directory); if (!dir) - return NULL; + return strdup(path); while ((entry = readdir(dir)) != NULL) { if (strncmp(entry->d_name, predict, predict_len) == 0) { @@ -82,7 +87,8 @@ const char *file_completion(const char *path) { closedir(dir); dir = NULL; free(entry); - return NULL; + fprintf(stderr, "[FILE COMP] no entries\n"); + return strdup(path); } /** @@ -243,43 +249,44 @@ char *key_to_string(int key) { * @note Updates global editor state E */ int editorMoveCursor(int key) { - erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y]; + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buf = bufferFindById(active->buffer_id); + frow *row = (active->cursor_y + active->row_offset >= buf->numrows) ? NULL : &buf->row[active->cursor_y + active->row_offset]; int row_len; + int x = active->cursor_x + active->col_offset; + int y = active->cursor_y + active->row_offset; switch (key) { case ARROW_RIGHT: - if (row && E.cursor_x < row->size) { - ++E.cursor_x; - } else if (row && E.cursor_x == row->size) { - E.cursor_y++; - E.cursor_x = 0; + if (row && x < row->size) { + active->cursor_x++; + + } else if (row && y == row->size) { + active->cursor_y++; + active->cursor_x = 0; } break; case ARROW_DOWN: - if (E.cursor_y < E.numrows) { - ++E.cursor_y; + if (y < buf->numrows) { + + active->cursor_y++; } break; case ARROW_UP: - if (E.cursor_y != 0) { - --E.cursor_y; + if (y != 0) { + --active->cursor_y; } break; case ARROW_LEFT: - if (E.cursor_x != 0) { - --E.cursor_x; - } else if (E.cursor_y > 0) { - --E.cursor_y; - E.cursor_x = E.row[E.cursor_y].size; + if (x != 0) { + --active->cursor_x; + } else if (y > 0) { + --active->cursor_y; + active->cursor_x = buf->row[y].size; } break; } - - row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y]; - row_len = row ? row->size : 0; - if (E.cursor_x > row_len) { - E.cursor_x = row_len; - return 0; - } + + return 1; } @@ -327,6 +334,6 @@ void editorProcessKeypress() { if (executeKeyBind(key_to_string(c))) { return; } - editorInsertChar(c); + bufferInsertChar(c); E.quit_times_buffer = E.constantes.QUIT_TIMES; } diff --git a/src/output.c b/src/output.c index c7074b4..343da98 100644 --- a/src/output.c +++ b/src/output.c @@ -7,6 +7,11 @@ #include "../include/output.h" #include "../include/append_buffer.h" +#include "../include/buffer.h" +#include "../include/data.h" +#include "../include/define.h" +#include "../include/row_op.h" +#include "../include/split_screen.h" #include "../include/syntax_highlighter.h" #include #include @@ -16,60 +21,128 @@ extern struct editorConfig E; /** - * @brief Renders all visible rows to the screen buffer - * @details Draws file content with syntax highlighting, handles line wrapping, - * and displays tilde characters (~) for empty lines. Shows welcome message - * when no file is open. - * @param ab Pointer to append buffer structure for accumulating output - * @note Respects E.row_offset and E.col_offset for scrolling - * @note Clears to end of each line after content - * @see editorRefreshScreen() + * @brief Renders a single pane with its buffer content */ -void editorDrawRows(struct abuf *ab) { - int y; - char welcome[80]; - int welcome_len; - int padding; - int len; - int file_row; - for (y = 0; y < E.screenrows; ++y) { - file_row = y + E.row_offset; +static void editorDrawPane(struct abuf *ab, EditorPane *pane) { + if (pane == NULL || pane->buffer_id < 0) + return; + + struct buffer_t *buf = bufferFindById(pane->buffer_id); + if (buf == NULL) + return; + + // Draw content for this pane + for (int y = 0; y < pane->height; y++) { + int file_row = y + pane->row_offset; + + // Position cursor at start of pane row + char pos_buf[32]; + int pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", + pane->start_row + y + 1, pane->start_col + 1); + abAppend(ab, pos_buf, pos_len); + + // Apply background color (6 bytes for RGB format) abAppend(ab, E.theme.BACKGROUND_COLOR, strlen(E.theme.BACKGROUND_COLOR)); - if (file_row >= E.numrows) { - if (E.numrows == 0 && y == E.screenrows / 3) { - welcome_len = - snprintf(welcome, sizeof(welcome), - "Beluga text editor -- version %s", BELUGA_VERSION); - if (welcome_len > E.screencols) { - welcome_len = E.screencols; - } - padding = (E.screencols - welcome_len) / 2; + + int chars_printed = 0; + + if (file_row >= buf->numrows) { + // Empty line - show tilde + if (buf->numrows == 0 && y == pane->height / 3) { + char welcome[80]; + int welcome_len = + snprintf(welcome, sizeof(welcome), "Buffer %d", pane->buffer_id); + if (welcome_len > pane->width) + welcome_len = pane->width; + + int padding = (pane->width - welcome_len) / 2; if (padding) { abAppend(ab, "~", 1); - --padding; + chars_printed++; + padding--; } - while (padding--) { + while (padding-- && chars_printed < pane->width) { abAppend(ab, " ", 1); + chars_printed++; } abAppend(ab, welcome, welcome_len); + chars_printed += welcome_len; } else { abAppend(ab, "~", 1); + chars_printed++; } } else { - len = E.row[file_row].rsize - E.col_offset; - if (len < 0) - len = 0; - if (len > E.screencols) - len = E.screencols; - char *highlighted = highlight_line(&E.row[file_row].render[E.col_offset], - &E.row[file_row].rsize); - abAppend(ab, highlighted, E.row[file_row].rsize); + // Render line with syntax highlighting, constrain to pane width + int start_offset = pane->col_offset; + int visible_len = buf->row[file_row].rsize - start_offset; + if (visible_len < 0) + visible_len = 0; + if (visible_len > pane->width) + visible_len = pane->width; - free(highlighted); + if (buf->filename[strlen(buf->filename) - 1] == 'c') { + int byte_len_to_print; + + char *highlighted = highlight_line( + &buf->row[file_row].render[start_offset], &byte_len_to_print); + + // Print only up to pane width + + abAppend(ab, highlighted, byte_len_to_print); + free(highlighted); + } else { + abAppend(ab, &buf->row[file_row].render[start_offset], buf->row[file_row].rsize); + } + + chars_printed = visible_len; } + + // Fill remaining space with background color to pane width abAppend(ab, E.theme.BACKGROUND_COLOR, strlen(E.theme.BACKGROUND_COLOR)); - abAppend(ab, ERASE_END_LINE, 3); - abAppend(ab, "\r\n", 2); + while (chars_printed < pane->width) { + abAppend(ab, " ", 1); + chars_printed++; + } + + // Restore background color at end of line + } +} + +/** + * @brief Renders all panes based on current split configuration + */ +static void editorDrawAllPanes(struct abuf *ab) { + ScreenLayout *layout = splitScreenGetLayout(); + + if (layout->num_panes == 1) { + // Single pane fullscreen + editorDrawPane(ab, &layout->panes[0]); + } else if (layout->num_panes == 2) { + // Draw both panes + for (int i = 0; i < 2; i++) { + editorDrawPane(ab, &layout->panes[i]); + + // Draw pane border/divider if not the last pane + if (layout->mode == SPLIT_VERTICAL && i == 0) { + // Draw vertical divider + int divider_col = layout->panes[0].width; + for (int y = 0; y < layout->panes[0].height; y++) { + char pos_buf[32]; + snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", y + 1, divider_col); + abAppend(ab, pos_buf, strlen(pos_buf)); + abAppend(ab, "\x1b[1m|\x1b[0m", 9); // Bold pipe divider + } + } else if (layout->mode == SPLIT_HORIZONTAL && i == 0) { + // Draw horizontal divider + int divider_row = layout->panes[0].height; + char pos_buf[32]; + snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", divider_row + 1, 1); + abAppend(ab, pos_buf, strlen(pos_buf)); + for (int x = 0; x < E.screencols; x++) { + abAppend(ab, "\x1b[1m-\x1b[0m", 9); // Bold dash divider + } + } + } } } @@ -81,22 +154,33 @@ void editorDrawRows(struct abuf *ab) { * @see editorRowCxToRx() */ void editorScroll() { - E.rx = E.cursor_x; - if (E.cursor_y < E.numrows) { - E.rx = editorRowCxToRx(&E.row[E.cursor_y], E.cursor_x); + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buf = bufferFindById(active->buffer_id); + active->rx = active->cursor_x; + + fprintf(stderr, "%d %d / %d %d\n", active->cursor_x, active->col_offset, + active->cursor_y, active->row_offset); + + if (active->cursor_x < 0) { + active->cursor_x = 0; + active->col_offset = active->col_offset == 0 ? 0 : --active->col_offset; } - if (E.cursor_y < E.row_offset) { - E.row_offset = E.cursor_y; + if (active->cursor_y < 0) { + active->cursor_y = 0; + active->row_offset = active->row_offset == 0 ? 0 : --active->row_offset; } - if (E.cursor_y >= E.row_offset + E.screenrows) { - E.row_offset = E.cursor_y - E.screenrows + 1; + + if (active->cursor_x == active->width) { + + active->cursor_x--; + active->col_offset++; } - if (E.rx < E.col_offset) { - E.col_offset = E.rx; - } - if (E.rx >= E.col_offset + E.screencols) { - E.col_offset = E.rx - E.screencols + 1; + + if (active->cursor_y == active->height) { + + active->cursor_y--; + active->row_offset++; } } @@ -111,16 +195,39 @@ void editorScroll() { void editorDrawStatusBar(struct abuf *ab) { int len, render_len; char status[80], render_status[80]; + EditorPane *active = splitScreenGetActivePane(); + struct buffer_t *buf = bufferFindById(active->buffer_id); abAppend(ab, "\x1b[7m", 4); // inverting colors - len = snprintf(status, sizeof(status), "%.20s - %d lines%s", - E.filename ? E.filename : "[No Name]", E.numrows, - E.dirty ? "*" : ""); + + const char *mode_str = ""; + ScreenLayout *layout = splitScreenGetLayout(); + if (layout->mode == SPLIT_VERTICAL) + mode_str = " [V-SPLIT]"; + else if (layout->mode == SPLIT_HORIZONTAL) + mode_str = " [H-SPLIT]"; + + // Build buffer status showing all buffers with dirty indicators + char buf_status[200] = ""; + int offset = 0; + 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) ? '>' : ' '; + char dirty_marker = b->dirty ? '*' : ' '; + offset += snprintf(&buf_status[offset], sizeof(buf_status) - offset, + "%c%d:%s%c ", marker, b->buffer_id, + b->filename ? b->filename : "[No Name]", dirty_marker); + } + + len = snprintf(status, sizeof(status), "%s%s", buf_status, mode_str); + render_len = snprintf(render_status, sizeof(render_status), "%d/%d", - E.cursor_y + 1, E.numrows); + active->cursor_y + 1, buf->numrows); + if (len > E.screencols) { len = E.screencols; } + abAppend(ab, status, len); while (len < E.screencols) { if (E.screencols - len == render_len) { @@ -131,6 +238,7 @@ void editorDrawStatusBar(struct abuf *ab) { ++len; } } + abAppend(ab, "\x1b[m", 3); // normal text mode abAppend(ab, "\r\n", 2); } @@ -163,23 +271,32 @@ void editorDrawMessageBar(struct abuf *ab) { * @see editorDrawStatusBar() * @see editorDrawMessageBar() */ + void editorRefreshScreen() { - editorScroll(); struct abuf ab = ABUF_INIT; char buf[32]; - abAppend(&ab, HIDE_CURSOR, 6); abAppend(&ab, CURSOR_TOP_LEFT, 3); + abAppend(&ab, E.theme.BACKGROUND_COLOR, + strlen(E.theme.BACKGROUND_COLOR)); // RGB background is 12 bytes + // Draw all panes + editorScroll(); + editorDrawAllPanes(&ab); - editorDrawRows(&ab); + // Draw status bar and message bar editorDrawStatusBar(&ab); editorDrawMessageBar(&ab); - snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cursor_y - E.row_offset) + 1, - (E.rx - E.col_offset) + 1); - abAppend(&ab, buf, strlen(buf)); + // Position cursor in active pane + EditorPane *active = splitScreenGetActivePane(); + if (active != NULL) { + snprintf(buf, sizeof(buf), "\x1b[%d;%dH", + active->cursor_y + active->start_row + 1, + active->cursor_x + active->start_col + 1); + abAppend(&ab, buf, strlen(buf)); + } abAppend(&ab, SHOW_CURSOR, 6); write(STDOUT_FILENO, ab.b, ab.len); diff --git a/src/row_op.c b/src/row_op.c index 29bd22d..ea22004 100644 --- a/src/row_op.c +++ b/src/row_op.c @@ -6,7 +6,7 @@ extern struct editorConfig E; -int editorRowCxToRx(erow *row, int cursor_x) { +int bufferRowCxToRx(frow *row, int cursor_x) { int render_x = 0; int i; for (i = 0; i < cursor_x; ++i) { @@ -18,7 +18,7 @@ int editorRowCxToRx(erow *row, int cursor_x) { return render_x; } -int editorRowRxToCx(erow *row, int rx) { +int bufferRowRxToCx(frow *row, int rx) { int cur_rx = 0; int cx; for (cx = 0; cx < row->size; cx++) { @@ -31,11 +31,11 @@ int editorRowRxToCx(erow *row, int rx) { } /** - * \fn editorUpdateRow(erow *row) + * \fn bufferUpdatfrow(frow *row) * \brief Copy content of \p row in \p row->render. * */ -void editorUpdateRow(erow *row) { +void bufferUpdatfrow(frow *row) { int i, i_render; int tabs = 0; @@ -68,52 +68,53 @@ void editorUpdateRow(erow *row) { row->rsize = i_render; } -void editorInsertRow(int at, char *s, size_t len) { - if (at < 0 || at > E.numrows) { +void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) { + + if (at < 0 || at > buffer->numrows) { return; } - erow *tmp = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1)); + frow *tmp = (frow *)realloc(buffer->row, sizeof(frow) * (buffer->numrows + 1)); if (!tmp) { return; } - E.row = tmp; - memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at)); + buffer->row = tmp; + memmove(&buffer->row[at + 1], &buffer->row[at], sizeof(frow) * (buffer->numrows - at)); - E.row[at].size = len; - E.row[at].chars = malloc(len + 1); - memcpy(E.row[at].chars, s, len); - E.row[at].chars[len] = '\0'; + buffer->row[at].size = len; + buffer->row[at].chars = malloc(len + 1); + memcpy(buffer->row[at].chars, s, len); + buffer->row[at].chars[len] = '\0'; - E.row[at].rsize = 0; - E.row[at].render = NULL; - editorUpdateRow(&E.row[at]); + buffer->row[at].rsize = 0; + buffer->row[at].render = NULL; + bufferUpdatfrow(&buffer->row[at]); - ++E.numrows; - ++E.dirty; + ++buffer->numrows; + ++buffer->dirty; } -void editorFreeRow(erow *row) { +void bufferFrefrow(frow *row) { free(row->render); free(row->chars); } -void editorDelRow(int at) { - if (at < 0 || at >= E.numrows) { +void bufferDelRow(struct buffer_t *buffer, int at) { + if (at < 0 || at >= buffer->numrows) { return; } - editorFreeRow(&E.row[at]); - memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1)); - --E.numrows; - ++E.dirty; + bufferFrefrow(&buffer->row[at]); + memmove(&buffer->row[at], &buffer->row[at + 1], sizeof(frow) * (buffer->numrows - at - 1)); + --buffer->numrows; + ++buffer->dirty; } /** - * \fn editorRowInsertChar(erow *row, int at, int c) + * \fn bufferRowInsertChar(frow *row, int at, int c) * \param at Index of where we want to insert the char */ -void editorRowInsertChar(erow *row, int at, int c) { - if (E.state == READ_ONLY) +void bufferRowInsertChar(struct buffer_t *buffer, frow *row, int at, int c) { + if (buffer->state == READ_ONLY) return; if (at < 0 || at > row->size) { at = row->size; @@ -122,30 +123,30 @@ void editorRowInsertChar(erow *row, int at, int c) { memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1); ++row->size; row->chars[at] = c; - editorUpdateRow(row); - ++E.dirty; + bufferUpdatfrow(row); + ++buffer->dirty; } -void editorRowAppendString(erow *row, char *s, size_t len) { +void bufferRowAppendString(struct buffer_t *buffer, frow *row, char *s, size_t len) { row->chars = realloc(row->chars, row->size + len + 1); memcpy(&row->chars[row->size], s, len); row->size += len; row->chars[row->size] = '\0'; - editorUpdateRow(row); - ++E.dirty; + bufferUpdatfrow(row); + ++buffer->dirty; } /** - * \fn editorRowDelChar(struct editorConfig *E, erow *erow, int at) + * \fn bufferRowDelChar(struct bufferConfig *E, frow *frow, int at) * \brief Delete the a char at the chosen position on the given row * \param at Index of the char to delete * \param row Row on operation is made */ -void editorRowDelchar(erow *row, int at) { +void bufferRowDelchar(struct buffer_t *buffer, frow *row, int at) { if (at < 0 || at >= row->size) { return; } memmove(&row->chars[at], &row->chars[at + 1], row->size - at); --row->size; - editorUpdateRow(row); - ++E.dirty; + bufferUpdatfrow(row); + ++buffer->dirty; } diff --git a/src/split_screen.c b/src/split_screen.c new file mode 100644 index 0000000..b59d1c3 --- /dev/null +++ b/src/split_screen.c @@ -0,0 +1,218 @@ +/** + * @file split_screen.c + * @brief Split screen implementation + */ + +#include "../include/split_screen.h" +#include "../include/buffer.h" +#include "../include/editor_op.h" +#include +#include + +extern struct editorConfig E; + +/** + * @brief Initializes split screen system with single fullscreen pane + */ +void splitScreenInit(void) { + E.layout.mode = SPLIT_NONE; + E.layout.num_panes = 1; + E.layout.active_pane = 0; + + E.layout.panes = malloc(sizeof(EditorPane) * 2); + + // Initialize single fullscreen pane + E.layout.panes[0].buffer_id = -1; // No buffer for now + E.layout.panes[0].start_row = 0; + E.layout.panes[0].start_col = 0; + E.layout.panes[0].height = E.screenrows - 2; // Leave room for status bar + E.layout.panes[0].width = E.screencols; + E.layout.panes[0].cursor_x = 0; + E.layout.panes[0].cursor_y = 0; + E.layout.panes[0].row_offset = 0; + E.layout.panes[0].col_offset = 0; + E.layout.panes[0].is_active = 1; +} + +/** + * @brief Splits screen vertically (left-right) + * @param buffer_id_left Buffer ID for left pane + * @param buffer_id_right Buffer ID for right pane + * @return 0 on success, -1 on failure + */ +int splitScreenVertical(int buffer_id_left, int buffer_id_right) { + // Verify both buffers exist + if (bufferFindById(buffer_id_left) == NULL || + bufferFindById(buffer_id_right) == NULL) { + editorSetStatusMessage("Error: invalid buffer IDs"); + return -1; + } + + // Reallocate panes array + E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2); + E.layout.mode = SPLIT_VERTICAL; + E.layout.num_panes = 2; + E.layout.active_pane = 0; + + int half_width = E.screencols / 2; + int pane_height = E.screenrows - 2; // Leave room for status bar + + // Left pane + E.layout.panes[0].buffer_id = buffer_id_left; + E.layout.panes[0].start_row = 0; + E.layout.panes[0].start_col = 0; + E.layout.panes[0].height = pane_height; + E.layout.panes[0].width = half_width; + E.layout.panes[0].cursor_x = 0; + E.layout.panes[0].cursor_y = 0; + E.layout.panes[0].row_offset = 0; + E.layout.panes[0].col_offset = 0; + E.layout.panes[0].is_active = 1; + + // Right pane + E.layout.panes[1].buffer_id = buffer_id_right; + E.layout.panes[1].start_row = 0; + E.layout.panes[1].start_col = half_width; + E.layout.panes[1].height = pane_height; + E.layout.panes[1].width = E.screencols - half_width; + E.layout.panes[1].cursor_x = 0; + E.layout.panes[1].cursor_y = 0; + E.layout.panes[1].row_offset = 0; + E.layout.panes[1].col_offset = 0; + E.layout.panes[1].is_active = 0; + + editorSetStatusMessage("Vertical split: %d | %d", buffer_id_left, buffer_id_right); + return 0; +} + +/** + * @brief Splits screen horizontally (top-bottom) + * @param buffer_id_top Buffer ID for top pane + * @param buffer_id_bottom Buffer ID for bottom pane + * @return 0 on success, -1 on failure + */ +int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom) { + // Verify both buffers exist + if (bufferFindById(buffer_id_top) == NULL || + bufferFindById(buffer_id_bottom) == NULL) { + editorSetStatusMessage("Error: invalid buffer IDs"); + return -1; + } + + // Reallocate panes array + E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2); + E.layout.mode = SPLIT_HORIZONTAL; + E.layout.num_panes = 2; + E.layout.active_pane = 0; + + int half_height = (E.screenrows - 2) / 2; // Account for status bar + + // Top pane + E.layout.panes[0].buffer_id = buffer_id_top; + E.layout.panes[0].start_row = 0; + E.layout.panes[0].start_col = 0; + E.layout.panes[0].height = half_height; + E.layout.panes[0].width = E.screencols; + E.layout.panes[0].cursor_x = 0; + E.layout.panes[0].cursor_y = 0; + E.layout.panes[0].row_offset = 0; + E.layout.panes[0].col_offset = 0; + E.layout.panes[0].is_active = 1; + + // Bottom pane + E.layout.panes[1].buffer_id = buffer_id_bottom; + E.layout.panes[1].start_row = half_height; + E.layout.panes[1].start_col = 0; + E.layout.panes[1].height = E.screenrows - 2 - half_height; + E.layout.panes[1].width = E.screencols; + E.layout.panes[1].cursor_x = 0; + E.layout.panes[1].cursor_y = 0; + E.layout.panes[1].row_offset = 0; + E.layout.panes[1].col_offset = 0; + E.layout.panes[1].is_active = 0; + + editorSetStatusMessage("Horizontal split: %d / %d", buffer_id_top, buffer_id_bottom); + return 0; +} + +/** + * @brief Returns to single buffer fullscreen + */ +void splitScreenUnify(void) { + E.layout.mode = SPLIT_NONE; + E.layout.num_panes = 1; + E.layout.active_pane = 0; + + E.layout.panes[0].start_row = 0; + E.layout.panes[0].start_col = 0; + E.layout.panes[0].height = E.screenrows - 2; + E.layout.panes[0].width = E.screencols; + E.layout.panes[0].is_active = 1; + + editorSetStatusMessage("Unified view"); +} + +/** + * @brief Switches active pane (focus moves between splits) + * @return 0 on success, -1 on failure + */ +int splitScreenSwitchPane(void) { + if (E.layout.num_panes < 2) { + editorSetStatusMessage("No split to switch"); + return -1; + } + + // Deactivate current pane + E.layout.panes[E.layout.active_pane].is_active = 0; + + // Move to next pane + E.layout.active_pane = (E.layout.active_pane + 1) % E.layout.num_panes; + + // Activate new pane + E.layout.panes[E.layout.active_pane].is_active = 1; + + editorSetStatusMessage("Switched to pane %d (buffer %d)", + E.layout.active_pane, + E.layout.panes[E.layout.active_pane].buffer_id); + return 0; +} + +/** + * @brief Updates the active pane's buffer + * @param buffer_id New buffer ID for active pane + * @return 0 on success, -1 on failure + */ +int splitScreenSetPaneBuffer(int buffer_id) { + if (bufferFindById(buffer_id) == NULL) { + editorSetStatusMessage("Error: invalid buffer ID"); + return -1; + } + + EditorPane *active = &E.layout.panes[E.layout.active_pane]; + active->buffer_id = buffer_id; + active->cursor_x = 0; + active->cursor_y = 0; + active->row_offset = 0; + active->col_offset = 0; + + editorSetStatusMessage("Pane %d now showing buffer %d", + E.layout.active_pane, buffer_id); + return 0; +} + +/** + * @brief Gets current screen layout + * @return Pointer to current ScreenLayout + */ +ScreenLayout *splitScreenGetLayout(void) { + return &E.layout; +} + +/** + * @brief Gets active pane + * @return Pointer to active EditorPane + */ +EditorPane *splitScreenGetActivePane(void) { + if (E.layout.num_panes == 0) return NULL; + return &E.layout.panes[E.layout.active_pane]; +}