├── 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 | [](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 |
--------------------------------------------------------------------------------