--- /dev/null
+#include<stdint.h>
+#include<stdbool.h>
+#include<stdio.h>
+
+// 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 <windows.h>
+#include <conio.h>
+#else
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdio.h>
+
+/* 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;
+}