Syntax highlighting and comment
This commit is contained in:
@@ -67,6 +67,7 @@ struct editorConfig {
|
||||
struct const_t constantes;
|
||||
int quit_times_buffer;
|
||||
|
||||
char *init_file_path;
|
||||
FILE *fd_init_file;
|
||||
Lisp env;
|
||||
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);
|
||||
}
|
||||
|
||||
if (out_error) *out_error = error;
|
||||
if (out_error)
|
||||
*out_error = error;
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
data = new_data;
|
||||
memset(data + *out_size, 0, cap - *out_size);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit");
|
||||
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-q = quit");
|
||||
|
||||
|
||||
while (1) {
|
||||
|
||||
@@ -13,6 +13,7 @@ src_files = files(
|
||||
'main.c',
|
||||
'src/append_buffer.c',
|
||||
'src/editor_op.c',
|
||||
'src/syntax_highlighter.c',
|
||||
'src/file_io.c',
|
||||
'src/init.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/data.h"
|
||||
#include "../include/define.h"
|
||||
@@ -11,6 +18,14 @@
|
||||
#include <stdlib.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]) {
|
||||
int i = E.number_of_prefix + 1;
|
||||
while (i--) {
|
||||
@@ -21,6 +36,19 @@ struct prefix_t find_prefix(const char prefix_name[64]) {
|
||||
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) {
|
||||
/*
|
||||
* 3 arguments keybind command prefix
|
||||
@@ -49,6 +77,17 @@ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
const char *direction = lisp_string(lisp_car(args));
|
||||
int is_in = 0;
|
||||
@@ -70,6 +109,12 @@ Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
int i;
|
||||
free(E.prefix);
|
||||
@@ -78,12 +123,28 @@ void free_structs(void) {
|
||||
}
|
||||
free(E.key_binds);
|
||||
free(E.filename);
|
||||
free(E.row->chars);
|
||||
free(E.row->render);
|
||||
for (i = 0; i < E.numrows; ++i) {
|
||||
free(E.row[i].render);
|
||||
free(E.row[i].chars);
|
||||
}
|
||||
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) {
|
||||
if (E.dirty && E.quit_times_buffer > 0) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
|
||||
editorSave();
|
||||
@@ -108,6 +178,15 @@ Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
|
||||
editorInsertNewLine();
|
||||
@@ -115,6 +194,15 @@ Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
E.cursor_x = 0;
|
||||
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) {
|
||||
if (E.cursor_y < E.numrows) {
|
||||
E.cursor_x = E.row[E.cursor_y].size;
|
||||
@@ -136,11 +242,30 @@ Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
editorDelChar();
|
||||
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) {
|
||||
E.cursor_y = E.row_offset;
|
||||
int times = E.screenrows;
|
||||
@@ -150,6 +275,16 @@ Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
E.cursor_y = E.row_offset + E.screenrows - 1;
|
||||
if (E.cursor_y > E.numrows) {
|
||||
@@ -163,6 +298,17 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
|
||||
if (filename){
|
||||
@@ -174,12 +320,32 @@ Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
char c = lisp_string(lisp_car(args))[0];
|
||||
editorInsertChar(c);
|
||||
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) {
|
||||
const char *package_name = lisp_string(lisp_car(args));
|
||||
fprintf(stderr, "%s\n", package_name);
|
||||
@@ -199,16 +365,45 @@ Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
|
||||
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) {
|
||||
editorDelRow(E.cursor_y);
|
||||
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) {
|
||||
fprintf(stderr, "LispFind\n");
|
||||
editorFind();
|
||||
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 returned_char;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
E.prefix = (struct prefix_t *)realloc(E.prefix, (++(E.number_of_prefix) + 1) *
|
||||
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/input.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 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) {
|
||||
int tot_len = 0;
|
||||
int j;
|
||||
@@ -36,20 +53,42 @@ char *editorRowsToString(int *buffer_len) {
|
||||
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) {
|
||||
E.cursor_x = 0;
|
||||
E.cursor_y = 0;
|
||||
E.rx = 0;
|
||||
E.row_offset = 0;
|
||||
E.col_offset = 0;
|
||||
for (int i = 0; i < E.numrows; ++i) {
|
||||
free(E.row[i].chars);
|
||||
free(E.row[i].render);
|
||||
}
|
||||
E.numrows = 0;
|
||||
E.row = NULL;
|
||||
E.dirty = 0;
|
||||
free(E.filename);
|
||||
E.filename = NULL;
|
||||
E.status_msg[0] = '\0';
|
||||
E.status_msg_time = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Opens a file for editing
|
||||
* @details Loads file content into editor rows, one line per row. If another file
|
||||
* is already open, it is closed first (without saving). File is opened in a+
|
||||
* (read/append) mode to allow both reading and modification.
|
||||
* @param filename Path to the file to open (relative or absolute)
|
||||
* @note Updates global editor state E
|
||||
* @note Calls die() on file open failure
|
||||
* @note Newline characters are stripped from loaded lines
|
||||
* @see editorInsertRow()
|
||||
*/
|
||||
void editorOpen(char *filename) {
|
||||
FILE *fp;
|
||||
|
||||
@@ -59,7 +98,6 @@ void editorOpen(char *filename) {
|
||||
E.state = READ_AND_WRITE;
|
||||
}
|
||||
|
||||
free(E.filename);
|
||||
E.filename = strdup(filename);
|
||||
|
||||
fp = fopen(filename, "a+");
|
||||
@@ -76,12 +114,24 @@ void editorOpen(char *filename) {
|
||||
--line_len;
|
||||
}
|
||||
editorInsertRow(E.numrows, line, line_len);
|
||||
free(line);
|
||||
line = NULL;
|
||||
}
|
||||
free(line);
|
||||
fclose(fp);
|
||||
E.dirty = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Saves the current file to disk
|
||||
* @details Prompts for filename if not set, converts all rows to a buffer,
|
||||
* writes to disk using open/ftruncate/write, and updates dirty flag.
|
||||
* Displays status messages on success or failure.
|
||||
* @note Updates global editor state E (dirty flag)
|
||||
* @note If no filename is set, prompts user via editorPrompt()
|
||||
* @note Uses O_RDWR | O_CREAT with mode 0644
|
||||
* @see editorRowsToString()
|
||||
*/
|
||||
void editorSave() {
|
||||
int len;
|
||||
char *buf;
|
||||
@@ -111,7 +161,18 @@ void editorSave() {
|
||||
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() {
|
||||
fprintf(stderr, "searching\n");
|
||||
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
|
||||
if (query == NULL) return;
|
||||
int i;
|
||||
@@ -127,4 +188,3 @@ void editorFind() {
|
||||
}
|
||||
free(query);
|
||||
}
|
||||
|
||||
|
||||
+6
-7
@@ -9,7 +9,7 @@
|
||||
#include "../include/lisp.h"
|
||||
#include "../include/lisp_lib.h"
|
||||
|
||||
extern struct editorConfig;
|
||||
struct editorConfig;
|
||||
|
||||
void registerBuiltin(char *key_sequence, LispCFunc f) {
|
||||
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
|
||||
@@ -40,7 +40,7 @@ void initBuiltins() {
|
||||
}
|
||||
|
||||
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_y = 0;
|
||||
E.rx = 0;
|
||||
@@ -67,10 +67,10 @@ void initEditor() {
|
||||
|
||||
E.prefix_state = 0;
|
||||
|
||||
strcat(init_file_path, getenv("HOME"));
|
||||
strcat(init_file_path, "/.beluga/config/init.lisp");
|
||||
printf("%s\n", init_file_path);
|
||||
E.fd_init_file = fopen(init_file_path, "r");
|
||||
strcat(E.init_file_path, getenv("HOME"));
|
||||
strcat(E.init_file_path, "/.beluga/config/init.lisp");
|
||||
// printf("%s\n", init_file_path);
|
||||
E.fd_init_file = fopen(E.init_file_path, "r");
|
||||
E.ctx = lisp_init();
|
||||
E.env = lisp_env(E.ctx);
|
||||
lisp_lib_load(E.ctx);
|
||||
@@ -79,7 +79,6 @@ void initEditor() {
|
||||
|
||||
// Read config file
|
||||
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) {
|
||||
die("init failed");
|
||||
}
|
||||
|
||||
+84
-10
@@ -9,25 +9,46 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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;
|
||||
struct dirent *entry;
|
||||
char directory[128];
|
||||
char predict[128];
|
||||
const char *last_slash;
|
||||
int predict_len = 0;
|
||||
size_t dir_len;
|
||||
|
||||
// path is a directory
|
||||
if (path[strlen(path) - 1] == '/') {
|
||||
return path;
|
||||
}
|
||||
|
||||
// Find dir name
|
||||
char *last_slash = strrchr(path, '/');
|
||||
last_slash = strrchr(path, '/');
|
||||
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);
|
||||
predict_len = strlen(path) - dir_len - 1;
|
||||
strncpy(predict, last_slash + 1, predict_len);
|
||||
@@ -44,13 +65,16 @@ char *file_completion(const char *path) {
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
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);
|
||||
|
||||
struct stat st;
|
||||
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
strcat(full_path, "/"); // add slash for directories
|
||||
}
|
||||
closedir(dir);
|
||||
dir = NULL;
|
||||
free(entry);
|
||||
|
||||
return strdup(full_path);
|
||||
}
|
||||
@@ -59,13 +83,24 @@ char *file_completion(const char *path) {
|
||||
// Cleanup when no more entries
|
||||
closedir(dir);
|
||||
dir = NULL;
|
||||
free(entry);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* \fn char * editorPrompt(struct editorConfig *E, char *prompt, char bPathMode)
|
||||
* \brief Return user input in a prompt when enter is hit. */
|
||||
|
||||
* @brief Displays an interactive prompt and returns user input
|
||||
* @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) {
|
||||
size_t buf_size = 128;
|
||||
char *buf = malloc(buf_size);
|
||||
@@ -106,7 +141,9 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||
}
|
||||
memset(buf, 0, 128);
|
||||
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[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) {
|
||||
static char key_str[32];
|
||||
|
||||
@@ -187,6 +235,15 @@ char *key_to_string(int key) {
|
||||
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) {
|
||||
erow *row = (E.cursor_y >= E.numrows) ? NULL : &E.row[E.cursor_y];
|
||||
int row_len;
|
||||
@@ -228,6 +285,15 @@ int editorMoveCursor(int key) {
|
||||
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 i;
|
||||
int previous_state = 0;
|
||||
@@ -235,7 +301,7 @@ int executeKeyBind(char *key_sequence) {
|
||||
for (i = 0; i < E.number_of_keybinds; ++i) {
|
||||
if (!strcmp(key_sequence, E.key_binds[i].key_sequence)) {
|
||||
if (E.prefix_state != E.key_binds[i].prefix_id) {
|
||||
return 0;
|
||||
continue;
|
||||
}
|
||||
previous_state = E.prefix_state;
|
||||
// It's a symbol, create a function call
|
||||
@@ -249,6 +315,14 @@ int executeKeyBind(char *key_sequence) {
|
||||
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() {
|
||||
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/syntax_highlighter.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
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) {
|
||||
int y;
|
||||
char welcome[80];
|
||||
@@ -41,13 +60,22 @@ void editorDrawRows(struct abuf *ab) {
|
||||
len = 0;
|
||||
if (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, "\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() {
|
||||
E.rx = E.cursor_x;
|
||||
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) {
|
||||
int len, render_len;
|
||||
char status[80], render_status[80];
|
||||
@@ -95,6 +130,13 @@ void editorDrawStatusBar(struct abuf *ab) {
|
||||
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) {
|
||||
int msg_len = strlen(E.status_msg);
|
||||
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() {
|
||||
editorScroll();
|
||||
struct abuf ab = ABUF_INIT;
|
||||
@@ -123,11 +175,20 @@ void editorRefreshScreen() {
|
||||
abAppend(&ab, buf, strlen(buf));
|
||||
|
||||
abAppend(&ab, SHOW_CURSOR, 6);
|
||||
|
||||
write(STDOUT_FILENO, ab.b, ab.len);
|
||||
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, ...) {
|
||||
va_list ap;
|
||||
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