Created
July 3, 2021 14:35
-
-
Save worldOneo/9220d5709f2b1f6a90af9562ac393974 to your computer and use it in GitHub Desktop.
Simple terminal editor, cant read or write files currently, but can edit memory 😉
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <termios.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <stdint.h> | |
#include <errno.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <time.h> | |
#include <sys/types.h> | |
#include <sys/ioctl.h> | |
#include <sys/time.h> | |
#include <unistd.h> | |
#include <stdarg.h> | |
#include <fcntl.h> | |
#include <signal.h> | |
#include <math.h> | |
struct termios orig_termios; | |
short currentSequence; | |
#define _chr2shrt(x, y) (((short)x) << 8 | y) | |
const short UP = _chr2shrt('[', 'A'); | |
const short DOWN = _chr2shrt('[', 'B'); | |
const short RIGHT = _chr2shrt('[', 'C'); | |
const short LEFT = _chr2shrt('[', 'D'); | |
short chr2shrt(char a, char b) | |
{ | |
return _chr2shrt(a, b); | |
} | |
#undef _chr2shrt | |
enum KEY_ACTION | |
{ | |
KEY_NULL = 0, | |
CTRL_C = 3, | |
CTRL_D = 4, | |
CTRL_F = 6, | |
CTRL_H = 8, | |
TAB = 9, | |
CTRL_L = 12, | |
ENTER = 13, | |
CTRL_Q = 17, | |
CTRL_S = 19, | |
CTRL_U = 21, | |
ESC = 27, | |
BACKSPACE = 127, | |
ARROW_LEFT = 1000, | |
ARROW_RIGHT, | |
ARROW_UP, | |
ARROW_DOWN, | |
DEL_KEY, | |
HOME_KEY, | |
END_KEY, | |
PAGE_UP, | |
PAGE_DOWN | |
}; | |
const char *moveCursorTo = "\033[%d;%dH"; | |
const char *hideCursor = "\033[?25l"; | |
const int hideCursorLen = 6; | |
const char *showCursor = "\033[?25h"; | |
const int showCursorLen = 6; | |
const char *moveCursorHome = "\033[H"; | |
const int moveCursorHomeLen = 3; | |
typedef struct | |
{ | |
int len; | |
char *chars; | |
} charbuff; | |
void charbuffAppend(charbuff *cb, const char *s, int len) | |
{ | |
char *new = realloc(cb->chars, cb->len + len); | |
if (new == NULL) | |
return; | |
memcpy(new + cb->len, s, len); | |
cb->chars = new; | |
cb->len += len; | |
} | |
void charbuffFree(charbuff *cb) | |
{ | |
free(cb->chars); | |
} | |
typedef struct | |
{ | |
char used; | |
int idx; | |
int size; | |
int displayed; | |
char *content; | |
} editorRow; | |
typedef struct | |
{ | |
int cx, cy; | |
int rowcount; | |
int width; | |
int height; | |
int yoff; | |
int xoff; | |
editorRow **rows; | |
} editor; | |
editorRow *create_row() | |
{ | |
editorRow *row = malloc(sizeof(editorRow)); | |
row->used = 0; | |
row->idx = 0; | |
row->size = 120; | |
row->content = (char *)malloc(120); | |
return row; | |
} | |
void renderRow(editorRow *row, int width, int offset) | |
{ | |
if (row->used == 0) | |
{ | |
printf("\033[1;35m!\033[0m"); | |
return; | |
} | |
for (int i = 0; i < width; i++) | |
{ | |
int pos = i + offset; | |
if (pos >= row->displayed) | |
break; | |
printf("%c", row->content[pos]); | |
} | |
} | |
int getCursorPosition(int ifd, int ofd, int *x, int *y) | |
{ | |
char buf[32]; | |
unsigned int i = 0; | |
if (write(ifd, "\033[6n", 4) != 4) | |
return -1; | |
while (i < sizeof(buf) - 1) | |
{ | |
if (read(ifd, buf + i, 1) != 1) | |
break; | |
if (buf[i] == 'R') | |
break; | |
i++; | |
} | |
if (sscanf(buf, "\033[%d;%dR", x, y) != 2) | |
return -1; | |
return 0; | |
} | |
int getWindowSize(int ifd, int ofd, int *rows, int *cols) | |
{ | |
struct winsize ws; | |
if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) | |
{ | |
int orig_row, orig_col, retval; | |
if (getCursorPosition(ifd, ofd, &orig_row, &orig_col) != 0) | |
return -1; | |
if (write(ofd, "\x1b[999C\x1b[999B", 12) != 12) | |
return -1; | |
if (getCursorPosition(ifd, ofd, rows, cols) != 0) | |
return -1; | |
char seq[32]; | |
snprintf(seq, 32, "\x1b[%d;%dH", orig_row, orig_col); | |
write(ofd, seq, strlen(seq)); | |
return 0; | |
} | |
else | |
{ | |
*cols = ws.ws_col; | |
*rows = ws.ws_row; | |
return 0; | |
} | |
} | |
void clearScreen(int width, int height) | |
{ | |
char cleanBuff[width]; | |
memset(&cleanBuff, ' ', width); | |
cleanBuff[width - 1] = '\0'; | |
for (int i = 0; i < height; i++) | |
{ | |
printf("%s\r\n", cleanBuff); | |
} | |
} | |
editor *make_editor() | |
{ | |
int w, h; | |
getWindowSize(STDIN_FILENO, STDOUT_FILENO, &h, &w); | |
h -= 1; | |
editorRow **rows = (editorRow **)malloc(sizeof(void *) * h); | |
clearScreen(w, h); | |
for (int i = 0; i < h; i++) | |
rows[i] = create_row(); | |
editor *new = malloc(sizeof(editor)); | |
new->rowcount = h; | |
new->width = w; | |
new->height = h; | |
new->yoff = 0; | |
new->xoff = 0; | |
new->rows = rows; | |
new->cx = 0; | |
new->cy = 0; | |
return new; | |
} | |
void printLineNumber(charbuff *out, int n) | |
{ | |
char linenum[50]; | |
int l = sprintf("%-5d |", linenum, ++n); | |
charbuffAppend(out, linenum, l); | |
} | |
void renderEditor(editor *e) | |
{ | |
charbuff out = {0, NULL}; | |
printf(hideCursor); | |
printf(moveCursorHome); | |
clearScreen(e->width, e->height); | |
printf(moveCursorHome); | |
for (int y = 0; y < e->height; y++) | |
{ | |
int rownum = e->yoff + y; | |
editorRow *row = e->rows[rownum]; | |
renderRow(row, e->width, e->xoff); | |
printf("\r\n"); | |
} | |
printf("%d:%d - %s", e->cy + 1, e->cx + 1, "unknown"); | |
printf(moveCursorTo, e->cy - e->yoff + 1, e->cx + 1); | |
printf(showCursor); | |
fflush(stdout); | |
charbuffFree(&out); | |
} | |
int insertToRow(editorRow *row, char c) | |
{ | |
row->displayed++; | |
row->used = 1; | |
if (row->idx > row->size) | |
{ | |
row->content = realloc(row->content, row->size + 100); | |
memset(row->content + row->size, ' ', 100); | |
row->content[row->size + 100] = '\0'; | |
row->size += 100; | |
} | |
else | |
{ | |
memmove(row->content + row->idx + 1, row->content + row->idx, row->size - row->idx + 1); | |
} | |
row->content[row->idx] = c; | |
row->idx++; | |
return row->idx - 1; | |
} | |
int editorRowMoveIdx(editorRow *row, int diff) | |
{ | |
int new = row->idx + diff; | |
if (new < 0 || new > row->displayed) | |
return row->idx; | |
row->idx = new; | |
return new; | |
} | |
char editorRowDeleteChar(editorRow *row) | |
{ | |
if (row->idx == 0) | |
return 1; | |
row->idx--; | |
memmove(row->content + row->idx, row->content + row->idx + 1, row->size - (row->size - row->idx)); | |
row->displayed--; | |
row->content[row->idx + 1] = '\0'; | |
return 0; | |
} | |
void editorRowConcat(editorRow *r1, editorRow *r2) | |
{ | |
r1->content = realloc(r1->content, r1->displayed + r2->displayed); | |
memcpy(r1->content + r1->displayed, r2->content, r2->displayed); | |
r1->displayed += r2->displayed; | |
r1->size = r1->displayed + r2->displayed; | |
free(r2->content); | |
free(r2); | |
} | |
void editorEnter(editor *e) | |
{ | |
e->rows[e->cy]->used = 1; | |
e->cy += 1; | |
e->rowcount += 1; | |
int size = sizeof(editorRow *) * e->rowcount; | |
e->rows = realloc(e->rows, size); | |
memmove(e->rows + e->cy + 1, e->rows + e->cy, sizeof(editorRow *) * (e->rowcount - e->cy - 1)); | |
e->rows[e->cy] = create_row(); | |
editorRow *prev = e->rows[e->cy - 1]; | |
editorRow *curr = e->rows[e->cy]; | |
int take = prev->displayed - prev->idx; | |
if (take > curr->size) | |
{ | |
curr->content = realloc(curr->content, take); | |
curr->size = take; | |
} | |
memcpy(curr->content, prev->content + prev->idx, take); | |
curr->displayed = take; | |
e->rows[e->cy]->used = 1; | |
prev->displayed -= take; | |
prev->idx = prev->idx > prev->idx ? prev->displayed : prev->idx; | |
if (e->cy + 1 > e->height + e->yoff) | |
e->yoff++; | |
e->cx = e->rows[e->cy]->idx; | |
} | |
int editorReadKey(int fd) | |
{ | |
int nread; | |
char c, seq[3]; | |
while ((nread = read(fd, &c, 1)) == 0) | |
; | |
if (nread == -1) | |
exit(1); | |
while (1) | |
{ | |
switch (c) | |
{ | |
case ESC: | |
if (read(fd, seq, 1) == 0) | |
return ESC; | |
if (read(fd, seq + 1, 1) == 0) | |
return ESC; | |
if (seq[0] == '[') | |
{ | |
if (seq[1] >= '0' && seq[1] <= '9') | |
{ | |
if (read(fd, seq + 2, 1) == 0) | |
return ESC; | |
if (seq[2] == '~') | |
{ | |
switch (seq[1]) | |
{ | |
case '3': | |
return DEL_KEY; | |
case '5': | |
return PAGE_UP; | |
case '6': | |
return PAGE_DOWN; | |
} | |
} | |
} | |
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; | |
} | |
} | |
} | |
/* ESC O sequences. */ | |
else if (seq[0] == 'O') | |
{ | |
switch (seq[1]) | |
{ | |
case 'H': | |
return HOME_KEY; | |
case 'F': | |
return END_KEY; | |
} | |
} | |
break; | |
default: | |
return c; | |
} | |
} | |
} | |
void editorAddjustOffset(editor *e) | |
{ | |
while (e->cy - e->yoff < 0) | |
--e->yoff; | |
} | |
void editorListenForKey(editor *e) | |
{ | |
int nread; | |
int key = editorReadKey(STDIN_FILENO); | |
printf(" %d ", key); | |
if (nread == -1) | |
exit(1); | |
switch (key) | |
{ | |
case CTRL_C: | |
exit(0); | |
case ENTER: | |
editorEnter(e); | |
return; | |
case BACKSPACE: | |
{ | |
char endOfLine; | |
endOfLine = editorRowDeleteChar(e->rows[e->cy]); | |
if (endOfLine == 0) | |
e->cx = e->rows[e->cy]->idx; | |
else if (e->cy > 0) | |
{ | |
--e->cy; | |
editorAddjustOffset(e); | |
editorRowConcat(e->rows[e->cy], e->rows[e->cy + 1]); | |
for (int i = e->cy + 1; i < e->rowcount; i++) | |
e->rows[i] = e->rows[i + 1]; | |
e->rows[e->rowcount - 1] = create_row(); | |
e->cx = e->rows[e->cy]->idx; | |
} | |
return; | |
} | |
case ARROW_LEFT: | |
e->cx = editorRowMoveIdx(e->rows[e->cy], -1); | |
return; | |
case ARROW_RIGHT: | |
e->cx = editorRowMoveIdx(e->rows[e->cy], 1); | |
return; | |
case ARROW_UP: | |
if (e->cy - 1 >= 0) | |
--e->cy; | |
e->cx = e->rows[e->cy]->idx; | |
return; | |
case ARROW_DOWN: | |
if (e->rowcount > e->cy) | |
++e->cy; | |
e->cx = e->rows[e->cy]->idx; | |
return; | |
} | |
e->cx = insertToRow(e->rows[e->cy], key) + 1; | |
} | |
void disableRawMode() | |
{ | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); | |
} | |
void enableRawMode() | |
{ | |
setvbuf(stdout, NULL, _IOFBF, 10000); | |
tcgetattr(STDIN_FILENO, &orig_termios); | |
atexit(disableRawMode); | |
struct termios raw = orig_termios; | |
raw.c_iflag &= ~(ICRNL | IXON); | |
raw.c_oflag &= ~(OPOST); | |
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); | |
} | |
int main() | |
{ | |
enableRawMode(); | |
char c; | |
editor *e = make_editor(); | |
renderEditor(e); | |
while (1) | |
{ | |
editorListenForKey(e); | |
renderEditor(e); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment