summarylogtreecommitdiffstats
path: root/hyprpad.c
diff options
context:
space:
mode:
authorgingrspacecadet2025-03-31 16:51:59 +0100
committergingrspacecadet2025-03-31 16:51:59 +0100
commit9673f4cbf83b991356f81b78fbcbfc7f29a1b827 (patch)
tree3a4fd7b626d83a4ba78f6cfe2c9a2938cc2b9273 /hyprpad.c
downloadaur-9673f4cbf83b991356f81b78fbcbfc7f29a1b827.tar.gz
Inital commit for hyprpad
Diffstat (limited to 'hyprpad.c')
-rw-r--r--hyprpad.c1376
1 files changed, 1376 insertions, 0 deletions
diff --git a/hyprpad.c b/hyprpad.c
new file mode 100644
index 000000000000..93fad3fc942f
--- /dev/null
+++ b/hyprpad.c
@@ -0,0 +1,1376 @@
+/*** includes ***/
+
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+/*** config parser ***/
+
+#define MAX_LINE_LENGTH 1024
+#define DEFAULT_CONFIG_DIR "~/.config"
+#define CONFIG_FILE "hyprpad.conf"
+
+int tab_stop = 8;
+int quit_times = 3;
+
+typedef struct {
+ char *key;
+ char *value;
+} ConfigEntry;
+
+char *get_config_file_path() {
+ char *config_dir = getenv("XDG_CONFIG_HOME");
+ if (!config_dir) {
+ config_dir = getenv("HOME");
+ if (!config_dir) {
+ fprintf(stderr, "Unable to determine config directory.\n");
+ exit(1);
+ }
+ }
+
+ size_t path_size = strlen(config_dir) + strlen("/.config/hypr/") + strlen(CONFIG_FILE) + 1;
+ char *config_path = malloc(path_size);
+
+ snprintf(config_path, path_size, "%s/.config/hypr/%s", config_dir, CONFIG_FILE);
+ return config_path;
+}
+
+int config_file_exists(const char *config_path) {
+ FILE *file = fopen(config_path, "r");
+ if (file) {
+ fclose(file);
+ return 1; // File exists
+ }
+ return 0; // File does not exist
+}
+
+void create_default_config(const char *config_path) {
+ FILE *file = fopen(config_path, "w");
+ if (!file) {
+ perror("Unable to create config file");
+ return;
+ }
+
+ fprintf(file, "# Hyprpad configuration file\n");
+ fprintf(file, "tab_stop=8\n");
+ fprintf(file, "quit_times=3\n");
+
+ fclose(file);
+}
+
+ConfigEntry *parse_config(const char *config_path, size_t *out_entry_count) {
+ FILE *file = fopen(config_path, "r");
+ if (!file) {
+ perror("Unable to open config file");
+ return NULL;
+ }
+
+ ConfigEntry *entries = malloc(sizeof(ConfigEntry) * 10);
+ size_t entry_count = 0;
+ char line[MAX_LINE_LENGTH];
+
+ while (fgets(line, sizeof(line), file)) {
+ if (line[0] == '\0' || line[0] == '#') {
+ continue;
+ }
+
+ char *equal_pos = strchr(line, '=');
+ if (equal_pos) {
+ *equal_pos = '\0';
+
+ char *key = line;
+ char *value = equal_pos + 1;
+ while (*key && isspace((unsigned char)*key)) key++;
+ while (*value && isspace((unsigned char)*value)) value++;
+
+ entries[entry_count].key = strdup(key);
+ entries[entry_count].value = strdup(value);
+ entry_count++;
+
+ if (entry_count % 10 == 0) {
+ entries = realloc(entries, sizeof(ConfigEntry) * (entry_count + 10));
+ }
+ }
+ }
+
+ fclose(file);
+
+ *out_entry_count = entry_count;
+ return entries;
+}
+
+
+/*** defines ***/
+
+#define HYPRPAD_VERSION "0.0.1"
+#define HYPRPAD_TAB_STOP 8
+#define HYPRPAD_QUIT_TIMES 3
+
+#define CTRL_KEY(k) ((k) & 0x1f)
+#define ESC_KEY 27
+
+enum editorKey {
+ BACKSPACE = 127,
+ ARROW_LEFT = 1000,
+ ARROW_RIGHT,
+ ARROW_UP,
+ ARROW_DOWN,
+ DEL_KEY,
+ HOME_KEY,
+ END_KEY,
+ PAGE_UP,
+ PAGE_DOWN
+};
+
+enum editorHighlight {
+ HL_NORMAL = 0,
+ HL_COMMENT,
+ HL_MLCOMMENT,
+ HL_KEYWORD1,
+ HL_KEYWORD2,
+ HL_STRING,
+ HL_NUMBER,
+ HL_MATCH
+};
+
+#define HL_HIGHLIGHT_NUMBERS (1<<0)
+#define HL_HIGHLIGHT_STRINGS (1<<1)
+
+/*** data ***/
+
+struct editorSyntax {
+ char *filetype;
+ char **filematch;
+ char **keywords;
+ char *singleline_comment_start;
+ char *multiline_comment_start;
+ char *multiline_comment_end;
+ int flags;
+};
+
+typedef struct erow {
+ int idx;
+ int size;
+ int rsize;
+ char *chars;
+ char *render;
+ unsigned char *hl;
+ int hl_open_comment;
+} erow;
+
+struct editorConfig {
+ int cx, cy;
+ int rx;
+ int rowoff;
+ int coloff;
+ int screenrows;
+ int screencols;
+ int numrows;
+ erow *row;
+ int dirty;
+ char *filename;
+ char statusmsg[80];
+ time_t statusmsg_time;
+ struct editorSyntax *syntax;
+ struct termios orig_termios;
+};
+
+struct editorConfig E;
+
+/*** filetypes ***/
+
+char *C_HL_extensions[] = { ".c", ".h", ".cpp", NULL };
+char *C_HL_keywords[] = {
+ "switch", "if", "while", "for", "break", "continue", "return", "else",
+ "struct", "union", "typedef", "static", "enum", "class", "case",
+
+ "int|", "long|", "double|", "float|", "char|", "unsigned|", "signed|",
+ "void|", NULL
+};
+
+struct editorSyntax HLDB[] = {
+ {
+ "c",
+ C_HL_extensions,
+ C_HL_keywords,
+ "//", "/*", "*/",
+ HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
+ },
+};
+
+#define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0]))
+
+/*** prototypes ***/
+
+void editorSetStatusMessage(const char *fmt, ...);
+void editorRefreshScreen();
+char *editorPrompt(char *prompt, void (*callback)(char *, int));
+
+/*** terminal ***/
+
+void use_config(ConfigEntry *entries, size_t entry_count) {
+ for (size_t i = 0; i < entry_count; i++) {
+ if (strcmp(entries[i].key, "tab_stop") == 0) {
+ tab_stop = atoi(entries[i].value);
+ printf("Set tab_stop to: %d\n", tab_stop);
+ } else if (strcmp(entries[i].key, "quit_times") == 0) {
+ quit_times = atoi(entries[i].value);
+ printf("Set quit_times to: %d\n", quit_times);
+ }
+ }
+}
+
+void die(const char *s) {
+ write(STDOUT_FILENO, "\x1b[2J", 4);
+ write(STDOUT_FILENO, "\x1b[H", 3);
+
+ perror(s);
+ exit(1);
+}
+
+void disableRawMode() {
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1)
+ die("tcsetattr");
+}
+
+void enableRawMode() {
+ if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) die("tcgetattr");
+ atexit(disableRawMode);
+
+ struct termios raw = E.orig_termios;
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ raw.c_oflag &= ~(OPOST);
+ raw.c_cflag |= (CS8);
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ raw.c_cc[VMIN] = 0;
+ raw.c_cc[VTIME] = 1;
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr");
+}
+
+int editorReadKey() {
+ int nread;
+ char c;
+ while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
+ if (nread == -1 && errno != EAGAIN) die("read");
+ }
+
+ if (c == '\x1b') {
+ char seq[3];
+
+ if (read(STDIN_FILENO, &seq[0], 1) != 1) return '\x1b';
+ if (read(STDIN_FILENO, &seq[1], 1) != 1) return '\x1b';
+
+ if (seq[0] == '[') {
+ if (seq[1] >= '0' && seq[1] <= '9') {
+ if (read(STDIN_FILENO, &seq[2], 1) != 1) return '\x1b';
+ if (seq[2] == '~') {
+ switch (seq[1]) {
+ case '1': return HOME_KEY;
+ case '3': return DEL_KEY;
+ case '4': return END_KEY;
+ case '5': return PAGE_UP;
+ case '6': return PAGE_DOWN;
+ case '7': return HOME_KEY;
+ case '8': return END_KEY;
+ }
+ }
+ } else {
+ switch (seq[1]) {
+ case 'A': return ARROW_UP;
+ case 'B': return ARROW_DOWN;
+ case 'C': return ARROW_RIGHT;
+ case 'D': return ARROW_LEFT;
+ case 'H': return HOME_KEY;
+ case 'F': return END_KEY;
+ }
+ }
+ } else if (seq[0] == 'O') {
+ switch (seq[1]) {
+ case 'H': return HOME_KEY;
+ case 'F': return END_KEY;
+ }
+ }
+
+ return '\x1b';
+ } else {
+ return c;
+ }
+}
+
+int getCursorPosition(int *rows, int *cols) {
+ char buf[32];
+ unsigned int i = 0;
+
+ if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1;
+
+ while (i < sizeof(buf) - 1) {
+ if (read(STDIN_FILENO, &buf[i], 1) != 1) break;
+ if (buf[i] == 'R') break;
+ i++;
+ }
+ buf[i] = '\0';
+
+ if (buf[0] != '\x1b' || buf[1] != '[') return -1;
+ if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) return -1;
+
+ return 0;
+}
+
+int getWindowSize(int *rows, int *cols) {
+ struct winsize ws;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
+ if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) return -1;
+ return getCursorPosition(rows, cols);
+ } else {
+ *cols = ws.ws_col;
+ *rows = ws.ws_row;
+ return 0;
+ }
+}
+
+/*** syntax highlighting ***/
+
+int is_separator(int c) {
+ return isspace(c) || c == '\0' || strchr(",.()+-/*=~%<>[];", c) != NULL;
+}
+
+void editorUpdateSyntax(erow *row) {
+ row->hl = realloc(row->hl, row->rsize);
+ memset(row->hl, HL_NORMAL, row->rsize);
+
+ if (E.syntax == NULL) return;
+
+ char **keywords = E.syntax->keywords;
+
+ char *scs = E.syntax->singleline_comment_start;
+ char *mcs = E.syntax->multiline_comment_start;
+ char *mce = E.syntax->multiline_comment_end;
+
+ int scs_len = scs ? strlen(scs) : 0;
+ int mcs_len = mcs ? strlen(mcs) : 0;
+ int mce_len = mce ? strlen(mce) : 0;
+
+ int prev_sep = 1;
+ int in_string = 0;
+ int in_comment = (row->idx > 0 && E.row[row->idx - 1].hl_open_comment);
+
+ int i = 0;
+ while (i < row->rsize) {
+ char c = row->render[i];
+ unsigned char prev_hl = (i > 0) ? row->hl[i - 1] : HL_NORMAL;
+
+ if (scs_len && !in_string && !in_comment) {
+ if (!strncmp(&row->render[i], scs, scs_len)) {
+ memset(&row->hl[i], HL_COMMENT, row->rsize - i);
+ break;
+ }
+ }
+
+ if (mcs_len && mce_len && !in_string) {
+ if (in_comment) {
+ row->hl[i] = HL_MLCOMMENT;
+ if (!strncmp(&row->render[i], mce, mce_len)) {
+ memset(&row->hl[i], HL_MLCOMMENT, mce_len);
+ i += mce_len;
+ in_comment = 0;
+ prev_sep = 1;
+ continue;
+ } else {
+ i++;
+ continue;
+ }
+ } else if (!strncmp(&row->render[i], mcs, mcs_len)) {
+ memset(&row->hl[i], HL_MLCOMMENT, mcs_len);
+ i += mcs_len;
+ in_comment = 1;
+ continue;
+ }
+ }
+
+ if (E.syntax->flags & HL_HIGHLIGHT_STRINGS) {
+ if (in_string) {
+ row->hl[i] = HL_STRING;
+ if (c == '\\' && i + 1 < row->rsize) {
+ row->hl[i + 1] = HL_STRING;
+ i += 2;
+ continue;
+ }
+ if (c == in_string) in_string = 0;
+ i++;
+ prev_sep = 1;
+ continue;
+ } else {
+ if (c == '"' || c == '\'') {
+ in_string = c;
+ row->hl[i] = HL_STRING;
+ i++;
+ continue;
+ }
+ }
+ }
+
+ if (E.syntax->flags & HL_HIGHLIGHT_NUMBERS) {
+ if ((isdigit(c) && (prev_sep || prev_hl == HL_NUMBER)) ||
+ (c == '.' && prev_hl == HL_NUMBER)) {
+ row->hl[i] = HL_NUMBER;
+ i++;
+ prev_sep = 0;
+ continue;
+ }
+ }
+
+ if (prev_sep) {
+ int j;
+ for (j = 0; keywords[j]; j++) {
+ int klen = strlen(keywords[j]);
+ int kw2 = keywords[j][klen - 1] == '|';
+ if (kw2) klen--;
+
+ if (!strncmp(&row->render[i], keywords[j], klen) &&
+ is_separator(row->render[i + klen])) {
+ memset(&row->hl[i], kw2 ? HL_KEYWORD2 : HL_KEYWORD1, klen);
+ i += klen;
+ break;
+ }
+ }
+ if (keywords[j] != NULL) {
+ prev_sep = 0;
+ continue;
+ }
+ }
+
+ prev_sep = is_separator(c);
+ i++;
+ }
+
+ int changed = (row->hl_open_comment != in_comment);
+ row->hl_open_comment = in_comment;
+ if (changed && row->idx + 1 < E.numrows)
+ editorUpdateSyntax(&E.row[row->idx + 1]);
+}
+
+int editorSyntaxToColor(int hl) {
+ switch (hl) {
+ case HL_COMMENT:
+ case HL_MLCOMMENT: return 36;
+ case HL_KEYWORD1: return 33;
+ case HL_KEYWORD2: return 32;
+ case HL_STRING: return 35;
+ case HL_NUMBER: return 31;
+ case HL_MATCH: return 34;
+ default: return 37;
+ }
+}
+
+void editorSelectSyntaxHighlight() {
+ E.syntax = NULL;
+ if (E.filename == NULL) return;
+
+ for (unsigned int j = 0; j < HLDB_ENTRIES; j++) {
+ struct editorSyntax *s = &HLDB[j];
+ unsigned int i = 0;
+ while (s->filematch[i]) {
+ char *p = strstr(E.filename, s->filematch[i]);
+ if (p != NULL) {
+ int patlen = strlen(s->filematch[i]);
+ if (s->filematch[i][0] != '.' || p[patlen] == '\0') {
+ E.syntax = s;
+
+ int filerow;
+ for (filerow = 0; filerow < E.numrows; filerow++) {
+ editorUpdateSyntax(&E.row[filerow]);
+ }
+
+ return;
+ }
+ }
+ i++;
+ }
+ }
+}
+
+/*** row operations ***/
+
+int editorRowCxToRx(erow *row, int cx) {
+ int rx = 0;
+ int j;
+ for (j = 0; j < cx; j++) {
+ if (row->chars[j] == '\t')
+ rx += (tab_stop - 1) - (rx % tab_stop); // Use tab_stop from config
+ rx++;
+ }
+ return rx;
+}
+
+int editorRowRxToCx(erow *row, int rx) {
+ int cur_rx = 0;
+ int cx;
+ for (cx = 0; cx < row->size; cx++) {
+ if (row->chars[cx] == '\t')
+ cur_rx += (tab_stop - 1) - (cur_rx % tab_stop); // Use tab_stop from config
+ cur_rx++;
+
+ if (cur_rx > rx) return cx;
+ }
+ return cx;
+}
+
+void editorUpdateRow(erow *row) {
+ int tabs = 0;
+ int j;
+ for (j = 0; j < row->size; j++)
+ if (row->chars[j] == '\t') tabs++;
+
+ free(row->render);
+ row->render = malloc(row->size + tabs * (tab_stop - 1) + 1); // Use tab_stop from config
+
+ int idx = 0;
+ for (j = 0; j < row->size; j++) {
+ if (row->chars[j] == '\t') {
+ row->render[idx++] = ' ';
+ while (idx % tab_stop != 0) row->render[idx++] = ' '; // Use tab_stop from config
+ } else {
+ row->render[idx++] = row->chars[j];
+ }
+ }
+ row->render[idx] = '\0';
+ row->rsize = idx;
+
+ editorUpdateSyntax(row);
+}
+
+void editorInsertRow(int at, char *s, size_t len) {
+ if (at < 0 || at > E.numrows) return;
+
+ E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1));
+ memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));
+ for (int j = at + 1; j <= E.numrows; j++) E.row[j].idx++;
+
+ E.row[at].idx = at;
+
+ E.row[at].size = len;
+ E.row[at].chars = malloc(len + 1);
+ memcpy(E.row[at].chars, s, len);
+ E.row[at].chars[len] = '\0';
+
+ E.row[at].rsize = 0;
+ E.row[at].render = NULL;
+ E.row[at].hl = NULL;
+ E.row[at].hl_open_comment = 0;
+ editorUpdateRow(&E.row[at]);
+
+ E.numrows++;
+ E.dirty++;
+}
+
+void editorFreeRow(erow *row) {
+ free(row->render);
+ free(row->chars);
+ free(row->hl);
+}
+
+void editorDelRow(int at) {
+ if (at < 0 || at >= E.numrows) return;
+ editorFreeRow(&E.row[at]);
+ memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1));
+ for (int j = at; j < E.numrows - 1; j++) E.row[j].idx--;
+ E.numrows--;
+ E.dirty++;
+}
+
+void editorRowInsertChar(erow *row, int at, int c) {
+ 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;
+ editorUpdateRow(row);
+ E.dirty++;
+}
+
+void editorRowAppendString(erow *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';
+ editorUpdateRow(row);
+ E.dirty++;
+}
+
+void editorRowDelChar(erow *row, int at) {
+ if (at < 0 || at >= row->size) return;
+ memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
+ row->size--;
+ editorUpdateRow(row);
+ E.dirty++;
+}
+
+/*** editor operations ***/
+
+void editorInsertChar(int c) {
+ if (E.cy == E.numrows) {
+ editorInsertRow(E.numrows, "", 0);
+ }
+ editorRowInsertChar(&E.row[E.cy], E.cx, c);
+ E.cx++;
+}
+
+void editorInsertNewline() {
+ if (E.cx == 0) {
+ editorInsertRow(E.cy, "", 0);
+ } else {
+ erow *row = &E.row[E.cy];
+ editorInsertRow(E.cy + 1, &row->chars[E.cx], row->size - E.cx);
+ row = &E.row[E.cy];
+ row->size = E.cx;
+ row->chars[row->size] = '\0';
+ editorUpdateRow(row);
+ }
+ E.cy++;
+ E.cx = 0;
+}
+
+void editorDelChar() {
+ if (E.cy == E.numrows) return;
+ if (E.cx == 0 && E.cy == 0) return;
+
+ erow *row = &E.row[E.cy];
+ if (E.cx > 0) {
+ editorRowDelChar(row, E.cx - 1);
+ E.cx--;
+ } else {
+ E.cx = E.row[E.cy - 1].size;
+ editorRowAppendString(&E.row[E.cy - 1], row->chars, row->size);
+ editorDelRow(E.cy);
+ E.cy--;
+ }
+}
+
+/*** file i/o ***/
+
+char *editorRowsToString(int *buflen) {
+ int totlen = 0;
+ int j;
+ for (j = 0; j < E.numrows; j++)
+ totlen += E.row[j].size + 1;
+ *buflen = totlen;
+
+ char *buf = malloc(totlen);
+ char *p = buf;
+ for (j = 0; j < E.numrows; j++) {
+ memcpy(p, E.row[j].chars, E.row[j].size);
+ p += E.row[j].size;
+ *p = '\n';
+ p++;
+ }
+
+ return buf;
+}
+
+void editorOpen(char *filename) {
+ free(E.filename);
+ E.filename = strdup(filename);
+
+ editorSelectSyntaxHighlight();
+
+ FILE *fp = fopen(filename, "r");
+ if (!fp) die("fopen");
+
+ char *line = NULL;
+ size_t linecap = 0;
+ ssize_t linelen;
+ while ((linelen = getline(&line, &linecap, fp)) != -1) {
+ while (linelen > 0 && (line[linelen - 1] == '\n' ||
+ line[linelen - 1] == '\r'))
+ linelen--;
+ editorInsertRow(E.numrows, line, linelen);
+ }
+ free(line);
+ fclose(fp);
+ E.dirty = 0;
+}
+
+void editorSave() {
+ if (E.filename == NULL) {
+ E.filename = editorPrompt("Save as: %s (ESC to cancel)", NULL);
+ if (E.filename == NULL) {
+ editorSetStatusMessage("Save aborted");
+ return;
+ }
+ editorSelectSyntaxHighlight();
+ }
+
+ int len;
+ char *buf = editorRowsToString(&len);
+
+ int fd = open(E.filename, O_RDWR | O_CREAT, 0644);
+ if (fd != -1) {
+ if (ftruncate(fd, len) != -1) {
+ if (write(fd, buf, len) == len) {
+ close(fd);
+ free(buf);
+ E.dirty = 0;
+ editorSetStatusMessage("%d bytes written to disk", len);
+ return;
+ }
+ }
+ close(fd);
+ }
+
+ free(buf);
+ editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
+}
+
+/*** find ***/
+
+void editorFindCallback(char *query, int key) {
+ static int last_match = -1;
+ static int direction = 1;
+
+ static int saved_hl_line;
+ static char *saved_hl = NULL;
+
+ if (saved_hl) {
+ memcpy(E.row[saved_hl_line].hl, saved_hl, E.row[saved_hl_line].rsize);
+ free(saved_hl);
+ saved_hl = NULL;
+ }
+
+ if (key == '\r' || key == '\x1b') {
+ last_match = -1;
+ direction = 1;
+ return;
+ } else if (key == ARROW_RIGHT || key == ARROW_DOWN) {
+ direction = 1;
+ } else if (key == ARROW_LEFT || key == ARROW_UP) {
+ direction = -1;
+ } else {
+ last_match = -1;
+ direction = 1;
+ }
+
+ if (last_match == -1) direction = 1;
+ int current = last_match;
+ int i;
+ for (i = 0; i < E.numrows; i++) {
+ current += direction;
+ if (current == -1) current = E.numrows - 1;
+ else if (current == E.numrows) current = 0;
+
+ erow *row = &E.row[current];
+ char *match = strstr(row->render, query);
+ if (match) {
+ last_match = current;
+ E.cy = current;
+ E.cx = editorRowRxToCx(row, match - row->render);
+ E.rowoff = E.numrows;
+
+ saved_hl_line = current;
+ saved_hl = malloc(row->rsize);
+ memcpy(saved_hl, row->hl, row->rsize);
+ memset(&row->hl[match - row->render], HL_MATCH, strlen(query));
+ break;
+ }
+ }
+}
+
+void editorFind() {
+ int saved_cx = E.cx;
+ int saved_cy = E.cy;
+ int saved_coloff = E.coloff;
+ int saved_rowoff = E.rowoff;
+
+ char *query = editorPrompt("Search: %s (Use ESC/Arrows/Enter)",
+ editorFindCallback);
+
+ if (query) {
+ free(query);
+ } else {
+ E.cx = saved_cx;
+ E.cy = saved_cy;
+ E.coloff = saved_coloff;
+ E.rowoff = saved_rowoff;
+ }
+}
+
+/*** append buffer ***/
+
+struct abuf {
+ char *b;
+ int len;
+};
+
+#define ABUF_INIT {NULL, 0}
+
+void abAppend(struct abuf *ab, const char *s, int len) {
+ char *new = realloc(ab->b, ab->len + len);
+
+ if (new == NULL) return;
+ memcpy(&new[ab->len], s, len);
+ ab->b = new;
+ ab->len += len;
+}
+
+void abFree(struct abuf *ab) {
+ free(ab->b);
+}
+
+/*** output ***/
+
+void editorScroll() {
+ E.rx = 0;
+ if (E.cy < E.numrows) {
+ E.rx = editorRowCxToRx(&E.row[E.cy], E.cx);
+ }
+
+ if (E.cy < E.rowoff) {
+ E.rowoff = E.cy;
+ }
+ if (E.cy >= E.rowoff + E.screenrows) {
+ E.rowoff = E.cy - E.screenrows + 1;
+ }
+ if (E.rx < E.coloff) {
+ E.coloff = E.rx;
+ }
+ if (E.rx >= E.coloff + E.screencols) {
+ E.coloff = E.rx - E.screencols + 1;
+ }
+}
+
+void editorDrawRows(struct abuf *ab) {
+ int y;
+ for (y = 0; y < E.screenrows; y++) {
+ int filerow = y + E.rowoff;
+ if (filerow >= E.numrows) {
+ if (E.numrows == 0 && y == E.screenrows / 3) {
+ char welcome[80];
+ int welcomelen = snprintf(welcome, sizeof(welcome),
+ "HYPRPAD editor -- version %s", HYPRPAD_VERSION);
+ if (welcomelen > E.screencols) welcomelen = E.screencols;
+ int padding = (E.screencols - welcomelen) / 2;
+ if (padding) {
+ abAppend(ab, "~", 1);
+ padding--;
+ }
+ while (padding--) abAppend(ab, " ", 1);
+ abAppend(ab, welcome, welcomelen);
+ } else {
+ abAppend(ab, "~", 1);
+ }
+ } else {
+ int len = E.row[filerow].rsize - E.coloff;
+ if (len < 0) len = 0;
+ if (len > E.screencols) len = E.screencols;
+ char *c = &E.row[filerow].render[E.coloff];
+ unsigned char *hl = &E.row[filerow].hl[E.coloff];
+ int current_color = -1;
+ int j;
+ for (j = 0; j < len; j++) {
+ if (iscntrl(c[j])) {
+ char sym = (c[j] <= 26) ? '@' + c[j] : '?';
+ abAppend(ab, "\x1b[7m", 4);
+ abAppend(ab, &sym, 1);
+ abAppend(ab, "\x1b[m", 3);
+ if (current_color != -1) {
+ char buf[16];
+ int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", current_color);
+ abAppend(ab, buf, clen);
+ }
+ } else if (hl[j] == HL_NORMAL) {
+ if (current_color != -1) {
+ abAppend(ab, "\x1b[39m", 5);
+ current_color = -1;
+ }
+ abAppend(ab, &c[j], 1);
+ } else {
+ int color = editorSyntaxToColor(hl[j]);
+ if (color != current_color) {
+ current_color = color;
+ char buf[16];
+ int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", color);
+ abAppend(ab, buf, clen);
+ }
+ abAppend(ab, &c[j], 1);
+ }
+ }
+ abAppend(ab, "\x1b[39m", 5);
+ }
+
+ abAppend(ab, "\x1b[K", 3);
+ abAppend(ab, "\r\n", 2);
+ }
+}
+
+void editorDrawStatusBar(struct abuf *ab) {
+ abAppend(ab, "\x1b[7m", 4);
+ char status[80], rstatus[80];
+ int len = snprintf(status, sizeof(status), "%.20s - %d lines %s",
+ E.filename ? E.filename : "[No Name]", E.numrows,
+ E.dirty ? "(modified)" : "");
+ int rlen = snprintf(rstatus, sizeof(rstatus), "%s | %d/%d",
+ E.syntax ? E.syntax->filetype : "no ft", E.cy + 1, E.numrows);
+ if (len > E.screencols) len = E.screencols;
+ abAppend(ab, status, len);
+ while (len < E.screencols) {
+ if (E.screencols - len == rlen) {
+ abAppend(ab, rstatus, rlen);
+ break;
+ } else {
+ abAppend(ab, " ", 1);
+ len++;
+ }
+ }
+ abAppend(ab, "\x1b[m", 3);
+ abAppend(ab, "\r\n", 2);
+}
+
+void editorDrawMessageBar(struct abuf *ab) {
+ abAppend(ab, "\x1b[K", 3);
+ int msglen = strlen(E.statusmsg);
+ if (msglen > E.screencols) msglen = E.screencols;
+ if (msglen && time(NULL) - E.statusmsg_time < 5)
+ abAppend(ab, E.statusmsg, msglen);
+}
+
+void editorRefreshScreen() {
+ editorScroll();
+
+ struct abuf ab = ABUF_INIT;
+
+ abAppend(&ab, "\x1b[?25l", 6);
+ abAppend(&ab, "\x1b[H", 3);
+
+ editorDrawRows(&ab);
+ editorDrawStatusBar(&ab);
+ editorDrawMessageBar(&ab);
+
+ char buf[32];
+ snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1,
+ (E.rx - E.coloff) + 1);
+ abAppend(&ab, buf, strlen(buf));
+
+ abAppend(&ab, "\x1b[?25h", 6);
+
+ write(STDOUT_FILENO, ab.b, ab.len);
+ abFree(&ab);
+}
+
+void editorSetStatusMessage(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap);
+ va_end(ap);
+ E.statusmsg_time = time(NULL);
+}
+
+/*** input ***/
+
+char *editorPrompt(char *prompt, void (*callback)(char *, int)) {
+ size_t bufsize = 128;
+ char *buf = malloc(bufsize);
+
+ size_t buflen = 0;
+ buf[0] = '\0';
+
+ while (1) {
+ editorSetStatusMessage("%s%s", prompt, buf); // Append buffer to prompt
+ editorRefreshScreen();
+
+ int c = editorReadKey();
+ if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
+ if (buflen != 0) buf[--buflen] = '\0';
+ } else if (c == '\x1b') {
+ editorSetStatusMessage(""); // Clear status message on escape
+ if (callback) callback(buf, c);
+ free(buf);
+ return NULL;
+ } else if (c == '\r') {
+ if (buflen != 0) {
+ editorSetStatusMessage(""); // Clear status message on enter
+ if (callback) callback(buf, c);
+ return buf;
+ }
+ } else if (!iscntrl(c) && c < 128) {
+ if (buflen == bufsize - 1) {
+ bufsize *= 2;
+ buf = realloc(buf, bufsize);
+ }
+ buf[buflen++] = c;
+ buf[buflen] = '\0';
+ }
+
+ if (callback) callback(buf, c);
+ }
+}
+
+void editorMoveCursor(int key) {
+ erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
+
+ switch (key) {
+ case ARROW_LEFT:
+ if (E.cx != 0) {
+ E.cx--;
+ } else if (E.cy > 0) {
+ E.cy--;
+ E.cx = E.row[E.cy].size;
+ }
+ break;
+ case ARROW_RIGHT:
+ if (row && E.cx < row->size) {
+ E.cx++;
+ } else if (row && E.cx == row->size) {
+ E.cy++;
+ E.cx = 0;
+ }
+ break;
+ case ARROW_UP:
+ if (E.cy != 0) {
+ E.cy--;
+ }
+ break;
+ case ARROW_DOWN:
+ if (E.cy < E.numrows) {
+ E.cy++;
+ }
+ break;
+ }
+
+ row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
+ int rowlen = row ? row->size : 0;
+ if (E.cx > rowlen) {
+ E.cx = rowlen;
+ }
+}
+
+/*** command line ***/
+
+void editorCommandLine() {
+ char *command = editorPrompt(":", NULL);
+ if (command) {
+ if (strcmp(command, "q") == 0) {
+ if (E.dirty) {
+ editorSetStatusMessage("Unsaved changes. Use :q! to force quit.");
+ } else {
+ free(command);
+ write(STDOUT_FILENO, "\x1b[2J", 4); // Clear the screen
+ write(STDOUT_FILENO, "\x1b[H", 3); // Move cursor to top-left
+ exit(0);
+ }
+ } else if (strcmp(command, "q!") == 0) {
+ free(command);
+ write(STDOUT_FILENO, "\x1b[2J", 4); // Clear the screen
+ write(STDOUT_FILENO, "\x1b[H", 3); // Move cursor to top-left
+ exit(0);
+ } else if (strcmp(command, "w") == 0) {
+ editorSave();
+ } else {
+ editorSetStatusMessage("Unknown command: %s", command);
+ }
+ free(command);
+ }
+}
+
+/*** input ***/
+
+void editorMoveCursorVi(int key) {
+ switch (key) {
+ case 'h':
+ editorMoveCursor(ARROW_LEFT);
+ break;
+ case 'l':
+ editorMoveCursor(ARROW_RIGHT);
+ break;
+ case 'k':
+ editorMoveCursor(ARROW_UP);
+ break;
+ case 'j':
+ editorMoveCursor(ARROW_DOWN);
+ break;
+ }
+}
+
+void editorMoveToWordStart() {
+ if (E.cy >= E.numrows) return;
+ erow *row = &E.row[E.cy];
+ while (E.cx > 0 && isspace(row->chars[E.cx - 1])) E.cx--;
+ while (E.cx > 0 && !isspace(row->chars[E.cx - 1])) E.cx--;
+}
+
+void editorMoveToWordEnd() {
+ if (E.cy >= E.numrows) return;
+ erow *row = &E.row[E.cy];
+ while (E.cx < row->size && !isspace(row->chars[E.cx])) E.cx++;
+ while (E.cx < row->size && isspace(row->chars[E.cx])) E.cx++;
+}
+
+void editorMoveToPrevWord() {
+ if (E.cy >= E.numrows) return;
+ erow *row = &E.row[E.cy];
+ while (E.cx > 0 && isspace(row->chars[E.cx - 1])) E.cx--;
+ while (E.cx > 0 && !isspace(row->chars[E.cx - 1])) E.cx--;
+}
+
+void editorJumpToMatchingBracket() {
+ if (E.cy >= E.numrows) return;
+ erow *row = &E.row[E.cy];
+ char current = row->chars[E.cx];
+ char match = 0;
+
+ if (current == '(') match = ')';
+ else if (current == ')') match = '(';
+ else if (current == '{') match = '}';
+ else if (current == '}') match = '{';
+ else if (current == '[') match = ']';
+ else if (current == ']') match = '[';
+
+ if (match == 0) return;
+
+ int direction = (current == '(' || current == '{' || current == '[') ? 1 : -1;
+ int count = 0;
+
+ while (1) {
+ if (direction > 0) {
+ if (E.cx < row->size - 1) {
+ E.cx++;
+ } else if (E.cy < E.numrows - 1) {
+ E.cy++;
+ row = &E.row[E.cy];
+ E.cx = 0;
+ } else {
+ break;
+ }
+ } else {
+ if (E.cx > 0) {
+ E.cx--;
+ } else if (E.cy > 0) {
+ E.cy--;
+ row = &E.row[E.cy];
+ E.cx = row->size;
+ } else {
+ break;
+ }
+ }
+
+ if (row->chars[E.cx] == current) count++;
+ if (row->chars[E.cx] == match) {
+ if (count == 0) break;
+ count--;
+ }
+ }
+}
+
+void editorMoveToLineStart() {
+ E.cx = 0;
+}
+
+void editorMoveToFirstNonWhitespace() {
+ if (E.cy >= E.numrows) return;
+ erow *row = &E.row[E.cy];
+ E.cx = 0;
+ while (E.cx < row->size && isspace(row->chars[E.cx])) E.cx++;
+}
+
+void editorMoveToLineEnd() {
+ if (E.cy >= E.numrows) return;
+ E.cx = E.row[E.cy].size;
+}
+
+void editorMoveToFileStart() {
+ E.cy = 0;
+ editorMoveToLineStart();
+}
+
+void editorMoveToFileEnd() {
+ E.cy = E.numrows - 1;
+ editorMoveToLineEnd();
+}
+
+void editorDeleteLine() {
+ if (E.cy >= E.numrows) return;
+ editorDelRow(E.cy);
+ if (E.cy >= E.numrows) E.cy--;
+ E.cx = 0;
+}
+
+void editorProcessKeypress() {
+ static int vi_mode = 1; // Start in normal mode
+ static int last_key = 0; // For multi-key commands like 'gg'
+ int c = editorReadKey();
+
+ if (vi_mode) {
+ if (last_key == 'g' && c == 'g') {
+ editorMoveToFileStart();
+ last_key = 0;
+ return;
+ }
+
+ last_key = 0;
+
+ switch (c) {
+ case 'i':
+ vi_mode = 0; // Switch to insert mode
+ editorSetStatusMessage("-- INSERT --");
+ break;
+ case 'h':
+ editorMoveCursor(ARROW_LEFT);
+ break;
+ case 'j':
+ editorMoveCursor(ARROW_DOWN);
+ break;
+ case 'k':
+ editorMoveCursor(ARROW_UP);
+ break;
+ case 'l':
+ editorMoveCursor(ARROW_RIGHT);
+ break;
+ case 'w':
+ editorMoveToWordEnd();
+ break;
+ case 'e':
+ editorMoveToWordEnd();
+ if (E.cx < E.row[E.cy].size) E.cx++; // Move to the end of the word
+ break;
+ case 'b':
+ editorMoveToPrevWord();
+ break;
+ case '%':
+ editorJumpToMatchingBracket();
+ break;
+ case '0':
+ editorMoveToLineStart();
+ break;
+ case '^':
+ editorMoveToFirstNonWhitespace();
+ break;
+ case '$':
+ editorMoveToLineEnd();
+ break;
+ case 'G':
+ editorMoveToFileEnd();
+ break;
+ case 'g':
+ last_key = 'g'; // Wait for the next key to complete 'gg'
+ break;
+ case 'd':
+ editorDeleteLine();
+ break;
+ case 'x':
+ editorDelChar();
+ break;
+ case 'o':
+ editorInsertNewline();
+ vi_mode = 0; // Switch to insert mode
+ editorSetStatusMessage("-- INSERT --");
+ break;
+ case ':':
+ editorCommandLine();
+ break;
+ case 'q':
+ editorSetStatusMessage("Use :q to quit.");
+ break;
+ default:
+ editorSetStatusMessage("Unknown command in normal mode.");
+ break;
+ }
+ } else {
+ switch (c) {
+ case '\r':
+ editorInsertNewline();
+ break;
+
+ case ESC_KEY:
+ vi_mode = 1; // Switch to normal mode
+ editorSetStatusMessage("-- NORMAL --");
+ break;
+
+ case BACKSPACE:
+ case CTRL_KEY('h'):
+ case DEL_KEY:
+ if (c == DEL_KEY) editorMoveCursor(ARROW_RIGHT);
+ editorDelChar();
+ break;
+
+ case ARROW_UP:
+ case ARROW_DOWN:
+ case ARROW_LEFT:
+ case ARROW_RIGHT:
+ editorMoveCursor(c);
+ break;
+
+ default:
+ editorInsertChar(c);
+ break;
+ }
+ }
+}
+
+/*** init ***/
+
+void initEditor() {
+ E.cx = 0;
+ E.cy = 0;
+ E.rx = 0;
+ E.rowoff = 0;
+ E.coloff = 0;
+ E.numrows = 0;
+ E.row = NULL;
+ E.dirty = 0;
+ E.filename = NULL;
+ E.statusmsg[0] = '\0';
+ E.statusmsg_time = 0;
+ E.syntax = NULL;
+
+ if (getWindowSize(&E.screenrows, &E.screencols) == -1) die("getWindowSize");
+ E.screenrows -= 2;
+}
+
+int main(int argc, char *argv[]) {
+ char *config_path = get_config_file_path();
+
+ if (!config_file_exists(config_path)) {
+ printf("Config file not found. Creating default config...\n");
+ create_default_config(config_path);
+ }
+
+ size_t entry_count = 0;
+ ConfigEntry *entries = parse_config(config_path, &entry_count);
+ use_config(entries, entry_count);
+
+ for (size_t i = 0; i < entry_count; i++) {
+ free(entries[i].key);
+ free(entries[i].value);
+ }
+ free(entries);
+ free(config_path);
+
+ enableRawMode();
+ initEditor();
+
+ if (argc >= 2) {
+ editorOpen(argv[1]);
+ }
+
+ editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find");
+
+ while (1) {
+ editorRefreshScreen();
+ editorProcessKeypress();
+ }
+
+ return 0;
+}