Syntax highlighting and comment

This commit is contained in:
Arthur Barraux
2026-01-11 19:41:30 +01:00
parent 815114923d
commit fc93832130
11 changed files with 623 additions and 25 deletions
+1
View File
@@ -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
View File
@@ -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);
+20
View 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);
+1 -1
View File
@@ -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) {
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+163
View File
@@ -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;
}