Subversion Repositories Kolibri OS

Rev

Rev 7414 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
7413 siemargl 1
/*
2
*   Copyright (C) 2017 Daniel Morales
3
*
4
*   This program is free software: you can redistribute it and/or modify
5
*   it under the terms of the GNU General Public License as published by
6
*   the Free Software Foundation, either version 3 of the License, or
7
*   any later version.
8
*
9
*   This program is distributed in the hope that it will be useful,
10
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
11
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
*   GNU General Public License for more details.
13
*
14
*   You should have received a copy of the GNU General Public License
15
*   along with this program.  If not, see .
16
*/
17
 
18
/*** Include section ***/
19
 
20
// We add them above our includes, because the header
21
// files we are including use the macros to decide what
22
// features to expose. These macros remove some compilation
23
// warnings. See
24
// https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html
25
// for more info.
26
#define _DEFAULT_SOURCE
27
#define _BSD_SOURCE
28
#define _GNU_SOURCE
29
 
30
#include 
31
#include 
32
#include 
33
#include 
34
#include 
35
#include 
36
#include 
37
#include 
38
#include 
39
#include 
40
#include 
41
#include 
42
#include 
43
 
44
/*** Define section ***/
45
 
46
// This mimics the Ctrl + whatever behavior, setting the
47
// 3 upper bits of the character pressed to 0.
48
#define CTRL_KEY(k) ((k) & 0x1f)
49
// Empty buffer
50
#define ABUF_INIT {NULL, 0}
51
// Version code
52
#define TTE_VERSION "0.0.5"
53
// Length of a tab stop
54
#define TTE_TAB_STOP 4
55
// Times to press Ctrl-Q before exiting
56
#define TTE_QUIT_TIMES 2
57
// Highlight flags
58
#define HL_HIGHLIGHT_NUMBERS (1 << 0)
59
#define HL_HIGHLIGHT_STRINGS (1 << 1)
60
 
61
/*** Data section ***/
62
 
63
typedef struct editor_row {
64
    int idx; // Row own index within the file.
65
    int size; // Size of the content (excluding NULL term)
66
    int render_size; // Size of the rendered content
67
    char* chars; // Row content
68
    char* render; // Row content "rendered" for screen (for TABs).
69
    unsigned char* highlight; // This will tell you if a character is part of a string, comment, number...
70
    int hl_open_comment; // True if the line is part of a ML comment.
71
} editor_row;
72
 
73
struct editor_syntax {
74
    // file_type field is the name of the filetype that will be displayed
75
    // to the user in the status bar.
76
    char* file_type;
77
    // file_match is an array of strings, where each string contains a
78
    // pattern to match a filename against. If the filename matches,
79
    // then the file will be recognized as having that filetype.
80
    char** file_match;
81
    // This will be a NULL-terminated array of strings, each string containing
82
    // a keyword. To differentiate between the two types of keywords,
83
    // we’ll terminate the second type of keywords with a pipe (|)
84
    // character (also known as a vertical bar).
85
    char** keywords;
86
    // We let each language specify its own single-line comment pattern.
87
    char* singleline_comment_start;
88
    // flags is a bit field that will contain flags for whether to
89
    // highlight numbers and whether to highlight strings for that
90
    // filetype.
91
    char* multiline_comment_start;
92
    char* multiline_comment_end;
93
    int flags;
94
};
95
 
96
struct editor_config {
97
    int cursor_x;
98
    int cursor_y;
99
    int render_x;
100
    int row_offset; // Offset of row displayed.
101
    int col_offset; // Offset of col displayed.
102
    int screen_rows; // Number of rows that we can show
103
    int screen_cols; // Number of cols that we can show
104
    int num_rows; // Number of rows
105
    editor_row* row;
106
    int dirty; // To know if a file has been modified since opening.
107
    char* file_name;
108
    char status_msg[80];
109
    time_t status_msg_time;
110
    char* copied_char_buffer;
111
    struct editor_syntax* syntax;
112
    struct termios orig_termios;
113
} ec;
114
 
115
// Having a dynamic buffer will allow us to write only one
116
// time once the screen is refreshing, instead of doing
117
// a lot of write's.
118
struct a_buf {
119
    char* buf;
120
    int len;
121
};
122
 
123
enum editor_key {
124
    BACKSPACE = 0x7f, // 127
125
    ARROW_LEFT = 0x3e8, // 1000, large value out of the range of a char.
126
    ARROW_RIGHT,
127
    ARROW_UP,
128
    ARROW_DOWN,
129
    PAGE_UP,
130
    PAGE_DOWN,
131
    HOME_KEY,
132
    END_KEY,
133
    DEL_KEY
134
};
135
 
136
enum editor_highlight {
137
    HL_NORMAL = 0,
138
    HL_SL_COMMENT,
139
    HL_ML_COMMENT,
140
    HL_KEYWORD_1,
141
    HL_KEYWORD_2,
142
    HL_STRING,
143
    HL_NUMBER,
144
    HL_MATCH
145
};
146
 
147
/*** Filetypes ***/
148
 
149
char* C_HL_extensions[] = {".c", ".h", ".cpp", ".hpp", ".cc", NULL}; // Array must be terminated with NULL.
150
char* JAVA_HL_extensions[] = {".java", NULL};
151
char* PYTHON_HL_extensions[] = {".py", NULL};
152
char* BASH_HL_extensions[] = {".sh", NULL};
153
char* JS_HL_extensions[] = {".js", ".jsx", NULL};
154
char* PHP_HL_extensions[] = {".php", NULL};
155
char* JSON_HL_extensions[] = {".json", ".jsonp", NULL};
156
char* XML_HL_extensions[] = {".xml", NULL};
157
char* SQL_HL_extensions[] = {".sql", NULL};
158
char* RUBY_HL_extensions[] = {".rb", NULL};
159
 
160
char* C_HL_keywords[] = {
161
    "switch", "if", "while", "for", "break", "continue", "return", "else",
162
    "struct", "union", "typedef", "static", "enum", "class", "case", "#include",
163
    "volatile", "register", "sizeof", "typedef", "union", "goto", "const", "auto",
164
    "#define", "#if", "#endif", "#error", "#ifdef", "#ifndef", "#undef",
165
 
166
    "int|", "long|", "double|", "float|", "char|", "unsigned|", "signed|",
167
    "void|", "bool|", NULL
168
};
169
 
170
char* JAVA_HL_keywords[] = {
171
    "switch", "if", "while", "for", "break", "continue", "return", "else",
172
    "in", "public", "private", "protected", "static", "final", "abstract",
173
    "enum", "class", "case", "try", "catch", "do", "extends", "implements",
174
    "finally", "import", "instanceof", "interface", "new", "package", "super",
175
    "native", "strictfp",
176
    "synchronized", "this", "throw", "throws", "transient", "volatile",
177
 
178
    "byte|", "char|", "double|", "float|", "int|", "long|", "short|",
179
    "boolean|", NULL
180
};
181
 
182
char* PYTHON_HL_keywords[] = {
183
    "and", "as", "assert", "break", "class", "continue", "def", "del", "elif",
184
    "else", "except", "exec", "finally", "for", "from", "global", "if", "import",
185
    "in", "is", "lambda", "not", "or", "pass", "print", "raise", "return", "try",
186
    "while", "with", "yield",
187
 
188
    "buffer|", "bytearray|", "complex|", "False|", "float|", "frozenset|", "int|",
189
    "list|", "long|", "None|", "set|", "str|", "tuple|", "True|", "type|",
190
    "unicode|", "xrange|", NULL
191
};
192
 
193
char* BASH_HL_keywords[] = {
194
    "case", "do", "done", "elif", "else", "esac", "fi", "for", "function", "if",
195
    "in", "select", "then", "time", "until", "while", "alias", "bg", "bind", "break",
196
    "builtin", "cd", "command", "continue", "declare", "dirs", "disown", "echo",
197
    "enable", "eval", "exec", "exit", "export", "fc", "fg", "getopts", "hash", "help",
198
    "history", "jobs", "kill", "let", "local", "logout", "popd", "pushd", "pwd", "read",
199
    "readonly", "return", "set", "shift", "suspend", "test", "times", "trap", "type",
200
    "typeset", "ulimit", "umask", "unalias", "unset", "wait", "printf", NULL
201
};
202
 
203
char* JS_HL_keywords[] = {
204
    "break", "case", "catch", "class", "const", "continue", "debugger", "default",
205
    "delete", "do", "else", "enum", "export", "extends", "finally", "for", "function",
206
    "if", "implements", "import", "in", "instanceof", "interface", "let", "new",
207
    "package", "private", "protected", "public", "return", "static", "super", "switch",
208
    "this", "throw", "try", "typeof", "var", "void", "while", "with", "yield", "true",
209
    "false", "null", "NaN", "global", "window", "prototype", "constructor", "document",
210
    "isNaN", "arguments", "undefined",
211
 
212
    "Infinity|", "Array|", "Object|", "Number|", "String|", "Boolean|", "Function|",
213
    "ArrayBuffer|", "DataView|", "Float32Array|", "Float64Array|", "Int8Array|",
214
    "Int16Array|", "Int32Array|", "Uint8Array|", "Uint8ClampedArray|", "Uint32Array|",
215
    "Date|", "Error|", "Map|", "RegExp|", "Symbol|", "WeakMap|", "WeakSet|", "Set|", NULL
216
};
217
 
218
char* PHP_HL_keywords[] = {
219
    "__halt_compiler", "break", "clone", "die", "empty", "endswitch", "final", "global",
220
    "include_once", "list", "private", "return", "try", "xor", "abstract", "callable",
221
    "const", "do", "enddeclare", "endwhile", "finally", "goto", "instanceof", "namespace",
222
    "protected", "static", "unset", "yield", "and", "case", "continue", "echo", "endfor",
223
    "eval", "for", "if", "insteadof", "new", "public", "switch", "use", "array", "catch",
224
    "declare", "else", "endforeach", "exit", "foreach", "implements", "interface", "or",
225
    "require", "throw", "var", "as", "class", "default", "elseif", "endif", "extends",
226
    "function", "include", "isset", "print", "require_once", "trait", "while", NULL
227
};
228
 
229
char* JSON_HL_keywords[] = {
230
    NULL
231
};
232
 
233
char* XML_HL_keywords[] = {
234
    NULL
235
};
236
 
237
char* SQL_HL_keywords[] = {
238
    "SELECT", "FROM", "DROP", "CREATE", "TABLE", "DEFAULT", "FOREIGN", "UPDATE", "LOCK",
239
    "INSERT", "INTO", "VALUES", "LOCK", "UNLOCK", "WHERE", "DINSTINCT", "BETWEEN", "NOT",
240
    "NULL", "TO", "ON", "ORDER", "GROUP", "IF", "BY", "HAVING", "USING", "UNION", "UNIQUE",
241
    "AUTO_INCREMENT", "LIKE", "WITH", "INNER", "OUTER", "JOIN", "COLUMN", "DATABASE", "EXISTS",
242
    "NATURAL", "LIMIT", "UNSIGNED", "MAX", "MIN", "PRECISION", "ALTER", "DELETE", "CASCADE",
243
    "PRIMARY", "KEY", "CONSTRAINT", "ENGINE", "CHARSET", "REFERENCES", "WRITE",
244
 
245
    "BIT|", "TINYINT|", "BOOL|", "BOOLEAN|", "SMALLINT|", "MEDIUMINT|", "INT|", "INTEGER|",
246
    "BIGINT|", "DOUBLE|", "DECIMAL|", "DEC|" "FLOAT|", "DATE|", "DATETIME|", "TIMESTAMP|",
247
    "TIME|", "YEAR|", "CHAR|", "VARCHAR|", "TEXT|", "ENUM|", "SET|", "BLOB|", "VARBINARY|",
248
    "TINYBLOB|", "TINYTEXT|", "MEDIUMBLOB|", "MEDIUMTEXT|", "LONGTEXT|",
249
 
250
    "select", "from", "drop", "create", "table", "default", "foreign", "update", "lock",
251
    "insert", "into", "values", "lock", "unlock", "where", "dinstinct", "between", "not",
252
    "null", "to", "on", "order", "group", "if", "by", "having", "using", "union", "unique",
253
    "auto_increment", "like", "with", "inner", "outer", "join", "column", "database", "exists",
254
    "natural", "limit", "unsigned", "max", "min", "precision", "alter", "delete", "cascade",
255
    "primary", "key", "constraint", "engine", "charset", "references", "write",
256
 
257
    "bit|", "tinyint|", "bool|", "boolean|", "smallint|", "mediumint|", "int|", "integer|",
258
    "bigint|", "double|", "decimal|", "dec|" "float|", "date|", "datetime|", "timestamp|",
259
    "time|", "year|", "char|", "varchar|", "text|", "enum|", "set|", "blob|", "varbinary|",
260
    "tinyblob|", "tinytext|", "mediumblob|", "mediumtext|", "longtext|", NULL
261
};
262
 
263
char* RUBY_HL_keywords[] = {
264
    "__ENCODING__", "__LINE__", "__FILE__", "BEGIN", "END", "alias", "and", "begin", "break",
265
    "case", "class", "def", "defined?", "do", "else", "elsif", "end", "ensure", "for", "if",
266
    "in", "module", "next", "not", "or", "redo", "rescue", "retry", "return", "self", "super",
267
    "then", "undef", "unless", "until", "when", "while", "yield", NULL
268
};
269
 
270
struct editor_syntax HL_DB[] = {
271
    {
272
        "c",
273
        C_HL_extensions,
274
        C_HL_keywords,
275
        "//",
276
        "/*",
277
        "*/",
278
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
279
    },
280
    {
281
        "java",
282
        JAVA_HL_extensions,
283
        JAVA_HL_keywords,
284
        "//",
285
        "/*",
286
        "*/",
287
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
288
    },
289
    {
290
        "python",
291
        PYTHON_HL_extensions,
292
        PYTHON_HL_keywords,
293
        "#",
294
        "'''",
295
        "'''",
296
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
297
    },
298
    {
299
        "bash",
300
        BASH_HL_extensions,
301
        BASH_HL_keywords,
302
        "#",
303
        NULL,
304
        NULL,
305
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
306
    },
307
    {
308
        "js",
309
        JS_HL_extensions,
310
        JS_HL_keywords,
311
        "//",
312
        "/*",
313
        "*/",
314
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
315
    },
316
    {
317
        "php",
318
        PHP_HL_extensions,
319
        PHP_HL_keywords,
320
        "//",
321
        "/*",
322
        "*/",
323
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
324
    },
325
    {
326
        "json",
327
        JSON_HL_extensions,
328
        JSON_HL_keywords,
329
        NULL,
330
        NULL,
331
        NULL,
332
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
333
    },
334
    {
335
        "xml",
336
        XML_HL_extensions,
337
        XML_HL_keywords,
338
        NULL,
339
        NULL,
340
        NULL,
341
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
342
    },
343
    {
344
        "sql",
345
        SQL_HL_extensions,
346
        SQL_HL_keywords,
347
        "--",
348
        "/*",
349
        "*/",
350
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
351
    },
352
    {
353
        "ruby",
354
        RUBY_HL_extensions,
355
        RUBY_HL_keywords,
356
        "#",
357
        "=begin",
358
        "=end",
359
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
360
    }
361
};
362
 
363
// Size of the "Hightlight Database" (HL_DB).
364
#define HL_DB_ENTRIES (sizeof(HL_DB) / sizeof(HL_DB[0]))
365
 
366
/*** Declarations section ***/
367
 
368
void editorClearScreen();
369
 
370
void editorRefreshScreen();
371
 
372
void editorSetStatusMessage(const char* msg, ...);
373
 
374
void consoleBufferOpen();
375
 
376
void abufFree();
377
 
378
void abufAppend();
379
 
380
char *editorPrompt(char* prompt, void (*callback)(char*, int));
381
 
382
void editorRowAppendString(editor_row* row, char* s, size_t len);
383
 
384
void editorInsertNewline();
385
 
386
/*** Terminal section ***/
387
 
388
void die(const char* s) {
389
    editorClearScreen();
390
    // perror looks for global errno variable and then prints
391
    // a descriptive error mesage for it.
392
    perror(s);
393
    printf("\r\n");
394
    exit(1);
395
}
396
 
397
void disableRawMode() {
398
    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &ec.orig_termios) == -1)
399
        die("Failed to disable raw mode");
400
}
401
 
402
void enableRawMode() {
403
    // Save original terminal state into orig_termios.
404
    if (tcgetattr(STDIN_FILENO, &ec.orig_termios) == -1)
405
        die("Failed to get current terminal state");
406
    // At exit, restore the original state.
407
    atexit(disableRawMode);
408
 
409
    // Modify the original state to enter in raw mode.
410
    struct termios raw = ec.orig_termios;
411
    // This disables Ctrl-M, Ctrl-S and Ctrl-Q commands.
412
    // (BRKINT, INPCK and ISTRIP are not estrictly mandatory,
413
    // but it is recommended to turn them off in case any
414
    // system needs it).
415
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
416
    // Turning off all output processing (\r\n).
417
    raw.c_oflag &= ~(OPOST);
418
    // Setting character size to 8 bits per byte (it should be
419
    // like that on most systems, but whatever).
420
    raw.c_cflag |= (CS8);
421
    // Using NOT operator on ECHO | ICANON | IEXTEN | ISIG and
422
    // then bitwise-AND them with flags field in order to
423
    // force c_lflag 4th bit to become 0. This disables
424
    // chars being printed (ECHO) and let us turn off
425
    // canonical mode in order to read input byte-by-byte
426
    // instead of line-by-line (ICANON), ISIG disables
427
    // Ctrl-C command and IEXTEN the Ctrl-V one.
428
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
429
    // read() function now returns as soon as there is any
430
    // input to be read.
431
    raw.c_cc[VMIN] = 0;
432
    // Forcing read() function to return every 1/10 of a
433
    // second if there is nothing to read.
434
    raw.c_cc[VTIME] = 1;
435
 
436
    consoleBufferOpen();
437
 
438
    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
439
        die("Failed to set raw mode");
440
}
441
 
442
int editorReadKey() {
443
    int nread;
444
    char c;
445
    while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
446
        // Ignoring EAGAIN to make it work on Cygwin.
447
        if (nread == -1 && errno != EAGAIN)
448
            die("Error reading input");
449
    }
450
 
451
    // Check escape sequences, if first byte
452
    // is an escape character then...
453
    if (c == '\x1b') {
454
        char seq[3];
455
 
456
        if (read(STDIN_FILENO, &seq[0], 1) != 1 ||
457
            read(STDIN_FILENO, &seq[1], 1) != 1)
458
            return '\x1b';
459
 
460
        if (seq[0] == '[') {
461
            if (seq[1] >= '0' && seq[1] <= '9') {
462
                if (read(STDIN_FILENO, &seq[2], 1) != 1)
463
                    return '\x1b';
464
                if (seq[2] == '~') {
465
                    switch (seq[1]) {
466
                        // Home and End keys may be sent in many ways depending on the OS
467
                        // \x1b[1~, \x1b[7~, \x1b[4~, \x1b[8~
468
                        case '1':
469
                        case '7':
470
                            return HOME_KEY;
471
                        case '4':
472
                        case '8':
473
                            return END_KEY;
474
                        // Del key is sent as \x1b[3~
475
                        case '3':
476
                            return DEL_KEY;
477
                        // Page Up and Page Down send '\x1b', '[', '5' or '6' and '~'.
478
                        case '5': return PAGE_UP;
479
                        case '6': return PAGE_DOWN;
480
                    }
481
                }
482
            } else {
483
                switch (seq[1]) {
484
                    // Arrow keys send multiple bytes starting with '\x1b', '[''
485
                    // and followed by an 'A', 'B', 'C' or 'D' depending on which
486
                    // arrow is pressed.
487
                    case 'A': return ARROW_UP;
488
                    case 'B': return ARROW_DOWN;
489
                    case 'C': return ARROW_RIGHT;
490
                    case 'D': return ARROW_LEFT;
491
                    // Home key can also be sent as \x1b[H
492
                    case 'H': return HOME_KEY;
493
                    // End key can also be sent as \x1b[F
494
                    case 'F': return END_KEY;
495
                }
496
            }
497
        } else if (seq[0] == 'O') {
498
            switch (seq[1]) {
499
                // Yes, Home key can ALSO be sent as \x1bOH
500
                case 'H': return HOME_KEY;
501
                // And... End key as \x1bOF
502
                case 'F': return END_KEY;
503
            }
504
        }
505
        return '\x1b';
506
    } else {
507
        return c;
508
    }
509
}
510
 
511
int getWindowSize(int* screen_rows, int* screen_cols) {
512
    struct winsize ws;
513
 
514
    // Getting window size thanks to ioctl into the given
515
    // winsize struct.
516
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
517
        return -1;
518
    } else {
519
        *screen_cols = ws.ws_col;
520
        *screen_rows = ws.ws_row;
521
        return 0;
522
    }
523
}
524
 
525
void editorUpdateWindowSize() {
526
    if (getWindowSize(&ec.screen_rows, &ec.screen_cols) == -1)
527
        die("Failed to get window size");
528
    ec.screen_rows -= 2; // Room for the status bar.
529
}
530
 
531
void editorHandleSigwinch() {
532
    editorUpdateWindowSize();
533
    if (ec.cursor_y > ec.screen_rows)
534
        ec.cursor_y = ec.screen_rows - 1;
535
    if (ec.cursor_x > ec.screen_cols)
536
        ec.cursor_x = ec.screen_cols - 1;
537
    editorRefreshScreen();
538
}
539
 
540
void editorHandleSigcont() {
541
    disableRawMode();
542
    consoleBufferOpen();
543
    enableRawMode();
544
    editorRefreshScreen();
545
}
546
 
547
void consoleBufferOpen() {
548
    // Switch to another terminal buffer in order to be able to restore state at exit
549
    // by calling consoleBufferClose().
550
    if (write(STDOUT_FILENO, "\x1b[?47h", 6) == -1)
551
        die("Error changing terminal buffer");
552
}
553
 
554
void consoleBufferClose() {
555
    // Restore console to the state tte opened.
556
    if (write(STDOUT_FILENO, "\x1b[?9l", 5) == -1 ||
557
        write(STDOUT_FILENO, "\x1b[?47l", 6) == -1)
558
        die("Error restoring buffer state");
559
 
560
    /*struct a_buf ab = {.buf = NULL, .len = 0};
561
    char* buf = NULL;
562
    if (asprintf(&buf, "\x1b[%d;%dH\r\n", ec.screen_rows + 1, 1) == -1)
563
        die("Error restoring buffer state");
564
    abufAppend(&ab, buf, strlen(buf));
565
    free(buf);
566
 
567
    if (write(STDOUT_FILENO, ab.buf, ab.len) == -1)
568
        die("Error restoring buffer state");
569
    abufFree(&ab);*/
570
 
571
    editorClearScreen();
572
}
573
 
574
/*** Syntax highlighting ***/
575
 
576
int isSeparator(int c) {
577
    // strchr() looks to see if any one of the characters in the first string
578
    // appear in the second string. If so, it returns a pointer to the
579
    // character in the second string that matched. Otherwise, it
580
    // returns NULL.
581
    return isspace(c) || c == '\0' || strchr(",.()+-/*=~%<>[]:;", c) != NULL;
582
}
583
 
584
int isAlsoNumber(int c) {
585
    return c == '.' || c == 'x' || c == 'a' || c == 'b' || c == 'c' || c == 'd' || c == 'e' || c == 'f';
586
}
587
 
588
void editorUpdateSyntax(editor_row* row) {
589
    row -> highlight = realloc(row -> highlight, row -> render_size);
590
    // void * memset ( void * ptr, int value, size_t num );
591
    // Sets the first num bytes of the block of memory pointed by ptr to
592
    // the specified value. With this we set all characters to HL_NORMAL.
593
    memset(row -> highlight, HL_NORMAL, row -> render_size);
594
 
595
    if (ec.syntax == NULL)
596
        return;
597
 
598
    char** keywords = ec.syntax -> keywords;
599
 
600
    char* scs = ec.syntax -> singleline_comment_start;
601
    char* mcs = ec.syntax -> multiline_comment_start;
602
    char* mce = ec.syntax -> multiline_comment_end;
603
 
604
    int scs_len = scs ? strlen(scs) : 0;
605
    int mcs_len = mcs ? strlen(mcs) : 0;
606
    int mce_len = mce ? strlen(mce) : 0;
607
 
608
    int prev_sep = 1; // True (1) if the previous char is a separator, false otherwise.
609
    int in_string = 0; // If != 0, inside a string. We also keep track if it's ' or "
610
    int in_comment = (row -> idx > 0 && ec.row[row -> idx - 1].hl_open_comment); // This is ONLY used on ML comments.
611
 
612
    int i = 0;
613
    while (i < row -> render_size) {
614
        char c = row -> render[i];
615
        // Highlight type of the previous character.
616
        unsigned char prev_highlight = (i > 0) ? row -> highlight[i - 1] : HL_NORMAL;
617
 
618
        if (scs_len && !in_string && !in_comment) {
619
            // int strncmp ( const char * str1, const char * str2, size_t num );
620
            // Compares up to num characters of the C string str1 to those of the C string str2.
621
            // This function starts comparing the first character of each string. If they are
622
            // equal to each other, it continues with the following pairs until the characters
623
            // differ, until a terminating null-character is reached, or until num characters
624
            // match in both strings, whichever happens first.
625
            if (!strncmp(&row -> render[i], scs, scs_len)) {
626
                memset(&row -> highlight[i], HL_SL_COMMENT, row -> render_size - i);
627
                break;
628
            }
629
        }
630
 
631
        if (mcs_len && mce_len && !in_string) {
632
            if (in_comment) {
633
                row -> highlight[i] = HL_ML_COMMENT;
634
                if (!strncmp(&row -> render[i], mce, mce_len)) {
635
                    memset(&row -> highlight[i], HL_ML_COMMENT, mce_len);
636
                    i += mce_len;
637
                    in_comment = 0;
638
                    prev_sep = 1;
639
                    continue;
640
                } else {
641
                    i++;
642
                    continue;
643
                }
644
            } else if (!strncmp(&row -> render[i], mcs, mcs_len)) {
645
                memset(&row -> highlight[i], HL_ML_COMMENT, mcs_len);
646
                i += mcs_len;
647
                in_comment = 1;
648
                continue;
649
            }
650
 
651
        }
652
 
653
        if (ec.syntax -> flags & HL_HIGHLIGHT_STRINGS) {
654
            if (in_string) {
655
                row -> highlight[i] = HL_STRING;
656
                // If we’re in a string and the current character is a backslash (\),
657
                // and there’s at least one more character in that line that comes
658
                // after the backslash, then we highlight the character that comes
659
                // after the backslash with HL_STRING and consume it. We increment
660
                // i by 2 to consume both characters at once.
661
                if (c == '\\' && i + 1 < row -> render_size) {
662
                    row -> highlight[i + 1] = HL_STRING;
663
                    i += 2;
664
                    continue;
665
                }
666
 
667
                if (c == in_string)
668
                    in_string = 0;
669
                i++;
670
                prev_sep = 1;
671
                continue;
672
            } else {
673
                if (c == '"' || c == '\'') {
674
                    in_string = c;
675
                    row -> highlight[i] = HL_STRING;
676
                    i++;
677
                    continue;
678
                }
679
            }
680
        }
681
 
682
        if (ec.syntax -> flags & HL_HIGHLIGHT_NUMBERS) {
683
            if ((isdigit(c) && (prev_sep || prev_highlight == HL_NUMBER)) ||
684
                (isAlsoNumber(c) && prev_highlight == HL_NUMBER)) {
685
                row -> highlight[i] = HL_NUMBER;
686
                i++;
687
                prev_sep = 0;
688
                continue;
689
            }
690
        }
691
 
692
        if (prev_sep) {
693
            int j;
694
            for (j = 0; keywords[j]; j++) {
695
                int kw_len = strlen(keywords[j]);
696
                int kw_2 = keywords[j][kw_len - 1] == '|';
697
                if (kw_2)
698
                    kw_len--;
699
 
700
                // Keywords require a separator both before and after the keyword.
701
                if (!strncmp(&row -> render[i], keywords[j], kw_len) &&
702
                    isSeparator(row -> render[i + kw_len])) {
703
                    memset(&row -> highlight[i], kw_2 ? HL_KEYWORD_2 : HL_KEYWORD_1, kw_len);
704
                    i += kw_len;
705
                    break;
706
                }
707
            }
708
            if (keywords[j] != NULL) {
709
                prev_sep = 0;
710
                continue;
711
            }
712
        }
713
 
714
        prev_sep = isSeparator(c);
715
        i++;
716
    }
717
 
718
    int changed = (row -> hl_open_comment != in_comment);
719
    // This tells us whether the row ended as an unclosed multi-line
720
    // comment or not.
721
    row -> hl_open_comment = in_comment;
722
    // A user could comment out an entire file just by changing one line.
723
    // So it seems like we need to update the syntax of all the lines
724
    // following the current line. However, we know the highlighting
725
    // of the next line will not change if the value of this line’s
726
    // // // hl_open_comment did not change. So we check if it changed, and
727
    // // only call editorUpdateSyntax() on the next line if
728
    // hl_open_comment changed (and if there is a next line in the file).
729
    // Because editorUpdateSyntax() keeps calling itself with the next
730
    // line, the change will continue to propagate to more and more lines
731
    // until one of them is unchanged, at which point we know that all
732
    // the lines after that one must be unchanged as well.
733
    if (changed && row -> idx + 1 < ec.num_rows)
734
        editorUpdateSyntax(&ec.row[row -> idx + 1]);
735
}
736
 
737
int editorSyntaxToColor(int highlight) {
738
    // We return ANSI codes for colors.
739
    // See https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
740
    // for a list of them.
741
    switch (highlight) {
742
        case HL_SL_COMMENT:
743
        case HL_ML_COMMENT: return 36;
744
        case HL_KEYWORD_1: return 31;
745
        case HL_KEYWORD_2: return 32;
746
        case HL_STRING: return 33;
747
        case HL_NUMBER: return 35;
748
        case HL_MATCH: return 34;
749
        default: return 37;
750
    }
751
}
752
 
753
void editorSelectSyntaxHighlight() {
754
    ec.syntax = NULL;
755
    if (ec.file_name == NULL)
756
        return;
757
 
758
    for (unsigned int j = 0; j < HL_DB_ENTRIES; j++) {
759
        struct editor_syntax* es = &HL_DB[j];
760
        unsigned int i = 0;
761
 
762
        while (es -> file_match[i]) {
763
            char* p = strstr(ec.file_name, es -> file_match[i]);
764
            if (p != NULL) {
765
                // Returns a pointer to the first occurrence of str2 in str1,
766
                // or a null pointer if str2 is not part of str1.
767
                int pat_len = strlen(es -> file_match[i]);
768
                if (es -> file_match[i][0] != '.' || p[pat_len] == '\0') {
769
                    ec.syntax = es;
770
 
771
                    int file_row;
772
                    for (file_row = 0; file_row < ec.num_rows; file_row++) {
773
                        editorUpdateSyntax(&ec.row[file_row]);
774
                    }
775
 
776
                    return;
777
                }
778
            }
779
            i++;
780
        }
781
    }
782
}
783
 
784
/*** Row operations ***/
785
 
786
int editorRowCursorXToRenderX(editor_row* row, int cursor_x) {
787
    int render_x = 0;
788
    int j;
789
    // For each character, if its a tab we use rx % TTE_TAB_STOP
790
    // to find out how many columns we are to the right of the last
791
    // tab stop, and then subtract that from TTE_TAB_STOP - 1 to
792
    // find out how many columns we are to the left of the next tab
793
    // stop. We add that amount to rx to get just to the left of the
794
    // next tab stop, and then the unconditional rx++ statement gets
795
    // us right on the next tab stop. Notice how this works even if
796
    // we are currently on a tab stop.
797
    for (j = 0; j < cursor_x; j++) {
798
        if (row -> chars[j] == '\t')
799
            render_x += (TTE_TAB_STOP - 1) - (render_x % TTE_TAB_STOP);
800
        render_x++;
801
    }
802
    return render_x;
803
}
804
 
805
int editorRowRenderXToCursorX(editor_row* row, int render_x) {
806
    int cur_render_x = 0;
807
    int cursor_x;
808
    for (cursor_x = 0; cursor_x < row -> size; cursor_x++) {
809
        if (row -> chars[cursor_x] == '\t')
810
            cur_render_x += (TTE_TAB_STOP - 1) - (cur_render_x % TTE_TAB_STOP);
811
        cur_render_x++;
812
 
813
        if (cur_render_x > render_x)
814
            return cursor_x;
815
    }
816
    return cursor_x;
817
}
818
 
819
void editorUpdateRow(editor_row* row) {
820
    // First, we have to loop through the chars of the row
821
    // and count the tabs in order to know how much memory
822
    // to allocate for render. The maximum number of characters
823
    // needed for each tab is 8. row->size already counts 1 for
824
    // each tab, so we multiply the number of tabs by 7 and add
825
    // that to row->size to get the maximum amount of memory we'll
826
    // need for the rendered row.
827
    int tabs = 0;
828
    int j;
829
    for (j = 0; j < row -> size; j++) {
830
        if (row -> chars[j] == '\t')
831
            tabs++;
832
    }
833
    free(row -> render);
834
    row -> render = malloc(row -> size + tabs * (TTE_TAB_STOP - 1) + 1);
835
 
836
    // After allocating the memory, we check whether the current character
837
    // is a tab. If it is, we append one space (because each tab must
838
    // advance the cursor forward at least one column), and then append
839
    // spaces until we get to a tab stop, which is a column that is
840
    // divisible by 8
841
    int idx = 0;
842
    for (j = 0; j < row -> size; j++) {
843
        if (row -> chars[j] == '\t') {
844
            row -> render[idx++] = ' ';
845
            while (idx % TTE_TAB_STOP != 0)
846
                row -> render[idx++] = ' ';
847
        } else
848
            row -> render[idx++] = row -> chars[j];
849
    }
850
    row -> render[idx] = '\0';
851
    row -> render_size = idx;
852
 
853
    editorUpdateSyntax(row);
854
}
855
 
856
void editorInsertRow(int at, char* s, size_t line_len) {
857
    if (at < 0 || at > ec.num_rows)
858
        return;
859
 
860
    ec.row = realloc(ec.row, sizeof(editor_row) * (ec.num_rows + 1));
861
    memmove(&ec.row[at + 1], &ec.row[at], sizeof(editor_row) * (ec.num_rows - at));
862
 
863
    for (int j = at + 1; j <= ec.num_rows; j++) {
864
        ec.row[j].idx++;
865
    }
866
 
867
    ec.row[at].idx = at;
868
 
869
    ec.row[at].size = line_len;
870
    ec.row[at].chars = malloc(line_len + 1); // We want to add terminator char '\0' at the end
871
    memcpy(ec.row[at].chars, s, line_len);
872
    ec.row[at].chars[line_len] = '\0';
873
 
874
    ec.row[at].render_size = 0;
875
    ec.row[at].render = NULL;
876
    ec.row[at].highlight = NULL;
877
    ec.row[at].hl_open_comment = 0;
878
    editorUpdateRow(&ec.row[at]);
879
 
880
    ec.num_rows++;
881
    ec.dirty++;
882
}
883
 
884
void editorFreeRow(editor_row* row) {
885
    free(row -> render);
886
    free(row -> chars);
887
    free(row -> highlight);
888
}
889
 
890
void editorDelRow(int at) {
891
    if (at < 0 || at >= ec.num_rows)
892
        return;
893
    editorFreeRow(&ec.row[at]);
894
    memmove(&ec.row[at], &ec.row[at + 1], sizeof(editor_row) * (ec.num_rows - at - 1));
895
 
896
    for (int j = at; j < ec.num_rows - 1; j++) {
897
        ec.row[j].idx--;
898
    }
899
 
900
    ec.num_rows--;
901
    ec.dirty++;
902
}
903
 
904
// -1 down, 1 up
905
void editorFlipRow(int dir) {
906
    editor_row c_row = ec.row[ec.cursor_y];
907
    ec.row[ec.cursor_y] = ec.row[ec.cursor_y - dir];
908
    ec.row[ec.cursor_y - dir] = c_row;
909
 
910
    ec.row[ec.cursor_y].idx += dir;
911
    ec.row[ec.cursor_y - dir].idx -= dir;
912
 
913
    int first = (dir == 1) ? ec.cursor_y - 1 : ec.cursor_y;
914
    editorUpdateSyntax(&ec.row[first]);
915
    editorUpdateSyntax(&ec.row[first] + 1);
916
    if (ec.num_rows - ec.cursor_y > 2)
917
      editorUpdateSyntax(&ec.row[first] + 2);
918
 
919
    ec.cursor_y -= dir;
920
    ec.dirty++;
921
}
922
 
923
void editorCopy(int cut) {
924
    ec.copied_char_buffer = realloc(ec.copied_char_buffer, strlen(ec.row[ec.cursor_y].chars) + 1);
925
    strcpy(ec.copied_char_buffer, ec.row[ec.cursor_y].chars);
926
    editorSetStatusMessage(cut ? "Content cut" : "Content copied");
927
}
928
 
929
void editorCut() {
930
    editorCopy(-1);
931
    editorDelRow(ec.cursor_y);
932
    if (ec.num_rows - ec.cursor_y > 0)
933
      editorUpdateSyntax(&ec.row[ec.cursor_y]);
934
    if (ec.num_rows - ec.cursor_y > 1)
935
      editorUpdateSyntax(&ec.row[ec.cursor_y + 1]);
936
    ec.cursor_x = ec.cursor_y == ec.num_rows ? 0 : ec.row[ec.cursor_y].size;
937
}
938
 
939
void editorPaste() {
940
    if (ec.copied_char_buffer == NULL)
941
      return;
942
 
943
    if (ec.cursor_y == ec.num_rows)
944
      editorInsertRow(ec.cursor_y, ec.copied_char_buffer, strlen(ec.copied_char_buffer));
945
    else
946
      editorRowAppendString(&ec.row[ec.cursor_y], ec.copied_char_buffer, strlen(ec.copied_char_buffer));
947
    ec.cursor_x += strlen(ec.copied_char_buffer);
948
}
949
 
950
void editorRowInsertChar(editor_row* row, int at, int c) {
951
    if (at < 0 || at > row -> size)
952
        at = row -> size;
953
    // We need to allocate 2 bytes because we also have to make room for
954
    // the null byte.
955
    row -> chars = realloc(row -> chars, row -> size + 2);
956
    // memmove it's like memcpy(), but is safe to use when the source and
957
    // destination arrays overlap
958
    memmove(&row -> chars[at + 1], &row -> chars[at], row -> size - at + 1);
959
    row -> size++;
960
    row -> chars[at] = c;
961
    editorUpdateRow(row);
962
    ec.dirty++; // This way we can see "how dirty" a file is.
963
}
964
 
965
void editorInsertNewline() {
966
    // If we're at the beginning of a line, all we have to do is insert
967
    // a new blank row before the line we're on.
968
    if (ec.cursor_x == 0) {
969
        editorInsertRow(ec.cursor_y, "", 0);
970
    // Otherwise, we have to split the line we're on into two rows.
971
    } else {
972
        editor_row* row = &ec.row[ec.cursor_y];
973
        editorInsertRow(ec.cursor_y + 1, &row -> chars[ec.cursor_x], row -> size - ec.cursor_x);
974
        row = &ec.row[ec.cursor_y];
975
        row -> size = ec.cursor_x;
976
        row -> chars[row -> size] = '\0';
977
        editorUpdateRow(row);
978
    }
979
    ec.cursor_y++;
980
    ec.cursor_x = 0;
981
}
982
 
983
void editorRowAppendString(editor_row* row, char* s, size_t len) {
984
    row -> chars = realloc(row -> chars, row -> size + len + 1);
985
    memcpy(&row -> chars[row -> size], s, len);
986
    row -> size += len;
987
    row -> chars[row -> size] = '\0';
988
    editorUpdateRow(row);
989
    ec.dirty++;
990
}
991
 
992
void editorRowDelChar(editor_row* row, int at) {
993
    if (at < 0 || at >= row -> size)
994
        return;
995
    // Overwriting the deleted character with the characters that come
996
    // after it.
997
    memmove(&row -> chars[at], &row -> chars[at + 1], row -> size - at);
998
    row -> size--;
999
    editorUpdateRow(row);
1000
    ec.dirty++;
1001
}
1002
 
1003
/*** Editor operations ***/
1004
 
1005
void editorInsertChar(int c) {
1006
    // If this is true, the cursor is on the tilde line after the end of
1007
    // the file, so we need to append a new row to the file before inserting
1008
    // a character there.
1009
    if (ec.cursor_y == ec.num_rows)
1010
        editorInsertRow(ec.num_rows, "", 0);
1011
    editorRowInsertChar(&ec.row[ec.cursor_y], ec.cursor_x, c);
1012
    ec.cursor_x++; // This way we can see "how dirty" a file is.
1013
}
1014
 
1015
void editorDelChar() {
1016
    // If the cursor is past the end of the file, there's nothing to delete.
1017
    if (ec.cursor_y == ec.num_rows)
1018
        return;
1019
    // Cursor is at the beginning of a file, there's nothing to delete.
1020
    if (ec.cursor_x == 0 && ec.cursor_y == 0)
1021
        return;
1022
 
1023
    editor_row* row = &ec.row[ec.cursor_y];
1024
    if (ec.cursor_x > 0) {
1025
        editorRowDelChar(row, ec.cursor_x - 1);
1026
        ec.cursor_x--;
1027
    // Deleting a line and moving up all the content.
1028
    } else {
1029
        ec.cursor_x = ec.row[ec.cursor_y - 1].size;
1030
        editorRowAppendString(&ec.row[ec.cursor_y -1], row -> chars, row -> size);
1031
        editorDelRow(ec.cursor_y);
1032
        ec.cursor_y--;
1033
    }
1034
}
1035
 
1036
/*** File I/O ***/
1037
 
1038
char* editorRowsToString(int* buf_len) {
1039
    int total_len = 0;
1040
    int j;
1041
    // Adding up the lengths of each row of text, adding 1
1042
    // to each one for the newline character we'll add to
1043
    // the end of each line.
1044
    for (j = 0; j < ec.num_rows; j++) {
1045
        total_len += ec.row[j].size + 1;
1046
    }
1047
    *buf_len = total_len;
1048
 
1049
    char* buf = malloc(total_len);
1050
    char* p = buf;
1051
    // Copying the contents of each row to the end of the
1052
    // buffer, appending a newline character after each
1053
    // row.
1054
    for (j = 0; j < ec.num_rows; j++) {
1055
        memcpy(p, ec.row[j].chars, ec.row[j].size);
1056
        p += ec.row[j].size;
1057
        *p = '\n';
1058
        p++;
1059
    }
1060
 
1061
    return buf;
1062
}
1063
 
1064
void editorOpen(char* file_name) {
1065
    free(ec.file_name);
1066
    ec.file_name = strdup(file_name);
1067
 
1068
    editorSelectSyntaxHighlight();
1069
 
1070
    FILE* file = fopen(file_name, "r+");
1071
    if (!file)
1072
        die("Failed to open the file");
1073
 
1074
    char* line = NULL;
1075
    // Unsigned int of at least 16 bit.
1076
    size_t line_cap = 0;
1077
    // Bigger than int
1078
    ssize_t line_len;
1079
    while ((line_len = getline(&line, &line_cap, file)) != -1) {
1080
        // We already know each row represents one line of text, there's no need
1081
        // to keep carriage return and newline characters.
1082
        if (line_len > 0 && (line[line_len - 1] == '\n' || line[line_len - 1] == '\r'))
1083
            line_len--;
1084
        editorInsertRow(ec.num_rows, line, line_len);
1085
    }
1086
    free(line);
1087
    fclose(file);
1088
    ec.dirty = 0;
1089
}
1090
 
1091
void editorSave() {
1092
    if (ec.file_name == NULL) {
1093
        ec.file_name = editorPrompt("Save as: %s (ESC to cancel)", NULL);
1094
        if (ec.file_name == NULL) {
1095
            editorSetStatusMessage("Save aborted");
1096
            return;
1097
        }
1098
        editorSelectSyntaxHighlight();
1099
    }
1100
 
1101
    int len;
1102
    char* buf = editorRowsToString(&len);
1103
 
1104
    // We want to create if it doesn't already exist (O_CREAT flag), giving
1105
    // 0644 permissions (the standard ones). O_RDWR stands for reading and
1106
    // writing.
1107
    int fd = open(ec.file_name, O_RDWR | O_CREAT, 0644);
1108
    if (fd != -1) {
1109
        // ftruncate sets the file's size to the specified length.
1110
        if (ftruncate(fd, len) != -1) {
1111
            // Writing the file.
1112
            if (write(fd, buf, len) == len) {
1113
                close(fd);
1114
                free(buf);
1115
                ec.dirty = 0;
1116
                editorSetStatusMessage("%d bytes written to disk", len);
1117
                return;
1118
            }
1119
        }
1120
        close(fd);
1121
    }
1122
 
1123
    free(buf);
1124
    editorSetStatusMessage("Cant's save file. Error occurred: %s", strerror(errno));
1125
}
1126
 
1127
/*** Search section ***/
1128
 
1129
void editorSearchCallback(char* query, int key) {
1130
    // Index of the row that the last match was on, -1 if there was
1131
    // no last match.
1132
    static int last_match = -1;
1133
    // 1 for searching forward and -1 for searching backwards.
1134
    static int direction = 1;
1135
 
1136
    static int saved_highlight_line;
1137
    static char* saved_hightlight = NULL;
1138
 
1139
    if (saved_hightlight) {
1140
        memcpy(ec.row[saved_highlight_line].highlight, saved_hightlight, ec.row[saved_highlight_line].render_size);
1141
        free(saved_hightlight);
1142
        saved_hightlight = NULL;
1143
    }
1144
 
1145
    // Checking if the user pressed Enter or Escape, in which case
1146
    // they are leaving search mode so we return immediately.
1147
    if (key == '\r' || key == '\x1b') {
1148
        last_match = -1;
1149
        direction = 1;
1150
        return;
1151
    } else if (key == ARROW_RIGHT || key == ARROW_DOWN) {
1152
        direction = 1;
1153
    } else if (key == ARROW_LEFT || key == ARROW_UP) {
1154
        if (last_match == -1) {
1155
            // If nothing matched and the left or up arrow key was pressed
1156
            return;
1157
        }
1158
        direction = -1;
1159
    } else {
1160
        last_match = -1;
1161
        direction = 1;
1162
    }
1163
 
1164
    int current = last_match;
1165
    int i;
1166
    for (i = 0; i < ec.num_rows; i++) {
1167
        current += direction;
1168
        if (current == -1)
1169
            current = ec.num_rows - 1;
1170
        else if (current == ec.num_rows)
1171
            current = 0;
1172
 
1173
        editor_row* row = &ec.row[current];
1174
        // We use strstr to check if query is a substring of the
1175
        // current row. It returns NULL if there is no match,
1176
        // oterwhise it returns a pointer to the matching substring.
1177
        char* match = strstr(row -> render, query);
1178
        if (match) {
1179
            last_match = current;
1180
            ec.cursor_y = current;
1181
            ec.cursor_x = editorRowRenderXToCursorX(row, match - row -> render);
1182
            // We set this like so to scroll to the bottom of the file so
1183
            // that the next screen refresh will cause the matching line to
1184
            // be at the very top of the screen.
1185
            ec.row_offset = ec.num_rows;
1186
 
1187
            saved_highlight_line = current;
1188
            saved_hightlight = malloc(row -> render_size);
1189
            memcpy(saved_hightlight, row -> highlight, row -> render_size);
1190
            memset(&row -> highlight[match - row -> render], HL_MATCH, strlen(query));
1191
            break;
1192
        }
1193
    }
1194
}
1195
 
1196
void editorSearch() {
1197
    int saved_cursor_x = ec.cursor_x;
1198
    int saved_cursor_y = ec.cursor_y;
1199
    int saved_col_offset = ec.col_offset;
1200
    int saved_row_offset = ec.row_offset;
1201
 
1202
    char* query = editorPrompt("Search: %s (Use ESC / Enter / Arrows)", editorSearchCallback);
1203
 
1204
    if (query) {
1205
        free(query);
1206
    // If query is NULL, that means they pressed Escape, so in that case we
1207
    // restore the cursor previous position.
1208
    } else {
1209
        ec.cursor_x = saved_cursor_x;
1210
        ec.cursor_y = saved_cursor_y;
1211
        ec.col_offset = saved_col_offset;
1212
        ec.row_offset = saved_row_offset;
1213
    }
1214
}
1215
 
1216
/*** Append buffer section **/
1217
 
1218
void abufAppend(struct a_buf* ab, const char* s, int len) {
1219
    // Using realloc to get a block of free memory that is
1220
    // the size of the current string + the size of the string
1221
    // to be appended.
1222
    char* new = realloc(ab -> buf, ab -> len + len);
1223
 
1224
    if (new == NULL)
1225
        return;
1226
 
1227
    // Copying the string s at the end of the current data in
1228
    // the buffer.
1229
    memcpy(&new[ab -> len], s, len);
1230
    ab -> buf = new;
1231
    ab -> len += len;
1232
}
1233
 
1234
void abufFree(struct a_buf* ab) {
1235
    // Deallocating buffer.
1236
    free(ab -> buf);
1237
}
1238
 
1239
/*** Output section ***/
1240
 
1241
void editorScroll() {
1242
    ec.render_x = 0;
1243
    if (ec.cursor_y < ec.num_rows)
1244
        ec.render_x = editorRowCursorXToRenderX(&ec.row[ec.cursor_y], ec.cursor_x);
1245
    // The first if statement checks if the cursor is above the visible window,
1246
    // and if so, scrolls up to where the cursor is. The second if statement checks
1247
    // if the cursor is past the bottom of the visible window, and contains slightly
1248
    // more complicated arithmetic because ec.row_offset refers to what's at the top
1249
    // of the screen, and we have to get ec.screen_rows involved to talk about what's
1250
    // at the bottom of the screen.
1251
    if (ec.cursor_y < ec.row_offset)
1252
        ec.row_offset = ec.cursor_y;
1253
    if (ec.cursor_y >= ec.row_offset + ec.screen_rows)
1254
        ec.row_offset = ec.cursor_y - ec.screen_rows + 1;
1255
 
1256
    if (ec.render_x < ec.col_offset)
1257
        ec.col_offset = ec.render_x;
1258
    if (ec.render_x >= ec.col_offset + ec.screen_cols)
1259
        ec.col_offset = ec.render_x - ec.screen_cols + 1;
1260
}
1261
 
1262
void editorDrawStatusBar(struct a_buf* ab) {
1263
    // This switches to inverted colors.
1264
    // NOTE:
1265
    // The m command (Select Graphic Rendition) causes the text printed
1266
    // after it to be printed with various possible attributes including
1267
    // bold (1), underscore (4), blink (5), and inverted colors (7). An
1268
    // argument of 0 clears all attributes (the default one). See
1269
    // http://vt100.net/docs/vt100-ug/chapter3.html#SGR for more info.
1270
    abufAppend(ab, "\x1b[7m", 4);
1271
 
1272
    char status[80], r_status[80];
1273
    // Showing up to 20 characters of the filename, followed by the number of lines.
1274
    int len = snprintf(status, sizeof(status), " Editing: %.20s %s", ec.file_name ? ec.file_name : "New file", ec.dirty ? "(modified)" : "");
1275
    int col_size = ec.row && ec.cursor_y <= ec.num_rows - 1 ? col_size = ec.row[ec.cursor_y].size : 0;
1276
    int r_len = snprintf(r_status, sizeof(r_status), "%d/%d lines  %d/%d cols ", ec.cursor_y + 1 > ec.num_rows ? ec.num_rows : ec.cursor_y + 1, ec.num_rows,
1277
        ec.cursor_x + 1 > col_size ? col_size : ec.cursor_x + 1, col_size);
1278
    if (len > ec.screen_cols)
1279
        len = ec.screen_cols;
1280
    abufAppend(ab, status, len);
1281
    while (len < ec.screen_cols) {
1282
        if (ec.screen_cols - len == r_len) {
1283
            abufAppend(ab, r_status, r_len);
1284
            break;
1285
        } else {
1286
            abufAppend(ab, " ", 1);
1287
            len++;
1288
        }
1289
    }
1290
    // This switches back to normal colors.
1291
    abufAppend(ab, "\x1b[m", 3);
1292
 
1293
    abufAppend(ab, "\r\n", 2);
1294
}
1295
 
1296
void editorDrawMessageBar(struct a_buf *ab) {
1297
    // Clearing the message bar.
1298
    abufAppend(ab, "\x1b[K", 3);
1299
    int msg_len = strlen(ec.status_msg);
1300
    if (msg_len > ec.screen_cols)
1301
        msg_len = ec.screen_cols;
1302
    // We only show the message if its less than 5 secons old, but
1303
    // remember the screen is only being refreshed after each keypress.
1304
    if (msg_len && time(NULL) - ec.status_msg_time < 5)
1305
        abufAppend(ab, ec.status_msg, msg_len);
1306
}
1307
 
1308
void editorDrawWelcomeMessage(struct a_buf* ab) {
1309
    char welcome[80];
1310
    // Using snprintf to truncate message in case the terminal
1311
    // is too tiny to handle the entire string.
1312
    int welcome_len = snprintf(welcome, sizeof(welcome),
1313
        "tte %s ", TTE_VERSION);
1314
    if (welcome_len > ec.screen_cols)
1315
        welcome_len = ec.screen_cols;
1316
    // Centering the message.
1317
    int padding = (ec.screen_cols - welcome_len) / 2;
1318
    // Remember that everything != 0 is true.
1319
    if (padding) {
1320
        abufAppend(ab, "~", 1);
1321
        padding--;
1322
    }
1323
    while (padding--)
1324
        abufAppend(ab, " ", 1);
1325
    abufAppend(ab, welcome, welcome_len);
1326
}
1327
 
1328
// The ... argument makes editorSetStatusMessage() a variadic function,
1329
// meaning it can take any number of arguments. C's way of dealing with
1330
// these arguments is by having you call va_start() and va_end() on a
1331
// // value of type va_list. The last argument before the ... (in this
1332
// case, msg) must be passed to va_start(), so that the address of
1333
// the next arguments is known. Then, between the va_start() and
1334
// va_end() calls, you would call va_arg() and pass it the type of
1335
// the next argument (which you usually get from the given format
1336
// string) and it would return the value of that argument. In
1337
// this case, we pass msg and args to vsnprintf() and it takes care
1338
// of reading the format string and calling va_arg() to get each
1339
// argument.
1340
void editorSetStatusMessage(const char* msg, ...) {
1341
    va_list args;
1342
    va_start(args, msg);
1343
    vsnprintf(ec.status_msg, sizeof(ec.status_msg), msg, args);
1344
    va_end(args);
1345
    ec.status_msg_time = time(NULL);
1346
}
1347
 
1348
void editorDrawRows(struct a_buf* ab) {
1349
    int y;
1350
    for (y = 0; y < ec.screen_rows; y++) {
1351
        int file_row = y + ec.row_offset;
1352
        if(file_row >= ec.num_rows) {
1353
            if (ec.num_rows == 0 && y == ec.screen_rows / 3)
1354
                editorDrawWelcomeMessage(ab);
1355
            else
1356
                abufAppend(ab, "~", 1);
1357
        } else {
1358
            int len = ec.row[file_row].render_size - ec.col_offset;
1359
            // len can be a negative number, meaning the user scrolled
1360
            // horizontally past the end of the line. In that case, we set
1361
            // len to 0 so that nothing is displayed on that line.
1362
            if (len < 0)
1363
                len = 0;
1364
            if (len > ec.screen_cols)
1365
                len = ec.screen_cols;
1366
 
1367
            char* c = &ec.row[file_row].render[ec.col_offset];
1368
            unsigned char* highlight = &ec.row[file_row].highlight[ec.col_offset];
1369
            int current_color = -1;
1370
            int j;
1371
            for (j = 0; j < len; j++) {
1372
                // Displaying nonprintable characters as (A-Z, @, and ?).
1373
                if (iscntrl(c[j])) {
1374
                    char sym = (c[j] <= 26) ? '@' + c[j] : '?';
1375
                    abufAppend(ab, "\x1b[7m", 4);
1376
                    abufAppend(ab, &sym, 1);
1377
                    abufAppend(ab, "\x1b[m", 3);
1378
                    if (current_color != -1) {
1379
                        char buf[16];
1380
                        int c_len = snprintf(buf, sizeof(buf), "\x1b[%dm", current_color);
1381
                        abufAppend(ab, buf, c_len);
1382
                    }
1383
                } else if (highlight[j] == HL_NORMAL) {
1384
                    if (current_color != -1) {
1385
                        abufAppend(ab, "\x1b[39m", 5);
1386
                        current_color = -1;
1387
                    }
1388
                    abufAppend(ab, &c[j], 1);
1389
                } else {
1390
                    int color = editorSyntaxToColor(highlight[j]);
1391
                    // We only use escape sequence if the new color is different
1392
                    // from the last character's color.
1393
                    if (color != current_color) {
1394
                        current_color = color;
1395
                        char buf[16];
1396
                        int c_len = snprintf(buf, sizeof(buf), "\x1b[%dm", color);
1397
                        abufAppend(ab, buf, c_len);
1398
                    }
1399
 
1400
                    abufAppend(ab, &c[j], 1);
1401
                }
1402
            }
1403
            abufAppend(ab, "\x1b[39m", 5);
1404
        }
1405
 
1406
        // Redrawing each line instead of the whole screen.
1407
        abufAppend(ab, "\x1b[K", 3);
1408
        // Addind a new line
1409
        abufAppend(ab, "\r\n", 2);
1410
    }
1411
}
1412
 
1413
void editorRefreshScreen() {
1414
    editorScroll();
1415
 
1416
    struct a_buf ab = ABUF_INIT;
1417
 
1418
    // Hiding the cursor while the screen is refreshing.
1419
    // See http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.4
1420
    // for more info.
1421
    abufAppend(&ab, "\x1b[?25l", 6);
1422
    abufAppend(&ab, "\x1b[H", 3);
1423
 
1424
    editorDrawRows(&ab);
1425
    editorDrawStatusBar(&ab);
1426
    editorDrawMessageBar(&ab);
1427
 
1428
    // Moving the cursor where it should be.
1429
    char buf[32];
1430
    snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (ec.cursor_y - ec.row_offset) + 1, (ec.render_x - ec.col_offset) + 1);
1431
    abufAppend(&ab, buf, strlen(buf));
1432
 
1433
    // Showing again the cursor.
1434
    abufAppend(&ab, "\x1b[?25h", 6);
1435
 
1436
    // Writing all content at once
1437
    write(STDOUT_FILENO, ab.buf, ab.len);
1438
    abufFree(&ab);
1439
}
1440
 
1441
void editorClearScreen() {
1442
    // Writing 4 bytes out to the terminal:
1443
    // - (1 byte) \x1b : escape character
1444
    // - (3 bytes) [2J : Clears the entire screen, see
1445
    // http://vt100.net/docs/vt100-ug/chapter3.html#ED
1446
    // for more info.
1447
    write(STDOUT_FILENO, "\x1b[2J", 4);
1448
    // Writing 3 bytes to reposition the cursor back at
1449
    // the top-left corner, see
1450
    // http://vt100.net/docs/vt100-ug/chapter3.html#CUP
1451
    // for more info.
1452
    write(STDOUT_FILENO, "\x1b[H", 3);
1453
}
1454
 
1455
/*** Input section ***/
1456
 
1457
char* editorPrompt(char* prompt, void (*callback)(char*, int)) {
1458
    size_t buf_size = 128;
1459
    char* buf = malloc(buf_size);
1460
 
1461
    size_t buf_len = 0;
1462
    buf[0] = '\0';
1463
 
1464
    while (1) {
1465
        editorSetStatusMessage(prompt, buf);
1466
        editorRefreshScreen();
1467
 
1468
        int c = editorReadKey();
1469
        if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
1470
            if (buf_len != 0)
1471
                buf[--buf_len] = '\0';
1472
        } else if (c == '\x1b') {
1473
            editorSetStatusMessage("");
1474
            if (callback)
1475
                callback(buf, c);
1476
            free(buf);
1477
            return NULL;
1478
        } else if (c == '\r') {
1479
            if (buf_len != 0) {
1480
                editorSetStatusMessage("");
1481
                if (callback)
1482
                    callback(buf, c);
1483
                return buf;
1484
            }
1485
        } else if (!iscntrl(c) && isprint(c)) {
1486
            if (buf_len == buf_size - 1) {
1487
                buf_size *= 2;
1488
                buf = realloc(buf, buf_size);
1489
            }
1490
            buf[buf_len++] = c;
1491
            buf[buf_len] = '\0';
1492
        }
1493
 
1494
        if (callback)
1495
            callback(buf, c);
1496
    }
1497
}
1498
 
1499
void editorMoveCursor(int key) {
1500
    editor_row* row = (ec.cursor_y >= ec.num_rows) ? NULL : &ec.row[ec.cursor_y];
1501
 
1502
    switch (key) {
1503
        case ARROW_LEFT:
1504
            if (ec.cursor_x != 0)
1505
                ec.cursor_x--;
1506
            // If <- is pressed, move to the end of the previous line
1507
            else if (ec.cursor_y > 0) {
1508
                ec.cursor_y--;
1509
                ec.cursor_x = ec.row[ec.cursor_y].size;
1510
            }
1511
            break;
1512
        case ARROW_RIGHT:
1513
            if (row && ec.cursor_x < row -> size)
1514
                ec.cursor_x++;
1515
            // If -> is pressed, move to the start of the next line
1516
            else if (row && ec.cursor_x == row -> size) {
1517
                ec.cursor_y++;
1518
                ec.cursor_x = 0;
1519
            }
1520
            break;
1521
        case ARROW_UP:
1522
            if (ec.cursor_y != 0)
1523
                ec.cursor_y--;
1524
            break;
1525
        case ARROW_DOWN:
1526
            if (ec.cursor_y < ec.num_rows)
1527
                ec.cursor_y++;
1528
            break;
1529
    }
1530
 
1531
    // Move cursor_x if it ends up past the end of the line it's on
1532
    row = (ec.cursor_y >= ec.num_rows) ? NULL : &ec.row[ec.cursor_y];
1533
    int row_len = row ? row -> size : 0;
1534
    if (ec.cursor_x > row_len)
1535
        ec.cursor_x = row_len;
1536
}
1537
 
1538
void editorProcessKeypress() {
1539
    static int quit_times = TTE_QUIT_TIMES;
1540
 
1541
    int c = editorReadKey();
1542
 
1543
    switch (c) {
1544
        case '\r': // Enter key
1545
            editorInsertNewline();
1546
            break;
1547
        case CTRL_KEY('q'):
1548
            if (ec.dirty && quit_times > 0) {
1549
                editorSetStatusMessage("Warning! File has unsaved changes. Press Ctrl-Q %d more time%s to quit", quit_times, quit_times > 1 ? "s" : "");
1550
                quit_times--;
1551
                return;
1552
            }
1553
            editorClearScreen();
1554
            consoleBufferClose();
1555
            exit(0);
1556
            break;
1557
        case CTRL_KEY('s'):
1558
            editorSave();
1559
            break;
1560
        case CTRL_KEY('e'):
1561
            if (ec.cursor_y > 0 && ec.cursor_y <= ec.num_rows - 1)
1562
                editorFlipRow(1);
1563
            break;
1564
        case CTRL_KEY('d'):
1565
            if (ec.cursor_y < ec.num_rows - 1)
1566
            editorFlipRow(-1);
1567
            break;
1568
        case CTRL_KEY('x'):
1569
            if (ec.cursor_y < ec.num_rows)
1570
            editorCut();
1571
            break;
1572
        case CTRL_KEY('c'):
1573
            if (ec.cursor_y < ec.num_rows)
1574
            editorCopy(0);
1575
            break;
1576
        case CTRL_KEY('v'):
1577
            editorPaste();
1578
            break;
1579
        case CTRL_KEY('p'):
1580
            consoleBufferClose();
1581
            kill(0, SIGTSTP);
1582
        case ARROW_UP:
1583
        case ARROW_DOWN:
1584
        case ARROW_LEFT:
1585
        case ARROW_RIGHT:
1586
            editorMoveCursor(c);
1587
            break;
1588
        case PAGE_UP:
1589
        case PAGE_DOWN:
1590
            { // You can't declare variables directly inside a switch statement.
1591
                if (c == PAGE_UP)
1592
                    ec.cursor_y = ec.row_offset;
1593
                else if (c == PAGE_DOWN)
1594
                    ec.cursor_y = ec.row_offset + ec.screen_rows - 1;
1595
 
1596
                int times = ec.screen_rows;
1597
                while (times--)
1598
                    editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
1599
            }
1600
            break;
1601
        case HOME_KEY:
1602
            ec.cursor_x = 0;
1603
            break;
1604
        case END_KEY:
1605
            if (ec.cursor_y < ec.num_rows)
1606
                ec.cursor_x = ec.row[ec.cursor_y].size;
1607
            break;
1608
        case CTRL_KEY('f'):
1609
            editorSearch();
1610
            break;
1611
        case BACKSPACE:
1612
        case CTRL_KEY('h'):
1613
        case DEL_KEY:
1614
            if (c == DEL_KEY)
1615
                editorMoveCursor(ARROW_RIGHT);
1616
            editorDelChar();
1617
            break;
1618
        case CTRL_KEY('l'):
1619
        case '\x1b': // Escape key
1620
            break;
1621
        default:
1622
            editorInsertChar(c);
1623
            break;
1624
    }
1625
 
1626
    quit_times = TTE_QUIT_TIMES;
1627
}
1628
 
1629
/*** Init section ***/
1630
 
1631
void initEditor() {
1632
    ec.cursor_x = 0;
1633
    ec.cursor_y = 0;
1634
    ec.render_x = 0;
1635
    ec.row_offset = 0;
1636
    ec.col_offset = 0;
1637
    ec.num_rows = 0;
1638
    ec.row = NULL;
1639
    ec.dirty = 0;
1640
    ec.file_name = NULL;
1641
    ec.status_msg[0] = '\0';
1642
    ec.status_msg_time = 0;
1643
    ec.copied_char_buffer = NULL;
1644
    ec.syntax = NULL;
1645
 
1646
    editorUpdateWindowSize();
1647
    // The SIGWINCH signal is sent to a process when its controlling
1648
    // terminal changes its size (a window change).
1649
    signal(SIGWINCH, editorHandleSigwinch);
1650
    // The SIGCONT signal instructs the operating system to continue
1651
    // (restart) a process previously paused by the SIGSTOP or SIGTSTP
1652
    // signal.
1653
    signal(SIGCONT, editorHandleSigcont);
1654
}
1655
 
1656
void printHelp() {
1657
    printf("Usage: tte [OPTIONS] [FILE]\n\n");
1658
    printf("\nKEYBINDINGS\n-----------\n\n");
1659
    printf("Keybinding\t\tAction\n\n");
1660
    printf("Ctrl-Q    \t\tExit\n");
1661
    printf("Ctrl-S    \t\tSave\n");
1662
    printf("Ctrl-F    \t\tSearch. Esc, enter and arrows to interact once searching\n");
1663
    printf("Ctrl-E    \t\tFlip line upwards\n");
1664
    printf("Ctrl-D    \t\tFlip line downwards\n");
1665
    printf("Ctrl-C    \t\tCopy line\n");
1666
    printf("Ctrl-X    \t\tCut line\n");
1667
    printf("Ctrl-V    \t\tPaste line\n");
1668
    printf("Ctrl-P    \t\tPause tte (type \"fg\" to resume)\n");
1669
 
1670
    printf("\n\nOPTIONS\n-------\n\n");
1671
    printf("Option        \t\tAction\n\n");
1672
    printf("-h | --help   \t\tPrints the help\n");
1673
    printf("-v | --version\t\tPrints the version of tte\n");
1674
 
1675
    printf("\n\nFor now, usage of ISO 8859-1 is recommended.\n");
1676
}
1677
 
1678
// 1 if editor should open, 0 otherwise, -1 if the program should exit
1679
int handleArgs(int argc, char* argv[]) {
1680
    if (argc == 1)
1681
        return 0;
1682
 
1683
    if (argc >= 2) {
1684
        if (strncmp("-h", argv[1], 2) == 0 || strncmp("--help", argv[1], 6) == 0) {
1685
            printHelp();
1686
            return -1;
1687
        } else if(strncmp("-v", argv[1], 2) == 0 || strncmp("--version", argv[1], 9) == 0) {
1688
            printf("tte - version %s\n", TTE_VERSION);
1689
            return -1;
1690
        }
1691
    }
1692
 
1693
    return 1;
1694
}
1695
 
1696
int main(int argc, char* argv[]) {
1697
    initEditor();
1698
    int arg_response = handleArgs(argc, argv);
1699
    if (arg_response == 1)
1700
        editorOpen(argv[1]);
1701
    else if (arg_response == -1)
1702
        return 0;
1703
    enableRawMode();
1704
 
1705
    editorSetStatusMessage(" Ctrl-Q to quit | Ctrl-S to save | (tte -h | --help for more info)");
1706
 
1707
    while (1) {
1708
        editorRefreshScreen();
1709
        editorProcessKeypress();
1710
    }
1711
 
1712
    return 0;
1713
}