├── kilo ├── .kilo.c.un~ ├── README.md ├── kilo.c └── kilo.ino /kilo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maksimKorzh/esp32-kilo/HEAD/kilo -------------------------------------------------------------------------------- /.kilo.c.un~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maksimKorzh/esp32-kilo/HEAD/.kilo.c.un~ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32-kilo 2 | Kilo text editor port to esp32 using FabGL
3 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/74_XJ8f4MvY/0.jpg)](https://www.youtube.com/watch?v=74_XJ8f4MvY) 4 | -------------------------------------------------------------------------------- /kilo.c: -------------------------------------------------------------------------------- 1 | /* 2 | KILO text editor 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define KILO_VERSION "0.0.1" 11 | #define KILO_TAB_STOP 2 12 | 13 | enum editorKey { 14 | BACKSPACE = 127, 15 | ARROW_LEFT = 1000, 16 | ARROW_RIGHT, 17 | ARROW_UP, 18 | ARROW_DOWN, 19 | DEL_KEY, 20 | HOME_KEY, 21 | END_KEY, 22 | PAGE_UP, 23 | PAGE_DOWN, 24 | ENTER_KEY, 25 | ESCAPE_KEY 26 | }; 27 | 28 | typedef struct erow { 29 | int size; 30 | int rsize; 31 | char *chars; 32 | char *render; 33 | } erow; 34 | 35 | struct editorConfig { 36 | int cx, cy; 37 | int rx; 38 | int rowoff; 39 | int coloff; 40 | int screenrows; 41 | int screencols; 42 | int dirty; 43 | int numrows; 44 | erow *row; 45 | char *filename; 46 | char statusmsg[80]; 47 | time_t statusmsg_time; 48 | }; struct editorConfig E; 49 | 50 | struct abuf { 51 | char *b; 52 | int len; 53 | }; 54 | 55 | #define ABUF_INIT {NULL, 0} 56 | 57 | int editorRowCxToRx(erow *row, int cx) { 58 | int rx = 0; 59 | int j; 60 | for (j = 0; j < cx; j++) { 61 | if (row->chars[j] == '\t') 62 | rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP); 63 | rx++; 64 | } 65 | return rx; 66 | } 67 | 68 | void editorUpdateRow(erow *row) { 69 | int tabs = 0; 70 | int j; 71 | for (j = 0; j < row->size; j++) 72 | if (row->chars[j] == '\t') tabs++; 73 | free(row->render); 74 | row->render = (char *)malloc(row->size + tabs*(KILO_TAB_STOP - 1) + 1); 75 | int idx = 0; 76 | for (j = 0; j < row->size; j++) { 77 | if (row->chars[j] == '\t') { 78 | row->render[idx++] = ' '; 79 | while (idx % KILO_TAB_STOP != 0) row->render[idx++] = ' '; 80 | } else { 81 | row->render[idx++] = row->chars[j]; 82 | } 83 | } 84 | row->render[idx] = '\0'; 85 | row->rsize = idx; 86 | } 87 | 88 | void editorInsertRow(int at, char *s, size_t len) { 89 | if (at < 0 || at > E.numrows) return; 90 | E.row = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1)); 91 | memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at)); 92 | E.row[at].size = len; 93 | E.row[at].chars = (char *)malloc(len + 1); 94 | memcpy(E.row[at].chars, s, len); 95 | E.row[at].chars[len] = '\0'; 96 | E.row[at].rsize = 0; 97 | E.row[at].render = NULL; 98 | editorUpdateRow(&E.row[at]); 99 | E.numrows++; 100 | E.dirty++; 101 | } 102 | 103 | void editorFreeRow(erow *row) { 104 | free(row->render); 105 | free(row->chars); 106 | } 107 | void editorDelRow(int at) { 108 | if (at < 0 || at >= E.numrows) return; 109 | editorFreeRow(&E.row[at]); 110 | memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1)); 111 | E.numrows--; 112 | E.dirty++; 113 | } 114 | 115 | void editorRowInsertChar(erow *row, int at, int c) { 116 | if (at < 0 || at > row->size) at = row->size; 117 | row->chars = (char *)realloc(row->chars, row->size + 2); 118 | memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1); 119 | row->size++; 120 | row->chars[at] = c; 121 | editorUpdateRow(row); 122 | E.dirty++; 123 | } 124 | 125 | void editorRowAppendString(erow *row, char *s, size_t len) { 126 | row->chars = (char *)realloc(row->chars, row->size + len + 1); 127 | memcpy(&row->chars[row->size], s, len); 128 | row->size += len; 129 | row->chars[row->size] = '\0'; 130 | editorUpdateRow(row); 131 | E.dirty++; 132 | } 133 | 134 | void editorInsertChar(int c) { 135 | if (E.cy == E.numrows) { 136 | editorInsertRow(E.numrows, "", 0); 137 | } 138 | editorRowInsertChar(&E.row[E.cy], E.cx, c); 139 | E.cx++; 140 | } 141 | 142 | void editorInsertNewline() { 143 | if (E.cx == 0) { 144 | editorInsertRow(E.cy, "", 0); 145 | } else { 146 | erow *row = &E.row[E.cy]; 147 | editorInsertRow(E.cy + 1, &row->chars[E.cx], row->size - E.cx); 148 | row = &E.row[E.cy]; 149 | row->size = E.cx; 150 | row->chars[row->size] = '\0'; 151 | editorUpdateRow(row); 152 | } 153 | E.cy++; 154 | E.cx = 0; 155 | } 156 | 157 | void editorRowDelChar(erow *row, int at) { 158 | if (at < 0 || at >= row->size) return; 159 | memmove(&row->chars[at], &row->chars[at + 1], row->size - at); 160 | row->size--; 161 | editorUpdateRow(row); 162 | E.dirty++; 163 | } 164 | 165 | void editorDelChar() { 166 | if (E.cy == E.numrows) return; 167 | if (E.cx == 0 && E.cy == 0) return; 168 | erow *row = &E.row[E.cy]; 169 | if (E.cx > 0) { 170 | editorRowDelChar(row, E.cx - 1); 171 | E.cx--; 172 | } else { 173 | E.cx = E.row[E.cy - 1].size; 174 | editorRowAppendString(&E.row[E.cy - 1], row->chars, row->size); 175 | editorDelRow(E.cy); 176 | E.cy--; 177 | } 178 | } 179 | 180 | /*size_t getline(char *buf, size_t *size, File *stream) 181 | { 182 | char c; 183 | size_t count = 0; 184 | 185 | while (c != '\r') { 186 | if (stream->available()) { 187 | c = stream->read(); 188 | buf[count] = c; 189 | count++; 190 | } else { 191 | if (count == 0) return -1; 192 | else break; 193 | } 194 | } 195 | 196 | buf[count] = '\0'; 197 | return count; 198 | }*/ 199 | 200 | char *editorRowsToString(int *buflen) { 201 | int totlen = 0; 202 | int j; 203 | for (j = 0; j < E.numrows; j++) 204 | totlen += E.row[j].size + 1; 205 | *buflen = totlen; 206 | char *buf = (char *)malloc(totlen); 207 | char *p = buf; 208 | for (j = 0; j < E.numrows; j++) { 209 | memcpy(p, E.row[j].chars, E.row[j].size); 210 | p += E.row[j].size; 211 | *p = '\r'; 212 | p++; 213 | } *--p = '\0'; 214 | return buf; 215 | } 216 | 217 | /*void editorSave(fs::FS &fs) { 218 | if (E.filename == NULL) return; 219 | File fp = fs.open(E.filename, FILE_WRITE); 220 | int len; 221 | char *buf = editorRowsToString(&len); 222 | 223 | if(!fp){ 224 | editorSetStatusMessage("Failed to open file"); 225 | return; 226 | } 227 | 228 | if(fp.print(buf)) editorSetStatusMessage("File written"); 229 | else editorSetStatusMessage("Failed to write file!"); 230 | E.dirty = 0; 231 | fp.close(); 232 | free(buf); 233 | } 234 | 235 | void editorOpen(fs::FS &fs, const char *filename) { 236 | free(E.filename); 237 | E.filename = strdup(filename); 238 | 239 | File fp = fs.open(filename); 240 | if(!fp || fp.isDirectory()) { 241 | xprintf("− failed to open file for reading\n\r"); 242 | return; 243 | } 244 | 245 | if (!fp) { xprintf("No file found\n\r"); return; } 246 | char line[200]; // 200 chars per line allowed 247 | size_t linecap = 0; 248 | ssize_t linelen; 249 | 250 | while ((linelen = getline(line, &linecap, &fp)) != -1) { 251 | while (linelen > 0 && (line[linelen - 1] == '\n' || line[linelen - 1] == '\r')) linelen--; 252 | editorInsertRow(E.numrows, line, linelen); 253 | } 254 | 255 | fp.close(); 256 | E.dirty = 0; 257 | }*/ 258 | 259 | void abAppend(struct abuf *ab, const char *s, int len) { 260 | char *newb = (char *)realloc(ab->b, ab->len + len); 261 | if (newb == NULL) return; 262 | memcpy(&newb[ab->len], s, len); 263 | ab->b = newb; 264 | ab->len += len; 265 | } 266 | 267 | void abFree(struct abuf *ab) { 268 | free(ab->b); 269 | } 270 | 271 | void editorScroll() { 272 | E.rx = 0; 273 | if (E.cy < E.numrows) { 274 | E.rx = editorRowCxToRx(&E.row[E.cy], E.cx); 275 | } 276 | if (E.cy < E.rowoff) { 277 | E.rowoff = E.cy; 278 | } 279 | if (E.cy >= E.rowoff + E.screenrows) { 280 | E.rowoff = E.cy - E.screenrows + 1; 281 | } 282 | if (E.rx < E.coloff) { 283 | E.coloff = E.rx; 284 | } 285 | if (E.rx >= E.coloff + E.screencols) { 286 | E.coloff = E.rx - E.screencols + 1; 287 | } 288 | } 289 | 290 | void editorDrawRows(struct abuf *ab) { 291 | int y; 292 | for (y = 0; y < E.screenrows; y++) { 293 | int filerow = y + E.rowoff; 294 | if (filerow >= E.numrows) { 295 | if (E.numrows == 0 && y == E.screenrows / 3) { 296 | char welcome[80]; 297 | int welcomelen = snprintf(welcome, sizeof(welcome), 298 | "Kilo editor -- version %s", KILO_VERSION); 299 | if (welcomelen > E.screencols) welcomelen = E.screencols; 300 | int padding = (E.screencols - welcomelen) / 2; 301 | if (padding) { 302 | abAppend(ab, "~", 1); 303 | padding--; 304 | } 305 | while (padding--) abAppend(ab, " ", 1); 306 | abAppend(ab, welcome, welcomelen); 307 | } else { 308 | abAppend(ab, "~", 1); 309 | } 310 | } else { 311 | int len = E.row[filerow].rsize - E.coloff; 312 | if (len < 0) len = 0; 313 | if (len >= E.screencols) len = E.screencols - 1; 314 | abAppend(ab, &E.row[filerow].render[E.coloff], len); 315 | } 316 | abAppend(ab, "\x1b[K", 3); // clear line 317 | abAppend(ab, "\n\r", 2); 318 | } 319 | } 320 | 321 | void editorDrawStatusBar(struct abuf *ab) { 322 | abAppend(ab, "\x1b[7m", 4); 323 | char status[80], rstatus[80]; 324 | int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", 325 | E.filename ? E.filename : "[No Name]", E.numrows, 326 | E.dirty ? "(modified)" : ""); 327 | int rlen = snprintf(rstatus, sizeof(rstatus), "row %d, col %d", 328 | E.cy + 1, E.cx + 1); 329 | if (len > E.screencols) len = E.screencols; 330 | abAppend(ab, status, len); 331 | while (len < E.screencols) { 332 | if (E.screencols - len == rlen) { 333 | abAppend(ab, rstatus, rlen); 334 | break; 335 | } else { 336 | abAppend(ab, " ", 1); 337 | len++; 338 | } 339 | } 340 | abAppend(ab, "\x1b[m", 3); 341 | abAppend(ab, "\r\n", 2); 342 | } 343 | 344 | void editorDrawMessageBar(struct abuf *ab) { 345 | abAppend(ab, "\x1b[K", 3); 346 | int msglen = strlen(E.statusmsg); 347 | if (msglen > E.screencols) msglen = E.screencols; 348 | if (msglen && time(NULL) - E.statusmsg_time < 5) 349 | abAppend(ab, E.statusmsg, msglen); 350 | } 351 | 352 | void editorRefreshScreen() { 353 | editorScroll(); 354 | struct abuf ab = ABUF_INIT; 355 | abAppend(&ab, "\x1b[?25l", 6); // hide cursor 356 | abAppend(&ab, "\x1b[H", 3); // cursor home 357 | editorDrawRows(&ab); 358 | editorDrawStatusBar(&ab); 359 | editorDrawMessageBar(&ab); 360 | abAppend(&ab, "\x1b[?25h", 6); // show cursor 361 | char buf[32]; 362 | snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1, 363 | (E.rx - E.coloff) + 1); 364 | abAppend(&ab, buf, strlen(buf)); 365 | // Write buffer ab to screen / serial port 366 | abFree(&ab); 367 | } 368 | 369 | void editorMoveCursor(int key) { 370 | erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; 371 | switch (key) { 372 | case ARROW_LEFT: 373 | if (E.cx != 0) { 374 | E.cx--; 375 | } else if (E.cy > 0) { 376 | E.cy--; 377 | E.cx = E.row[E.cy].size; 378 | } 379 | break; 380 | case ARROW_RIGHT: 381 | if (row && E.cx < row->size) { 382 | E.cx++; 383 | } else if (row && E.cx == row->size) { 384 | E.cy++; 385 | E.cx = 0; 386 | } 387 | break; 388 | case ARROW_UP: 389 | if (E.cy != 0) { 390 | E.cy--; 391 | } 392 | break; 393 | case ARROW_DOWN: 394 | if (E.cy < E.numrows) { 395 | E.cy++; 396 | } 397 | break; 398 | } 399 | 400 | row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; 401 | int rowlen = row ? row->size : 0; 402 | if (E.cx > rowlen) { 403 | E.cx = rowlen; 404 | } 405 | } 406 | 407 | int editorReadKey() { 408 | // Implement function to return key code from keyboard 409 | // return KEY_CODE 410 | } 411 | 412 | void editorProcessKeypress() { 413 | int c = editorReadKey(); 414 | if (!c) return; 415 | switch (c) { 416 | case ENTER_KEY: 417 | editorInsertNewline(); 418 | break; 419 | case HOME_KEY: 420 | E.cx = 0; 421 | break; 422 | case END_KEY: 423 | if (E.cy < E.numrows) 424 | E.cx = E.row[E.cy].size; 425 | break; 426 | 427 | case BACKSPACE: 428 | if (c == DEL_KEY) editorMoveCursor(ARROW_RIGHT); 429 | editorDelChar(); 430 | break; 431 | case ESCAPE_KEY: 432 | break; 433 | 434 | case PAGE_UP: 435 | case PAGE_DOWN: 436 | { 437 | if (c == PAGE_UP) { 438 | E.cy = E.rowoff; 439 | } else if (c == PAGE_DOWN) { 440 | E.cy = E.rowoff + E.screenrows - 1; 441 | if (E.cy > E.numrows) E.cy = E.numrows; 442 | } 443 | int times = E.screenrows; 444 | while (times--) 445 | editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN); 446 | } 447 | break; 448 | case ARROW_UP: 449 | case ARROW_DOWN: 450 | case ARROW_LEFT: 451 | case ARROW_RIGHT: 452 | editorMoveCursor(c); 453 | break; 454 | 455 | default: 456 | editorInsertChar(c); 457 | break; 458 | } 459 | } 460 | 461 | void initEditor() { 462 | E.cx = 0; 463 | E.cy = 0; 464 | E.rx = 0; 465 | E.rowoff = 0; 466 | E.coloff = 0; 467 | E.screenrows = 25; 468 | E.screencols = 80; 469 | E.numrows = 0; 470 | E.row = NULL; 471 | E.dirty = 0; 472 | E.filename = NULL; 473 | E.statusmsg[0] = '\0'; 474 | E.statusmsg_time = 0; 475 | E.screenrows -= 2; 476 | } 477 | 478 | void setup() { 479 | // Init keyboard 480 | // Init VT100 481 | } 482 | 483 | void loop() { 484 | editorRefreshScreen(); 485 | editorProcessKeypress(); 486 | } 487 | -------------------------------------------------------------------------------- /kilo.ino: -------------------------------------------------------------------------------- 1 | /* 2 | KILO text editor 3 | */ 4 | 5 | #include "fabgl.h" 6 | #include "SPIFFS.h" 7 | 8 | fabgl::VGA16Controller DisplayController; 9 | fabgl::Terminal Terminal; 10 | fabgl::PS2Controller PS2Controller; 11 | 12 | /* You only need to format SPIFFS the first time you run a 13 | test or else use the SPIFFS plugin to create a partition 14 | https://github.com/me−no−dev/arduino−esp32fs−plugin */ 15 | #define FORMAT_SPIFFS_IF_FAILED false 16 | 17 | #define KILO_VERSION "0.0.1" 18 | #define KILO_TAB_STOP 2 19 | 20 | enum editorKey { 21 | BACKSPACE = 127, 22 | ARROW_LEFT = 1000, 23 | ARROW_RIGHT, 24 | ARROW_UP, 25 | ARROW_DOWN, 26 | DEL_KEY, 27 | HOME_KEY, 28 | END_KEY, 29 | PAGE_UP, 30 | PAGE_DOWN, 31 | ENTER_KEY, 32 | ESCAPE_KEY 33 | }; 34 | 35 | typedef struct erow { 36 | int size; 37 | int rsize; 38 | char *chars; 39 | char *render; 40 | } erow; 41 | 42 | struct editorConfig { 43 | int cx, cy; 44 | int rx; 45 | int rowoff; 46 | int coloff; 47 | int screenrows; 48 | int screencols; 49 | int dirty; 50 | int numrows; 51 | erow *row; 52 | char *filename; 53 | char statusmsg[80]; 54 | time_t statusmsg_time; 55 | }; struct editorConfig E; 56 | 57 | struct abuf { 58 | char *b; 59 | int len; 60 | }; 61 | 62 | #define ABUF_INIT {NULL, 0} 63 | 64 | int editorRowCxToRx(erow *row, int cx) { 65 | int rx = 0; 66 | int j; 67 | for (j = 0; j < cx; j++) { 68 | if (row->chars[j] == '\t') 69 | rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP); 70 | rx++; 71 | } 72 | return rx; 73 | } 74 | 75 | void editorUpdateRow(erow *row) { 76 | int tabs = 0; 77 | int j; 78 | for (j = 0; j < row->size; j++) 79 | if (row->chars[j] == '\t') tabs++; 80 | free(row->render); 81 | row->render = (char *)malloc(row->size + tabs*(KILO_TAB_STOP - 1) + 1); 82 | int idx = 0; 83 | for (j = 0; j < row->size; j++) { 84 | if (row->chars[j] == '\t') { 85 | row->render[idx++] = ' '; 86 | while (idx % KILO_TAB_STOP != 0) row->render[idx++] = ' '; 87 | } else { 88 | row->render[idx++] = row->chars[j]; 89 | } 90 | } 91 | row->render[idx] = '\0'; 92 | row->rsize = idx; 93 | } 94 | 95 | void editorInsertRow(int at, char *s, size_t len) { 96 | if (at < 0 || at > E.numrows) return; 97 | E.row = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1)); 98 | memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at)); 99 | E.row[at].size = len; 100 | E.row[at].chars = (char *)malloc(len + 1); 101 | memcpy(E.row[at].chars, s, len); 102 | E.row[at].chars[len] = '\0'; 103 | E.row[at].rsize = 0; 104 | E.row[at].render = NULL; 105 | editorUpdateRow(&E.row[at]); 106 | E.numrows++; 107 | E.dirty++; 108 | } 109 | 110 | void editorFreeRow(erow *row) { 111 | free(row->render); 112 | free(row->chars); 113 | } 114 | void editorDelRow(int at) { 115 | if (at < 0 || at >= E.numrows) return; 116 | editorFreeRow(&E.row[at]); 117 | memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1)); 118 | E.numrows--; 119 | E.dirty++; 120 | } 121 | 122 | void editorRowInsertChar(erow *row, int at, int c) { 123 | if (at < 0 || at > row->size) at = row->size; 124 | row->chars = (char *)realloc(row->chars, row->size + 2); 125 | memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1); 126 | row->size++; 127 | row->chars[at] = c; 128 | editorUpdateRow(row); 129 | E.dirty++; 130 | } 131 | 132 | void editorRowAppendString(erow *row, char *s, size_t len) { 133 | row->chars = (char *)realloc(row->chars, row->size + len + 1); 134 | memcpy(&row->chars[row->size], s, len); 135 | row->size += len; 136 | row->chars[row->size] = '\0'; 137 | editorUpdateRow(row); 138 | E.dirty++; 139 | } 140 | 141 | void editorInsertChar(int c) { 142 | if (E.cy == E.numrows) { 143 | editorInsertRow(E.numrows, "", 0); 144 | } 145 | editorRowInsertChar(&E.row[E.cy], E.cx, c); 146 | E.cx++; 147 | } 148 | 149 | void editorInsertNewline() { 150 | if (E.cx == 0) { 151 | editorInsertRow(E.cy, "", 0); 152 | } else { 153 | erow *row = &E.row[E.cy]; 154 | editorInsertRow(E.cy + 1, &row->chars[E.cx], row->size - E.cx); 155 | row = &E.row[E.cy]; 156 | row->size = E.cx; 157 | row->chars[row->size] = '\0'; 158 | editorUpdateRow(row); 159 | } 160 | E.cy++; 161 | E.cx = 0; 162 | } 163 | 164 | void editorDelChar() { 165 | if (E.cy == E.numrows) return; 166 | if (E.cx == 0 && E.cy == 0) return; 167 | erow *row = &E.row[E.cy]; 168 | if (E.cx > 0) { 169 | editorRowDelChar(row, E.cx - 1); 170 | E.cx--; 171 | } else { 172 | E.cx = E.row[E.cy - 1].size; 173 | editorRowAppendString(&E.row[E.cy - 1], row->chars, row->size); 174 | editorDelRow(E.cy); 175 | E.cy--; 176 | } 177 | } 178 | 179 | void editorRowDelChar(erow *row, int at) { 180 | if (at < 0 || at >= row->size) return; 181 | memmove(&row->chars[at], &row->chars[at + 1], row->size - at); 182 | row->size--; 183 | editorUpdateRow(row); 184 | E.dirty++; 185 | } 186 | 187 | size_t getline(char *buf, size_t *size, File *stream) 188 | { 189 | char c; 190 | size_t count = 0; 191 | 192 | while (c != '\r') { 193 | if (stream->available()) { 194 | c = stream->read(); 195 | buf[count] = c; 196 | count++; 197 | } else { 198 | if (count == 0) return -1; 199 | else break; 200 | } 201 | } 202 | 203 | buf[count] = '\0'; 204 | return count; 205 | } 206 | 207 | char *editorRowsToString(int *buflen) { 208 | int totlen = 0; 209 | int j; 210 | for (j = 0; j < E.numrows; j++) 211 | totlen += E.row[j].size + 1; 212 | *buflen = totlen; 213 | char *buf = (char *)malloc(totlen); 214 | char *p = buf; 215 | for (j = 0; j < E.numrows; j++) { 216 | memcpy(p, E.row[j].chars, E.row[j].size); 217 | p += E.row[j].size; 218 | *p = '\r'; 219 | p++; 220 | } *--p = '\0'; 221 | return buf; 222 | } 223 | 224 | void editorSave(fs::FS &fs) { 225 | if (E.filename == NULL) return; 226 | File fp = fs.open(E.filename, FILE_WRITE); 227 | int len; 228 | char *buf = editorRowsToString(&len); 229 | 230 | if(!fp){ 231 | editorSetStatusMessage("Failed to open file"); 232 | return; 233 | } 234 | 235 | if(fp.print(buf)) editorSetStatusMessage("File written"); 236 | else editorSetStatusMessage("Failed to write file!"); 237 | E.dirty = 0; 238 | fp.close(); 239 | free(buf); 240 | } 241 | 242 | void editorOpen(fs::FS &fs, const char *filename) { 243 | free(E.filename); 244 | E.filename = strdup(filename); 245 | 246 | File fp = fs.open(filename); 247 | if(!fp || fp.isDirectory()) { 248 | xprintf("− failed to open file for reading\n\r"); 249 | return; 250 | } 251 | 252 | if (!fp) { xprintf("No file found\n\r"); return; } 253 | char line[200]; // 200 chars per line allowed 254 | size_t linecap = 0; 255 | ssize_t linelen; 256 | 257 | while ((linelen = getline(line, &linecap, &fp)) != -1) { 258 | while (linelen > 0 && (line[linelen - 1] == '\n' || line[linelen - 1] == '\r')) linelen--; 259 | editorInsertRow(E.numrows, line, linelen); 260 | } 261 | 262 | fp.close(); 263 | E.dirty = 0; 264 | } 265 | 266 | void abAppend(struct abuf *ab, const char *s, int len) { 267 | char *newb = (char *)realloc(ab->b, ab->len + len); 268 | if (newb == NULL) return; 269 | memcpy(&newb[ab->len], s, len); 270 | ab->b = newb; 271 | ab->len += len; 272 | } 273 | 274 | void abFree(struct abuf *ab) { 275 | free(ab->b); 276 | } 277 | 278 | void editorScroll() { 279 | E.rx = 0; 280 | if (E.cy < E.numrows) { 281 | E.rx = editorRowCxToRx(&E.row[E.cy], E.cx); 282 | } 283 | if (E.cy < E.rowoff) { 284 | E.rowoff = E.cy; 285 | } 286 | if (E.cy >= E.rowoff + E.screenrows) { 287 | E.rowoff = E.cy - E.screenrows + 1; 288 | } 289 | if (E.rx < E.coloff) { 290 | E.coloff = E.rx; 291 | } 292 | if (E.rx >= E.coloff + E.screencols) { 293 | E.coloff = E.rx - E.screencols + 1; 294 | } 295 | } 296 | 297 | void editorDrawRows(struct abuf *ab) { 298 | int y; 299 | for (y = 0; y < E.screenrows; y++) { 300 | int filerow = y + E.rowoff; 301 | if (filerow >= E.numrows) { 302 | if (E.numrows == 0 && y == E.screenrows / 3) { 303 | char welcome[80]; 304 | int welcomelen = snprintf(welcome, sizeof(welcome), 305 | "Kilo editor -- version %s", KILO_VERSION); 306 | if (welcomelen > E.screencols) welcomelen = E.screencols; 307 | int padding = (E.screencols - welcomelen) / 2; 308 | if (padding) { 309 | abAppend(ab, "~", 1); 310 | padding--; 311 | } 312 | while (padding--) abAppend(ab, " ", 1); 313 | abAppend(ab, welcome, welcomelen); 314 | } else { 315 | abAppend(ab, "~", 1); 316 | } 317 | } else { 318 | int len = E.row[filerow].rsize - E.coloff; 319 | if (len < 0) len = 0; 320 | if (len >= E.screencols) len = E.screencols - 1; 321 | abAppend(ab, &E.row[filerow].render[E.coloff], len); 322 | } 323 | abAppend(ab, "\x1b[K", 3); // clear line 324 | abAppend(ab, "\n\r", 2); 325 | } 326 | } 327 | 328 | void editorDrawStatusBar(struct abuf *ab) { 329 | abAppend(ab, "\x1b[7m", 4); 330 | char status[80], rstatus[80]; 331 | int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", 332 | E.filename ? E.filename : "[No Name]", E.numrows, 333 | E.dirty ? "(modified)" : ""); 334 | int rlen = snprintf(rstatus, sizeof(rstatus), "row %d, col %d", 335 | E.cy + 1, E.cx + 1); 336 | if (len > E.screencols) len = E.screencols; 337 | abAppend(ab, status, len); 338 | while (len < E.screencols) { 339 | if (E.screencols - len == rlen) { 340 | abAppend(ab, rstatus, rlen); 341 | break; 342 | } else { 343 | abAppend(ab, " ", 1); 344 | len++; 345 | } 346 | } 347 | abAppend(ab, "\x1b[m", 3); 348 | abAppend(ab, "\r\n", 2); 349 | } 350 | 351 | void editorDrawMessageBar(struct abuf *ab) { 352 | abAppend(ab, "\x1b[K", 3); 353 | int msglen = strlen(E.statusmsg); 354 | if (msglen > E.screencols) msglen = E.screencols; 355 | if (msglen && time(NULL) - E.statusmsg_time < 5) 356 | abAppend(ab, E.statusmsg, msglen); 357 | } 358 | 359 | void editorRefreshScreen() { 360 | editorScroll(); 361 | struct abuf ab = ABUF_INIT; 362 | abAppend(&ab, "\x1b[?25l", 6); // hide cursor 363 | abAppend(&ab, "\x1b[H", 3); // cursor home 364 | editorDrawRows(&ab); 365 | editorDrawStatusBar(&ab); 366 | editorDrawMessageBar(&ab); 367 | abAppend(&ab, "\x1b[?25h", 6); // show cursor 368 | char buf[32]; 369 | snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1, 370 | (E.rx - E.coloff) + 1); 371 | abAppend(&ab, buf, strlen(buf)); 372 | Terminal.write(ab.b, ab.len); 373 | abFree(&ab); 374 | } 375 | 376 | void editorSetStatusMessage(const char *fmt, ...) { 377 | va_list ap; 378 | va_start(ap, fmt); 379 | vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap); 380 | va_end(ap); 381 | E.statusmsg_time = time(NULL); 382 | } 383 | 384 | int getCursorPosition(int *rows, int *cols) { 385 | char buf[32]; 386 | unsigned int i = 0; 387 | //if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1; 388 | while (i < sizeof(buf) - 1) { 389 | if (read(STDIN_FILENO, &buf[i], 1) != 1) break; 390 | if (buf[i] == 'R') break; 391 | i++; 392 | } 393 | buf[i] = '\0'; 394 | if (buf[0] != '\x1b' || buf[1] != '[') return -1; 395 | if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) return -1; 396 | return 0; 397 | } 398 | 399 | void editorMoveCursor(int key) { 400 | erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; 401 | switch (key) { 402 | case ARROW_LEFT: 403 | if (E.cx != 0) { 404 | E.cx--; 405 | } else if (E.cy > 0) { 406 | E.cy--; 407 | E.cx = E.row[E.cy].size; 408 | } 409 | break; 410 | case ARROW_RIGHT: 411 | if (row && E.cx < row->size) { 412 | E.cx++; 413 | } else if (row && E.cx == row->size) { 414 | E.cy++; 415 | E.cx = 0; 416 | } 417 | break; 418 | case ARROW_UP: 419 | if (E.cy != 0) { 420 | E.cy--; 421 | } 422 | break; 423 | case ARROW_DOWN: 424 | if (E.cy < E.numrows) { 425 | E.cy++; 426 | } 427 | break; 428 | } 429 | 430 | row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; 431 | int rowlen = row ? row->size : 0; 432 | if (E.cx > rowlen) { 433 | E.cx = rowlen; 434 | } 435 | } 436 | 437 | int editorReadKey() { 438 | auto keyboard = PS2Controller.keyboard(); 439 | while (!keyboard->virtualKeyAvailable()) {/* wait for a key press */} 440 | VirtualKeyItem item; 441 | if (keyboard->getNextVirtualKey(&item)) { 442 | // debug 443 | /*if (item.down) { 444 | xprintf("Scan codes: "); 445 | xprintf("ctrl %d 0x%02X 0x%02X 0x%02X\n\r", control_key, item.scancode[0], item.scancode[1], item.scancode[2]); 446 | }*/ 447 | 448 | if (item.down) { 449 | if (item.scancode[0] == 0xE0) { 450 | switch (item.scancode[1]) { 451 | case 0x6B: return ARROW_LEFT; 452 | case 0x74: return ARROW_RIGHT; 453 | case 0x75: return ARROW_UP; 454 | case 0x72: return ARROW_DOWN; 455 | case 0x71: return DEL_KEY; 456 | case 0x6C: return HOME_KEY; 457 | case 0x69: return END_KEY; 458 | case 0x7D: return PAGE_UP; 459 | case 0x7A: return PAGE_DOWN; 460 | } 461 | } else { 462 | switch (item.scancode[0]) { 463 | case 0x76: return ESCAPE_KEY; 464 | case 0x5A: return ENTER_KEY; 465 | case 0x66: return BACKSPACE; 466 | default: return item.ASCII; 467 | } 468 | } 469 | } return 0x00; 470 | } 471 | 472 | } 473 | 474 | void editorProcessKeypress() { 475 | int c = editorReadKey(); 476 | if (!c) return; 477 | switch (c) { 478 | case ENTER_KEY: 479 | editorInsertNewline(); 480 | //editorSave(SPIFFS); 481 | break; 482 | case HOME_KEY: 483 | E.cx = 0; 484 | break; 485 | case END_KEY: 486 | if (E.cy < E.numrows) 487 | E.cx = E.row[E.cy].size; 488 | break; 489 | 490 | case BACKSPACE: 491 | if (c == DEL_KEY) editorMoveCursor(ARROW_RIGHT); 492 | editorDelChar(); 493 | break; 494 | case ESCAPE_KEY: 495 | editorSave(SPIFFS); 496 | break; 497 | 498 | case PAGE_UP: 499 | case PAGE_DOWN: 500 | { 501 | if (c == PAGE_UP) { 502 | E.cy = E.rowoff; 503 | } else if (c == PAGE_DOWN) { 504 | E.cy = E.rowoff + E.screenrows - 1; 505 | if (E.cy > E.numrows) E.cy = E.numrows; 506 | } 507 | int times = E.screenrows; 508 | while (times--) 509 | editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN); 510 | } 511 | break; 512 | case ARROW_UP: 513 | case ARROW_DOWN: 514 | case ARROW_LEFT: 515 | case ARROW_RIGHT: 516 | editorMoveCursor(c); 517 | break; 518 | 519 | default: 520 | editorInsertChar(c); 521 | editorSetStatusMessage(""); 522 | break; 523 | } 524 | } 525 | 526 | void readFile(fs::FS &fs, const char * path) { 527 | File file = fs.open(path); 528 | while(file.available()) xprintf("%c", file.read()); 529 | } 530 | 531 | void writeFile(fs::FS &fs, const char * path, const char * message) { 532 | File file = fs.open(path, FILE_WRITE); 533 | if(!file){ 534 | xprintf("− failed to open file for writing\n\r"); 535 | return; 536 | } 537 | if(file.print(message)) xprintf("− file written\n\r"); 538 | else xprintf("− frite failed\n\r"); 539 | } 540 | 541 | void deleteFile(fs::FS &fs, const char * path){ 542 | xprintf("Deleting file: %s\r\n", path); 543 | if(fs.remove(path)) xprintf("− file deleted\n\r"); 544 | else { xprintf("− delete failed\n\r"); } 545 | } 546 | 547 | void listDir(fs::FS &fs){ 548 | File root = fs.open("/"); 549 | File file = root.openNextFile(); 550 | while(file){ 551 | xprintf(" FILE: "); 552 | xprintf("%s", file.name()); 553 | xprintf("\tSIZE: "); 554 | xprintf("%d\n\r", file.size()); 555 | file = root.openNextFile(); 556 | } 557 | } 558 | 559 | void initEditor() { 560 | E.cx = 0; 561 | E.cy = 0; 562 | E.rx = 0; 563 | E.rowoff = 0; 564 | E.coloff = 0; 565 | E.screenrows = 25; 566 | E.screencols = 80; 567 | E.numrows = 0; 568 | E.row = NULL; 569 | E.dirty = 0; 570 | E.filename = NULL; 571 | E.statusmsg[0] = '\0'; 572 | E.statusmsg_time = 0; 573 | E.screenrows -= 2; 574 | } 575 | 576 | void xprintf(const char * format, ...) { 577 | va_list ap; 578 | va_start(ap, format); 579 | int size = vsnprintf(nullptr, 0, format, ap) + 1; 580 | if (size > 0) { 581 | va_end(ap); 582 | va_start(ap, format); 583 | char buf[size + 1]; 584 | vsnprintf(buf, size, format, ap); 585 | Terminal.write(buf); 586 | } va_end(ap); 587 | } 588 | 589 | void setup() { 590 | // init serial port 591 | Serial.begin(115200); 592 | delay(500); // avoid garbage into the UART 593 | 594 | // ESP32 peripherals setup 595 | PS2Controller.begin(PS2Preset::KeyboardPort0); 596 | DisplayController.begin(); 597 | DisplayController.setResolution(VGA_640x480_60Hz); 598 | Terminal.begin(&DisplayController); 599 | 600 | // init SPIFFS 601 | if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){ 602 | xprintf("SPIFFS Mount Failed"); 603 | return; 604 | } 605 | 606 | // init text editor 607 | initEditor(); 608 | 609 | //deleteFile(SPIFFS, "/hello.txt"); 610 | //deleteFile(SPIFFS, "/session.txt"); 611 | //writeFile(SPIFFS, "/hello.txt", "Hello world!\n\r"); 612 | //readFile(SPIFFS, "/hello.txt"); 613 | //listDir(SPIFFS); 614 | editorOpen(SPIFFS, "/hello.c"); // you need to write it first via writeFile() 615 | editorSetStatusMessage(" Press ESCAPE to save file "); 616 | 617 | } 618 | 619 | void loop() { 620 | editorRefreshScreen(); 621 | editorProcessKeypress(); 622 | } 623 | 624 | 625 | --------------------------------------------------------------------------------