From 2b3be619337536146e4e01269ded7c0041e428ef Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Wed, 17 Jul 2024 16:23:53 -0400 Subject: [PATCH] Add demo code folder --- demo/colors.ts | 111 ++++ demo/kilo.c | 1468 ++++++++++++++++++++++++++++++++++++++++++++++++ demo/test.css | 271 +++++++++ 3 files changed, 1850 insertions(+) create mode 100644 demo/colors.ts create mode 100644 demo/kilo.c create mode 100644 demo/test.css diff --git a/demo/colors.ts b/demo/colors.ts new file mode 100644 index 0000000..f3c75ac --- /dev/null +++ b/demo/colors.ts @@ -0,0 +1,111 @@ +/** + * This is a test file and a terminal color table program + */ +// ---------------------------------------------------------------------------- +// Display table of the 256 type color console escape codes +// ---------------------------------------------------------------------------- + +export enum Ground { + Fore = 38, + Back = 48, +} + +const code = ( + param: any, + suffix: string = '', +): string => { + if (Array.isArray(param)) { + param = param.join(';'); + } + + return ['\x1b[', param, suffix].join(''); +}; +const textFormat = (param: any): string => code(param, 'm'); +const color256 = (value: number, ground: Ground): string => + textFormat([ground, 5, value]); + +let colorTable = ''; +const doubleBreaks = [15, 51, 87, 123, 159, 195, 231, 255]; +[ + 7, + 15, + 21, + 27, + 33, + 39, + 45, + 51, + 57, + 63, + 69, + 75, + 81, + 87, + 93, + 99, + 105, + 111, + 117, + 123, + 129, + 135, + 141, + 147, + 153, + 159, + 165, + 171, + 177, + 183, + 189, + 195, + 201, + 207, + 213, + 219, + 225, + 231, + 237, + 243, + 249, + 255, +].forEach((line, n, breaks) => { + const start = (n > 0) ? breaks[n - 1] + 1 : 0; + const end = line + 1; + + let i = 0; + + for (i = start; i < end; i++) { + colorTable += color256(i, Ground.Fore); + colorTable += String(i).padStart(4, ' ').padEnd(5, ' '); + colorTable += textFormat(''); + } + + colorTable += ' '.repeat(5); + + for (i = start; i < end; i++) { + colorTable += color256(i, Ground.Back); + colorTable += String(i).padStart(4, ' ').padEnd(5, ' '); + colorTable += textFormat(''); + } + + colorTable += '\n'; + + if (doubleBreaks.includes(line)) { + colorTable += '\n'; + } +}); + +console.log(colorTable); + +/** + * Test code for highlighting + */ +const decimal: number[] = [0, 117]; +const bigDecimal = 123456789123456789n; +const octal: number[] = [0o15, 0o1]; +const bigOctal = 0o777777777777n; +const hexadecimal: number[] = [0x1123, 0x00111]; +const bigHex = 0x123456789ABCDEFn; +const binary: number[] = [0b11, 0b0011]; +const bigBinary = 0b11101001010101010101n; diff --git a/demo/kilo.c b/demo/kilo.c new file mode 100644 index 0000000..17ad3fa --- /dev/null +++ b/demo/kilo.c @@ -0,0 +1,1468 @@ +/*** includes ***/ + +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*** defines ***/ + +#define KILO_VERSION "0.0.1" +#define KILO_TAB_STOP 4 +#define KILO_QUIT_TIMES 3 + +#define CTRL_KEY(k) ((k) & 0x1f) + +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 representing parsing parameters for a file type + */ +struct editorSyntax { + char *filetype; + char **filematch; + char **keywords; + char *singleline_comment_start; + char *multiline_comment_start; + char *multiline_comment_end; + int flags; +}; + +/** + * Struct representing a row in the editor + */ +typedef struct erow { + int idx; // Row number in the file + int size; // Number of characters + int rsize; // Number of characters rendered to screen + char *chars; // Input characters + char *render; // Display characters + unsigned char *hl; // Highlighted representation of characters + int hl_open_comment; // Is this row part of a multiline comment? +} erow; + +/** + * Global editor state + * + * // Nested comment to check for double highlight + */ +struct editorConfig { + int cx, cy; // Cursor position + int rx; // Cursor render position + int rowoff; // Vertical scroll offset + int coloff; // Horizontal scroll offset + int screenrows; // Number of rows visible in the current terminal + int screencols; // Number of columns visible in the current terminal + int numrows; + erow *row; // Current row + int dirty; // File modification check flag + char *filename; // Name of the current file + char statusmsg[80]; // Message for the status bar + time_t statusmsg_time; + struct editorSyntax *syntax; // Type of syntax for current file + struct termios orig_termios; +}; + +struct editorConfig E; + +/*** filetypes ***/ + +char *C_HL_extensions[] = { ".c", ".h", ".cpp", NULL }; +char *C_HL_keywords[] = { + // Keywords + "switch", "if", "while", "for", "break", "continue", "return", "else", + "struct", "union", "typedef", "static", "enum", "class", "case", + + // Types + "int|", "long|", "double|", "float|", "char|", "unsigned|", "signed|", + "void|", + + // Preprocessor Directives + "#define|", "#endif|", "#error|", "#if|", "#ifdef|", "#ifndef|", "#include|", + "#undef|", 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 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"); + } + } + + // The character code starts with the escape sequence (\x1b) + 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'; + } + + 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; + } + else + { + return getCursorPosition(rows, cols); + } + } + + *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; + + // Single line comments + 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; + } + } + + // Multi-line comments + 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; + } + } + + // Strings + 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; + } + } + } + + // Numbers + 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; + } + } + + // Keywords + 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; // cyan + + case HL_KEYWORD1: + return 33; // yellow + + case HL_KEYWORD2: + return 32; // green + + case HL_STRING: + return 35; // magenta + + case HL_NUMBER: + return 31; // red + + case HL_MATCH: + return 34; // blue + + default: + return 37; + } +} + +void editorSelectSyntaxHighlight() +{ + E.syntax = NULL; + if (E.filename == NULL) + { + return; + } + + char *ext = strrchr(E.filename, '.'); + + for (unsigned int j = 0; j < HLDB_ENTRIES; j++) + { + struct editorSyntax *s = &HLDB[j]; + unsigned int i = 0; + while (s->filematch[i]) + { + int is_ext = (s->filematch[i][0] == '.'); + if ((is_ext && ext && !strcmp(ext, s->filematch[i])) || + ( ! is_ext && strstr(E.filename, s->filematch[i]))) + { + 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 += (KILO_TAB_STOP -1) - (rx % KILO_TAB_STOP); + } + 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 += (KILO_TAB_STOP - 1) - (cur_rx % KILO_TAB_STOP); + } + cur_rx++; + + if (cur_rx > rx) + { + return rx; + } + } + + 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 *(KILO_TAB_STOP - 1) + 1); + + int idx = 0; + for (j = 0; j < row->size; j++) + { + if (row->chars[j] == '\t') + { + row->render[idx++] = ' '; + while (idx % KILO_TAB_STOP != 0) + { + row->render[idx++] = ' '; + } + } + 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)); + // Update index of following rows on insert + 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)); + // Update index of following rows on delete + 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.cx < E.coloff) + { + E.coloff = E.rx; + } + + if (E.cx >= 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), + "Kilo editor -- version %s", KILO_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++) + { + // Make control characters "readable" + 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); + // Restore the previous highlighting color + 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(prompt, buf); + 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(""); + if (callback) + { + callback(buf, c); + } + free(buf); + return NULL; + } + else if (c == '\r') + { + if (buflen != 0) + { + editorSetStatusMessage(""); + 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; + } +} + +void editorProcessKeypress() +{ + static int quit_times = KILO_QUIT_TIMES; + + int c = editorReadKey(); + + switch (c) + { + case '\r': + editorInsertNewline(); + break; + + case CTRL_KEY('q'): + if (E.dirty && quit_times > 0) + { + editorSetStatusMessage("WARNING!!! File has unsaved changes ." + "Press Ctrl-Q %d more time(s) to quit.", quit_times); + quit_times--; + return; + } + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, "\x1b[H", 3); + exit(0); + break; + + case CTRL_KEY('s'): + editorSave(); + break; + + case HOME_KEY: + E.cx = 0; + break; + + case END_KEY: + if (E.cy < E.numrows) + { + E.cx = E.row[E.cy].size; + } + break; + + case CTRL_KEY('f'): + editorFind(); + break; + + case BACKSPACE: + case CTRL_KEY('h'): + case DEL_KEY: + if (c == DEL_KEY) + { + editorMoveCursor(ARROW_RIGHT); + } + editorDelChar(); + break; + + case PAGE_UP: + case PAGE_DOWN: + { + if (c == PAGE_UP) + { + E.cy = E.rowoff; + } + else if (c == PAGE_DOWN) + { + E.cy = E.rowoff + E.screenrows - 1; + if (E.cy > E.numrows) + { + E.cy = E.numrows; + } + } + + int times = E.screenrows; + while (times--) + { + editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN); + } + } + break; + + case ARROW_UP: + case ARROW_DOWN: + case ARROW_LEFT: + case ARROW_RIGHT: + editorMoveCursor(c); + break; + + case CTRL_KEY('l'): + case '\x1b': + break; + + default: + editorInsertChar(c); + break; + } + + quit_times = KILO_QUIT_TIMES; +} + +/*** 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[]) +{ + 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; +} + diff --git a/demo/test.css b/demo/test.css new file mode 100644 index 0000000..f0181df --- /dev/null +++ b/demo/test.css @@ -0,0 +1,271 @@ +/* ----------------------------------------------------------------------------- + CSS loading icon +------------------------------------------------------------------------------*/ +.cssload-loader { + position: relative; + left: calc(50% - 31px); + width: 62px; + height: 62px; + border-radius: 50%; + perspective: 780px; +} + +.cssload-inner { + position: absolute; + width: 100%; + height: 100%; + box-sizing: border-box; + border-radius: 50%; +} + +.cssload-inner.cssload-one { + left: 0%; + top: 0%; + animation: cssload-rotate-one 1.15s linear infinite; + border-bottom: 3px solid rgb(0, 0, 0); +} + +.cssload-inner.cssload-two { + right: 0%; + top: 0%; + animation: cssload-rotate-two 1.15s linear infinite; + border-right: 3px solid rgb(0, 0, 0); +} + +.cssload-inner.cssload-three { + right: 0%; + bottom: 0%; + animation: cssload-rotate-three 1.15s linear infinite; + border-top: 3px solid rgb(0, 0, 0); +} + +@keyframes cssload-rotate-one { + 0% { + transform: rotateX(35deg) rotateY(-45deg) rotateZ(0deg); + } + 100% { + transform: rotateX(35deg) rotateY(-45deg) rotateZ(360deg); + } +} + +@keyframes cssload-rotate-two { + 0% { + transform: rotateX(50deg) rotateY(10deg) rotateZ(0deg); + } + 100% { + transform: rotateX(50deg) rotateY(10deg) rotateZ(360deg); + } +} + +@keyframes cssload-rotate-three { + 0% { + transform: rotateX(35deg) rotateY(55deg) rotateZ(0deg); + } + 100% { + transform: rotateX(35deg) rotateY(55deg) rotateZ(360deg); + } +} + +/* ---------------------------------------------------------------------------- + Loading overlay +-----------------------------------------------------------------------------*/ +#loading-shadow { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + z-index: 500; +} + +#loading-shadow .loading-wrapper { + position: fixed; + z-index: 501; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +#loading-shadow .loading-content { + position: relative; + color: #fff +} + +.loading-content .cssload-inner.cssload-one, +.loading-content .cssload-inner.cssload-two, +.loading-content .cssload-inner.cssload-three { + border-color: #fff +} + +/* ---------------------------------------------------------------------------- +CSS Tabs +-----------------------------------------------------------------------------*/ +.tabs { + display: inline-block; + display: flex; + flex-wrap: wrap; + background: #efefef; + box-shadow: 0 48px 80px -32px rgba(0, 0, 0, 0.3); + margin-top: 1.5em; +} + +.tabs > label { + border: 1px solid #e5e5e5; + width: 100%; + padding: 20px 30px; + background: #e5e5e5; + cursor: pointer; + font-weight: bold; + font-size: 18px; + color: #7f7f7f; + transition: background 0.1s, color 0.1s; + /* margin-left: 4em; */ +} + +.tabs > label:hover { + background: #d8d8d8; +} + +.tabs > label:active { + background: #ccc; +} + +.tabs > [type=radio]:focus + label { + box-shadow: inset 0px 0px 0px 3px #2aa1c0; + z-index: 1; +} + +.tabs > [type=radio] { + position: absolute; + opacity: 0; +} + +.tabs > [type=radio]:checked + label { + border-bottom: 1px solid #fff; + background: #fff; + color: #000; +} + +.tabs > [type=radio]:checked + label + .content { + border: 1px solid #e5e5e5; + border-top: 0; + display: block; + padding: 15px; + background: #fff; + width: 100%; + margin: 0 auto; + overflow: auto; + /* text-align: center; */ +} + +.tabs .content, .single-tab { + display: none; + max-height: 950px; + border: 1px solid #e5e5e5; + border-top: 0; + padding: 15px; + background: #fff; + width: 100%; + margin: 0 auto; + overflow: auto; +} + +.single-tab { + display: block; + border: 1px solid #e5e5e5; + box-shadow: 0 48px 80px -32px rgba(0, 0, 0, 0.3); + margin-top: 1.5em; +} + +.tabs .content.full-height, .single-tab.full-height { + max-height: none; +} + +@media (min-width: 800px) { + .tabs > label { + width: auto; + } + + .tabs .content { + order: 99; + } +} + +/* --------------------------------------------------------------------------- + Vertical Tabs + ----------------------------------------------------------------------------*/ + +.vertical-tabs { + border: 1px solid #e5e5e5; + box-shadow: 0 48px 80px -32px rgba(0, 0, 0, 0.3); + margin: 0 auto; + position: relative; + width: 100%; +} + +.vertical-tabs input[type="radio"] { + position: absolute; + opacity: 0; +} + +.vertical-tabs .tab { + align-items: center; + display: inline-block; + display: flex; + flex-wrap: nowrap; +} + +.vertical-tabs .tab label { + align-items: center; + background: #e5e5e5; + border: 1px solid #e5e5e5; + color: #7f7f7f; + cursor: pointer; + font-size: 18px; + font-weight: bold; + padding: 0 20px; + width: 28%; +} + +.vertical-tabs .tab label:hover { + background: #d8d8d8; +} + +.vertical-tabs .tab label:active { + background: #ccc; +} + +.vertical-tabs .tab .content { + display: none; + border: 1px solid #e5e5e5; + border-left: 0; + border-right: 0; + max-height: 950px; + overflow: auto; +} + +.vertical-tabs .tab .content.full-height { + max-height: none; +} + +.vertical-tabs [type=radio]:checked + label { + border: 0; + background: #fff; + color: #000; + width: 38%; +} + +.vertical-tabs [type=radio]:focus + label { + box-shadow: inset 0px 0px 0px 3px #2aa1c0; + z-index: 1; +} + +.vertical-tabs [type=radio]:checked ~ .content { + display: block; +} +