#include #include #include // Global defines #define STR_SIZE 128 #define BUF_SIZE 1024*1024 // Memory static char g_memory[1024*1024]; static int g_memory_index = 0; void * alloc(int num, int size) { void * result = g_memory + g_memory_index; for (int i = 0; i < num*size; i++) g_memory[g_memory_index+i] = 0; g_memory_index += num*size; return result; } #define NEW(TYPE) ((TYPE *)alloc(1, sizeof(TYPE))) #define NEWARR(TYPE, NUM) ((TYPE *)alloc(NUM, sizeof(TYPE))) // getch #ifdef _WIN32 #include #include #else #include #include #include #include /* reads from keypress, doesn't echo */ int getch(void) { struct termios oldattr, newattr; int ch; tcgetattr( STDIN_FILENO, &oldattr ); newattr = oldattr; newattr.c_lflag &= ~( ICANON | ECHO ); // no ECHO for echo(?) tcsetattr( STDIN_FILENO, TCSANOW, &newattr ); ch = getchar(); tcsetattr( STDIN_FILENO, TCSANOW, &oldattr ); return ch; } /* ungets keypress */ void ungetch(int ch) { struct termios oldattr, newattr; tcgetattr( STDIN_FILENO, &oldattr ); newattr = oldattr; newattr.c_lflag &= ~( ICANON | ECHO ); tcsetattr( STDIN_FILENO, TCSANOW, &newattr ); ungetc(ch, stdin); tcsetattr( STDIN_FILENO, TCSANOW, &oldattr ); } #endif int peekch() { int c = getch(); ungetch(c); return c; } // VT100 #define ASCII_ESC 27 void vt100Escape(const char * str, ...) { va_list args; va_start(args, str); printf("%c", ASCII_ESC); vprintf(str, args); } void vt100ClearScreen() { vt100Escape("[2J"); } void vt100CursorHome() { vt100Escape("[H"); } void vt100CursorPos(int v, int h) { vt100Escape("[%d;%dH", v, h); } void vt100SaveCursor() { vt100Escape("7"); } void vt100RestoreCursor() { vt100Escape("8"); } void vt100EnableAlternateBuffer() { vt100Escape("[?1049h"); } void vt100DisableAlternateBuffer() { vt100Escape("[?1049l"); } void vt100EnableNegative() { vt100Escape("[7m"); } void vt100DisableNegative() { vt100Escape("[27m"); } void vt100ShowCursor() { vt100Escape("[?25h"); } void vt100HideCursor() { vt100Escape("[?25l"); } void vt100GetScreenSize(int * v, int * h) { #ifdef _WIN32 CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); *h = csbi.srWindow.Right - csbi.srWindow.Left + 1; *v = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; #else struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); *h = w.ws_row; *v = w.ws_col; #endif } // Node typedef enum { NK_Nul, NK_Arr, NK_Str, NK_COUNT } NodeKind; typedef struct Node Node; struct Node { NodeKind kind; void * data; int childCount; Node *next, *prev, *parent, *child; }; Node *NodeGetChild(Node *n, unsigned int index) { if (index >= n->childCount) return NULL; Node *result = n->child; for (int i = 0; i < index; i++) result = result->next; return result; } void NodeAppend(Node *n1, Node *n2) { if (n1->childCount == 0) { n1->child = n2; } else { Node *lastChild = NodeGetChild(n1, n1->childCount-1); lastChild->next = n2; n2->prev = lastChild; n2->next = NULL; } n2->parent = n1; n1->childCount += 1; } void NodeInsert(Node *n1, Node *n2, unsigned int index) { if (index >= n1->childCount-1) { NodeAppend(n1, n2); return; } Node *insertAfter = NodeGetChild(n1, index); Node *insertBefore = NodeGetChild(n1, index+1); insertAfter->next = n2; insertBefore->prev = n2; n2->prev = insertAfter; n2->next = insertBefore; n1->childCount += 1; } Node *NodeRemove(Node *n1, Node *n2) { if (n1 == NULL) return n2; Node *prev = n2->prev; Node *next = n2->next; if (prev != NULL) prev->next = next; if (next != NULL) next->prev = prev; n2->prev = n2->next = n2->parent = NULL; if (n2 == n1->child) n1->child = next; n1->childCount -= 1; if (prev == NULL) return n1; else return prev; } void NodeDraw(Node *n, Node *selected) { static int indent = 0; for (int i = 0; i < indent; i++) printf(" "); if (n == selected) vt100EnableNegative(); switch (n->kind) { case NK_Nul: printf("null\n"); break; case NK_Arr: printf("(%d) [\n", n->childCount); for (int i = 0; i < n->childCount; i++) NodeDraw(NodeGetChild(n, i), selected); printf("]\n"); break; case NK_Str: printf("'%.16s'\n", (char *)n->data); break; } if (n == selected) vt100DisableNegative(); } // Input #define KEY_CTRL_C 3 #define KEY_BACKSPACE1 8 #define KEY_BACKSPACE2 127 typedef enum Input { IN_None, IN_A, IN_S, IN_D, IN_H, IN_J, IN_K, IN_L, IN_SPACE, IN_Quit, IN_COUNT } Input; Input InputGet() { int c; while (true) { c = getch(); switch (c) { case 'a': return IN_A; case 's': return IN_S; case 'd': return IN_D; case 'h': return IN_H; case 'j': return IN_J; case 'k': return IN_K; case 'l': return IN_L; case ' ': return IN_SPACE; case 'q': return IN_Quit; } } } typedef enum InputAction { IA_None, IA_AppendArray, IA_AppendString, IA_StartEditing, IA_Delete, IA_MoveLeft, IA_MoveDown, IA_MoveUp, IA_MoveRight, IA_COUNT } InputAction; Node *GetNode(InputAction actions[NK_COUNT][IN_COUNT]) { Node * result = NEW(Node); result->kind = NK_Arr; Node * n = result; Input in; InputAction action; enum { Mode_Normal, Mode_Editing, } mode = Mode_Normal; while (true) { vt100ClearScreen(); vt100CursorHome(); NodeDraw(result, n); if (mode == Mode_Editing) { int c = getch(); char *s = (char*)n->data; int slen = strlen(s); if (c == KEY_BACKSPACE1 || c == KEY_BACKSPACE2) { s[slen-1] = '\0'; } else if (c == 13) { mode = Mode_Normal; } else if (slen < STR_SIZE) { s[slen++] = (char)c; } } else if (mode == Mode_Normal) { in = InputGet(); if (in == IN_Quit) break; action = actions[n->kind][in]; #define NODE_APPEND(KIND, DATA) Node *newNode = NEW(Node); newNode->kind = KIND; newNode->data = DATA; NodeAppend(n, newNode); n = newNode; switch (action) { case IA_AppendArray: { NODE_APPEND(NK_Arr, 0); break; } case IA_AppendString: { NODE_APPEND(NK_Str, NEWARR(char, STR_SIZE)); mode = Mode_Editing; break; } case IA_StartEditing: { mode = Mode_Editing; break; } case IA_Delete: { n = NodeRemove(n->parent, n); break; } case IA_MoveLeft: { if (n->prev != NULL) n = n->prev; break; } case IA_MoveDown: { if (n->child != NULL) n = n->child; break; } case IA_MoveUp: { if (n->parent != NULL) n = n->parent; break; } case IA_MoveRight: { if (n->next != NULL) n = n->next; break; } } #undef NODE_APPEND } } return result; } int main() { // Setup InputAction actions[NK_COUNT][IN_COUNT]; actions[NK_Arr][IN_A] = IA_AppendArray; actions[NK_Arr][IN_S] = IA_AppendString; actions[NK_Arr][IN_D] = IA_Delete; actions[NK_Arr][IN_H] = IA_MoveLeft; actions[NK_Arr][IN_J] = IA_MoveDown; actions[NK_Arr][IN_K] = IA_MoveUp; actions[NK_Arr][IN_L] = IA_MoveRight; actions[NK_Str][IN_SPACE] = IA_StartEditing; actions[NK_Str][IN_D] = IA_Delete; actions[NK_Str][IN_H] = IA_MoveLeft; actions[NK_Str][IN_J] = IA_MoveDown; actions[NK_Str][IN_K] = IA_MoveUp; actions[NK_Str][IN_L] = IA_MoveRight; // Main vt100EnableAlternateBuffer(); vt100HideCursor(); Node *n = GetNode(actions); vt100DisableAlternateBuffer(); vt100ShowCursor(); int v, h; vt100GetScreenSize(&v, &h); printf("Screen Size: %d | %d\n", v, h); NodeDraw(n, NULL); return 0; }