561 lines
17 KiB
C
561 lines
17 KiB
C
/**
|
|
* @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/buffer.h"
|
|
#include "../include/data.h"
|
|
#include "../include/define.h"
|
|
#include "../include/editor_op.h"
|
|
#include "../include/file_io.h"
|
|
#include "../include/input.h"
|
|
#include "../include/terminal.h"
|
|
#include "../include/split_screen.h"
|
|
#include "../include/completion.h"
|
|
|
|
#include <stdio.h>
|
|
#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--) {
|
|
if (!strcmp(prefix_name, E.prefix[i].prefix_name)) {
|
|
return E.prefix[i];
|
|
}
|
|
}
|
|
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
|
|
*/
|
|
const char *key_sequence = lisp_string(lisp_car(args));
|
|
args = lisp_cdr(args);
|
|
// second argument
|
|
const Lisp func = lisp_car(args);
|
|
|
|
E.key_binds = (struct keyBind_t *)realloc(
|
|
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
|
|
E.key_binds[E.number_of_keybinds - 1].key_sequence =
|
|
(char *)malloc(50 * sizeof(char));
|
|
|
|
strncpy(E.key_binds[E.number_of_keybinds - 1].key_sequence, key_sequence, 50);
|
|
|
|
E.key_binds[E.number_of_keybinds - 1].command = func;
|
|
|
|
// Third argument
|
|
args = lisp_cdr(args);
|
|
const char *prefix_name = lisp_string(lisp_car(args));
|
|
struct prefix_t prefix = find_prefix(prefix_name);
|
|
|
|
E.key_binds[E.number_of_keybinds - 1].prefix_id = prefix.prefix_id;
|
|
|
|
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;
|
|
switch (direction[0]) {
|
|
case 'u':
|
|
is_in = editorMoveCursor(ARROW_UP);
|
|
break;
|
|
case 'd':
|
|
is_in = editorMoveCursor(ARROW_DOWN);
|
|
break;
|
|
case 'r':
|
|
is_in = editorMoveCursor(ARROW_RIGHT);
|
|
break;
|
|
case 'l':
|
|
is_in = editorMoveCursor(ARROW_LEFT);
|
|
break;
|
|
}
|
|
appDebug("move lisp %d\n", 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) {
|
|
int i, j;
|
|
free(E.prefix);
|
|
for (i = 0; i < E.number_of_keybinds; ++i) {
|
|
free(E.key_binds[i].key_sequence);
|
|
}
|
|
free(E.key_binds);
|
|
// free layout
|
|
free(E.layout.panes);
|
|
|
|
// Free buffers
|
|
for (i = 0; i < E.number_of_buffer; ++i) {
|
|
free(E.buffers[i].filename);
|
|
for (j = 0; j < E.buffers[i].numrows; ++j) {
|
|
free(E.buffers[i].row[j].chars);
|
|
}
|
|
free(E.buffers[i].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 "
|
|
"another time to quit.");
|
|
--E.quit_times_buffer;
|
|
return lisp_null();
|
|
}
|
|
free_structs();
|
|
write(STDOUT_FILENO, "\x1b[2J", 4);
|
|
write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3);
|
|
disableRawMode();
|
|
lisp_shutdown(E.ctx);
|
|
exit(0);
|
|
|
|
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_editorSplitScreenVertical(Lisp args, LispError *e, LispContext ctx) {
|
|
if (E.number_of_buffer >= 2) {
|
|
splitScreenVertical(E.buffers[0].buffer_id, E.buffers[1].buffer_id);
|
|
} else {
|
|
editorSetStatusMessage("Need at least 2 buffers open");
|
|
}
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp l_editorSave(Lisp args, LispError *e, LispContext ctx) {
|
|
|
|
editorSave();
|
|
|
|
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) {
|
|
|
|
bufferInsertNewLine();
|
|
|
|
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) {
|
|
bufferInsertBytes(" ", 1);
|
|
}
|
|
|
|
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) {
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
|
buf->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) {
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
|
buf->x = buf->row[buf->y].size;
|
|
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) {
|
|
bufferDelBytes();
|
|
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) {
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
active->cursor_y = active->y_offset;
|
|
int times = E.screenrows;
|
|
while (--times) {
|
|
editorMoveCursor(ARROW_UP);
|
|
}
|
|
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) {
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
|
active->cursor_y = active->y_offset + E.screenrows - 1;
|
|
if (active->cursor_y > buffer->numrows) {
|
|
active->cursor_y = buffer->numrows;
|
|
}
|
|
int times = E.screenrows;
|
|
while (--times) {
|
|
editorMoveCursor(ARROW_DOWN);
|
|
}
|
|
|
|
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) {
|
|
// editorOpen(filename);
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
active->buffer_id = bufferCreate(filename, READ_AND_WRITE);
|
|
}
|
|
free(filename);
|
|
|
|
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) {
|
|
const char *src = lisp_string(lisp_car(args));
|
|
bufferInsertBytes(src, strlen(src));
|
|
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));
|
|
appDebug("%s\n", package_name);
|
|
char *package_dir = (char *)calloc(256, sizeof(char));
|
|
FILE *fd_package = NULL;
|
|
strcat(package_dir, getenv("HOME"));
|
|
strcat(package_dir, "/.beluga/packages/");
|
|
strcat(package_dir, package_name);
|
|
strcat(package_dir, "/init.lisp");
|
|
appDebug("%s\n", package_dir);
|
|
fd_package = fopen(package_dir, "r");
|
|
lisp_eval(lisp_read_file(fd_package, &E.ctx_error, E.ctx), &E.ctx_error,
|
|
E.ctx);
|
|
fclose(fd_package);
|
|
free(package_dir);
|
|
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) {
|
|
appDebug("switch buffer\n");
|
|
if (E.number_of_buffer > 0) {
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
int next_idx = (active->buffer_id + 1) % E.number_of_buffer;
|
|
active->buffer_id = next_idx;
|
|
}
|
|
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx) {
|
|
splitScreenSwitchPane();
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx) {
|
|
if (E.layout.num_panes - 1) {
|
|
splitScreenUnify();
|
|
}
|
|
return lisp_null();
|
|
}
|
|
|
|
/**
|
|
* @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 bufferFind_L(Lisp args, LispError *e, LispContext ctx) {
|
|
appDebug("LispFind\n");
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
|
bufferFind(buffer);
|
|
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 bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx) {
|
|
appDebug("LispFind\n");
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
|
bufferFindReverse(buffer);
|
|
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;
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
|
if (buffer->row[buffer->y].chars[buffer->x] == 0) {
|
|
returned_char = lisp_make_char('a');
|
|
} else {
|
|
returned_char = lisp_make_char(buffer->row[buffer->y].chars[buffer->x]);
|
|
}
|
|
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
|
|
*/
|
|
const char *prefix_name = lisp_string(lisp_car(args));
|
|
struct prefix_t prefix = find_prefix(prefix_name);
|
|
E.prefix_state = prefix.prefix_id;
|
|
editorSetStatusMessage("prefix %s", prefix.prefix_name);
|
|
appDebug("%s set\n", prefix_name);
|
|
|
|
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));
|
|
E.prefix[E.number_of_prefix].prefix_id = E.number_of_prefix;
|
|
strncpy(E.prefix[E.number_of_prefix].prefix_name, lisp_string(lisp_car(args)),
|
|
64);
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp editorPaste(Lisp args, LispError *e, LispContext ctx)
|
|
{
|
|
const char *char_to_paste = editorGetClipboard();
|
|
appDebug("editor-paste, %s\n", char_to_paste);
|
|
bufferInsertBytes(char_to_paste, (int) strlen(char_to_paste));
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp editorCutEndLine(Lisp args, LispError *e, LispContext ctx)
|
|
{
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
|
int bytes_to_delete = buffer->row[buffer->y].size - buffer->x;
|
|
editorSetClipboard(&buffer->row[buffer->y].chars[buffer->x], bytes_to_delete);
|
|
buffer->x = buffer->row[buffer->y].size;
|
|
while (bytes_to_delete--)
|
|
{
|
|
bufferDelBytes();
|
|
}
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp editorMoveBegBuffer(Lisp args, LispError *e, LispContext ctx)
|
|
{
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
|
buffer->x = 0;
|
|
buffer->y = 0;
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp editorMoveEndBuffer(Lisp args, LispError *e, LispContext ctx)
|
|
{
|
|
EditorPane *active = splitScreenGetActivePane();
|
|
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
|
buffer->x = buffer->row[buffer->numrows-1].size;
|
|
buffer->y = buffer->numrows-1;
|
|
return lisp_null();
|
|
}
|
|
|
|
Lisp editorAutoComplete(Lisp args, LispError *e, LispContext ctx)
|
|
{
|
|
createContextBuffer(E.cursor_x - 2, E.cursor_y + 1, "hello");
|
|
return lisp_null();
|
|
} |