Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b6b3d1766 | |||
| 3d49a0e2eb | |||
| 777dc5cb74 | |||
| c4d772e465 | |||
| 310498d582 | |||
| edb5384f0e | |||
| bc66e673fa | |||
| 8e1b4d2f86 | |||
| 87746f09ce | |||
| 5a7b073041 | |||
| eae85c32ca |
+36
-36
@@ -1,43 +1,43 @@
|
||||
**#%#*****###%**
|
||||
*##+--------------------=##*
|
||||
#*=----------------------------=*#*
|
||||
#*------------------------------------*#
|
||||
%+----------------------------------------=#*
|
||||
#+---------------------------------------------##
|
||||
*#-------------------------------------------------=##
|
||||
*##+--------------------=##*
|
||||
#*=----------------------------=*#*
|
||||
#*------------------------------------*#
|
||||
%+----------------------------------------=#*
|
||||
#+---------------------------------------------##
|
||||
*#-------------------------------------------------=##
|
||||
*#----------------------------------------------------:-##
|
||||
#----------------------------------------------------------##
|
||||
#=--------------------------------------------------------------##
|
||||
+--------------------------+@#-%*-----------------------------------#*
|
||||
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.3
|
||||
#----------------------------------------------------------##
|
||||
#=--------------------------------------------------------------##
|
||||
+--------------------------+@#-%*-----------------------------------#*
|
||||
+--------------------------%@@@@#-------------------------------------** BELUGA - VERSION 2.4
|
||||
*-=-------------------------#@@*---------------------------------------=%
|
||||
%*#==--------------------------------------------------------------------+# ----- KEY-BINDS -----
|
||||
*%%=-=--------------------------------------------------------------------=# CTRL-x CTRL-c leave
|
||||
%=--------------------------------------------------------------------------#* CTRL-x CTRL-s save
|
||||
%-----------------------------------------------------------------------------** CTRL-x f open-file
|
||||
*+--=---===----=---------------=*-----------------------------------------------**
|
||||
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
|
||||
*%**=-----------==== ==---------------------------------=+#+-----------------=#
|
||||
*%=----------------------------------------------------------=#*---------------#
|
||||
##=----------------------------------=------------------------+%=------------+#
|
||||
#%+---------------------------------=*------------------------+%------------#
|
||||
*#%*=-------------=-----------------#-------------------------#+----------#
|
||||
**#%#*******+=======-------------#=------------------------#----------#
|
||||
#===#*=======------------------#*----=-----------=--=##*-----------#
|
||||
-====##=------------------------*%+------------=*#+=====----------#
|
||||
--=====+#*=----------------------=-=+*#####***+=======-----------=*
|
||||
%------=====*%*=-------------------------========-----------------+*
|
||||
*-=--------====%%###+=--------------------------=-----------------#
|
||||
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
|
||||
%=-------#* #%*=-----------------+#
|
||||
*%+--=## ##=-----------------=#*
|
||||
** #+----=-------------------#*
|
||||
%+----------------------------#*
|
||||
*%-------------==----------------+#
|
||||
##--------------==------------------#
|
||||
*#--------------===%-----------------=%
|
||||
##---------------=-##*-----------------+#
|
||||
*#---------------==#+=#%-----------------%
|
||||
*%---------------+# %*---------------#*
|
||||
*#------------=+#* #%*=-----------#*
|
||||
#****##****** *#%%##+=----%
|
||||
*+--=---===----=---------------=*-----------------------------------------------**
|
||||
#--=## *#%#*+==----==+**+----------------------= ***=---------------------%
|
||||
*%**=-----------==== ==---------------------------------=+#+-----------------=#
|
||||
*%=----------------------------------------------------------=#*---------------#
|
||||
##=----------------------------------=------------------------+%=------------+#
|
||||
#%+---------------------------------=*------------------------+%------------#
|
||||
*#%*=-------------=-----------------#-------------------------#+----------#
|
||||
**#%#*******+=======-------------#=------------------------#----------#
|
||||
#===#*=======------------------#*----=-----------=--=##*-----------#
|
||||
-====##=------------------------*%+------------=*#+=====----------#
|
||||
--=====+#*=----------------------=-=+*#####***+=======-----------=*
|
||||
%------=====*%*=-------------------------========-----------------+*
|
||||
*-=--------====%%###+=--------------------------=-----------------#
|
||||
#-----------=% +*##%%%%%%@@%%%%####*==---------------------**
|
||||
%=-------#* #%*=-----------------+#
|
||||
*%+--=## ##=-----------------=#*
|
||||
** #+----=-------------------#*
|
||||
%+----------------------------#*
|
||||
*%-------------==----------------+#
|
||||
##--------------==------------------#
|
||||
*#--------------===%-----------------=%
|
||||
##---------------=-##*-----------------+#
|
||||
*#---------------==#+=#%-----------------%
|
||||
*%---------------+# %*---------------#*
|
||||
*#------------=+#* #%*=-----------#*
|
||||
#****##****** *#%%##+=----%
|
||||
|
||||
+8
-5
@@ -48,8 +48,8 @@
|
||||
))
|
||||
))
|
||||
|
||||
(define enter-and-tab
|
||||
(lambda ()
|
||||
(define enter-and-tab
|
||||
(lambda ()
|
||||
(editor-insert-new-line)
|
||||
(let ((is-in (move-cursor "up")))
|
||||
(do ((ch (editor-read-char) (editor-read-char)))
|
||||
@@ -80,10 +80,13 @@
|
||||
(map-key "PAGE-DOWN" move-cursor-page-down "no-prefix")
|
||||
(map-key "f" editor-open-file "user")
|
||||
(map-key "TAB" editor-insert-tab "no-prefix")
|
||||
(map-key "CTRL-k" editor-del-row "no-prefix")
|
||||
(map-key "CTRL-s" buffer-find "no-prefix")
|
||||
(map-key "CTRL-r" editor-move-to-end-of-word "no-prefix")
|
||||
(map-key "CTRL-r" buffer-find-reverse "no-prefix")
|
||||
(map-key "ARROW-RIGHT" editor-switch-next-buffer "user")
|
||||
(map-key "\"" editor-split-screen-vertical "user")
|
||||
(map-key "o" editor-switch-next-pane "user")
|
||||
(map-key "*" editor-unify-panes "user")
|
||||
|
||||
(map-key "CTRL-y" editor-paste "no-prefix")
|
||||
(map-key "CTRL-k" editor-cut-end-line "no-prefix")
|
||||
(map-key "a" editor-move-cursor-beg-buffer "user")
|
||||
(map-key "z" editor-move-cursor-end-buffer "user")
|
||||
|
||||
+12
-1
@@ -13,7 +13,7 @@
|
||||
* @param filename Path to the file
|
||||
* @return Buffer ID on success, -1 on failure
|
||||
*/
|
||||
int bufferCreate(const char *filename);
|
||||
int bufferCreate(const char *filename, enum bufferStatus_e state);
|
||||
|
||||
/**
|
||||
* @brief Switches to a specific buffer by ID
|
||||
@@ -55,4 +55,15 @@ int bufferSave(void);
|
||||
*/
|
||||
int bufferSaveAll(void);
|
||||
|
||||
void bufferFind(struct buffer_t *buf);
|
||||
void bufferFindReverse(struct buffer_t* buf);
|
||||
void bufferInsertNewLine();
|
||||
void bufferInsertBytes(const char *src, int n);
|
||||
void bufferDelBytes();
|
||||
void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at, const char *src, int n);
|
||||
void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n);
|
||||
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len);
|
||||
void bufferFreeRow(row_t *row);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
+7
-3
@@ -17,7 +17,6 @@ Lisp l_editorInsertNewLine(Lisp args, LispError* e, LispContext ctx);
|
||||
Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp editorDelRow_L(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp editorReadChar_L(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
// Editor
|
||||
@@ -36,6 +35,7 @@ Lisp addPackage(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp bufferFindReverse_L(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
|
||||
// Pane
|
||||
@@ -45,12 +45,16 @@ Lisp editorSwitchNextPane(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
|
||||
Lisp editorPaste(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp editorCutEndLine(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
|
||||
|
||||
Lisp editorMoveBegBuffer(Lisp args, LispError *e, LispContext ctx);
|
||||
Lisp editorMoveEndBuffer(Lisp args, LispError *e, LispContext ctx);
|
||||
|
||||
// Other
|
||||
|
||||
|
||||
|
||||
void free_structs(void);
|
||||
|
||||
#endif
|
||||
|
||||
+24
-18
@@ -8,18 +8,23 @@
|
||||
#include "lisp.h"
|
||||
|
||||
/**
|
||||
* \struct erow
|
||||
* \struct row_t
|
||||
* \brief Store one editor row
|
||||
* \param
|
||||
* */
|
||||
|
||||
typedef struct frow {
|
||||
typedef struct row {
|
||||
int size; /**< Size of the line */
|
||||
int rsize; /**< Size of the render line */
|
||||
int cap; /**< Size of the render line */
|
||||
char *chars; /**< Characters of the line */
|
||||
char *render; /**< The actual line we will print */
|
||||
} frow;
|
||||
} row_t;
|
||||
|
||||
typedef struct context_buffer_t
|
||||
{
|
||||
int editor_x, editor_y;
|
||||
int width, height;
|
||||
row_t *rows;
|
||||
} ContextBuffer;
|
||||
|
||||
/**
|
||||
* @brief Split modes for screen layout
|
||||
@@ -35,15 +40,12 @@ typedef enum {
|
||||
*/
|
||||
typedef struct {
|
||||
int buffer_id; // Which buffer this pane displays
|
||||
int start_row; // Starting row on screen
|
||||
int start_col; // Starting column on screen
|
||||
int height; // Height of this pane
|
||||
int width; // Width of this pane
|
||||
int origin_x, origin_y;
|
||||
int cursor_x; // Local cursor x in this pane
|
||||
int cursor_y; // Local cursor y in this pane
|
||||
int rx, ry;
|
||||
int row_offset; // Scroll offset for rows
|
||||
int col_offset; // Scroll offset for columns
|
||||
int x_offset, y_offset;
|
||||
int is_active; // Is this pane currently active
|
||||
} EditorPane;
|
||||
|
||||
@@ -97,15 +99,12 @@ enum buffer_type { FILE_BUFF, TERMINAL_BUFF };
|
||||
struct buffer_t {
|
||||
enum buffer_type type;
|
||||
int buffer_id;
|
||||
int width, height;
|
||||
int x, y; /**< Position in the buffer (cursor) */
|
||||
frow *row;
|
||||
int x, y; /**< Position in the file */
|
||||
row_t *row;
|
||||
int numrows;
|
||||
char *filename;
|
||||
enum bufferStatus_e state;
|
||||
int dirty; /**< Has this buffer been modified since last save */
|
||||
int row_offset; /**< Scroll offset for rows in this buffer */
|
||||
int col_offset; /**< Scroll offset for columns in this buffer */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -113,14 +112,19 @@ struct buffer_t {
|
||||
* \brief Containing our editor state.
|
||||
*/
|
||||
struct editorConfig {
|
||||
int cursor_x, cursor_y; /**< Cursor position */
|
||||
int screenrows; /**< Terminal height*/
|
||||
int screencols; /**< Terminal width*/
|
||||
|
||||
ScreenLayout layout;
|
||||
|
||||
|
||||
row_t *rows; /**< Store all the rows printed */
|
||||
|
||||
ContextBuffer* context_buffers;
|
||||
|
||||
int dirty;
|
||||
int prefix_state;
|
||||
char status_msg[80];
|
||||
|
||||
char *status_msg;
|
||||
time_t status_msg_time;
|
||||
struct termios orig_termios; /**< Terminal communication interface */
|
||||
|
||||
@@ -139,6 +143,7 @@ struct editorConfig {
|
||||
|
||||
struct prefix_t *prefix;
|
||||
int number_of_prefix;
|
||||
int prefix_state;
|
||||
|
||||
struct buffer_t buffers[64];
|
||||
int number_of_buffer;
|
||||
@@ -158,4 +163,5 @@ struct abuf {
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
+26
-13
@@ -2,28 +2,41 @@
|
||||
#define DEFINE_H_
|
||||
|
||||
#define CTRL_KEY(k) ((k) & 0x1f)
|
||||
#define ALT(k) ((k) | 0x200) // set bit 9 to mark as Option combo
|
||||
|
||||
|
||||
#define ESCAPE '\x1b'
|
||||
#define CURSOR_TOP_LEFT "\x1b[H"
|
||||
#define HIDE_CURSOR "\x1b[?25l"
|
||||
#define SHOW_CURSOR "\x1b[?25h"
|
||||
#define ERASE_END_LINE "\x1b[K"
|
||||
#define TAB "\t"
|
||||
#define SPACE "\x20"
|
||||
#define APP_DEBUG
|
||||
|
||||
enum editorKey {
|
||||
BACKSPACE = 127,
|
||||
ARROW_LEFT = 1000,
|
||||
ARROW_RIGHT,
|
||||
ARROW_UP,
|
||||
ARROW_DOWN,
|
||||
DEL_KEY,
|
||||
BEG_LINE,
|
||||
END_LINE,
|
||||
PAGE_UP,
|
||||
PAGE_DOWN,
|
||||
};
|
||||
enum editorKey_e {
|
||||
BACKSPACE = 127,
|
||||
ARROW_LEFT = 1000,
|
||||
ARROW_RIGHT,
|
||||
ARROW_UP,
|
||||
ARROW_DOWN,
|
||||
DEL_KEY,
|
||||
BEG_LINE,
|
||||
END_LINE,
|
||||
PAGE_UP,
|
||||
PAGE_DOWN,
|
||||
};
|
||||
|
||||
#define ABUF_INIT {NULL, 0}
|
||||
|
||||
#define BELUGA_VERSION "1.1"
|
||||
#define BELUGA_VERSION "2.4"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define CLIPBOARD_COPY_CMD "pbcopy"
|
||||
#define CLIPBOARD_PASTE_CMD "pbpaste"
|
||||
#else
|
||||
#define CLIPBOARD_COPY_CMD "xclip -selection clipboard"
|
||||
#define CLIPBOARD_PASTE_CMD "xclip -selection clipboard -o"
|
||||
#endif
|
||||
|
||||
#endif // DEFINE_H_
|
||||
|
||||
+5
-5
@@ -2,12 +2,12 @@
|
||||
#define EDITOR_OP_H_
|
||||
|
||||
#include "data.h"
|
||||
void bufferInsertChar(int c);
|
||||
|
||||
void bufferInsertNewLine();
|
||||
|
||||
void bufferDelChar();
|
||||
|
||||
void editorSetStatusMessage(const char *fmt, ...);
|
||||
int editorRowCharCount(row_t *row, int x);
|
||||
int editorRowCxToByte(const row_t *row, int cursor_x);
|
||||
char *editorGetClipboard(void);
|
||||
void editorSetClipboard(const char *text, int len);
|
||||
|
||||
|
||||
#endif // EDITOR_OP_H_
|
||||
|
||||
@@ -2,21 +2,14 @@
|
||||
#define FILE_IO_H_
|
||||
|
||||
#include "data.h"
|
||||
#include "row_op.h"
|
||||
#include "terminal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
char *bufferRowsToString(struct buffer_t *buf,int *buffer_len);
|
||||
|
||||
|
||||
void editorCloseFile(void);
|
||||
|
||||
void editorOpen(struct buffer_t *buffer);
|
||||
|
||||
void editorSave();
|
||||
|
||||
void bufferFind(struct buffer_t *buf);
|
||||
|
||||
#endif // FILE_IO_H_
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
|
||||
char *editorPrompt(char *prompt, char * PlaceHolder, char bPathMode);
|
||||
|
||||
char *key_to_string(int key);
|
||||
const char *fileCompletion(const char *path);
|
||||
|
||||
int editorMoveCursor(int key);
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
* \brief Draws left rows of the editor.
|
||||
*/
|
||||
|
||||
void editorDrawRows(struct abuf *ab);
|
||||
|
||||
void editorRefreshScreen();
|
||||
|
||||
void editorScroll();
|
||||
|
||||
@@ -8,22 +8,8 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int bufferRowCxToRx(frow *row, int cursor_x);
|
||||
|
||||
int bufferRowRxToCx(frow *row, int rx);
|
||||
|
||||
void bufferUpdatfrow(frow *row);
|
||||
|
||||
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len);
|
||||
|
||||
void bufferFrefrow(frow *row);
|
||||
|
||||
void bufferDelRow(struct buffer_t *buffer, int at);
|
||||
|
||||
void bufferRowInsertChar(struct buffer_t *buffer, frow *row, int at, int c);
|
||||
|
||||
void bufferRowAppendString(struct buffer_t *buffer, frow *row, char *s, size_t len);
|
||||
|
||||
void bufferRowDelchar(struct buffer_t *buffer, frow *row, int at);
|
||||
|
||||
#endif // ROW_OP_H_
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef SYNTAX_HIGHLIGHTER_H_
|
||||
#define SYNTAX_HIGHLIGHTER_H_
|
||||
|
||||
#include "color.h"
|
||||
|
||||
// Color codes that only affect foreground, preserving your background color
|
||||
#define COLOR_RESET "\x1b[39m" // Reset all (4 bytes)
|
||||
@@ -20,4 +19,3 @@ typedef enum {
|
||||
char *highlight_line(const char * line, int *length);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
+4
-2
@@ -4,8 +4,6 @@
|
||||
|
||||
/* includes */
|
||||
|
||||
#include "data.h"
|
||||
#include "define.h"
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -31,4 +29,8 @@ int getCursorPosition(int *rows, int *cols);
|
||||
|
||||
int getWindowSize(int *rows, int *cols);
|
||||
|
||||
char *keyToString(int key);
|
||||
|
||||
void appDebug(const char *fmt, ...);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by Giorgio on 01/05/2026.
|
||||
//
|
||||
|
||||
#ifndef BELUGA_UTF8_H
|
||||
#define BELUGA_UTF8_H
|
||||
#include <stdint.h>
|
||||
|
||||
uint32_t readUtf8Char(void);
|
||||
int utf8Encode(uint32_t cp, char *buf);
|
||||
int utf8Seqlen(unsigned char c);
|
||||
int codepointWidth(uint32_t codepoint);
|
||||
uint32_t utf8Decode(const char** s);
|
||||
|
||||
|
||||
#endif //BELUGA_UTF8_H
|
||||
Executable → Regular
@@ -5,19 +5,16 @@
|
||||
* interactions. \version 0.1 \date 21 septembre 2024
|
||||
*/
|
||||
|
||||
#include "include/buffer.h"
|
||||
#include "include/split_screen.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define _DEFAULT_SOURCE
|
||||
#define _BSD_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "include/buffer.h"
|
||||
#include "include/split_screen.h"
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "include/data.h"
|
||||
#include "include/file_io.h"
|
||||
#include "include/init.h"
|
||||
#include "include/input.h"
|
||||
#include "include/editor_op.h"
|
||||
@@ -28,20 +25,21 @@ struct editorConfig E;
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
char * splash_screen = (char *) calloc(256, sizeof(char));
|
||||
|
||||
|
||||
enableRawMode();
|
||||
initEditor();
|
||||
if (argc >= 2) {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
active->buffer_id = bufferCreate(argv[1]);
|
||||
active->buffer_id = bufferCreate(argv[1], READ_AND_WRITE);
|
||||
} else {
|
||||
strcat(splash_screen, getenv("HOME"));
|
||||
strcat(splash_screen, "/.beluga/assets/beluga.txt");
|
||||
fprintf(stderr, "%s\n", splash_screen);
|
||||
|
||||
appDebug("splash : %s\n", splash_screen);
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
active->buffer_id = bufferCreate(splash_screen);
|
||||
active->buffer_id = bufferCreate(splash_screen, READ_ONLY);
|
||||
}
|
||||
//free(splash_screen);
|
||||
free(splash_screen);
|
||||
|
||||
editorSetStatusMessage("HELP: Ctrl-x Ctrl-s = save | Ctrl-x Ctrl-c = quit");
|
||||
|
||||
|
||||
+4
-1
@@ -22,11 +22,14 @@ src_files = files(
|
||||
'src/terminal.c',
|
||||
'src/builtins.c',
|
||||
'src/buffer.c',
|
||||
'src/split_screen.c'
|
||||
'src/split_screen.c',
|
||||
'src/utf8.c'
|
||||
)
|
||||
|
||||
# Executable
|
||||
executable('beluga',
|
||||
src_files,
|
||||
dependencies: [m]
|
||||
)
|
||||
dependencies: [m]
|
||||
)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "../include/append_buffer.h"
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
void abAppend(struct abuf *ab, const char *s, int len) {
|
||||
char *new = realloc(ab->b, ab->len + len);
|
||||
|
||||
|
||||
+312
-71
@@ -11,18 +11,23 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "include/input.h"
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
/**
|
||||
* @brief Finds a buffer by filename
|
||||
* @param filename The filename to search for
|
||||
* @return Buffer ID if found, -1 if not found
|
||||
*/
|
||||
static int bufferFindByFilename(const char *filename) {
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
if (E.buffers[i].filename != NULL &&
|
||||
strcmp(E.buffers[i].filename, filename) == 0) {
|
||||
static int bufferFindByFilename(const char* filename)
|
||||
{
|
||||
for (int i = 0; i < E.number_of_buffer; i++)
|
||||
{
|
||||
if (E.buffers[i].filename != NULL &&
|
||||
strcmp(E.buffers[i].filename, filename) == 0)
|
||||
{
|
||||
return E.buffers[i].buffer_id;
|
||||
}
|
||||
}
|
||||
@@ -34,9 +39,12 @@ static int bufferFindByFilename(const char *filename) {
|
||||
* @param buffer_id The buffer ID to find
|
||||
* @return Pointer to buffer, NULL if not found
|
||||
*/
|
||||
struct buffer_t *bufferFindById(int buffer_id) {
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
if (E.buffers[i].buffer_id == buffer_id) {
|
||||
struct buffer_t* bufferFindById(int buffer_id)
|
||||
{
|
||||
for (int i = 0; i < E.number_of_buffer; i++)
|
||||
{
|
||||
if (E.buffers[i].buffer_id == buffer_id)
|
||||
{
|
||||
return &E.buffers[i];
|
||||
}
|
||||
}
|
||||
@@ -50,36 +58,37 @@ struct buffer_t *bufferFindById(int buffer_id) {
|
||||
* @param filename Path to the file
|
||||
* @return Buffer ID on success, -1 on failure
|
||||
*/
|
||||
int bufferCreate(const char *filename) {
|
||||
int bufferCreate(const char* filename, enum bufferStatus_e state)
|
||||
{
|
||||
// Check if file is already open
|
||||
int existing_id = bufferFindByFilename(filename);
|
||||
if (existing_id != -1) {
|
||||
const int existing_id = bufferFindByFilename(filename);
|
||||
if (existing_id != -1)
|
||||
{
|
||||
return bufferSwitch(existing_id);
|
||||
}
|
||||
|
||||
|
||||
// Check if we have space for more buffers
|
||||
if (E.number_of_buffer >= 64) {
|
||||
if (E.number_of_buffer >= 64)
|
||||
{
|
||||
editorSetStatusMessage("Error: maximum buffers reached (64)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Initialize new buffer
|
||||
struct buffer_t *new_buf = &E.buffers[E.number_of_buffer];
|
||||
struct buffer_t* new_buf = &E.buffers[E.number_of_buffer];
|
||||
new_buf->buffer_id = E.number_of_buffer;
|
||||
new_buf->filename = strdup(filename);
|
||||
new_buf->type = FILE_BUFF;
|
||||
new_buf->state = READ_AND_WRITE;
|
||||
new_buf->state = state;
|
||||
new_buf->x = 0;
|
||||
new_buf->y = 0;
|
||||
new_buf->row_offset = 0;
|
||||
new_buf->col_offset = 0;
|
||||
new_buf->dirty = 0; // New file starts clean
|
||||
|
||||
new_buf->dirty = 0; // New file starts clean
|
||||
|
||||
// Load file content using existing editorOpen
|
||||
editorOpen(new_buf);
|
||||
|
||||
|
||||
E.number_of_buffer++;
|
||||
|
||||
|
||||
editorSetStatusMessage("Opened: %s (buffer %d)", filename, new_buf->buffer_id);
|
||||
return new_buf->buffer_id;
|
||||
}
|
||||
@@ -90,17 +99,19 @@ int bufferCreate(const char *filename) {
|
||||
* @param buffer_id The buffer ID to switch to
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int bufferSwitch(int buffer_id) {
|
||||
struct buffer_t *target = bufferFindById(buffer_id);
|
||||
if (target == NULL) {
|
||||
E.layout.panes[E.layout.active_pane].buffer_id = buffer_id;
|
||||
int bufferSwitch(int buffer_id)
|
||||
{
|
||||
struct buffer_t* target = bufferFindById(buffer_id);
|
||||
if (target == NULL)
|
||||
{
|
||||
E.layout.panes[E.layout.active_pane].buffer_id = buffer_id;
|
||||
editorSetStatusMessage("Error: buffer not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
editorSetStatusMessage("Switched to: %s (buffer %d)",
|
||||
target->filename, buffer_id);
|
||||
|
||||
|
||||
editorSetStatusMessage("Switched to: %s (buffer %d)",
|
||||
target->filename, buffer_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -112,51 +123,62 @@ int bufferSwitch(int buffer_id) {
|
||||
* @param buffer_id The buffer ID to close
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int bufferClose(int buffer_id) {
|
||||
struct buffer_t *buf = bufferFindById(buffer_id);
|
||||
if (buf == NULL) {
|
||||
int bufferClose(int buffer_id)
|
||||
{
|
||||
struct buffer_t* buf = bufferFindById(buffer_id);
|
||||
if (buf == NULL)
|
||||
{
|
||||
editorSetStatusMessage("Error: buffer not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
EditorPane * active = splitScreenGetActivePane();
|
||||
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
|
||||
// If this is the current buffer, find next buffer to switch to
|
||||
if (active->buffer_id == buffer_id) {
|
||||
if (active->buffer_id == buffer_id)
|
||||
{
|
||||
int next_id = -1;
|
||||
|
||||
|
||||
// Try to switch to next buffer
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
if (E.buffers[i].buffer_id != buffer_id) {
|
||||
for (int i = 0; i < E.number_of_buffer; i++)
|
||||
{
|
||||
if (E.buffers[i].buffer_id != buffer_id)
|
||||
{
|
||||
next_id = E.buffers[i].buffer_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next_id != -1) {
|
||||
|
||||
if (next_id != -1)
|
||||
{
|
||||
bufferSwitch(next_id);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// No other buffers, clear editor
|
||||
editorCloseFile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Free buffer resources
|
||||
free(buf->filename);
|
||||
buf->filename = NULL;
|
||||
buf->buffer_id = -1;
|
||||
|
||||
|
||||
// Remove from buffer list by shifting
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
if (E.buffers[i].buffer_id == buffer_id) {
|
||||
for (int j = i; j < E.number_of_buffer - 1; j++) {
|
||||
for (int i = 0; i < E.number_of_buffer; i++)
|
||||
{
|
||||
if (E.buffers[i].buffer_id == buffer_id)
|
||||
{
|
||||
for (int j = i; j < E.number_of_buffer - 1; j++)
|
||||
{
|
||||
E.buffers[j] = E.buffers[j + 1];
|
||||
}
|
||||
E.number_of_buffer--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
editorSetStatusMessage("Closed buffer %d", buffer_id);
|
||||
return 0;
|
||||
}
|
||||
@@ -165,8 +187,9 @@ int bufferClose(int buffer_id) {
|
||||
* @brief Gets the current active buffer
|
||||
* @return Pointer to current buffer_t, NULL if none
|
||||
*/
|
||||
struct buffer_t *bufferGetCurrent(void) {
|
||||
EditorPane * active = splitScreenGetActivePane();
|
||||
struct buffer_t* bufferGetCurrent(void)
|
||||
{
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
return bufferFindById(active->buffer_id);
|
||||
}
|
||||
|
||||
@@ -175,24 +198,27 @@ struct buffer_t *bufferGetCurrent(void) {
|
||||
* @details Prints buffer information to status message
|
||||
* @return Number of open buffers
|
||||
*/
|
||||
int bufferListAll(void) {
|
||||
if (E.number_of_buffer == 0) {
|
||||
int bufferListAll(void)
|
||||
{
|
||||
if (E.number_of_buffer == 0)
|
||||
{
|
||||
editorSetStatusMessage("No buffers open");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
char buf[256] = "";
|
||||
int offset = 0;
|
||||
EditorPane * active = splitScreenGetActivePane();
|
||||
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
struct buffer_t *b = &E.buffers[i];
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
|
||||
for (int i = 0; i < E.number_of_buffer; i++)
|
||||
{
|
||||
struct buffer_t* b = &E.buffers[i];
|
||||
char marker = (b->buffer_id == active->buffer_id) ? '*' : ' ';
|
||||
offset += snprintf(&buf[offset], sizeof(buf) - offset,
|
||||
"%c%d:%s ", marker, b->buffer_id,
|
||||
b->filename ? b->filename : "[No Name]");
|
||||
"%c%d:%s ", marker, b->buffer_id,
|
||||
b->filename ? b->filename : "[No Name]");
|
||||
}
|
||||
|
||||
|
||||
editorSetStatusMessage("Buffers: %s", buf);
|
||||
return E.number_of_buffer;
|
||||
}
|
||||
@@ -201,13 +227,15 @@ int bufferListAll(void) {
|
||||
* @brief Saves current buffer to disk
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int bufferSave(void) {
|
||||
EditorPane * active = splitScreenGetActivePane();
|
||||
if (active->buffer_id == -1) {
|
||||
int bufferSave(void)
|
||||
{
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
if (active->buffer_id == -1)
|
||||
{
|
||||
editorSetStatusMessage("Error: no buffer active");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
editorSave();
|
||||
return 0;
|
||||
}
|
||||
@@ -217,20 +245,233 @@ int bufferSave(void) {
|
||||
* @details Iterates through all buffers, saves each one
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int bufferSaveAll(void) {
|
||||
int bufferSaveAll(void)
|
||||
{
|
||||
int saved = 0;
|
||||
int failed = 0;
|
||||
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
if (bufferSwitch(E.buffers[i].buffer_id) == 0) {
|
||||
if (E.dirty && bufferSave() == 0) {
|
||||
|
||||
for (int i = 0; i < E.number_of_buffer; i++)
|
||||
{
|
||||
if (bufferSwitch(E.buffers[i].buffer_id) == 0)
|
||||
{
|
||||
if (E.dirty && bufferSave() == 0)
|
||||
{
|
||||
saved++;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
editorSetStatusMessage("Saved %d buffers (%d failed)", saved, failed);
|
||||
return (failed == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 bufferFind(struct buffer_t* buf)
|
||||
{
|
||||
appDebug("searching\n");
|
||||
char* query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
|
||||
|
||||
if (query == NULL)
|
||||
return;
|
||||
int i;
|
||||
for (i = buf->y+1; i < buf->numrows; i++)
|
||||
{
|
||||
row_t* row = &buf->row[i];
|
||||
char* match = strstr(row->chars, query);
|
||||
if (match)
|
||||
{
|
||||
buf->y = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(query);
|
||||
}
|
||||
|
||||
void bufferFindReverse(struct buffer_t* buf)
|
||||
{
|
||||
appDebug("searching\n");
|
||||
char* query = editorPrompt("Reverse search: %s (ESC to cancel)", "", 0);
|
||||
|
||||
if (query == NULL)
|
||||
return;
|
||||
int i;
|
||||
if (!buf->y)
|
||||
return;
|
||||
for (i = buf->y - 1; i >= 0; i--)
|
||||
{
|
||||
row_t* row = &buf->row[i];
|
||||
char* match = strstr(row->chars, query);
|
||||
if (match)
|
||||
{
|
||||
buf->y = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(query);
|
||||
}
|
||||
|
||||
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) {
|
||||
if (at < 0 || at > buffer->numrows)
|
||||
return;
|
||||
|
||||
row_t *tmp = realloc(buffer->row, sizeof(row_t) * (buffer->numrows + 1));
|
||||
if (!tmp)
|
||||
return;
|
||||
buffer->row = tmp;
|
||||
|
||||
/* Shift existing rows to make room at 'at' — no at++ */
|
||||
if (at < buffer->numrows) {
|
||||
memmove(&buffer->row[at + 1], &buffer->row[at],
|
||||
sizeof(row_t) * (buffer->numrows - at)); /* not -at+1 */
|
||||
}
|
||||
|
||||
buffer->row[at].size = len;
|
||||
buffer->row[at].cap = len + 1;
|
||||
buffer->row[at].chars = malloc(len + 1);
|
||||
if (!buffer->row[at].chars)
|
||||
return;
|
||||
memcpy(buffer->row[at].chars, s, len);
|
||||
buffer->row[at].chars[len] = '\0'; /* always NUL-terminate */
|
||||
|
||||
buffer->numrows++;
|
||||
buffer->dirty++;
|
||||
}
|
||||
|
||||
void bufferFreeRow(row_t *row) { free(row->chars); }
|
||||
|
||||
/**
|
||||
* \fn editorRowInsertChar(erow *row, int at, int c)
|
||||
* \param at Index of where we want to insert the char */
|
||||
|
||||
void bufferRowInsertBytes(struct buffer_t *buffer, row_t *row, int at,
|
||||
const char *src, int n) {
|
||||
if (buffer->state == READ_ONLY)
|
||||
return;
|
||||
if (row->size + n + 1 > row->cap) {
|
||||
row->cap = (row->size + n + 1) * 2;
|
||||
row->chars = realloc(row->chars, row->cap);
|
||||
}
|
||||
memmove(row->chars + at + n, row->chars + at, row->size - at);
|
||||
memcpy(row->chars + at, src, n);
|
||||
row->size += n;
|
||||
row->chars = realloc(row->chars, row->size + 2);
|
||||
++buffer->dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* \fn bufferRowDelChar(struct bufferConfig *E, frow *frow, int at)
|
||||
* \brief Delete the a char at the chosen position on the given row
|
||||
* \param at Index of the char to delete
|
||||
* \param row Row on operation is made */
|
||||
void bufferRowDelByte(struct buffer_t *buffer, row_t *row, int at, int n) {
|
||||
if (buffer->state == READ_ONLY)
|
||||
return;
|
||||
if (at < 0 || at >= row->size)
|
||||
return;
|
||||
memmove(row->chars + at, row->chars + at + n, row->size - at - n);
|
||||
row->size -= n;
|
||||
row->chars[row->size] = '\0';
|
||||
buffer->x -= n;
|
||||
++buffer->dirty;
|
||||
}
|
||||
|
||||
|
||||
void bufferInsertBytes(const char* src, int n)
|
||||
{
|
||||
appDebug("bufferInsertBytes \r\n");
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||
if (buf->y == buf->numrows)
|
||||
{
|
||||
bufferInsertRow(buf, buf->numrows, "", 0);
|
||||
}
|
||||
bufferRowInsertBytes(buf, &buf->row[buf->y], buf->x, src, n);
|
||||
buf->x += n;
|
||||
}
|
||||
|
||||
void bufferDelBytes(void)
|
||||
{
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||
|
||||
/* Nothing to delete */
|
||||
if (buf->numrows == 0) return;
|
||||
if (buf->x == 0 && buf->y == 0) return;
|
||||
|
||||
/* Use row_offset, not col_offset, for row indexing */
|
||||
row_t* r = &buf->row[buf->y];
|
||||
|
||||
if (buf->x > 0)
|
||||
{
|
||||
int byte_end = editorRowCxToByte(r, buf->x);
|
||||
int byte_start = editorRowCxToByte(r, buf->x - 1);
|
||||
int char_width = byte_end - byte_start; /* byte width of the character */
|
||||
|
||||
bufferRowDelByte(buf, r, byte_start, char_width);
|
||||
E.dirty = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Merge current row into the previous one */
|
||||
row_t* prev = &buf->row[buf->y - 1]; // FIX: was buf->y (same as r)
|
||||
int prev_char_count = editorRowCharCount(prev, prev->size);
|
||||
|
||||
bufferRowInsertBytes(buf, prev, prev->size, r->chars, r->size);
|
||||
free(r->chars);
|
||||
r->chars = NULL;
|
||||
|
||||
memmove(&buf->row[buf->y],
|
||||
&buf->row[buf->y + 1],
|
||||
sizeof(row_t) * (buf->numrows - buf->y - 1));
|
||||
buf->numrows--;
|
||||
|
||||
active->cursor_x = prev_char_count;
|
||||
buf->x = prev_char_count;
|
||||
active->cursor_y--;
|
||||
buf->y--;
|
||||
E.dirty = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void bufferInsertNewLine(void) {
|
||||
appDebug("Inserting new line\n");
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
|
||||
appDebug("buf x %d\n", buf->x);
|
||||
|
||||
if (buf->y >= buf->numrows) {
|
||||
/* Cursor is past the last row: just append a blank line */
|
||||
bufferInsertRow(buf, buf->numrows, "", 0);
|
||||
} else {
|
||||
row_t *row = &buf->row[buf->y];
|
||||
|
||||
/* Insert the tail (from cursor to end) as a new row below */
|
||||
bufferInsertRow(buf, buf->y + 1, &row->chars[buf->x], row->size - buf->x);
|
||||
|
||||
/* Re-fetch: realloc inside bufferInsertRow may have moved the array */
|
||||
row = &buf->row[buf->y];
|
||||
|
||||
/* Truncate the current row at the cursor */
|
||||
row->size = buf->x;
|
||||
row->chars[row->size] = '\0';
|
||||
/* Do NOT touch row->cap — the allocation is still valid */
|
||||
}
|
||||
|
||||
buf->y++;
|
||||
buf->x = 0;
|
||||
appDebug("Insert new line done\n");
|
||||
}
|
||||
+110
-62
@@ -2,18 +2,20 @@
|
||||
* @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
|
||||
* 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/row_op.h"
|
||||
#include "../include/buffer.h"
|
||||
#include "../include/terminal.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include "../include/completion.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -57,7 +59,7 @@ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
||||
const char *key_sequence = lisp_string(lisp_car(args));
|
||||
args = lisp_cdr(args);
|
||||
// second argument
|
||||
Lisp func = lisp_car(args);
|
||||
const Lisp func = lisp_car(args);
|
||||
|
||||
E.key_binds = (struct keyBind_t *)realloc(
|
||||
E.key_binds, ++E.number_of_keybinds * sizeof(struct keyBind_t));
|
||||
@@ -74,13 +76,14 @@ Lisp mapKey(Lisp args, LispError *e, LispContext ctx) {
|
||||
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.
|
||||
* @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
|
||||
@@ -106,7 +109,7 @@ Lisp moveCursor(Lisp args, LispError *e, LispContext ctx) {
|
||||
is_in = editorMoveCursor(ARROW_LEFT);
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "move lisp %d\n", is_in);
|
||||
appDebug("move lisp %d\n", is_in);
|
||||
return lisp_make_bool(is_in);
|
||||
}
|
||||
|
||||
@@ -130,21 +133,20 @@ void free_structs(void) {
|
||||
for (i = 0; i < E.number_of_buffer; ++i) {
|
||||
free(E.buffers[i].filename);
|
||||
for (j = 0; j < E.buffers[i].numrows; ++j) {
|
||||
free(E.buffers[i].row[j].render);
|
||||
free(E.buffers[i].row[j].chars);
|
||||
}
|
||||
free(E.buffers[i].row);
|
||||
}
|
||||
|
||||
|
||||
free(E.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.
|
||||
* @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
|
||||
@@ -182,10 +184,10 @@ Lisp editorQuit(Lisp args, LispError *e, LispContext ctx) {
|
||||
|
||||
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");
|
||||
}
|
||||
splitScreenVertical(E.buffers[0].buffer_id, E.buffers[1].buffer_id);
|
||||
} else {
|
||||
editorSetStatusMessage("Need at least 2 buffers open");
|
||||
}
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
@@ -222,9 +224,8 @@ Lisp l_editorInsertNewLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
* @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) {
|
||||
bufferInsertChar(' ');
|
||||
for (int i = 0; i < E.constantes.TAB_LENGTH; ++i) {
|
||||
bufferInsertBytes(" ", 1);
|
||||
}
|
||||
|
||||
return lisp_null();
|
||||
@@ -240,7 +241,9 @@ Lisp l_editorInserTab(Lisp args, LispError *e, LispContext ctx) {
|
||||
* @note Updates global editor state E
|
||||
*/
|
||||
Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
E.layout.panes[E.layout.active_pane].cursor_x = 0;
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
buf->x = 0;
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
@@ -256,9 +259,7 @@ Lisp moveCursorBeginLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
if (active->cursor_y < buf->numrows) {
|
||||
active->cursor_x = buf->row[active->cursor_y].size;
|
||||
}
|
||||
buf->x = buf->row[buf->y].size;
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
@@ -272,13 +273,14 @@ Lisp moveCursorEndLine(Lisp args, LispError *e, LispContext ctx) {
|
||||
* @see editorDelChar()
|
||||
*/
|
||||
Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
|
||||
bufferDelChar();
|
||||
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.
|
||||
* @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
|
||||
@@ -288,7 +290,7 @@ Lisp deletePreviousChar(Lisp args, LispError *e, LispContext ctx) {
|
||||
*/
|
||||
Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
active->cursor_y = active->row_offset;
|
||||
active->cursor_y = active->y_offset;
|
||||
int times = E.screenrows;
|
||||
while (--times) {
|
||||
editorMoveCursor(ARROW_UP);
|
||||
@@ -298,7 +300,8 @@ Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
||||
|
||||
/**
|
||||
* @brief Lisp function to move cursor down by one screen
|
||||
* @details Scrolls down one full screen height, moving cursor to bottom of visible area.
|
||||
* @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
|
||||
@@ -309,7 +312,7 @@ Lisp editorMoveCursorPageUp(Lisp args, LispError *e, LispContext ctx) {
|
||||
Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||
active->cursor_y = active->row_offset + E.screenrows - 1;
|
||||
active->cursor_y = active->y_offset + E.screenrows - 1;
|
||||
if (active->cursor_y > buffer->numrows) {
|
||||
active->cursor_y = buffer->numrows;
|
||||
}
|
||||
@@ -334,20 +337,20 @@ Lisp editorMoveCursorPageDown(Lisp args, LispError *e, LispContext ctx) {
|
||||
*/
|
||||
Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
||||
char *filename = editorPrompt("Open : %s", getenv("PWD"), 1);
|
||||
if (filename){
|
||||
if (filename) {
|
||||
// editorOpen(filename);
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
active->buffer_id = bufferCreate(filename);
|
||||
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.
|
||||
* @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
|
||||
@@ -355,8 +358,8 @@ Lisp editorOpenFile(Lisp args, LispError *e, LispContext ctx) {
|
||||
* @note Uses first character of the string argument
|
||||
*/
|
||||
Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
|
||||
char c = lisp_string(lisp_car(args))[0];
|
||||
bufferInsertChar(c);
|
||||
const char *src = lisp_string(lisp_car(args));
|
||||
bufferInsertBytes(src, strlen(src));
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
@@ -373,14 +376,14 @@ Lisp editorPrintC(Lisp args, LispError *e, LispContext ctx) {
|
||||
*/
|
||||
Lisp addPackage(Lisp args, LispError *e, LispContext ctx) {
|
||||
const char *package_name = lisp_string(lisp_car(args));
|
||||
fprintf(stderr, "%s\n", package_name);
|
||||
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");
|
||||
fprintf(stderr, "%s\n", package_dir);
|
||||
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);
|
||||
@@ -390,30 +393,13 @@ 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) {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||
bufferDelRow(buffer, active->cursor_y);
|
||||
return lisp_null();
|
||||
}
|
||||
|
||||
Lisp editorSwitchNextBuffer(Lisp args, LispError *e, LispContext ctx) {
|
||||
fprintf(stderr, "switch buffer\n");
|
||||
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();
|
||||
}
|
||||
@@ -440,17 +426,34 @@ Lisp editorUnifiedPanes(Lisp args, LispError *e, LispContext ctx) {
|
||||
* @see editorFind()
|
||||
*/
|
||||
Lisp bufferFind_L(Lisp args, LispError *e, LispContext ctx) {
|
||||
fprintf(stderr, "LispFind\n");
|
||||
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.
|
||||
* @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
|
||||
@@ -460,11 +463,10 @@ 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[active->cursor_y].render[active->cursor_x] == 0) {
|
||||
if (buffer->row[buffer->y].chars[buffer->x] == 0) {
|
||||
returned_char = lisp_make_char('a');
|
||||
} else {
|
||||
|
||||
returned_char = lisp_make_char(buffer->row[active->cursor_y].render[active->cursor_x]);
|
||||
returned_char = lisp_make_char(buffer->row[buffer->y].chars[buffer->x]);
|
||||
}
|
||||
return returned_char;
|
||||
}
|
||||
@@ -488,7 +490,7 @@ Lisp editorSetPrefix(Lisp args, LispError *e, LispContext ctx) {
|
||||
struct prefix_t prefix = find_prefix(prefix_name);
|
||||
E.prefix_state = prefix.prefix_id;
|
||||
editorSetStatusMessage("prefix %s", prefix.prefix_name);
|
||||
fprintf(stderr, "%s set\n", prefix_name);
|
||||
appDebug("%s set\n", prefix_name);
|
||||
|
||||
return lisp_null();
|
||||
}
|
||||
@@ -511,3 +513,49 @@ Lisp editorPrefix(Lisp args, LispError *e, LispContext ctx) {
|
||||
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();
|
||||
}
|
||||
+104
-56
@@ -1,10 +1,13 @@
|
||||
#include "../include/editor_op.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/row_op.h"
|
||||
#include "include/buffer.h"
|
||||
#include "include/data.h"
|
||||
#include "include/split_screen.h"
|
||||
#include "../include/buffer.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include "../include/terminal.h"
|
||||
#include "../include/utf8.h"
|
||||
|
||||
|
||||
extern struct editorConfig E;
|
||||
@@ -19,64 +22,109 @@ extern struct editorConfig E;
|
||||
* @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);
|
||||
vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
|
||||
va_end(ap);
|
||||
E.status_msg_time = time(NULL);
|
||||
void editorSetStatusMessage(const char* fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(E.status_msg, E.screencols, fmt, ap);
|
||||
va_end(ap);
|
||||
E.status_msg_time = time(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
row_t *row = &buf->row[buf->y];
|
||||
switch (key) {
|
||||
case ARROW_RIGHT:
|
||||
if (row && buf->x < row->size) {
|
||||
int len = utf8Seqlen(row->chars[buf->x]);
|
||||
buf->x += len;
|
||||
} else if (row && buf->y < buf->numrows) {
|
||||
buf->y++;
|
||||
buf->x = 0;
|
||||
}
|
||||
break;
|
||||
case ARROW_DOWN:
|
||||
if (buf->y < buf->numrows) {
|
||||
|
||||
void bufferInsertChar(int c) {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
if (active->cursor_y == buf->numrows) {
|
||||
bufferInsertRow(buf, buf->numrows, "", 0);
|
||||
buf->y++;
|
||||
}
|
||||
break;
|
||||
case ARROW_UP:
|
||||
if (buf->y != 0) {
|
||||
--buf->y;
|
||||
}
|
||||
break;
|
||||
case ARROW_LEFT:
|
||||
if (buf->x != 0) {
|
||||
--buf->x;
|
||||
} else if (buf->y > 0) {
|
||||
--buf->y;
|
||||
buf->x = buf->row[buf->y].size;
|
||||
}
|
||||
break;
|
||||
}
|
||||
bufferRowInsertChar(buf, &buf->row[active->cursor_y], active->cursor_x, c);
|
||||
active->cursor_x++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void bufferInsertNewLine() {
|
||||
/*
|
||||
* Add new line and place the cursor at the beginning of it
|
||||
*/
|
||||
fprintf(stderr, "Inserting new line\n");
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
frow *row;
|
||||
if (!active->cursor_x) {
|
||||
bufferInsertRow(buf, active->cursor_y, "", 0);
|
||||
} else {
|
||||
row = &buf->row[active->cursor_y];
|
||||
bufferInsertRow(buf, active->cursor_y + 1, &row->chars[active->cursor_x],
|
||||
row->size - active->cursor_x);
|
||||
row = &buf->row[active->cursor_y];
|
||||
row->size = active->cursor_x;
|
||||
row->chars[row->size] = '\0';
|
||||
bufferUpdatfrow(row);
|
||||
}
|
||||
++active->cursor_y;
|
||||
active->cursor_x = 0;
|
||||
fprintf(stderr, "Insert new line done\n");
|
||||
char *editorGetClipboard(void) {
|
||||
FILE *pipe = popen(CLIPBOARD_PASTE_CMD, "r");
|
||||
if (!pipe) return NULL;
|
||||
|
||||
size_t cap = 4096;
|
||||
size_t len = 0;
|
||||
char *buf = malloc(cap);
|
||||
|
||||
int c;
|
||||
while ((c = fgetc(pipe)) != EOF) {
|
||||
if (len + 1 >= cap) {
|
||||
cap *= 2;
|
||||
buf = realloc(buf, cap);
|
||||
}
|
||||
buf[len++] = (char)c;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
pclose(pipe);
|
||||
return buf; // caller must free
|
||||
}
|
||||
|
||||
void bufferDelChar() {
|
||||
frow *row;
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
if (active->cursor_y == buf->numrows || !(active->cursor_x || active->cursor_y)) {
|
||||
return;
|
||||
}
|
||||
row = &buf->row[active->cursor_y];
|
||||
if (active->cursor_x > 0) {
|
||||
bufferRowDelchar(buf, row, active->cursor_x - 1);
|
||||
--active->cursor_x;
|
||||
} else {
|
||||
active->cursor_x = buf->row[active->cursor_y - 1].size;
|
||||
bufferRowAppendString(buf, &buf->row[active->cursor_y - 1], row->chars, row->size);
|
||||
bufferDelRow(buf, active->cursor_y);
|
||||
--active->cursor_y;
|
||||
}
|
||||
void editorSetClipboard(const char *text, int len) {
|
||||
FILE *pipe = popen(CLIPBOARD_COPY_CMD, "w");
|
||||
if (!pipe) return;
|
||||
fwrite(text, 1, len, pipe);
|
||||
pclose(pipe);
|
||||
}
|
||||
|
||||
int editorRowCxToByte(const row_t *row, int cursor_x) {
|
||||
int i = 0, col = 0;
|
||||
while (col < cursor_x && i < row->size) {
|
||||
int sl = utf8Seqlen((unsigned char)row->chars[i]);
|
||||
if (sl < 1)
|
||||
sl = 1;
|
||||
col++;
|
||||
i += sl;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int editorRowCharCount(row_t *row, int x) {
|
||||
int n = 0, i = 0;
|
||||
while (i < x && i < row->size) {
|
||||
int sl = utf8Seqlen((unsigned char)row->chars[i]);
|
||||
if (sl < 1)
|
||||
sl = 1;
|
||||
n++;
|
||||
i += sl;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
+20
-83
@@ -9,53 +9,20 @@
|
||||
#include "../include/file_io.h"
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/input.h"
|
||||
#include "include/buffer.h"
|
||||
#include "include/data.h"
|
||||
#include "include/split_screen.h"
|
||||
#include "../include/buffer.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include "../include/row_op.h"
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
extern char *strdup(const char *);
|
||||
extern ssize_t getline(char **restrict lineptr, size_t *restrict n,
|
||||
FILE *restrict stream);
|
||||
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 *bufferRowsToString(struct buffer_t *buf, int *buffer_len) {
|
||||
int tot_len = 0;
|
||||
int j;
|
||||
char *buffer;
|
||||
char *p;
|
||||
|
||||
for (j = 0; j < buf->numrows; ++j) {
|
||||
tot_len += buf->row[j].size + 1;
|
||||
}
|
||||
*buffer_len = tot_len;
|
||||
buffer = malloc(tot_len);
|
||||
p = buffer;
|
||||
for (j = 0; j < buf->numrows; ++j) {
|
||||
memcpy(p, buf->row[j].chars, buf->row[j].size);
|
||||
p += buf->row[j].size;
|
||||
*p = '\n';
|
||||
p++;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Closes the current file and resets editor state
|
||||
* @details Clears all rows, resets cursor position, scroll offsets, and file
|
||||
@@ -67,12 +34,10 @@ void editorCloseFile(void) {
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
active->cursor_x = 0;
|
||||
active->cursor_y = 0;
|
||||
active->rx = 0;
|
||||
active->row_offset = 0;
|
||||
active->col_offset = 0;
|
||||
active->x_offset = 0;
|
||||
active->y_offset = 0;
|
||||
for (int i = 0; i < buf->numrows; ++i) {
|
||||
free(buf->row[i].chars);
|
||||
free(buf->row[i].render);
|
||||
}
|
||||
buf->numrows = 0;
|
||||
free(buf->row);
|
||||
@@ -103,14 +68,17 @@ void editorOpen(struct buffer_t* buffer) {
|
||||
die("fopen");
|
||||
|
||||
char *line = NULL;
|
||||
size_t line_cap = 0;
|
||||
size_t line_cap;
|
||||
ssize_t line_len;
|
||||
|
||||
rewind(fp);
|
||||
|
||||
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
|
||||
while (line_len > 0 &&
|
||||
(line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) {
|
||||
--line_len;
|
||||
}
|
||||
appDebug("line %s\n", line);
|
||||
bufferInsertRow(buffer, buffer->numrows, line, line_len);
|
||||
free(line);
|
||||
line = NULL;
|
||||
@@ -134,7 +102,6 @@ void editorSave() {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buffer = bufferFindById(active->buffer_id);
|
||||
int len;
|
||||
char *buf;
|
||||
int fd;
|
||||
if (buffer->filename == NULL) {
|
||||
buffer->filename = editorPrompt("Save as: %s (ESC to cancel)", "", 1);
|
||||
@@ -143,51 +110,21 @@ void editorSave() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
buf = bufferRowsToString(buffer, &len);
|
||||
fd = open(buffer->filename, O_RDWR | O_CREAT, 0644);
|
||||
if (fd != -1) {
|
||||
if (ftruncate(fd, len) != -1) {
|
||||
if (write(fd, buf, len) == len) {
|
||||
for (int i = 0; i < buffer->numrows; ++i)
|
||||
{
|
||||
len = strlen(buffer->row[i].chars);
|
||||
if (write(fd, buffer->row[i].chars, len) != len) {
|
||||
close(fd);
|
||||
free(buf);
|
||||
E.dirty = 0;
|
||||
editorSetStatusMessage("%d bytes written to disk", len);
|
||||
editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
|
||||
|
||||
return;
|
||||
}
|
||||
write(fd, "\n", 1);
|
||||
}
|
||||
buffer->dirty = 0;
|
||||
close(fd);
|
||||
}
|
||||
free(buf);
|
||||
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 bufferFind(struct buffer_t *buf) {
|
||||
fprintf(stderr, "searching\n");
|
||||
char *query = editorPrompt("Search: %s (ESC to cancel)", "", 0);
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
|
||||
if (query == NULL)
|
||||
return;
|
||||
int i;
|
||||
for (i = active->cursor_y + 1; i < buf->numrows; i++) {
|
||||
frow *row = &buf->row[i];
|
||||
char *match = strstr(row->render, query);
|
||||
if (match) {
|
||||
active->cursor_y = i;
|
||||
active->cursor_x = bufferRowRxToCx(row, match - row->render);
|
||||
buf->row_offset = buf->numrows;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(query);
|
||||
editorSetStatusMessage("File saved");
|
||||
}
|
||||
|
||||
+22
-8
@@ -3,7 +3,7 @@
|
||||
#include "../include/color.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/terminal.h"
|
||||
#include "include/split_screen.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -17,6 +17,7 @@ struct editorConfig;
|
||||
void registerBuiltin(char *key_sequence, LispCFunc f) {
|
||||
lisp_env_define(E.ctx.p->env, lisp_make_symbol(key_sequence, E.ctx),
|
||||
lisp_make_func(f), E.ctx);
|
||||
|
||||
}
|
||||
|
||||
void initBuiltins() {
|
||||
@@ -34,8 +35,8 @@ void initBuiltins() {
|
||||
registerBuiltin("editor-open-file", editorOpenFile);
|
||||
registerBuiltin("editor-insert-char", editorPrintC);
|
||||
registerBuiltin("add-package", addPackage);
|
||||
registerBuiltin("editor-del-row", editorDelRow_L);
|
||||
registerBuiltin("buffer-find", bufferFind_L);
|
||||
registerBuiltin("buffer-find-reverse", bufferFindReverse_L);
|
||||
registerBuiltin("editor-read-char", editorReadChar_L);
|
||||
registerBuiltin("add-prefix", editorPrefix);
|
||||
registerBuiltin("editor-set-prefix", editorSetPrefix);
|
||||
@@ -44,11 +45,16 @@ void initBuiltins() {
|
||||
registerBuiltin("editor-split-screen-vertical", l_editorSplitScreenVertical);
|
||||
registerBuiltin("editor-switch-next-pane", editorSwitchNextPane);
|
||||
registerBuiltin("editor-unify-panes", editorUnifiedPanes);
|
||||
registerBuiltin("editor-paste", editorPaste);
|
||||
registerBuiltin("editor-cut-end-line", editorCutEndLine);
|
||||
registerBuiltin("editor-move-cursor-beg-buffer", editorMoveBegBuffer);
|
||||
registerBuiltin("editor-move-cursor-end-buffer", editorMoveEndBuffer);
|
||||
}
|
||||
|
||||
void initConfig() {
|
||||
|
||||
E.ctx = lisp_init();
|
||||
E.ctx.p->err_port = fopen("log.err", "w");
|
||||
E.env = lisp_env(E.ctx);
|
||||
lisp_lib_load(E.ctx);
|
||||
// Init builtins lisp functions
|
||||
@@ -62,7 +68,7 @@ void initConfig() {
|
||||
lisp_eval(E.ctx_data, &E.ctx_error, E.ctx);
|
||||
}
|
||||
|
||||
void init_theme() {
|
||||
void initTheme() {
|
||||
E.constantes.THEME = (char *)lisp_string(
|
||||
lisp_eval(lisp_read("THEME", &E.ctx_error, E.ctx), &E.ctx_error, E.ctx));
|
||||
if (strcmp(E.constantes.THEME, "dark") == 0) {
|
||||
@@ -89,23 +95,31 @@ void initEditor() {
|
||||
if (getWindowSize(&E.screenrows, &E.screencols) == -1) {
|
||||
die("getWindowSize");
|
||||
}
|
||||
appDebug("%d %d\n", E.screenrows, E.screencols);
|
||||
E.screenrows -= 2;
|
||||
|
||||
|
||||
|
||||
// Init graphics variables
|
||||
splitScreenInit();
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
E.cursor_x = 0;
|
||||
E.cursor_y = 0;
|
||||
|
||||
active->cursor_x = 0;
|
||||
active->cursor_y = 0;
|
||||
active->rx = 0;
|
||||
active->row_offset = 0;
|
||||
active->col_offset = 0;
|
||||
active->origin_x = 0;
|
||||
active->origin_y = 0;
|
||||
active->width = E.screencols;
|
||||
active->height = E.screenrows;
|
||||
|
||||
E.init_file_path = (char *)calloc(256, sizeof(char));
|
||||
strcat(E.init_file_path, getenv("HOME"));
|
||||
strcat(E.init_file_path, "/.beluga/config/init.lisp");
|
||||
appDebug("%s\n", E.init_file_path);
|
||||
E.fd_init_file = fopen(E.init_file_path, "r");
|
||||
|
||||
// Status bar
|
||||
E.status_msg = (char *)calloc(E.screencols, sizeof(char));
|
||||
E.status_msg[0] = '\0';
|
||||
E.status_msg_time = 0;
|
||||
|
||||
@@ -120,7 +134,7 @@ void initEditor() {
|
||||
|
||||
initConfig();
|
||||
|
||||
init_theme();
|
||||
initTheme();
|
||||
|
||||
// To modify
|
||||
|
||||
|
||||
+28
-144
@@ -2,16 +2,22 @@
|
||||
#include "../include/define.h"
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/output.h"
|
||||
#include "include/data.h"
|
||||
#include "include/buffer.h"
|
||||
#include "include/data.h"
|
||||
#include "include/split_screen.h"
|
||||
#include <ctype.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "include/terminal.h"
|
||||
#include "include/utf8.h"
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
@@ -34,10 +40,10 @@ extern struct editorConfig E;
|
||||
* @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) {
|
||||
const char *fileCompletion(const char *path) {
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
char directory[128];
|
||||
char directory[256];
|
||||
char predict[128];
|
||||
const char *last_slash;
|
||||
int predict_len = 0;
|
||||
@@ -45,8 +51,8 @@ const char *file_completion(const char *path) {
|
||||
|
||||
// path is a directory
|
||||
if (path[strlen(path) - 1] == '/') {
|
||||
fprintf(stderr, "[FILE COMP] is dir\n");
|
||||
strncpy(directory, path, 128);
|
||||
appDebug("[FILE COMP] is dir\n");
|
||||
strncpy(directory, path, 256);
|
||||
}
|
||||
|
||||
// Find dir name
|
||||
@@ -58,9 +64,9 @@ const char *file_completion(const char *path) {
|
||||
strncpy(predict, last_slash + 1, predict_len);
|
||||
directory[dir_len] = '\0';
|
||||
predict[predict_len] = '\0';
|
||||
fprintf(stderr, "%s %s\n", directory, predict);
|
||||
appDebug("%s %s\n", directory, predict);
|
||||
} else {
|
||||
fprintf(stderr, "[FILE COMP] dir not found\n");
|
||||
appDebug("[FILE COMP] dir not found\n");
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
@@ -70,7 +76,7 @@ const char *file_completion(const char *path) {
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strncmp(entry->d_name, predict, predict_len) == 0) {
|
||||
static char full_path[512];
|
||||
static char full_path[1024];
|
||||
snprintf(full_path, sizeof(full_path), "%s%s", directory, entry->d_name);
|
||||
|
||||
struct stat st;
|
||||
@@ -78,7 +84,7 @@ const char *file_completion(const char *path) {
|
||||
strcat(full_path, "/"); // add slash for directories
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
|
||||
return strdup(full_path);
|
||||
}
|
||||
}
|
||||
@@ -87,7 +93,7 @@ const char *file_completion(const char *path) {
|
||||
closedir(dir);
|
||||
dir = NULL;
|
||||
free(entry);
|
||||
fprintf(stderr, "[FILE COMP] no entries\n");
|
||||
appDebug("[FILE COMP] no entries\n");
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
@@ -106,7 +112,8 @@ const char *file_completion(const char *path) {
|
||||
* @note Uses editorReadKey() for input and editorRefreshScreen() for display
|
||||
*/
|
||||
char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||
size_t buf_size = 128;
|
||||
size_t buf_size = 256;
|
||||
appDebug("[FILE COMP] %s %d\n", placeHolder, strlen(placeHolder));
|
||||
char *buf = malloc(buf_size);
|
||||
size_t buf_len = 0;
|
||||
int c = 0;
|
||||
@@ -132,26 +139,26 @@ char *editorPrompt(char *prompt, char *placeHolder, char bPathMode) {
|
||||
return buf;
|
||||
}
|
||||
} else if (bPathMode && c == '\t') {
|
||||
char path[128];
|
||||
char path[256];
|
||||
char *pwd;
|
||||
if (buf[0] != '/') {
|
||||
pwd = getenv("PWD");
|
||||
fprintf(stderr, "%s\n", pwd);
|
||||
appDebug("%s\n", pwd);
|
||||
memcpy(path, pwd, strlen(pwd));
|
||||
path[strlen(pwd)] = '/';
|
||||
strncat(path, buf, buf_len);
|
||||
} else {
|
||||
strcpy(path, buf);
|
||||
}
|
||||
memset(buf, 0, 128);
|
||||
memset(buf, 0, 256);
|
||||
buf_len = 0;
|
||||
char * buf_complete = (char *) file_completion(path);
|
||||
char * buf_complete = (char *) fileCompletion(path);
|
||||
strcpy(buf, buf_complete);
|
||||
free(buf_complete);
|
||||
buf_len = strlen(buf);
|
||||
buf[buf_len] = '\0';
|
||||
|
||||
} else if (!iscntrl(c) && c < 128) {
|
||||
} else if (!iscntrl(c) && c < 256) {
|
||||
if (buf_len == buf_size - 1) {
|
||||
buf_size *= 2;
|
||||
buf = realloc(buf, buf_size);
|
||||
@@ -162,133 +169,7 @@ 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];
|
||||
|
||||
char tmp[10];
|
||||
sprintf(tmp, "%d", key);
|
||||
|
||||
// First test enter key
|
||||
|
||||
if (key == '\r') {
|
||||
strcpy(key_str, "ENTER");
|
||||
} else if (key == '\t') {
|
||||
strcpy(key_str, "TAB");
|
||||
} else if (key >= 1 && key <= 26) { // CTRL keys
|
||||
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
|
||||
} else {
|
||||
switch (key) {
|
||||
case ARROW_UP:
|
||||
strcpy(key_str, "ARROW-UP");
|
||||
break;
|
||||
case ARROW_DOWN:
|
||||
strcpy(key_str, "ARROW-DOWN");
|
||||
break;
|
||||
case ARROW_LEFT:
|
||||
strcpy(key_str, "ARROW-LEFT");
|
||||
break;
|
||||
case ARROW_RIGHT:
|
||||
strcpy(key_str, "ARROW-RIGHT");
|
||||
break;
|
||||
case PAGE_UP:
|
||||
strcpy(key_str, "PAGE-UP");
|
||||
fprintf(stderr, "pagr up\n");
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
strcpy(key_str, "PAGE-DOWN");
|
||||
break;
|
||||
case DEL_KEY:
|
||||
strcpy(key_str, "DEL");
|
||||
|
||||
break;
|
||||
case BACKSPACE:
|
||||
strcpy(key_str, "BACKSPACE");
|
||||
break;
|
||||
case '\r':
|
||||
strcpy(key_str, "ENTER");
|
||||
break;
|
||||
case '\x1b':
|
||||
strcpy(key_str, "ESCAPE");
|
||||
break;
|
||||
case BEG_LINE:
|
||||
strcpy(key_str, "HOME");
|
||||
break;
|
||||
case END_LINE:
|
||||
strcpy(key_str, "END");
|
||||
break;
|
||||
default:
|
||||
// For regular characters
|
||||
if (isprint(key)) {
|
||||
snprintf(key_str, sizeof(key_str), "%c", key);
|
||||
} else {
|
||||
snprintf(key_str, sizeof(key_str), "KEY-%d", 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) {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
frow *row = (active->cursor_y + active->row_offset >= buf->numrows) ? NULL : &buf->row[active->cursor_y + active->row_offset];
|
||||
int row_len;
|
||||
int x = active->cursor_x + active->col_offset;
|
||||
int y = active->cursor_y + active->row_offset;
|
||||
switch (key) {
|
||||
case ARROW_RIGHT:
|
||||
if (row && x < row->size) {
|
||||
active->cursor_x++;
|
||||
|
||||
} else if (row && y == row->size) {
|
||||
active->cursor_y++;
|
||||
active->cursor_x = 0;
|
||||
}
|
||||
break;
|
||||
case ARROW_DOWN:
|
||||
if (y < buf->numrows) {
|
||||
|
||||
active->cursor_y++;
|
||||
}
|
||||
break;
|
||||
case ARROW_UP:
|
||||
if (y != 0) {
|
||||
--active->cursor_y;
|
||||
}
|
||||
break;
|
||||
case ARROW_LEFT:
|
||||
if (x != 0) {
|
||||
--active->cursor_x;
|
||||
} else if (y > 0) {
|
||||
--active->cursor_y;
|
||||
active->cursor_x = buf->row[y].size;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the command bound to a key sequence
|
||||
@@ -302,7 +183,7 @@ int editorMoveCursor(int key) {
|
||||
int executeKeyBind(char *key_sequence) {
|
||||
int i;
|
||||
int previous_state = 0;
|
||||
fprintf(stderr, "pressed %s\n", key_sequence);
|
||||
appDebug("pressed %s\n", 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) {
|
||||
@@ -330,10 +211,13 @@ int executeKeyBind(char *key_sequence) {
|
||||
*/
|
||||
void editorProcessKeypress() {
|
||||
int c = editorReadKey();
|
||||
char key_sequence[8];
|
||||
|
||||
if (executeKeyBind(key_to_string(c))) {
|
||||
if (executeKeyBind(keyToString(c))) {
|
||||
return;
|
||||
}
|
||||
bufferInsertChar(c);
|
||||
int seq_len = utf8Encode(c, key_sequence);
|
||||
appDebug("key seq : %s\n", key_sequence);
|
||||
bufferInsertBytes(key_sequence, seq_len);
|
||||
E.quit_times_buffer = E.constantes.QUIT_TIMES;
|
||||
}
|
||||
|
||||
+276
-205
@@ -8,11 +8,13 @@
|
||||
#include "../include/output.h"
|
||||
#include "../include/append_buffer.h"
|
||||
#include "../include/buffer.h"
|
||||
#include "../include/editor_op.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/define.h"
|
||||
#include "../include/row_op.h"
|
||||
#include "../include/split_screen.h"
|
||||
#include "../include/syntax_highlighter.h"
|
||||
#include "../include/terminal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -23,127 +25,129 @@ extern struct editorConfig E;
|
||||
/**
|
||||
* @brief Renders a single pane with its buffer content
|
||||
*/
|
||||
static void editorDrawPane(struct abuf *ab, EditorPane *pane) {
|
||||
if (pane == NULL || pane->buffer_id < 0)
|
||||
return;
|
||||
|
||||
struct buffer_t *buf = bufferFindById(pane->buffer_id);
|
||||
if (buf == NULL)
|
||||
return;
|
||||
|
||||
// Draw content for this pane
|
||||
for (int y = 0; y < pane->height; y++) {
|
||||
int file_row = y + pane->row_offset;
|
||||
|
||||
// Position cursor at start of pane row
|
||||
static void editorDrawPane(struct abuf* ab, EditorPane* pane)
|
||||
{
|
||||
int file_row;
|
||||
char pos_buf[32];
|
||||
int pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH",
|
||||
pane->start_row + y + 1, pane->start_col + 1);
|
||||
abAppend(ab, pos_buf, pos_len);
|
||||
int pos_len;
|
||||
int byte_len_to_print;
|
||||
int bytes_to_print;
|
||||
char* highlighted;
|
||||
|
||||
// Apply background color (6 bytes for RGB format)
|
||||
abAppend(ab, E.theme.BACKGROUND_COLOR, strlen(E.theme.BACKGROUND_COLOR));
|
||||
if (pane == NULL || pane->buffer_id < 0)
|
||||
return;
|
||||
|
||||
int chars_printed = 0;
|
||||
const struct buffer_t* buf = bufferFindById(pane->buffer_id);
|
||||
if (buf == NULL)
|
||||
return;
|
||||
|
||||
if (file_row >= buf->numrows) {
|
||||
// Empty line - show tilde
|
||||
if (buf->numrows == 0 && y == pane->height / 3) {
|
||||
char welcome[80];
|
||||
int welcome_len =
|
||||
snprintf(welcome, sizeof(welcome), "Buffer %d", pane->buffer_id);
|
||||
if (welcome_len > pane->width)
|
||||
welcome_len = pane->width;
|
||||
// Draw all pane
|
||||
for (int y = 0; y < pane->height; y++)
|
||||
{
|
||||
file_row = y + pane->y_offset;
|
||||
|
||||
int padding = (pane->width - welcome_len) / 2;
|
||||
if (padding) {
|
||||
abAppend(ab, "~", 1);
|
||||
chars_printed++;
|
||||
padding--;
|
||||
// Set cursor at start of pane row
|
||||
pos_len = snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH",
|
||||
pane->origin_y + y + 1, pane->origin_x + 1);
|
||||
abAppend(ab, pos_buf, pos_len);
|
||||
|
||||
// Apply background color (6 bytes for RGB format)
|
||||
abAppend(ab, E.theme.BACKGROUND_COLOR, (int) strlen(E.theme.BACKGROUND_COLOR));
|
||||
|
||||
|
||||
// pane line is out of buffer
|
||||
if (file_row >= buf->numrows)
|
||||
{
|
||||
// Empty line - show tilde
|
||||
|
||||
abAppend(ab, "~", 1);
|
||||
for (int i = 0;
|
||||
i < pane->width - 1;
|
||||
++i)
|
||||
{
|
||||
abAppend(ab, " ", 1);
|
||||
}
|
||||
}
|
||||
while (padding-- && chars_printed < pane->width) {
|
||||
abAppend(ab, " ", 1);
|
||||
chars_printed++;
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
if (buf->filename[strlen(buf->filename) - 1] == 'c' || buf->filename[strlen(buf->filename) - 1] == 'h')
|
||||
{
|
||||
// Render line with syntax highlighting, constrain to pane width
|
||||
highlighted = highlight_line(
|
||||
&buf->row[file_row].chars[pane->x_offset], &byte_len_to_print);
|
||||
|
||||
// Print only up to pane width
|
||||
abAppend(ab, highlighted, byte_len_to_print);
|
||||
free(highlighted);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Render basic line
|
||||
bytes_to_print =
|
||||
buf->row[file_row].size - pane->x_offset < pane->width
|
||||
? buf->row[file_row].size - pane->x_offset
|
||||
: pane->width;
|
||||
abAppend(ab, &buf->row[file_row].chars[pane->x_offset], bytes_to_print);
|
||||
}
|
||||
// Fill remaining space with background color to pane width
|
||||
for (int i = 0;
|
||||
i < pane->width - editorRowCharCount(&buf->row[file_row], pane->width) + pane->x_offset;
|
||||
++i)
|
||||
{
|
||||
abAppend(ab, " ", 1);
|
||||
}
|
||||
}
|
||||
abAppend(ab, welcome, welcome_len);
|
||||
chars_printed += welcome_len;
|
||||
} else {
|
||||
abAppend(ab, "~", 1);
|
||||
chars_printed++;
|
||||
}
|
||||
} else {
|
||||
// Render line with syntax highlighting, constrain to pane width
|
||||
int start_offset = pane->col_offset;
|
||||
int visible_len = buf->row[file_row].rsize - start_offset;
|
||||
if (visible_len < 0)
|
||||
visible_len = 0;
|
||||
if (visible_len > pane->width)
|
||||
visible_len = pane->width;
|
||||
|
||||
if (buf->filename[strlen(buf->filename) - 1] == 'c') {
|
||||
int byte_len_to_print;
|
||||
|
||||
char *highlighted = highlight_line(
|
||||
&buf->row[file_row].render[start_offset], &byte_len_to_print);
|
||||
|
||||
// Print only up to pane width
|
||||
|
||||
abAppend(ab, highlighted, byte_len_to_print);
|
||||
free(highlighted);
|
||||
} else {
|
||||
abAppend(ab, &buf->row[file_row].render[start_offset], buf->row[file_row].rsize);
|
||||
}
|
||||
|
||||
chars_printed = visible_len;
|
||||
}
|
||||
|
||||
// Fill remaining space with background color to pane width
|
||||
abAppend(ab, E.theme.BACKGROUND_COLOR, strlen(E.theme.BACKGROUND_COLOR));
|
||||
while (chars_printed < pane->width) {
|
||||
abAppend(ab, " ", 1);
|
||||
chars_printed++;
|
||||
}
|
||||
|
||||
// Restore background color at end of line
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Renders all panes based on current split configuration
|
||||
*/
|
||||
static void editorDrawAllPanes(struct abuf *ab) {
|
||||
ScreenLayout *layout = splitScreenGetLayout();
|
||||
static void editorDrawAllPanes(struct abuf* ab)
|
||||
{
|
||||
const ScreenLayout* layout = splitScreenGetLayout();
|
||||
|
||||
if (layout->num_panes == 1) {
|
||||
// Single pane fullscreen
|
||||
editorDrawPane(ab, &layout->panes[0]);
|
||||
} else if (layout->num_panes == 2) {
|
||||
// Draw both panes
|
||||
for (int i = 0; i < 2; i++) {
|
||||
editorDrawPane(ab, &layout->panes[i]);
|
||||
|
||||
// Draw pane border/divider if not the last pane
|
||||
if (layout->mode == SPLIT_VERTICAL && i == 0) {
|
||||
// Draw vertical divider
|
||||
int divider_col = layout->panes[0].width;
|
||||
for (int y = 0; y < layout->panes[0].height; y++) {
|
||||
char pos_buf[32];
|
||||
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", y + 1, divider_col);
|
||||
abAppend(ab, pos_buf, strlen(pos_buf));
|
||||
abAppend(ab, "\x1b[1m|\x1b[0m", 9); // Bold pipe divider
|
||||
}
|
||||
} else if (layout->mode == SPLIT_HORIZONTAL && i == 0) {
|
||||
// Draw horizontal divider
|
||||
int divider_row = layout->panes[0].height;
|
||||
char pos_buf[32];
|
||||
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", divider_row + 1, 1);
|
||||
abAppend(ab, pos_buf, strlen(pos_buf));
|
||||
for (int x = 0; x < E.screencols; x++) {
|
||||
abAppend(ab, "\x1b[1m-\x1b[0m", 9); // Bold dash divider
|
||||
}
|
||||
}
|
||||
if (layout->num_panes == 1)
|
||||
{
|
||||
// Single pane fullscreen
|
||||
editorDrawPane(ab, &layout->panes[0]);
|
||||
}
|
||||
else if (layout->num_panes == 2)
|
||||
{
|
||||
// Draw both panes
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
editorDrawPane(ab, &layout->panes[i]);
|
||||
|
||||
// Draw pane border/divider if not the last pane
|
||||
if (layout->mode == SPLIT_VERTICAL && i == 0)
|
||||
{
|
||||
// Draw vertical divider
|
||||
int divider_col = layout->panes[0].width;
|
||||
for (int y = 0; y < layout->panes[0].height; y++)
|
||||
{
|
||||
char pos_buf[32];
|
||||
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", y + 1, divider_col);
|
||||
abAppend(ab, pos_buf, (int) strlen(pos_buf));
|
||||
abAppend(ab, "\x1b[1m|\x1b[0m", 9); // Bold pipe divider
|
||||
}
|
||||
}
|
||||
else if (layout->mode == SPLIT_HORIZONTAL && i == 0)
|
||||
{
|
||||
// Draw horizontal divider
|
||||
int divider_row = layout->panes[0].height;
|
||||
char pos_buf[32];
|
||||
snprintf(pos_buf, sizeof(pos_buf), "\x1b[%d;%dH", divider_row + 1, 1);
|
||||
abAppend(ab, pos_buf, (int) strlen(pos_buf));
|
||||
for (int x = 0; x < E.screencols; x++)
|
||||
{
|
||||
abAppend(ab, "\x1b[1m-\x1b[0m", 9); // Bold dash divider
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,35 +157,87 @@ static void editorDrawAllPanes(struct abuf *ab) {
|
||||
* @note Updates global editor state E
|
||||
* @see editorRowCxToRx()
|
||||
*/
|
||||
void editorScroll() {
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
active->rx = active->cursor_x;
|
||||
void editorScroll()
|
||||
{
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||
int rel_x, rel_y;
|
||||
|
||||
fprintf(stderr, "%d %d / %d %d\n", active->cursor_x, active->col_offset,
|
||||
active->cursor_y, active->row_offset);
|
||||
// compute relative coordinates
|
||||
rel_x = editorRowCharCount(&buf->row[buf->y], buf->x);
|
||||
appDebug("counting %d\n", rel_x);
|
||||
rel_y = buf->y;
|
||||
|
||||
if (active->cursor_x < 0) {
|
||||
active->cursor_x = 0;
|
||||
active->col_offset = active->col_offset == 0 ? 0 : --active->col_offset;
|
||||
}
|
||||
appDebug("%d %d / %d %d\n", active->cursor_x, active->x_offset,
|
||||
active->cursor_y, active->y_offset);
|
||||
|
||||
if (active->cursor_y < 0) {
|
||||
active->cursor_y = 0;
|
||||
active->row_offset = active->row_offset == 0 ? 0 : --active->row_offset;
|
||||
}
|
||||
while (rel_x != active->cursor_x + active->x_offset ||
|
||||
rel_y != active->cursor_y + active->y_offset)
|
||||
{
|
||||
if (rel_x < active->cursor_x + active->x_offset)
|
||||
{
|
||||
// LEFT
|
||||
if (active->cursor_x == 0 && active->x_offset)
|
||||
{
|
||||
active->x_offset--;
|
||||
} else
|
||||
{
|
||||
active->cursor_x--;
|
||||
}
|
||||
} else
|
||||
{
|
||||
// RIGHT
|
||||
if (rel_x > active->cursor_x + active->x_offset)
|
||||
{
|
||||
if (active->cursor_x == active->width - 1)
|
||||
{
|
||||
active->x_offset++;
|
||||
} else
|
||||
{
|
||||
active->cursor_x++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (active->cursor_x == active->width) {
|
||||
if (rel_y < active->cursor_y + active->y_offset)
|
||||
{
|
||||
if (active->cursor_y == 0 && active->y_offset)
|
||||
{
|
||||
active->y_offset--;
|
||||
} else
|
||||
{
|
||||
active->cursor_y--;
|
||||
}
|
||||
}
|
||||
|
||||
active->cursor_x--;
|
||||
active->col_offset++;
|
||||
}
|
||||
if (rel_y > active->cursor_y + active->y_offset)
|
||||
{
|
||||
if (active->cursor_y == active->height - 1)
|
||||
{
|
||||
active->y_offset++;
|
||||
} else
|
||||
{
|
||||
active->cursor_y++;
|
||||
}
|
||||
}
|
||||
|
||||
if (active->cursor_y == active->height) {
|
||||
}
|
||||
}
|
||||
|
||||
active->cursor_y--;
|
||||
active->row_offset++;
|
||||
}
|
||||
char * basename(char *path)
|
||||
{
|
||||
int len = (int) strlen(path);
|
||||
|
||||
|
||||
for(int i=len-1; i>0; i--)
|
||||
{
|
||||
if(path[i]=='/' )
|
||||
{
|
||||
path = path+i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,55 +248,63 @@ void editorScroll() {
|
||||
* @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];
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
struct buffer_t *buf = bufferFindById(active->buffer_id);
|
||||
void editorDrawStatusBar(struct abuf* ab)
|
||||
{
|
||||
// TO MODIFY
|
||||
int len, render_len;
|
||||
char status[E.screencols], render_status[E.screencols * 4];
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
struct buffer_t* buf = bufferFindById(active->buffer_id);
|
||||
|
||||
abAppend(ab, "\x1b[7m", 4); // inverting colors
|
||||
abAppend(ab, "\x1b[7m", 4); // inverting colors
|
||||
|
||||
const char *mode_str = "";
|
||||
ScreenLayout *layout = splitScreenGetLayout();
|
||||
if (layout->mode == SPLIT_VERTICAL)
|
||||
mode_str = " [V-SPLIT]";
|
||||
else if (layout->mode == SPLIT_HORIZONTAL)
|
||||
mode_str = " [H-SPLIT]";
|
||||
const char* mode_str = "";
|
||||
ScreenLayout* layout = splitScreenGetLayout();
|
||||
if (layout->mode == SPLIT_VERTICAL)
|
||||
mode_str = " [V-SPLIT]";
|
||||
else if (layout->mode == SPLIT_HORIZONTAL)
|
||||
mode_str = " [H-SPLIT]";
|
||||
|
||||
// Build buffer status showing all buffers with dirty indicators
|
||||
char buf_status[200] = "";
|
||||
int offset = 0;
|
||||
for (int i = 0; i < E.number_of_buffer; i++) {
|
||||
struct buffer_t *b = &E.buffers[i];
|
||||
char marker = (b->buffer_id == active->buffer_id) ? '>' : ' ';
|
||||
char dirty_marker = b->dirty ? '*' : ' ';
|
||||
offset += snprintf(&buf_status[offset], sizeof(buf_status) - offset,
|
||||
"%c%d:%s%c ", marker, b->buffer_id,
|
||||
b->filename ? b->filename : "[No Name]", dirty_marker);
|
||||
}
|
||||
|
||||
len = snprintf(status, sizeof(status), "%s%s", buf_status, mode_str);
|
||||
|
||||
render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
|
||||
active->cursor_y + 1, buf->numrows);
|
||||
|
||||
if (len > E.screencols) {
|
||||
len = E.screencols;
|
||||
}
|
||||
|
||||
abAppend(ab, status, len);
|
||||
while (len < E.screencols) {
|
||||
if (E.screencols - len == render_len) {
|
||||
abAppend(ab, render_status, render_len);
|
||||
break;
|
||||
} else {
|
||||
abAppend(ab, " ", 1);
|
||||
++len;
|
||||
// Build buffer status showing all buffers with dirty indicators
|
||||
char buf_status[1024] = "";
|
||||
int offset = 0;
|
||||
for (int i = 0; i < E.number_of_buffer; i++)
|
||||
{
|
||||
struct buffer_t* b = &E.buffers[i];
|
||||
char marker = (b->buffer_id == active->buffer_id) ? '>' : ' ';
|
||||
char dirty_marker = b->dirty ? '*' : ' ';
|
||||
offset += snprintf(&buf_status[offset], sizeof(buf_status) - offset,
|
||||
"%c%d:%s%c ", marker, b->buffer_id,
|
||||
b->filename ? basename(b->filename) : "[No Name]", dirty_marker);
|
||||
}
|
||||
}
|
||||
|
||||
abAppend(ab, "\x1b[m", 3); // normal text mode
|
||||
abAppend(ab, "\r\n", 2);
|
||||
len = snprintf(status, sizeof(status), "%s%s", buf_status, mode_str);
|
||||
|
||||
render_len = snprintf(render_status, sizeof(render_status), "%d/%d",
|
||||
active->cursor_y + 1, buf->numrows);
|
||||
|
||||
if (len > E.screencols)
|
||||
{
|
||||
len = E.screencols;
|
||||
}
|
||||
|
||||
abAppend(ab, status, len);
|
||||
while (len < E.screencols)
|
||||
{
|
||||
if (E.screencols - len == render_len + 1)
|
||||
{
|
||||
abAppend(ab, render_status, render_len);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
abAppend(ab, " ", 1);
|
||||
++len;
|
||||
}
|
||||
}
|
||||
|
||||
abAppend(ab, "\x1b[m", 3); // normal text mode
|
||||
abAppend(ab, "\r\n", 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,17 +314,21 @@ void editorDrawStatusBar(struct abuf *ab) {
|
||||
* @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);
|
||||
if (msg_len > E.screencols) {
|
||||
msg_len = E.screencols;
|
||||
}
|
||||
if (msg_len && time(NULL) - E.status_msg_time < 5) {
|
||||
abAppend(ab, E.status_msg, msg_len);
|
||||
}
|
||||
void editorDrawMessageBar(struct abuf* ab)
|
||||
{
|
||||
int msg_len = (int) strlen(E.status_msg);
|
||||
abAppend(ab, ERASE_END_LINE, 3);
|
||||
if (msg_len > E.screencols)
|
||||
{
|
||||
msg_len = E.screencols;
|
||||
}
|
||||
if (msg_len && time(NULL) - E.status_msg_time < 5)
|
||||
{
|
||||
abAppend(ab, E.status_msg, msg_len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Performs complete screen refresh and buffer synchronization
|
||||
* @details Clears screen, redraws all visible content (rows, status bar,
|
||||
@@ -272,33 +340,36 @@ void editorDrawMessageBar(struct abuf *ab) {
|
||||
* @see editorDrawMessageBar()
|
||||
*/
|
||||
|
||||
void editorRefreshScreen() {
|
||||
struct abuf ab = ABUF_INIT;
|
||||
char buf[32];
|
||||
void editorRefreshScreen()
|
||||
{
|
||||
struct abuf ab = ABUF_INIT;
|
||||
char buf[32];
|
||||
|
||||
abAppend(&ab, HIDE_CURSOR, 6);
|
||||
abAppend(&ab, CURSOR_TOP_LEFT, 3);
|
||||
abAppend(&ab, E.theme.BACKGROUND_COLOR,
|
||||
strlen(E.theme.BACKGROUND_COLOR)); // RGB background is 12 bytes
|
||||
abAppend(&ab, HIDE_CURSOR, 6);
|
||||
abAppend(&ab, CURSOR_TOP_LEFT, 3);
|
||||
abAppend(&ab, E.theme.BACKGROUND_COLOR,
|
||||
(int) strlen(E.theme.BACKGROUND_COLOR)); // RGB background is 12 bytes
|
||||
|
||||
// Draw all panes
|
||||
editorScroll();
|
||||
editorDrawAllPanes(&ab);
|
||||
// Draw all panes
|
||||
editorScroll();
|
||||
editorDrawAllPanes(&ab);
|
||||
|
||||
// Draw status bar and message bar
|
||||
editorDrawStatusBar(&ab);
|
||||
editorDrawMessageBar(&ab);
|
||||
// Draw status bar and message bar
|
||||
editorDrawStatusBar(&ab);
|
||||
editorDrawMessageBar(&ab);
|
||||
// editorDrawContextBuffer(&ab);
|
||||
|
||||
// Position cursor in active pane
|
||||
EditorPane *active = splitScreenGetActivePane();
|
||||
if (active != NULL) {
|
||||
snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
|
||||
active->cursor_y + active->start_row + 1,
|
||||
active->cursor_x + active->start_col + 1);
|
||||
abAppend(&ab, buf, strlen(buf));
|
||||
}
|
||||
// Position cursor in active pane
|
||||
EditorPane* active = splitScreenGetActivePane();
|
||||
if (active != NULL)
|
||||
{
|
||||
snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
|
||||
active->cursor_y + active->origin_y + 1,
|
||||
active->cursor_x + active->origin_x + 1);
|
||||
abAppend(&ab, buf, (int) strlen(buf));
|
||||
}
|
||||
|
||||
abAppend(&ab, SHOW_CURSOR, 6);
|
||||
write(STDOUT_FILENO, ab.b, ab.len);
|
||||
abFree(&ab);
|
||||
abAppend(&ab, SHOW_CURSOR, 6);
|
||||
write(STDOUT_FILENO, ab.b, ab.len);
|
||||
abFree(&ab);
|
||||
}
|
||||
|
||||
+2
-142
@@ -1,4 +1,6 @@
|
||||
#include "../include/row_op.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/utf8.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -6,147 +8,5 @@
|
||||
|
||||
extern struct editorConfig E;
|
||||
|
||||
int bufferRowCxToRx(frow *row, int cursor_x) {
|
||||
int render_x = 0;
|
||||
int i;
|
||||
for (i = 0; i < cursor_x; ++i) {
|
||||
if (row->chars[i] == '\t') {
|
||||
render_x += (E.constantes.TAB_LENGTH - 1) - (render_x % E.constantes.TAB_LENGTH);
|
||||
}
|
||||
render_x++;
|
||||
}
|
||||
return render_x;
|
||||
}
|
||||
|
||||
int bufferRowRxToCx(frow *row, int rx) {
|
||||
int cur_rx = 0;
|
||||
int cx;
|
||||
for (cx = 0; cx < row->size; cx++) {
|
||||
if (row->chars[cx] == '\t')
|
||||
cur_rx += (E.constantes.TAB_LENGTH - 1) - (cur_rx % E.constantes.TAB_LENGTH);
|
||||
cur_rx++;
|
||||
if (cur_rx > rx) return cx;
|
||||
}
|
||||
return cx;
|
||||
}
|
||||
|
||||
/**
|
||||
* \fn bufferUpdatfrow(frow *row)
|
||||
* \brief Copy content of \p row in \p row->render.
|
||||
* */
|
||||
|
||||
void bufferUpdatfrow(frow *row) {
|
||||
int i, i_render;
|
||||
int tabs = 0;
|
||||
|
||||
// counting number of tabs
|
||||
|
||||
for (i = 0; i < row->size; ++i) {
|
||||
tabs +=
|
||||
(row->chars[i] == '\t'); /**< increment tabs of 1 if chars[i] is one. */
|
||||
}
|
||||
|
||||
free(row->render);
|
||||
row->render = malloc(row->size + tabs * (E.constantes.TAB_LENGTH - 1) +
|
||||
1); /**< Tabs needs E.constantes.TAB_LENGTH chars so E.constantes.TAB_LENGTH - 1
|
||||
more than the first already counted. */
|
||||
|
||||
// end of counting
|
||||
i_render = 0;
|
||||
for (i = 0; i < row->size; ++i) {
|
||||
if (row->chars[i] == '\t') {
|
||||
row->render[i_render++] = ' ';
|
||||
while (i_render % E.constantes.TAB_LENGTH) {
|
||||
row->render[i_render++] =
|
||||
' '; /**< Addind the right amount of spaces for tabs */
|
||||
}
|
||||
} else {
|
||||
row->render[i_render++] = row->chars[i];
|
||||
}
|
||||
}
|
||||
row->render[i_render] = '\0'; // Don't forget the end of string character.
|
||||
row->rsize = i_render;
|
||||
}
|
||||
|
||||
void bufferInsertRow(struct buffer_t *buffer, int at, char *s, size_t len) {
|
||||
|
||||
if (at < 0 || at > buffer->numrows) {
|
||||
|
||||
return;
|
||||
}
|
||||
frow *tmp = (frow *)realloc(buffer->row, sizeof(frow) * (buffer->numrows + 1));
|
||||
if (!tmp) {
|
||||
return;
|
||||
}
|
||||
buffer->row = tmp;
|
||||
memmove(&buffer->row[at + 1], &buffer->row[at], sizeof(frow) * (buffer->numrows - at));
|
||||
|
||||
buffer->row[at].size = len;
|
||||
buffer->row[at].chars = malloc(len + 1);
|
||||
memcpy(buffer->row[at].chars, s, len);
|
||||
buffer->row[at].chars[len] = '\0';
|
||||
|
||||
buffer->row[at].rsize = 0;
|
||||
buffer->row[at].render = NULL;
|
||||
bufferUpdatfrow(&buffer->row[at]);
|
||||
|
||||
++buffer->numrows;
|
||||
++buffer->dirty;
|
||||
}
|
||||
|
||||
void bufferFrefrow(frow *row) {
|
||||
free(row->render);
|
||||
free(row->chars);
|
||||
}
|
||||
|
||||
void bufferDelRow(struct buffer_t *buffer, int at) {
|
||||
if (at < 0 || at >= buffer->numrows) {
|
||||
return;
|
||||
}
|
||||
bufferFrefrow(&buffer->row[at]);
|
||||
memmove(&buffer->row[at], &buffer->row[at + 1], sizeof(frow) * (buffer->numrows - at - 1));
|
||||
--buffer->numrows;
|
||||
++buffer->dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* \fn bufferRowInsertChar(frow *row, int at, int c)
|
||||
* \param at Index of where we want to insert the char */
|
||||
|
||||
void bufferRowInsertChar(struct buffer_t *buffer, frow *row, int at, int c) {
|
||||
if (buffer->state == READ_ONLY)
|
||||
return;
|
||||
if (at < 0 || at > row->size) {
|
||||
at = row->size;
|
||||
}
|
||||
row->chars = realloc(row->chars, row->size + 2);
|
||||
memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
|
||||
++row->size;
|
||||
row->chars[at] = c;
|
||||
bufferUpdatfrow(row);
|
||||
++buffer->dirty;
|
||||
}
|
||||
|
||||
void bufferRowAppendString(struct buffer_t *buffer, frow *row, char *s, size_t len) {
|
||||
row->chars = realloc(row->chars, row->size + len + 1);
|
||||
memcpy(&row->chars[row->size], s, len);
|
||||
row->size += len;
|
||||
row->chars[row->size] = '\0';
|
||||
bufferUpdatfrow(row);
|
||||
++buffer->dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* \fn bufferRowDelChar(struct bufferConfig *E, frow *frow, int at)
|
||||
* \brief Delete the a char at the chosen position on the given row
|
||||
* \param at Index of the char to delete
|
||||
* \param row Row on operation is made */
|
||||
void bufferRowDelchar(struct buffer_t *buffer, frow *row, int at) {
|
||||
if (at < 0 || at >= row->size) {
|
||||
return;
|
||||
}
|
||||
memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
|
||||
--row->size;
|
||||
bufferUpdatfrow(row);
|
||||
++buffer->dirty;
|
||||
}
|
||||
|
||||
+48
-48
@@ -18,19 +18,19 @@ void splitScreenInit(void) {
|
||||
E.layout.mode = SPLIT_NONE;
|
||||
E.layout.num_panes = 1;
|
||||
E.layout.active_pane = 0;
|
||||
|
||||
|
||||
E.layout.panes = malloc(sizeof(EditorPane) * 2);
|
||||
|
||||
|
||||
// Initialize single fullscreen pane
|
||||
E.layout.panes[0].buffer_id = -1; // No buffer for now
|
||||
E.layout.panes[0].start_row = 0;
|
||||
E.layout.panes[0].start_col = 0;
|
||||
E.layout.panes[0].origin_y = 0;
|
||||
E.layout.panes[0].origin_x = 0;
|
||||
E.layout.panes[0].height = E.screenrows - 2; // Leave room for status bar
|
||||
E.layout.panes[0].width = E.screencols;
|
||||
E.layout.panes[0].cursor_x = 0;
|
||||
E.layout.panes[0].cursor_y = 0;
|
||||
E.layout.panes[0].row_offset = 0;
|
||||
E.layout.panes[0].col_offset = 0;
|
||||
E.layout.panes[0].y_offset = 0;
|
||||
E.layout.panes[0].x_offset = 0;
|
||||
E.layout.panes[0].is_active = 1;
|
||||
}
|
||||
|
||||
@@ -47,40 +47,40 @@ int splitScreenVertical(int buffer_id_left, int buffer_id_right) {
|
||||
editorSetStatusMessage("Error: invalid buffer IDs");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Reallocate panes array
|
||||
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
|
||||
E.layout.mode = SPLIT_VERTICAL;
|
||||
E.layout.num_panes = 2;
|
||||
E.layout.active_pane = 0;
|
||||
|
||||
|
||||
int half_width = E.screencols / 2;
|
||||
int pane_height = E.screenrows - 2; // Leave room for status bar
|
||||
|
||||
int pane_height = E.screenrows; // Leave room for status bar
|
||||
|
||||
// Left pane
|
||||
E.layout.panes[0].buffer_id = buffer_id_left;
|
||||
E.layout.panes[0].start_row = 0;
|
||||
E.layout.panes[0].start_col = 0;
|
||||
E.layout.panes[0].origin_y = 0;
|
||||
E.layout.panes[0].origin_x = 0;
|
||||
E.layout.panes[0].height = pane_height;
|
||||
E.layout.panes[0].width = half_width;
|
||||
E.layout.panes[0].cursor_x = 0;
|
||||
E.layout.panes[0].cursor_y = 0;
|
||||
E.layout.panes[0].row_offset = 0;
|
||||
E.layout.panes[0].col_offset = 0;
|
||||
E.layout.panes[0].y_offset = 0;
|
||||
E.layout.panes[0].x_offset = 0;
|
||||
E.layout.panes[0].is_active = 1;
|
||||
|
||||
|
||||
// Right pane
|
||||
E.layout.panes[1].buffer_id = buffer_id_right;
|
||||
E.layout.panes[1].start_row = 0;
|
||||
E.layout.panes[1].start_col = half_width;
|
||||
E.layout.panes[1].origin_y = 0;
|
||||
E.layout.panes[1].origin_x = half_width;
|
||||
E.layout.panes[1].height = pane_height;
|
||||
E.layout.panes[1].width = E.screencols - half_width;
|
||||
E.layout.panes[1].cursor_x = 0;
|
||||
E.layout.panes[1].cursor_y = 0;
|
||||
E.layout.panes[1].row_offset = 0;
|
||||
E.layout.panes[1].col_offset = 0;
|
||||
E.layout.panes[1].y_offset = 0;
|
||||
E.layout.panes[1].x_offset = 0;
|
||||
E.layout.panes[1].is_active = 0;
|
||||
|
||||
|
||||
editorSetStatusMessage("Vertical split: %d | %d", buffer_id_left, buffer_id_right);
|
||||
return 0;
|
||||
}
|
||||
@@ -98,39 +98,39 @@ int splitScreenHorizontal(int buffer_id_top, int buffer_id_bottom) {
|
||||
editorSetStatusMessage("Error: invalid buffer IDs");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Reallocate panes array
|
||||
E.layout.panes = realloc(E.layout.panes, sizeof(EditorPane) * 2);
|
||||
E.layout.mode = SPLIT_HORIZONTAL;
|
||||
E.layout.num_panes = 2;
|
||||
E.layout.active_pane = 0;
|
||||
|
||||
|
||||
int half_height = (E.screenrows - 2) / 2; // Account for status bar
|
||||
|
||||
|
||||
// Top pane
|
||||
E.layout.panes[0].buffer_id = buffer_id_top;
|
||||
E.layout.panes[0].start_row = 0;
|
||||
E.layout.panes[0].start_col = 0;
|
||||
E.layout.panes[0].origin_y = 0;
|
||||
E.layout.panes[0].origin_x = 0;
|
||||
E.layout.panes[0].height = half_height;
|
||||
E.layout.panes[0].width = E.screencols;
|
||||
E.layout.panes[0].cursor_x = 0;
|
||||
E.layout.panes[0].cursor_y = 0;
|
||||
E.layout.panes[0].row_offset = 0;
|
||||
E.layout.panes[0].col_offset = 0;
|
||||
E.layout.panes[0].y_offset = 0;
|
||||
E.layout.panes[0].x_offset = 0;
|
||||
E.layout.panes[0].is_active = 1;
|
||||
|
||||
|
||||
// Bottom pane
|
||||
E.layout.panes[1].buffer_id = buffer_id_bottom;
|
||||
E.layout.panes[1].start_row = half_height;
|
||||
E.layout.panes[1].start_col = 0;
|
||||
E.layout.panes[1].origin_y = half_height;
|
||||
E.layout.panes[1].origin_x = 0;
|
||||
E.layout.panes[1].height = E.screenrows - 2 - half_height;
|
||||
E.layout.panes[1].width = E.screencols;
|
||||
E.layout.panes[1].cursor_x = 0;
|
||||
E.layout.panes[1].cursor_y = 0;
|
||||
E.layout.panes[1].row_offset = 0;
|
||||
E.layout.panes[1].col_offset = 0;
|
||||
E.layout.panes[1].x_offset = 0;
|
||||
E.layout.panes[1].y_offset = 0;
|
||||
E.layout.panes[1].is_active = 0;
|
||||
|
||||
|
||||
editorSetStatusMessage("Horizontal split: %d / %d", buffer_id_top, buffer_id_bottom);
|
||||
return 0;
|
||||
}
|
||||
@@ -142,13 +142,13 @@ void splitScreenUnify(void) {
|
||||
E.layout.mode = SPLIT_NONE;
|
||||
E.layout.num_panes = 1;
|
||||
E.layout.active_pane = 0;
|
||||
|
||||
E.layout.panes[0].start_row = 0;
|
||||
E.layout.panes[0].start_col = 0;
|
||||
E.layout.panes[0].height = E.screenrows - 2;
|
||||
|
||||
E.layout.panes[0].origin_x = 0;
|
||||
E.layout.panes[0].origin_y = 0;
|
||||
E.layout.panes[0].height = E.screenrows;
|
||||
E.layout.panes[0].width = E.screencols;
|
||||
E.layout.panes[0].is_active = 1;
|
||||
|
||||
|
||||
editorSetStatusMessage("Unified view");
|
||||
}
|
||||
|
||||
@@ -161,17 +161,17 @@ int splitScreenSwitchPane(void) {
|
||||
editorSetStatusMessage("No split to switch");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Deactivate current pane
|
||||
E.layout.panes[E.layout.active_pane].is_active = 0;
|
||||
|
||||
|
||||
// Move to next pane
|
||||
E.layout.active_pane = (E.layout.active_pane + 1) % E.layout.num_panes;
|
||||
|
||||
|
||||
// Activate new pane
|
||||
E.layout.panes[E.layout.active_pane].is_active = 1;
|
||||
|
||||
editorSetStatusMessage("Switched to pane %d (buffer %d)",
|
||||
|
||||
editorSetStatusMessage("Switched to pane %d (buffer %d)",
|
||||
E.layout.active_pane,
|
||||
E.layout.panes[E.layout.active_pane].buffer_id);
|
||||
return 0;
|
||||
@@ -187,15 +187,15 @@ int splitScreenSetPaneBuffer(int buffer_id) {
|
||||
editorSetStatusMessage("Error: invalid buffer ID");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
EditorPane *active = &E.layout.panes[E.layout.active_pane];
|
||||
active->buffer_id = buffer_id;
|
||||
active->cursor_x = 0;
|
||||
active->cursor_y = 0;
|
||||
active->row_offset = 0;
|
||||
active->col_offset = 0;
|
||||
|
||||
editorSetStatusMessage("Pane %d now showing buffer %d",
|
||||
active->origin_x = 0;
|
||||
active->origin_y = 0;
|
||||
|
||||
editorSetStatusMessage("Pane %d now showing buffer %d",
|
||||
E.layout.active_pane, buffer_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+81
-30
@@ -1,5 +1,6 @@
|
||||
#include "../include/syntax_highlighter.h"
|
||||
#include "../include/data.h"
|
||||
#include "../include/utf8.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -8,7 +9,7 @@ extern struct editorConfig E;
|
||||
|
||||
const char *c_keywords[] = {
|
||||
"if", "else", "while", "for", "do", "switch",
|
||||
"case", "break", "#include", "#define", "continue", "return",
|
||||
"case", "break", "#include", "#define", "#if", "#endif", "#ifndef", "continue", "return",
|
||||
"goto", "struct", "union", "enum", "typedef", "static",
|
||||
"extern", "const", "volatile", "sizeof", "auto", "register",
|
||||
"inline", "restrict", NULL};
|
||||
@@ -18,10 +19,52 @@ const char *c_types[] = {"int", "char", "float", "double",
|
||||
"void", "long", "short", "unsigned",
|
||||
"signed", "bool", NULL};
|
||||
|
||||
// Returns the byte length of the UTF-8 character starting at s.
|
||||
// Never returns 0 for a non-NUL byte, so callers won't infinite-loop.
|
||||
static int utf8_char_len(const char *s)
|
||||
{
|
||||
unsigned char c = (unsigned char)*s;
|
||||
if (c == 0) return 0;
|
||||
if (c < 0x80) return 1;
|
||||
if ((c & 0xE0) == 0xC0) return 2;
|
||||
if ((c & 0xF0) == 0xE0) return 3;
|
||||
if ((c & 0xF8) == 0xF0) return 4;
|
||||
return 1; // continuation byte or invalid — advance 1 to avoid infinite loop
|
||||
}
|
||||
|
||||
// Copy one full UTF-8 character from src+i into dst+pos, advance both indices.
|
||||
static void copy_utf8_char(char *dst, int *dst_pos, const char *src, int *src_pos)
|
||||
{
|
||||
int len = utf8_char_len(&src[*src_pos]);
|
||||
for (int b = 0; b < len; b++)
|
||||
dst[(*dst_pos)++] = src[(*src_pos)++];
|
||||
}
|
||||
|
||||
// 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 == '_' || c == '#';
|
||||
int is_word_char(const char *s)
|
||||
{
|
||||
uint32_t cp = utf8Decode(&s);
|
||||
|
||||
if ((cp >= 'a' && cp <= 'z') || (cp >= 'A' && cp <= 'Z') ||
|
||||
(cp >= '0' && cp <= '9') || cp == '_' || cp == '#')
|
||||
return 1;
|
||||
|
||||
if (cp == 0xFFFD) return 0;
|
||||
|
||||
if (cp >= 0x00C0 && cp <= 0x017F) return 1;
|
||||
if (cp >= 0x0370 && cp <= 0x03FF) return 1;
|
||||
if (cp >= 0x0400 && cp <= 0x04FF) return 1;
|
||||
if (cp >= 0x0600 && cp <= 0x06FF) return 1;
|
||||
if (cp >= 0x05D0 && cp <= 0x05EA) return 1;
|
||||
if (cp >= 0x0900 && cp <= 0x097F) return 1;
|
||||
if (cp >= 0x4E00 && cp <= 0x9FFF) return 1;
|
||||
if ((cp >= 0x3040 && cp <= 0x309F) ||
|
||||
(cp >= 0x30A0 && cp <= 0x30FF)) return 1;
|
||||
if (cp >= 0xAC00 && cp <= 0xD7A3) return 1;
|
||||
if ((cp >= 0x0660 && cp <= 0x0669) ||
|
||||
(cp >= 0x06F0 && cp <= 0x06F9)) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if string is a keyword
|
||||
@@ -65,26 +108,29 @@ int comment_section = 0;
|
||||
// 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
|
||||
// Each byte can expand to at most (color_prefix + 4 bytes + color_reset).
|
||||
// Allocate generously based on line length to avoid overflow.
|
||||
int line_len = strlen(line);
|
||||
int buf_size = line_len * 32 + 256;
|
||||
char *result = malloc(buf_size);
|
||||
int result_pos = 0;
|
||||
int i = 0;
|
||||
|
||||
while (line[i] != '\0' && line[i] != '\n') {
|
||||
// Skip whitespace
|
||||
// Skip whitespace — copy full UTF-8 char (whitespace is always ASCII,
|
||||
// but using copy_utf8_char keeps the pattern consistent)
|
||||
if (line[i] == ' ' || line[i] == '\t') {
|
||||
result[result_pos++] = line[i++];
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (comment_section) {
|
||||
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
|
||||
while (line[i] != '\0' && line[i] != '\n') {
|
||||
|
||||
if (line[i] == '*' && line[i + 1] == '/') {
|
||||
comment_section = 0;
|
||||
}
|
||||
|
||||
result[result_pos++] = line[i++];
|
||||
if (line[i] == '*' && line[i + 1] == '/') {
|
||||
comment_section = 0;
|
||||
}
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
}
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
continue;
|
||||
@@ -94,14 +140,14 @@ char *highlight_line(const char *line, int *length) {
|
||||
if (line[i] == '/' && line[i + 1] == '/') {
|
||||
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
|
||||
while (line[i] != '\0' && line[i] != '\n') {
|
||||
result[result_pos++] = line[i++];
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
}
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle block comments
|
||||
if ((line[i] == '/' && line[i + 1] == '*')) {
|
||||
if (line[i] == '/' && line[i + 1] == '*') {
|
||||
comment_section = 1;
|
||||
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_COMMENT);
|
||||
result[result_pos++] = line[i++];
|
||||
@@ -110,9 +156,10 @@ char *highlight_line(const char *line, int *length) {
|
||||
if (line[i] == '*' && line[i + 1] == '/') {
|
||||
result[result_pos++] = line[i++];
|
||||
result[result_pos++] = line[i++];
|
||||
comment_section = 0;
|
||||
break;
|
||||
}
|
||||
result[result_pos++] = line[i++];
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
}
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
continue;
|
||||
@@ -125,9 +172,9 @@ char *highlight_line(const char *line, int *length) {
|
||||
while (line[i] != '\0' && line[i] != '"') {
|
||||
if (line[i] == '\\') {
|
||||
result[result_pos++] = line[i++];
|
||||
result[result_pos++] = line[i++];
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
} else {
|
||||
result[result_pos++] = line[i++];
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
}
|
||||
}
|
||||
if (line[i] == '"')
|
||||
@@ -143,9 +190,9 @@ char *highlight_line(const char *line, int *length) {
|
||||
while (line[i] != '\0' && line[i] != '\'') {
|
||||
if (line[i] == '\\') {
|
||||
result[result_pos++] = line[i++];
|
||||
result[result_pos++] = line[i++];
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
} else {
|
||||
result[result_pos++] = line[i++];
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
}
|
||||
}
|
||||
if (line[i] == '\'')
|
||||
@@ -157,22 +204,26 @@ char *highlight_line(const char *line, int *length) {
|
||||
// Handle numbers
|
||||
if (line[i] >= '0' && line[i] <= '9') {
|
||||
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_NUMBER);
|
||||
while (is_word_char(line[i]) || line[i] == '.') {
|
||||
result[result_pos++] = line[i++];
|
||||
while (is_word_char(&line[i]) || line[i] == '.') {
|
||||
copy_utf8_char(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])) {
|
||||
if (is_word_char(&line[i])) {
|
||||
int start = i;
|
||||
while (is_word_char(line[i]))
|
||||
i++;
|
||||
// Advance by full UTF-8 character widths, not single bytes
|
||||
while (line[i] != '\0' && is_word_char(&line[i]))
|
||||
i += utf8_char_len(&line[i]);
|
||||
|
||||
char word[256];
|
||||
strncpy(word, &line[start], i - start);
|
||||
word[i - start] = '\0';
|
||||
int word_len = i - start;
|
||||
if (word_len >= (int)sizeof(word))
|
||||
word_len = (int)sizeof(word) - 1;
|
||||
strncpy(word, &line[start], word_len);
|
||||
word[word_len] = '\0';
|
||||
|
||||
TokenType type = TOKEN_DEFAULT;
|
||||
if (is_keyword(word))
|
||||
@@ -185,9 +236,9 @@ char *highlight_line(const char *line, int *length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle operators and other characters
|
||||
// Handle operators and other characters (including non-ASCII multi-byte)
|
||||
result_pos += sprintf(&result[result_pos], "%s", E.theme.COLOR_DEFAULT);
|
||||
result[result_pos++] = line[i++];
|
||||
copy_utf8_char(result, &result_pos, line, &i);
|
||||
result_pos += sprintf(&result[result_pos], "%s", COLOR_RESET);
|
||||
}
|
||||
|
||||
|
||||
+112
-23
@@ -1,7 +1,16 @@
|
||||
#include "../include/terminal.h"
|
||||
#include "../include/data.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "../include/data.h"
|
||||
#include "../include/define.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "include/utf8.h"
|
||||
|
||||
void die(const char *s) {
|
||||
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||
@@ -35,26 +44,88 @@ void enableRawMode() {
|
||||
}
|
||||
}
|
||||
|
||||
int editorReadKey() {
|
||||
int nread;
|
||||
char c;
|
||||
char seq[3];
|
||||
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
|
||||
if (nread == -1 && errno != EAGAIN) {
|
||||
die("read");
|
||||
#include <ctype.h> /* isprint */
|
||||
|
||||
char *keyToString(int key) {
|
||||
static char key_str[32];
|
||||
|
||||
if (key == '\r') {
|
||||
strcpy(key_str, "ENTER");
|
||||
} else if (key == 0x09) {
|
||||
strcpy(key_str, "TAB");
|
||||
} else if (key >= 1 && key <= 26) {
|
||||
snprintf(key_str, sizeof(key_str), "CTRL-%c", 'a' + key - 1);
|
||||
} else {
|
||||
switch (key) {
|
||||
case ARROW_UP:
|
||||
strcpy(key_str, "ARROW-UP");
|
||||
break;
|
||||
case ARROW_DOWN:
|
||||
strcpy(key_str, "ARROW-DOWN");
|
||||
break;
|
||||
case ARROW_LEFT:
|
||||
strcpy(key_str, "ARROW-LEFT");
|
||||
break;
|
||||
case ARROW_RIGHT:
|
||||
strcpy(key_str, "ARROW-RIGHT");
|
||||
break;
|
||||
case PAGE_UP:
|
||||
strcpy(key_str, "PAGE-UP");
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
strcpy(key_str, "PAGE-DOWN");
|
||||
break;
|
||||
case DEL_KEY:
|
||||
strcpy(key_str, "DEL");
|
||||
break;
|
||||
case BACKSPACE:
|
||||
strcpy(key_str, "BACKSPACE");
|
||||
break;
|
||||
case BEG_LINE:
|
||||
strcpy(key_str, "HOME");
|
||||
break;
|
||||
case END_LINE:
|
||||
strcpy(key_str, "END");
|
||||
break;
|
||||
case '\x1b':
|
||||
strcpy(key_str, "ESCAPE");
|
||||
break;
|
||||
default:
|
||||
if (key > 127) {
|
||||
/* UTF-8 code point — re-encode into the buffer */
|
||||
char buf[5] = {0};
|
||||
int n = utf8Encode((uint32_t)key, buf);
|
||||
snprintf(key_str, sizeof(key_str), "%.*s", n, buf);
|
||||
} else if (isprint(key)) {
|
||||
snprintf(key_str, sizeof(key_str), "%c", key);
|
||||
} else {
|
||||
snprintf(key_str, sizeof(key_str), "KEY-%d", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return key_str;
|
||||
}
|
||||
|
||||
int editorReadKey() {
|
||||
char c;
|
||||
/* read first byte — may be start of UTF-8 or escape */
|
||||
while (read(STDIN_FILENO, &c, 1) != 1)
|
||||
;
|
||||
appDebug("f : %hhu %ld\r\n", c, 0x200);
|
||||
|
||||
if (c == '\x1b') {
|
||||
if (read(STDIN_FILENO, &seq[0], 1) != 1 ||
|
||||
read(STDIN_FILENO, &seq[1], 1) != 1) {
|
||||
char seq[6];
|
||||
/* try to read escape sequence */
|
||||
if (read(STDIN_FILENO, &seq[0], 1) != 1)
|
||||
return '\x1b';
|
||||
}
|
||||
if (read(STDIN_FILENO, &seq[1], 1) != 1)
|
||||
return '\x1b';
|
||||
appDebug("f2 : %s\r\n", seq);
|
||||
|
||||
if (seq[0] == '[') {
|
||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||
if (read(STDIN_FILENO, &seq[2], 1) != 1) {
|
||||
if (read(STDIN_FILENO, &seq[2], 1) != 1)
|
||||
return '\x1b';
|
||||
}
|
||||
if (seq[2] == '~') {
|
||||
switch (seq[1]) {
|
||||
case '1':
|
||||
@@ -74,7 +145,6 @@ int editorReadKey() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
switch (seq[1]) {
|
||||
case 'A':
|
||||
return ARROW_UP;
|
||||
@@ -90,18 +160,26 @@ int editorReadKey() {
|
||||
return END_LINE;
|
||||
}
|
||||
}
|
||||
} else if (seq[0] == 'O') {
|
||||
switch (seq[1]) {
|
||||
case 'H':
|
||||
return BEG_LINE;
|
||||
case 'F':
|
||||
return END_LINE;
|
||||
}
|
||||
}
|
||||
return '\x1b';
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
|
||||
/* multi-byte UTF-8: read remaining bytes */
|
||||
int seqlen = utf8Seqlen((unsigned char)c);
|
||||
if (seqlen > 1) {
|
||||
/* pack into a pseudo-codepoint just to pass bytes through;
|
||||
we handle encoding/decoding at the row level */
|
||||
char buf[4] = {c, 0, 0, 0};
|
||||
for (int i = 1; i < seqlen; i++)
|
||||
if (read(STDIN_FILENO, &buf[i], 1) != 1)
|
||||
break;
|
||||
/* decode and return as uint32, but we need int — use high range */
|
||||
const char *p = buf;
|
||||
uint32_t cp = utf8Decode(&p);
|
||||
return (int)cp; /* caller re-encodes when inserting */
|
||||
}
|
||||
|
||||
return (unsigned char)c;
|
||||
}
|
||||
|
||||
int getCursorPosition(int *rows, int *cols) {
|
||||
@@ -147,3 +225,14 @@ int getWindowSize(int *rows, int *cols) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void appDebug(const char *fmt, ...) {
|
||||
#ifdef APP_DEBUG
|
||||
va_list ap;
|
||||
char message[256];
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(message, 256, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(stderr, "%s\n", message);
|
||||
#endif
|
||||
}
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @file utf8.c
|
||||
*/
|
||||
|
||||
#include "../include/utf8.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
uint32_t readUtf8Char(void)
|
||||
{
|
||||
unsigned char buf[4];
|
||||
|
||||
read(STDIN_FILENO, &buf[0], 1);
|
||||
|
||||
int extra;
|
||||
uint32_t cp;
|
||||
|
||||
if (buf[0] < 0x80)
|
||||
{
|
||||
cp = buf[0];
|
||||
extra = 0;
|
||||
}
|
||||
else if (buf[0] < 0xC0) { return 0xFFFD; } // stray continuation
|
||||
else if (buf[0] < 0xE0)
|
||||
{
|
||||
cp = buf[0] & 0x1F;
|
||||
extra = 1;
|
||||
}
|
||||
else if (buf[0] < 0xF0)
|
||||
{
|
||||
cp = buf[0] & 0x0F;
|
||||
extra = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
cp = buf[0] & 0x07;
|
||||
extra = 3;
|
||||
}
|
||||
|
||||
if (extra > 0)
|
||||
{
|
||||
read(STDIN_FILENO, &buf[1], extra); // read remaining bytes at once
|
||||
for (int i = 0; i < extra; i++)
|
||||
cp = (cp << 6) | (buf[1 + i] & 0x3F);
|
||||
}
|
||||
|
||||
return cp;
|
||||
}
|
||||
|
||||
uint32_t utf8Decode(const char** s)
|
||||
{
|
||||
unsigned char c = (unsigned char)**s;
|
||||
uint32_t cp;
|
||||
int extra;
|
||||
if (c < 0x80)
|
||||
{
|
||||
cp = c;
|
||||
extra = 0;
|
||||
}
|
||||
else if (c < 0xC0)
|
||||
{
|
||||
(*s)++;
|
||||
return 0xFFFD;
|
||||
}
|
||||
else if (c < 0xE0)
|
||||
{
|
||||
cp = c & 0x1F;
|
||||
extra = 1;
|
||||
}
|
||||
else if (c < 0xF0)
|
||||
{
|
||||
cp = c & 0x0F;
|
||||
extra = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
cp = c & 0x07;
|
||||
extra = 3;
|
||||
}
|
||||
(*s)++;
|
||||
while (extra--)
|
||||
{
|
||||
c = (unsigned char)**s;
|
||||
if ((c & 0xC0) != 0x80) return 0xFFFD;
|
||||
cp = (cp << 6) | (c & 0x3F);
|
||||
(*s)++;
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
// buf must have at least 4 bytes; returns bytes written
|
||||
int utf8Encode(uint32_t cp, char* buf)
|
||||
{
|
||||
if (cp < 0x80)
|
||||
{
|
||||
buf[0] = cp;
|
||||
return 1;
|
||||
}
|
||||
if (cp < 0x800)
|
||||
{
|
||||
buf[0] = 0xC0 | (cp >> 6);
|
||||
buf[1] = 0x80 | (cp & 0x3F);
|
||||
return 2;
|
||||
}
|
||||
if (cp < 0x10000)
|
||||
{
|
||||
buf[0] = 0xE0 | (cp >> 12);
|
||||
buf[1] = 0x80 | ((cp >> 6) & 0x3F);
|
||||
buf[2] = 0x80 | (cp & 0x3F);
|
||||
return 3;
|
||||
}
|
||||
buf[0] = 0xF0 | (cp >> 18);
|
||||
buf[1] = 0x80 | ((cp >> 12) & 0x3F);
|
||||
buf[2] = 0x80 | ((cp >> 6) & 0x3F);
|
||||
buf[3] = 0x80 | (cp & 0x3F);
|
||||
return 4;
|
||||
}
|
||||
|
||||
int utf8Seqlen(unsigned char c)
|
||||
{
|
||||
if (c < 0x80) return 1;
|
||||
if (c < 0xC0) return 0; /* continuation — shouldn't be leading */
|
||||
if (c < 0xE0) return 2;
|
||||
if (c < 0xF0) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param codepoint utf8 codepoint of a char
|
||||
* @return length of the codepoint
|
||||
*/
|
||||
int codepointWidth(uint32_t codepoint)
|
||||
{
|
||||
if (codepoint < 0x20 || codepoint == 0x7F) return 0;
|
||||
/* rough double-width ranges */
|
||||
if ((codepoint >= 0x1100 && codepoint <= 0x115F) ||
|
||||
(codepoint >= 0x2E80 && codepoint <= 0x303E) ||
|
||||
(codepoint >= 0x3041 && codepoint <= 0x33BF) ||
|
||||
(codepoint >= 0xAC00 && codepoint <= 0xD7AF) ||
|
||||
(codepoint >= 0xF900 && codepoint <= 0xFAFF) ||
|
||||
(codepoint >= 0xFF01 && codepoint <= 0xFF60) ||
|
||||
(codepoint >= 0x1F300 && codepoint <= 0x1FAFF))
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user