Syntax highlighting and comment
This commit is contained in:
@@ -67,6 +67,7 @@ struct editorConfig {
|
|||||||
struct const_t constantes;
|
struct const_t constantes;
|
||||||
int quit_times_buffer;
|
int quit_times_buffer;
|
||||||
|
|
||||||
|
char *init_file_path;
|
||||||
FILE *fd_init_file;
|
FILE *fd_init_file;
|
||||||
Lisp env;
|
Lisp env;
|
||||||
LispContext ctx; /** Lisp context */
|
LispContext ctx; /** Lisp context */
|
||||||
|
|||||||
+4
-1
@@ -2075,8 +2075,10 @@ static Lisp parse(Lexer* lex, LispError* out_error, LispContext ctx)
|
|||||||
result = lisp_cons(get_sym(SYM_BEGIN, ctx), lisp_list_reverse(result), ctx);
|
result = lisp_cons(get_sym(SYM_BEGIN, ctx), lisp_list_reverse(result), ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out_error) *out_error = error;
|
if (out_error)
|
||||||
|
*out_error = error;
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Lisp lisp_read(const char *program, LispError* out_error, LispContext ctx)
|
Lisp lisp_read(const char *program, LispError* out_error, LispContext ctx)
|
||||||
@@ -2122,6 +2124,7 @@ void* fread_all_(FILE* file, size_t* out_size) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
data = new_data;
|
data = new_data;
|
||||||
|
memset(data + *out_size, 0, cap - *out_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t read = fread(data + *out_size, 1, BLOCK_SIZE, file);
|
size_t read = fread(data + *out_size, 1, BLOCK_SIZE, file);
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#define COLOR_RESET "\033[0m"
|
||||||
|
#define COLOR_KEYWORD "\033[1;35m" // Bold magenta
|
||||||
|
#define COLOR_TYPE "\033[1;34m" // Bold blue
|
||||||
|
#define COLOR_STRING "\033[1;32m" // Bold green
|
||||||
|
#define COLOR_COMMENT "\033[0;36m" // Cyan
|
||||||
|
#define COLOR_NUMBER "\033[1;33m" // Bold yellow
|
||||||
|
#define COLOR_DEFAULT "\033[0;37m" // White
|
||||||
|
|
||||||
|
// Token types
|
||||||
|
typedef enum {
|
||||||
|
TOKEN_KEYWORD,
|
||||||
|
TOKEN_TYPE,
|
||||||
|
TOKEN_STRING,
|
||||||
|
TOKEN_COMMENT,
|
||||||
|
TOKEN_NUMBER,
|
||||||
|
TOKEN_OPERATOR,
|
||||||
|
TOKEN_DEFAULT
|
||||||
|
} TokenType;
|
||||||
|
|
||||||
|
char *highlight_line(const char * line, int *length);
|
||||||
@@ -40,7 +40,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
free(splash_screen);
|
free(splash_screen);
|
||||||
|
|
||||||
editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit");
|
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-q = quit");
|
||||||
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ src_files = files(
|
|||||||
'main.c',
|
'main.c',
|
||||||
'src/append_buffer.c',
|
'src/append_buffer.c',
|
||||||
'src/editor_op.c',
|
'src/editor_op.c',
|
||||||
|
'src/syntax_highlighter.c',
|
||||||
'src/file_io.c',
|
'src/file_io.c',
|
||||||
'src/init.c',
|
'src/init.c',
|
||||||
'src/input.c',
|
'src/input.c',
|
||||||
|
|||||||
+218
-2
@@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @file builtins.c
|
||||||
|
* @brief Built-in Lisp functions for editor operations
|
||||||
|
* @details Provides Lisp bindings for core editor functionality including
|
||||||
|
* cursor movement, file operations, keybinding management, and text manipulation
|
||||||
|
*/
|
||||||
|
|
||||||
#include "../include/builtins.h"
|
#include "../include/builtins.h"
|
||||||
#include "../include/data.h"
|
#include "../include/data.h"
|
||||||
#include "../include/define.h"
|
#include "../include/define.h"
|
||||||
@@ -11,6 +18,14 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds a prefix configuration by name
|
||||||
|
* @details Searches the prefix array for a prefix matching the given name.
|
||||||
|
* Returns the first prefix (default) if no match is found.
|
||||||
|
* @param prefix_name Name of the prefix to search for (max 64 chars)
|
||||||
|
* @return Matching prefix_t structure, or E.prefix[0] if not found
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
struct prefix_t find_prefix(const char prefix_name[64]) {
|
struct prefix_t find_prefix(const char prefix_name[64]) {
|
||||||
int i = E.number_of_prefix + 1;
|
int i = E.number_of_prefix + 1;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
@@ -21,6 +36,19 @@ struct prefix_t find_prefix(const char prefix_name[64]) {
|
|||||||
return E.prefix[0];
|
return E.prefix[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to bind a command to a key sequence
|
||||||
|
* @details Registers a keybinding with an associated Lisp command and optional
|
||||||
|
* prefix modifier. Dynamically extends the keybind array.
|
||||||
|
* @param args Lisp list of 3 arguments: (key-sequence command prefix-name)
|
||||||
|
* - key_sequence (string): The key or key combination to bind
|
||||||
|
* - command (Lisp): The Lisp command/function to execute
|
||||||
|
* - prefix_name (string): Prefix modifier name
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E (key_binds array)
|
||||||
|
*/
|
||||||
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
||||||
/*
|
/*
|
||||||
* 3 arguments keybind command prefix
|
* 3 arguments keybind command prefix
|
||||||
@@ -49,6 +77,17 @@ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor in a specified direction
|
||||||
|
* @details Moves the editor cursor up, down, left, or right based on direction string.
|
||||||
|
* @param args Lisp list with one argument: direction string
|
||||||
|
* - "u": up, "d": down, "r": right, "l": left
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return Lisp boolean indicating whether movement was valid
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorMoveCursor()
|
||||||
|
*/
|
||||||
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
|
Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
|
||||||
const char *direction = lisp_string(lisp_car(args));
|
const char *direction = lisp_string(lisp_car(args));
|
||||||
int is_in = 0;
|
int is_in = 0;
|
||||||
@@ -70,6 +109,12 @@ Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_make_bool(is_in);
|
return lisp_make_bool(is_in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Frees all dynamically allocated editor structures
|
||||||
|
* @details Releases memory for prefix table, keybinds, filename, and all rows.
|
||||||
|
* Called during shutdown to prevent memory leaks.
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
void free_structs(void) {
|
void free_structs(void) {
|
||||||
int i;
|
int i;
|
||||||
free(E.prefix);
|
free(E.prefix);
|
||||||
@@ -78,12 +123,28 @@ void free_structs(void) {
|
|||||||
}
|
}
|
||||||
free(E.key_binds);
|
free(E.key_binds);
|
||||||
free(E.filename);
|
free(E.filename);
|
||||||
free(E.row->chars);
|
for (i = 0; i < E.numrows; ++i) {
|
||||||
free(E.row->render);
|
free(E.row[i].render);
|
||||||
|
free(E.row[i].chars);
|
||||||
|
}
|
||||||
free(E.row);
|
free(E.row);
|
||||||
|
free(E.init_file_path);
|
||||||
|
fclose(E.fd_init_file);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to quit the editor
|
||||||
|
* @details Closes editor with unsaved changes protection. Prompts user to confirm
|
||||||
|
* quit after multiple attempts if file is dirty. Cleans up resources and restores terminal.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null() (never returns on successful exit)
|
||||||
|
* @note Calls exit(0) to terminate program
|
||||||
|
* @note Updates quit_times_buffer counter
|
||||||
|
* @see free_structs()
|
||||||
|
*/
|
||||||
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
|
||||||
if (E.dirty && E.quit_times_buffer > 0) {
|
if (E.dirty && E.quit_times_buffer > 0) {
|
||||||
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
|
editorSetStatusMessage("WARNING! Changes hasn't been saved. Press Ctrl-Q "
|
||||||
@@ -101,6 +162,15 @@ Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to save the current file
|
||||||
|
* @details Wrapper around editorSave() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorSave()
|
||||||
|
*/
|
||||||
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
|
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
|
||||||
editorSave();
|
editorSave();
|
||||||
@@ -108,6 +178,15 @@ Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to insert a new line at cursor
|
||||||
|
* @details Wrapper around editorInsertNewLine() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorInsertNewLine()
|
||||||
|
*/
|
||||||
Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
|
Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
|
||||||
editorInsertNewLine();
|
editorInsertNewLine();
|
||||||
@@ -115,6 +194,15 @@ Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to insert a tab (spaces) at cursor
|
||||||
|
* @details Inserts TAB_LENGTH spaces at the current cursor position.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Uses E.constantes.TAB_LENGTH for indentation width
|
||||||
|
*/
|
||||||
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
|
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
|
||||||
for (int i = 0; i<E.constantes.TAB_LENGTH; ++i) {
|
for (int i = 0; i<E.constantes.TAB_LENGTH; ++i) {
|
||||||
@@ -124,11 +212,29 @@ Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor to beginning of line
|
||||||
|
* @details Moves cursor to column 0 of the current line.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
|
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
|
||||||
E.cursor_x = 0;
|
E.cursor_x = 0;
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor to end of line
|
||||||
|
* @details Moves cursor to the end of the current line content.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
|
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
|
||||||
if (E.cursor_y < E.numrows) {
|
if (E.cursor_y < E.numrows) {
|
||||||
E.cursor_x = E.row[E.cursor_y].size;
|
E.cursor_x = E.row[E.cursor_y].size;
|
||||||
@@ -136,11 +242,30 @@ Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to delete character before cursor
|
||||||
|
* @details Wrapper around editorDelChar() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorDelChar()
|
||||||
|
*/
|
||||||
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
|
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
|
||||||
editorDelChar();
|
editorDelChar();
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor up by one screen
|
||||||
|
* @details Scrolls up one full screen height, moving cursor to top of visible area.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorMoveCursor()
|
||||||
|
*/
|
||||||
Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
||||||
E.cursor_y = E.row_offset;
|
E.cursor_y = E.row_offset;
|
||||||
int times = E.screenrows;
|
int times = E.screenrows;
|
||||||
@@ -150,6 +275,16 @@ Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to move cursor down by one screen
|
||||||
|
* @details Scrolls down one full screen height, moving cursor to bottom of visible area.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorMoveCursor()
|
||||||
|
*/
|
||||||
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
|
||||||
E.cursor_y = E.row_offset + E.screenrows - 1;
|
E.cursor_y = E.row_offset + E.screenrows - 1;
|
||||||
if (E.cursor_y > E.numrows) {
|
if (E.cursor_y > E.numrows) {
|
||||||
@@ -163,6 +298,17 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to open a file
|
||||||
|
* @details Prompts user for filename and opens the file for editing.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorOpen()
|
||||||
|
* @see editorPrompt()
|
||||||
|
*/
|
||||||
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
||||||
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
|
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
|
||||||
if (filename){
|
if (filename){
|
||||||
@@ -174,12 +320,32 @@ Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to insert a character
|
||||||
|
* @details Extracts a character from Lisp string argument and inserts it at cursor.
|
||||||
|
* @param args Lisp list with one string argument
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Uses first character of the string argument
|
||||||
|
*/
|
||||||
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
|
||||||
char c = lisp_string(lisp_car(args))[0];
|
char c = lisp_string(lisp_car(args))[0];
|
||||||
editorInsertChar(c);
|
editorInsertChar(c);
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to load and execute a package
|
||||||
|
* @details Loads a Lisp package from the user's packages directory
|
||||||
|
* (~/.beluga/packages/<package_name>/init.lisp) and evaluates it.
|
||||||
|
* @param args Lisp list with one argument: package name (string)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Package files must be valid Lisp code
|
||||||
|
* @note Package directory defaults to ~/.beluga/packages/
|
||||||
|
*/
|
||||||
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
|
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
|
||||||
const char *package_name = lisp_string(lisp_car(args));
|
const char *package_name = lisp_string(lisp_car(args));
|
||||||
fprintf(stderr, "%s\n", package_name);
|
fprintf(stderr, "%s\n", package_name);
|
||||||
@@ -199,16 +365,45 @@ Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to delete the current row
|
||||||
|
* @details Removes the line at the current cursor position.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorDelRow()
|
||||||
|
*/
|
||||||
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx) {
|
||||||
editorDelRow(E.cursor_y);
|
editorDelRow(E.cursor_y);
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to search for text
|
||||||
|
* @details Wrapper around editorFind() for use in Lisp keybindings.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @see editorFind()
|
||||||
|
*/
|
||||||
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorFind_L(Lisp args, LispError *e, LispContext ctx) {
|
||||||
|
fprintf(stderr, "LispFind\n");
|
||||||
editorFind();
|
editorFind();
|
||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to read character at cursor
|
||||||
|
* @details Returns the character at the current cursor position as a Lisp character.
|
||||||
|
* Returns 'a' if at end of line.
|
||||||
|
* @param args Lisp arguments (unused)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return Lisp character object representing the character at cursor
|
||||||
|
*/
|
||||||
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
|
||||||
Lisp returned_char;
|
Lisp returned_char;
|
||||||
if (E.row[E.cursor_y].render[E.cursor_x] == 0) {
|
if (E.row[E.cursor_y].render[E.cursor_x] == 0) {
|
||||||
@@ -220,6 +415,17 @@ Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return returned_char;
|
return returned_char;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to set the current prefix mode
|
||||||
|
* @details Changes the editor's prefix state to a named prefix, affecting which
|
||||||
|
* keybindings are active. Updates status message to show active prefix.
|
||||||
|
* @param args Lisp list with one argument: prefix name (string)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E (prefix_state)
|
||||||
|
* @see find_prefix()
|
||||||
|
*/
|
||||||
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
|
||||||
/*
|
/*
|
||||||
* Set the prefix state of editor to the prefix in argument
|
* Set the prefix state of editor to the prefix in argument
|
||||||
@@ -233,6 +439,16 @@ Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
|
|||||||
return lisp_null();
|
return lisp_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lisp function to define a new prefix modifier
|
||||||
|
* @details Registers a named prefix modifier that can be used with keybindings
|
||||||
|
* to create context-aware command sequences (e.g., Ctrl-X as a prefix).
|
||||||
|
* @param args Lisp list with one argument: prefix name (string, max 64 chars)
|
||||||
|
* @param e Error pointer for Lisp error handling
|
||||||
|
* @param ctx Lisp context
|
||||||
|
* @return lisp_null()
|
||||||
|
* @note Updates global editor state E (prefix array)
|
||||||
|
*/
|
||||||
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx) {
|
Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx) {
|
||||||
E.prefix = (struct prefix_t *)realloc(E.prefix, (++(E.number_of_prefix) + 1) *
|
E.prefix = (struct prefix_t *)realloc(E.prefix, (++(E.number_of_prefix) + 1) *
|
||||||
sizeof(struct prefix_t));
|
sizeof(struct prefix_t));
|
||||||
|
|||||||
+62
-2
@@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @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/file_io.h"
|
||||||
#include "../include/input.h"
|
#include "../include/input.h"
|
||||||
#include "../include/output.h"
|
#include "../include/output.h"
|
||||||
@@ -14,6 +22,15 @@ extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
|
|||||||
extern int ftruncate(int fd, off_t length);
|
extern int ftruncate(int fd, off_t length);
|
||||||
extern struct editorConfig E;
|
extern struct editorConfig E;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts all editor rows to a single string buffer
|
||||||
|
* @details Concatenates all row content into a single allocated buffer with
|
||||||
|
* newlines between rows. Useful for file saving and buffer operations.
|
||||||
|
* @param buffer_len Pointer to integer where total buffer length will be stored
|
||||||
|
* @return Pointer to dynamically allocated buffer containing all row data.
|
||||||
|
* Rows are separated by newline characters.
|
||||||
|
* @note Caller is responsible for freeing the returned buffer
|
||||||
|
*/
|
||||||
char *editorRowsToString(int *buffer_len) {
|
char *editorRowsToString(int *buffer_len) {
|
||||||
int tot_len = 0;
|
int tot_len = 0;
|
||||||
int j;
|
int j;
|
||||||
@@ -36,20 +53,42 @@ char *editorRowsToString(int *buffer_len) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) {
|
void editorCloseFile(void) {
|
||||||
E.cursor_x = 0;
|
E.cursor_x = 0;
|
||||||
E.cursor_y = 0;
|
E.cursor_y = 0;
|
||||||
E.rx = 0;
|
E.rx = 0;
|
||||||
E.row_offset = 0;
|
E.row_offset = 0;
|
||||||
E.col_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;
|
E.numrows = 0;
|
||||||
E.row = NULL;
|
E.row = NULL;
|
||||||
E.dirty = 0;
|
E.dirty = 0;
|
||||||
|
free(E.filename);
|
||||||
E.filename = NULL;
|
E.filename = NULL;
|
||||||
E.status_msg[0] = '\0';
|
E.status_msg[0] = '\0';
|
||||||
E.status_msg_time = 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(char *filename) {
|
void editorOpen(char *filename) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
|
||||||
@@ -59,7 +98,6 @@ void editorOpen(char *filename) {
|
|||||||
E.state = READ_AND_WRITE;
|
E.state = READ_AND_WRITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(E.filename);
|
|
||||||
E.filename = strdup(filename);
|
E.filename = strdup(filename);
|
||||||
|
|
||||||
fp = fopen(filename, "a+");
|
fp = fopen(filename, "a+");
|
||||||
@@ -76,12 +114,24 @@ void editorOpen(char *filename) {
|
|||||||
--line_len;
|
--line_len;
|
||||||
}
|
}
|
||||||
editorInsertRow(E.numrows, line, line_len);
|
editorInsertRow(E.numrows, line, line_len);
|
||||||
|
free(line);
|
||||||
|
line = NULL;
|
||||||
}
|
}
|
||||||
free(line);
|
free(line);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
E.dirty = 0;
|
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() {
|
void editorSave() {
|
||||||
int len;
|
int len;
|
||||||
char *buf;
|
char *buf;
|
||||||
@@ -111,7 +161,18 @@ void editorSave() {
|
|||||||
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
|
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 editorFind() {
|
void editorFind() {
|
||||||
|
fprintf(stderr, "searching\n");
|
||||||
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
|
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
|
||||||
if (query == NULL) return;
|
if (query == NULL) return;
|
||||||
int i;
|
int i;
|
||||||
@@ -127,4 +188,3 @@ void editorFind() {
|
|||||||
}
|
}
|
||||||
free(query);
|
free(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -9,7 +9,7 @@
|
|||||||
#include "../include/lisp.h"
|
#include "../include/lisp.h"
|
||||||
#include "../include/lisp_lib.h"
|
#include "../include/lisp_lib.h"
|
||||||
|
|
||||||
extern struct editorConfig;
|
struct editorConfig;
|
||||||
|
|
||||||
void registerBuiltin(char *key_sequence, LispCFunc f) {
|
void registerBuiltin(char *key_sequence, LispCFunc f) {
|
||||||
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
|
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
|
||||||
@@ -40,7 +40,7 @@ void initBuiltins() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void initEditor() {
|
void initEditor() {
|
||||||
char *init_file_path = (char *)calloc(256, sizeof(char));
|
E.init_file_path = (char *)calloc(256, sizeof(char));
|
||||||
E.cursor_x = 0;
|
E.cursor_x = 0;
|
||||||
E.cursor_y = 0;
|
E.cursor_y = 0;
|
||||||
E.rx = 0;
|
E.rx = 0;
|
||||||
@@ -67,10 +67,10 @@ void initEditor() {
|
|||||||
|
|
||||||
E.prefix_state = 0;
|
E.prefix_state = 0;
|
||||||
|
|
||||||
strcat(init_file_path, getenv("HOME"));
|
strcat(E.init_file_path, getenv("HOME"));
|
||||||
strcat(init_file_path, "/.beluga/config/init.lisp");
|
strcat(E.init_file_path, "/.beluga/config/init.lisp");
|
||||||
printf("%s\n", init_file_path);
|
// printf("%s\n", init_file_path);
|
||||||
E.fd_init_file = fopen(init_file_path, "r");
|
E.fd_init_file = fopen(E.init_file_path, "r");
|
||||||
E.ctx = lisp_init();
|
E.ctx = lisp_init();
|
||||||
E.env = lisp_env(E.ctx);
|
E.env = lisp_env(E.ctx);
|
||||||
lisp_lib_load(E.ctx);
|
lisp_lib_load(E.ctx);
|
||||||
@@ -79,7 +79,6 @@ void initEditor() {
|
|||||||
|
|
||||||
// Read config file
|
// Read config file
|
||||||
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
|
E.ctx_data = lisp_read_file(E.fd_init_file, &E.ctx_error, E.ctx);
|
||||||
free(init_file_path);
|
|
||||||
if (E.ctx_error != LISP_ERROR_NONE) {
|
if (E.ctx_error != LISP_ERROR_NONE) {
|
||||||
die("init failed");
|
die("init failed");
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-10
@@ -9,25 +9,46 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
extern struct editorConfig E;
|
extern struct editorConfig E;
|
||||||
|
|
||||||
char *file_completion(const char *path) {
|
/**
|
||||||
|
* @file input.c
|
||||||
|
* @brief Input handling module for the Beluga text editor
|
||||||
|
* @details Manages user input processing, key bindings, cursor movement, and file path completion
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the first file completion match for the given path
|
||||||
|
* @details Searches the directory containing the given path prefix and returns
|
||||||
|
* the first file or directory entry that matches the filename prefix.
|
||||||
|
* Appends a trailing slash for directory entries.
|
||||||
|
* @param path The file path to complete (can be relative or absolute)
|
||||||
|
* @return Pointer to the completed file path (dynamically allocated), or NULL if:
|
||||||
|
* - path ends with '/' (already a directory)
|
||||||
|
* - no matching entries found
|
||||||
|
* - directory cannot be opened
|
||||||
|
* @note Caller is responsible for freeing the returned string
|
||||||
|
* @note Uses static buffer internally; may return stale pointers across calls
|
||||||
|
*/
|
||||||
|
const char *file_completion(const char *path) {
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *entry;
|
struct dirent *entry;
|
||||||
char directory[128];
|
char directory[128];
|
||||||
char predict[128];
|
char predict[128];
|
||||||
|
const char *last_slash;
|
||||||
int predict_len = 0;
|
int predict_len = 0;
|
||||||
|
size_t dir_len;
|
||||||
|
|
||||||
|
// path is a directory
|
||||||
if (path[strlen(path) - 1] == '/') {
|
if (path[strlen(path) - 1] == '/') {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find dir name
|
// Find dir name
|
||||||
char *last_slash = strrchr(path, '/');
|
last_slash = strrchr(path, '/');
|
||||||
if (last_slash) {
|
if (last_slash) {
|
||||||
size_t dir_len = last_slash - path + 1; // length of dir_path
|
dir_len = last_slash - path + 1; // length of dir_path
|
||||||
strncpy(directory, path, dir_len);
|
strncpy(directory, path, dir_len);
|
||||||
predict_len = strlen(path) - dir_len - 1;
|
predict_len = strlen(path) - dir_len - 1;
|
||||||
strncpy(predict, last_slash + 1, predict_len);
|
strncpy(predict, last_slash + 1, predict_len);
|
||||||
@@ -44,13 +65,16 @@ char *file_completion(const char *path) {
|
|||||||
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
if (strncmp(entry->d_name, predict, predict_len) == 0) {
|
if (strncmp(entry->d_name, predict, predict_len) == 0) {
|
||||||
static char full_path[128];
|
static char full_path[512];
|
||||||
snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name);
|
snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name);
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
strcat(full_path, "/"); // add slash for directories
|
strcat(full_path, "/"); // add slash for directories
|
||||||
}
|
}
|
||||||
|
closedir(dir);
|
||||||
|
dir = NULL;
|
||||||
|
free(entry);
|
||||||
|
|
||||||
return strdup(full_path);
|
return strdup(full_path);
|
||||||
}
|
}
|
||||||
@@ -59,13 +83,24 @@ char *file_completion(const char *path) {
|
|||||||
// Cleanup when no more entries
|
// Cleanup when no more entries
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
dir = NULL;
|
dir = NULL;
|
||||||
|
free(entry);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \fn char * editorPrompt(struct editorConfig *E, char *prompt, char bPathMode)
|
* @brief Displays an interactive prompt and returns user input
|
||||||
* \brief Return user input in a prompt when enter is hit. */
|
* @details Allows the user to enter text in a prompt with optional path completion
|
||||||
|
* via Tab key. Supports backspace, delete, and escape key handling. Dynamically
|
||||||
|
* allocates memory for the input buffer.
|
||||||
|
* @param prompt The prompt message format string (printf-style)
|
||||||
|
* @param placeHolder Initial text to display in the input buffer
|
||||||
|
* @param bPathMode If non-zero, enables Tab key file path completion
|
||||||
|
* @return Pointer to the user-entered text (dynamically allocated), or NULL if:
|
||||||
|
* - User pressed ESC to cancel
|
||||||
|
* - Input buffer is empty when Enter is pressed
|
||||||
|
* @note Caller is responsible for freeing the returned string
|
||||||
|
* @note Uses editorReadKey() for input and editorRefreshScreen() for display
|
||||||
|
*/
|
||||||
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||||
size_t buf_size = 128;
|
size_t buf_size = 128;
|
||||||
char *buf = malloc(buf_size);
|
char *buf = malloc(buf_size);
|
||||||
@@ -106,7 +141,9 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
|||||||
}
|
}
|
||||||
memset(buf, 0, 128);
|
memset(buf, 0, 128);
|
||||||
buf_len = 0;
|
buf_len = 0;
|
||||||
strcpy(buf, file_completion(path));
|
char * buf_complete = (char *) file_completion(path);
|
||||||
|
strcpy(buf, buf_complete);
|
||||||
|
free(buf_complete);
|
||||||
buf_len = strlen(buf);
|
buf_len = strlen(buf);
|
||||||
buf[buf_len] = '\0';
|
buf[buf_len] = '\0';
|
||||||
|
|
||||||
@@ -121,6 +158,17 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts a key code to its string representation
|
||||||
|
* @details Translates raw key codes (including special keys, control keys,
|
||||||
|
* and regular characters) into human-readable string formats suitable for
|
||||||
|
* display and keybinding configuration.
|
||||||
|
* @param key The key code to convert
|
||||||
|
* @return Pointer to static buffer containing the string representation.
|
||||||
|
* Examples: "ENTER", "ARROW-UP", "CTRL-a", "TAB", "DELETE", etc.
|
||||||
|
* @note Returns pointer to static buffer; string is overwritten on next call
|
||||||
|
* @note Non-printable characters are formatted as "KEY-<number>"
|
||||||
|
*/
|
||||||
char *key_to_string(int key) {
|
char *key_to_string(int key) {
|
||||||
static char key_str[32];
|
static char key_str[32];
|
||||||
|
|
||||||
@@ -187,6 +235,15 @@ char *key_to_string(int key) {
|
|||||||
return key_str;
|
return key_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Moves the cursor based on arrow key input
|
||||||
|
* @details Updates cursor position (E.cursor_x, E.cursor_y) based on the given
|
||||||
|
* key direction. Handles line wrapping and boundary conditions. Prevents cursor
|
||||||
|
* from exceeding line lengths.
|
||||||
|
* @param key The arrow key code (ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT)
|
||||||
|
* @return 1 if cursor movement was valid, 0 if cursor was constrained to line boundary
|
||||||
|
* @note Updates global editor state E
|
||||||
|
*/
|
||||||
int editorMoveCursor(int key) {
|
int editorMoveCursor(int key) {
|
||||||
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
|
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
|
||||||
int row_len;
|
int row_len;
|
||||||
@@ -228,6 +285,15 @@ int editorMoveCursor(int key) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Executes the command bound to a key sequence
|
||||||
|
* @details Searches the keybinding table for a matching key sequence and
|
||||||
|
* prefix state, then evaluates the associated Lisp command.
|
||||||
|
* @param key_sequence The string representation of the key sequence
|
||||||
|
* @return 1 if a matching keybinding was found and executed, 0 otherwise
|
||||||
|
* @note Updates global editor state E (prefix_state)
|
||||||
|
* @note Uses Lisp interpreter to evaluate bound commands
|
||||||
|
*/
|
||||||
int executeKeyBind(char *key_sequence) {
|
int executeKeyBind(char *key_sequence) {
|
||||||
int i;
|
int i;
|
||||||
int previous_state = 0;
|
int previous_state = 0;
|
||||||
@@ -235,7 +301,7 @@ int executeKeyBind(char *key_sequence) {
|
|||||||
for (i = 0; i < E.number_of_keybinds; ++i) {
|
for (i = 0; i < E.number_of_keybinds; ++i) {
|
||||||
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
|
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
|
||||||
if (E.prefix_state != E.key_binds[i].prefix_id) {
|
if (E.prefix_state != E.key_binds[i].prefix_id) {
|
||||||
return 0;
|
continue;
|
||||||
}
|
}
|
||||||
previous_state = E.prefix_state;
|
previous_state = E.prefix_state;
|
||||||
// It's a symbol, create a function call
|
// It's a symbol, create a function call
|
||||||
@@ -249,6 +315,14 @@ int executeKeyBind(char *key_sequence) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Processes a single keypress from the user
|
||||||
|
* @details Reads a key, checks if it matches any registered keybinding,
|
||||||
|
* and either executes the bound command or inserts the character. Resets
|
||||||
|
* the quit buffer counter on successful key processing.
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @note Calls editorReadKey() to get input and editorInsertChar() for unbound keys
|
||||||
|
*/
|
||||||
void editorProcessKeypress() {
|
void editorProcessKeypress() {
|
||||||
int c = editorReadKey();
|
int c = editorReadKey();
|
||||||
|
|
||||||
|
|||||||
+63
-2
@@ -1,11 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @file output.c
|
||||||
|
* @brief Screen rendering and output module for the Beluga text editor
|
||||||
|
* @details Handles all screen updates, cursor positioning, status bar rendering,
|
||||||
|
* and display synchronization using ANSI escape sequences
|
||||||
|
*/
|
||||||
|
|
||||||
#include "../include/output.h"
|
#include "../include/output.h"
|
||||||
|
#include "../include/syntax_highlighter.h"
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
extern struct editorConfig E;
|
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()
|
||||||
|
*/
|
||||||
void editorDrawRows(struct abuf *ab) {
|
void editorDrawRows(struct abuf *ab) {
|
||||||
int y;
|
int y;
|
||||||
char welcome[80];
|
char welcome[80];
|
||||||
@@ -41,13 +60,22 @@ void editorDrawRows(struct abuf *ab) {
|
|||||||
len = 0;
|
len = 0;
|
||||||
if (len > E.screencols)
|
if (len > E.screencols)
|
||||||
len = E.screencols;
|
len = E.screencols;
|
||||||
abAppend(ab, &E.row[file_row].render[E.col_offset], len);
|
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);
|
||||||
|
free(highlighted);
|
||||||
}
|
}
|
||||||
abAppend(ab, ERASE_END_LINE, 3);
|
abAppend(ab, ERASE_END_LINE, 3);
|
||||||
abAppend(ab, "\r\n", 2);
|
abAppend(ab, "\r\n", 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates scroll offsets to keep cursor visible on screen
|
||||||
|
* @details Adjusts E.row_offset and E.col_offset to ensure the cursor remains
|
||||||
|
* within the visible viewport. Also updates E.rx (rendered x-coordinate).
|
||||||
|
* @note Updates global editor state E
|
||||||
|
* @see editorRowCxToRx()
|
||||||
|
*/
|
||||||
void editorScroll() {
|
void editorScroll() {
|
||||||
E.rx = E.cursor_x;
|
E.rx = E.cursor_x;
|
||||||
if (E.cursor_y < E.numrows) {
|
if (E.cursor_y < E.numrows) {
|
||||||
@@ -68,6 +96,13 @@ void editorScroll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renders the status bar at the bottom of the screen
|
||||||
|
* @details Displays filename, line count, dirty flag, and current cursor position
|
||||||
|
* in an inverted color bar. Right-aligns the cursor position indicator.
|
||||||
|
* @param ab Pointer to append buffer structure for accumulating output
|
||||||
|
* @note Uses ANSI escape codes for color inversion
|
||||||
|
*/
|
||||||
void editorDrawStatusBar(struct abuf *ab) {
|
void editorDrawStatusBar(struct abuf *ab) {
|
||||||
int len, render_len;
|
int len, render_len;
|
||||||
char status[80], render_status[80];
|
char status[80], render_status[80];
|
||||||
@@ -95,6 +130,13 @@ void editorDrawStatusBar(struct abuf *ab) {
|
|||||||
abAppend(ab, "\r\n", 2);
|
abAppend(ab, "\r\n", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renders the message bar below the status bar
|
||||||
|
* @details Displays temporary status messages for a limited time (5 seconds).
|
||||||
|
* Only displays message if within time window and within screen width.
|
||||||
|
* @param ab Pointer to append buffer structure for accumulating output
|
||||||
|
* @note Messages are set by editorSetStatusMessage()
|
||||||
|
*/
|
||||||
void editorDrawMessageBar(struct abuf *ab) {
|
void editorDrawMessageBar(struct abuf *ab) {
|
||||||
int msg_len = strlen(E.status_msg);
|
int msg_len = strlen(E.status_msg);
|
||||||
abAppend(ab, ERASE_END_LINE, 3);
|
abAppend(ab, ERASE_END_LINE, 3);
|
||||||
@@ -106,6 +148,16 @@ void editorDrawMessageBar(struct abuf *ab) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Performs complete screen refresh and buffer synchronization
|
||||||
|
* @details Clears screen, redraws all visible content (rows, status bar, message bar),
|
||||||
|
* positions cursor, and writes accumulated buffer to stdout. This is the main
|
||||||
|
* rendering function called each frame.
|
||||||
|
* @note Updates global editor state E (via editorScroll())
|
||||||
|
* @see editorDrawRows()
|
||||||
|
* @see editorDrawStatusBar()
|
||||||
|
* @see editorDrawMessageBar()
|
||||||
|
*/
|
||||||
void editorRefreshScreen() {
|
void editorRefreshScreen() {
|
||||||
editorScroll();
|
editorScroll();
|
||||||
struct abuf ab = ABUF_INIT;
|
struct abuf ab = ABUF_INIT;
|
||||||
@@ -123,11 +175,20 @@ void editorRefreshScreen() {
|
|||||||
abAppend(&ab, buf, strlen(buf));
|
abAppend(&ab, buf, strlen(buf));
|
||||||
|
|
||||||
abAppend(&ab, SHOW_CURSOR, 6);
|
abAppend(&ab, SHOW_CURSOR, 6);
|
||||||
|
|
||||||
write(STDOUT_FILENO, ab.b, ab.len);
|
write(STDOUT_FILENO, ab.b, ab.len);
|
||||||
abFree(&ab);
|
abFree(&ab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets a temporary status message for display
|
||||||
|
* @details Formats and stores a message that will be displayed in the message bar
|
||||||
|
* for 5 seconds. Uses printf-style variable argument formatting.
|
||||||
|
* @param fmt Printf-style format string
|
||||||
|
* @param ... Variable arguments for format string
|
||||||
|
* @note Updates global editor state E (status_msg, status_msg_time)
|
||||||
|
* @see editorDrawMessageBar()
|
||||||
|
*/
|
||||||
void editorSetStatusMessage(const char *fmt, ...) {
|
void editorSetStatusMessage(const char *fmt, ...) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
#include "../include/syntax_highlighter.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
const char *c_keywords[] = {
|
||||||
|
"if", "else", "while", "for", "do", "switch", "case", "break",
|
||||||
|
"continue", "return", "goto", "struct", "union", "enum",
|
||||||
|
"typedef", "static", "extern", "const", "volatile", "sizeof",
|
||||||
|
"auto", "register", "inline", "restrict", NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
// C types
|
||||||
|
const char *c_types[] = {
|
||||||
|
"int", "char", "float", "double", "void", "long", "short",
|
||||||
|
"unsigned", "signed", "bool", NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if character is alphanumeric or underscore
|
||||||
|
int is_word_char(char c) {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||||
|
(c >= '0' && c <= '9') || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if string is a keyword
|
||||||
|
int is_keyword(const char *word) {
|
||||||
|
for (int i = 0; c_keywords[i] != NULL; i++) {
|
||||||
|
if (strcmp(word, c_keywords[i]) == 0) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if string is a type
|
||||||
|
int is_type(const char *word) {
|
||||||
|
for (int i = 0; c_types[i] != NULL; i++) {
|
||||||
|
if (strcmp(word, c_types[i]) == 0) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get color code for token type
|
||||||
|
const char *get_color(TokenType type) {
|
||||||
|
switch (type) {
|
||||||
|
case TOKEN_KEYWORD: return COLOR_KEYWORD;
|
||||||
|
case TOKEN_TYPE: return COLOR_TYPE;
|
||||||
|
case TOKEN_STRING: return COLOR_STRING;
|
||||||
|
case TOKEN_COMMENT: return COLOR_COMMENT;
|
||||||
|
case TOKEN_NUMBER: return COLOR_NUMBER;
|
||||||
|
default: return COLOR_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight a line of C code and return the highlighted string
|
||||||
|
// Returns a newly allocated string that must be freed by the caller
|
||||||
|
char *highlight_line(const char *line, int *length) {
|
||||||
|
char *result = malloc(1024); // Allocate space for result
|
||||||
|
int result_pos = 0;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (line[i] != '\0' && line[i] != '\n') {
|
||||||
|
// Skip whitespace
|
||||||
|
if (line[i] == ' ' || line[i] == '\t') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle line comments
|
||||||
|
if (line[i] == '/' && line[i + 1] == '/') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_COMMENT);
|
||||||
|
while (line[i] != '\0' && line[i] != '\n') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
}
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle block comments
|
||||||
|
if (line[i] == '/' && line[i + 1] == '*') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_COMMENT);
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
while (line[i] != '\0') {
|
||||||
|
if (line[i] == '*' && line[i + 1] == '/') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
}
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle strings
|
||||||
|
if (line[i] == '"') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s\"", COLOR_STRING);
|
||||||
|
i++;
|
||||||
|
while (line[i] != '\0' && line[i] != '"') {
|
||||||
|
if (line[i] == '\\') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
} else {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line[i] == '"') result[result_pos++] = line[i++];
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle character literals
|
||||||
|
if (line[i] == '\'') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s'", COLOR_STRING);
|
||||||
|
i++;
|
||||||
|
while (line[i] != '\0' && line[i] != '\'') {
|
||||||
|
if (line[i] == '\\') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
} else {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line[i] == '\'') result[result_pos++] = line[i++];
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle numbers
|
||||||
|
if (line[i] >= '0' && line[i] <= '9') {
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_NUMBER);
|
||||||
|
while (is_word_char(line[i]) || line[i] == '.') {
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
}
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle identifiers and keywords
|
||||||
|
if (is_word_char(line[i])) {
|
||||||
|
int start = i;
|
||||||
|
while (is_word_char(line[i])) i++;
|
||||||
|
|
||||||
|
char word[256];
|
||||||
|
strncpy(word, &line[start], i - start);
|
||||||
|
word[i - start] = '\0';
|
||||||
|
|
||||||
|
TokenType type = TOKEN_DEFAULT;
|
||||||
|
if (is_keyword(word)) type = TOKEN_KEYWORD;
|
||||||
|
else if (is_type(word)) type = TOKEN_TYPE;
|
||||||
|
|
||||||
|
result_pos += sprintf(&result[result_pos], "%s%s%s",
|
||||||
|
get_color(type), word, COLOR_RESET);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle operators and other characters
|
||||||
|
result[result_pos++] = line[i++];
|
||||||
|
}
|
||||||
|
|
||||||
|
result[result_pos] = '\0';
|
||||||
|
*length = result_pos + 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user