-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodible.c
More file actions
1438 lines (1354 loc) · 37.2 KB
/
codible.c
File metadata and controls
1438 lines (1354 loc) · 37.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*** includes ***/
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _GNU_SOURCE
#include <ctype.h> // iscntrl() resides in it
#include <stdio.h>
/* printf(), perror(), sscanf(), snprintf(), FILE,
fopen(), getline(), vsnprintf() reside in it
*/
#include <stdlib.h>
// atexit(), exit(), realloc(), free(), malloc() reside in it
#include <termios.h>
/* struct termios, tcgetattr(), tcsetattr(), ECHO, TCSAFLUSH,
ICANON, ISIG, IXON. IEXTEN, ICRNL, OPOST, BRKINT, INPCK,
ISTRIP, CS8, VMIN, VTIME reside in it
*/
#include <unistd.h>
/* read(), STDIN_FILENO, write(), STDOUT_FILENO
ftruncate(), close() reside in it
*/
#include <errno.h> // errno, EAGAIN reside in it
#include <sys/ioctl.h>
// ioctl(), TIOCGWINSZ, struct winsize reside in it.
#include <string.h>
/* memcpy(), strlen(), strdup(), memmove(), strerror(),
strstr(), memset(), strchr(), strrchr(), strcmp(),
strncmp() reside in it
*/
#include <sys/types.h> // ssize_t resides in it
#include <time.h> // time_t, time() reside in it
#include <stdarg.h> // va_list, va_start(), va_end() reside in it
#include <fcntl.h> // open(), O_RDWR, O_CREAT reside in it
/*** defines ***/
#define CODIBLE_VERSION "0.0.1"
#define CTRL_KEY(k) ((k) & 0x1f)
#define CODIBLE_TAB_STOP 8
// press Ctrl-Q 3 more times to quit the editor without saving
#define CODIBLE_QUIT_TIMES 3
// mapping WASD keys with the arrow constants
enum editorKey {
BACKSPACE = 127,
ARROW_LEFT = 1524,
// arbitrary number that is out of range of char
ARROW_RIGHT, // 1525
ARROW_UP, // 1526
ARROW_DOWN, // 1527
DEL_KEY, // 1528
HOME_KEY, // 1529
END_KEY, // 1530
PAGE_UP, // 1531
PAGE_DOWN // 1532
};
enum editorHighlight {
HL_NORMAL = 0,
HL_COMMENT,
HL_MLCOMMENT,
HL_KEYWORD1,
HL_KEYWORD2,
HL_STRING,
HL_NUMBER,
HL_MATCH
};
#define HL_HIGHLIGHT_NUMBERS (1<<0)
#define HL_HIGHLIGHT_STRINGS (1<<1)
/*** data ***/
struct editorSyntax {
char *filetype;
char **filematch;
char **keywords;
char *singleline_comment_start;
char *multiline_comment_start;
char *multiline_comment_end;
int flags;
};
// editor row
typedef struct erow {
// index of the row in the file at the time it is inserted
int index;
// the size of rendering characters
int size;
int rsize;
// store a line of text as a pointer
char *chars;
// for rendering the non-printable characters;
char *render;
// highlighted array
unsigned char *highlight;
int hl_open_comment;
} erow;
struct editorConfig {
int cx, cy;
int rx;
int rowoff;
int coloff;
int screenrows;
int screencolumns;
int numrows; // number of rows to be displayed
erow *row; // making erow arrays for taking multiple lines
int dirty; // identify if the buffer is changed
char *filename;
char statusmessage[80];
time_t statusmessage_time;
struct editorSyntax *syntax;
struct termios original;
};
struct editorConfig E;
/*** filetypes ***/
char *C_HL_extensions[] = {".c", ".h", ".cpp", NULL};
/* C specific keywords are Null terminated &
common keywords are pipe terminated
*/
char *C_HL_keywords[] = {
"switch", "if", "while", "for", "break", "continue", "return",
"else", "struct", "union", "typedef", "static", "enum", "class",
"case",
"int|", "long|", "double|", "float|", "char|", "unsigned|",
"signed|", "void|", NULL
};
// highlight database;
struct editorSyntax HLDB[] = {
{
"c",
C_HL_extensions,
C_HL_keywords,
"//", "/*", "*/",
HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
},
};
#define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0]))
/*** prototypes ***/
void editorSetStatusMessage(const char *fmt, ...);
void editorRefreshScreen();
char *editorPrompt(char *prompt, void (*callback)(char *, int));
/*** terminal ***/
void die(const char *s) {
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, "\x1b[H", 3);
perror(s); // returns error messages from errno global variable
exit(1);
}
void disableRawMode() {
// error checking for setting up
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.original) == -1) {
die("tcsetattr");
}
}
void enableRawMode() {
// error checking for loading up
if (tcgetattr(STDIN_FILENO, &E.original) == -1) {
die("tcgetattr");
}
atexit(disableRawMode);
struct termios raw = E.original;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
/* IXON used to ignore XOFF and XON & ICRNL used to handle
Carriage Return (CR) and New Line (NL),
// BRKINT used to handle SIGINT,
// INPCK used to handle Parity Check,
// ISTRIP used to handle 8-bit stripping,
// CS8 used to handle character size (CS) to 8 bits per byte
*/
raw.c_oflag &= ~(OPOST);
// OPOST used to handle post-processing output
raw.c_cflag |= ~(CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
// ICANON used for reading input byte by byte &
// ISIG used to ignore SIGINT and SIGTSTP,
// IEXTEN used to disable Ctrl-o and Ctrl-V
raw.c_cc[VMIN] = 0; // control characters for terminal settings
raw.c_cc[VTIME] = 1; // control characters for terminal settings
// error checking for setting up raw mode
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
die("tcsetattr");
}
}
int editorReadKey() {
// read the keypress
int nread;
char c;
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
if (nread == -1 && errno != EAGAIN) {
die("read");
}
}
if (c=='\x1b') {
char seq[3];
/* checking the escape sequence for determining "Escape" or
"Arrow" keys.
*/
if (read(STDIN_FILENO, &seq[0], 1) != 1) {
return '\x1b';
}
if (read(STDIN_FILENO, &seq[1], 1) != 1) {
return '\x1b';
}
if (seq[0] == '[') {
if (seq[1]>='0' && seq[1]<='9') {
// checking that after '[', is it a digit or not
if (read(STDIN_FILENO, &seq[2], 1) != 1) {
return '\x1b';
}
if (seq[2] == '~') {
/* digit 5 for page up, 6 for page down
digit 1 or 7 for Home, 4 or 8 for End
digit 3 for Delete
*/
switch (seq[1]) {
case '1' : return HOME_KEY;
case '3' : return DEL_KEY;
case '4' : return END_KEY;
case '5' : return PAGE_UP;
case '6' : return PAGE_DOWN;
case '7' : return HOME_KEY;
case '8' : return END_KEY;
}
}
}
else {
switch (seq[1]) {
// mapping arrow keys to the Arrow Constants.
case 'A' : return ARROW_UP;
case 'B' : return ARROW_DOWN;
case 'C' : return ARROW_RIGHT;
case 'D' : return ARROW_LEFT;
case 'H' : return HOME_KEY; // handling the Home keys with 'H'
case 'F' : return END_KEY; // handling the end keys with 'F'
}
}
}
else if (seq[0]=='O') {
/* handling Home & End keys escape sequence starting with O
(ooo, not zero)
*/
switch (seq[1]) {
case 'H' : return HOME_KEY;
case 'F' : return END_KEY;
}
}
return '\x1b';
}
else {
return c;
}
}
int getCursorPosition(int *rows, int *columns) {
char buffer[32];
unsigned int i = 0;
if (write(STDOUT_FILENO, "\x1b[6n", 4)!=4) {
return -1;
}
while (i < sizeof(buffer)-1) {
if (read(STDIN_FILENO, &buffer[i], 1)!=1) {
break;
}
if (buffer[i]=='R') {
break;
}
i++;
}
buffer[i] = '\0';
if (buffer[0] != '\x1b' || buffer[1] != '[') {
// making sure it responded with escape sequence.
return -1;
}
if (sscanf(&buffer[2], "%d;%d", rows, columns) != 2) {
/* passing the third character of buffer because of skipping
'\x1b' & '[' characters.
sscanf() will parse the integers separated by the semicolon ;
*/
return -1;
}
return 0;
}
int getWindowSize(int *rows, int *columns) {
struct winsize ws;
// the terminal size will initially stored here.
if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)==-1 || ws.ws_col==0) {
if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12)!=12) {
/* 999C command used for cursor to go forward (right)
999B command used for cursor to go downward (down);
these two commands are used for finding the bottom-right
position of the cursor.
*/
return -1;
}
return getCursorPosition(rows, columns);
}
else {
*columns = ws.ws_col;
*rows = ws.ws_row;
return 0;
}
}
/*** syntax highlighting ***/
int is_separator (int c) {
/* if the string doesn't contain the characters
provided in the strchr(), the function will
return NULL
*/
return (isspace(c) || c=='\0' ||
strchr(",.()+-/*=~%<>[];", c) != NULL);
}
void editorUpdateSyntax(erow *row) {
row->highlight = realloc(row->highlight, row->rsize);
memset(row->highlight, HL_NORMAL, row->rsize);
if (E.syntax == NULL) {
return;
}
char **keywords = E.syntax->keywords;
char *scs = E.syntax->singleline_comment_start;
char *mcs = E.syntax->multiline_comment_start;
char *mce = E.syntax->multiline_comment_end;
int scs_len = scs ? strlen(scs) : 0;
int mcs_len = mcs ? strlen(mcs) : 0;
int mce_len = mce ? strlen(mce) : 0;
// considering the starting of a line as a separator
int i = 0, prev_separator = 1, in_string = 0;
int in_comment = (row->index > 0 && E.row[row->index - 1].hl_open_comment);
while (i < row->rsize) {
char c = row->render[i];
unsigned char prev_highlight = (i>0) ? row->highlight[i-1] :
HL_NORMAL;
if (scs_len && !in_string && !in_comment) {
/* using strncmp() to check if this character
is the start of a single line comment
*/
if (!strncmp(&row->render[i], scs, scs_len)) {
memset(&row->highlight[i], HL_COMMENT, row->rsize-i);
break;
}
}
if (mcs_len && mce_len && !in_string) {
if (in_comment) {
row->highlight[i] = HL_MLCOMMENT;
if (!strncmp(&row->render[i], mce, mce_len)) {
// checking if we are at the end of a multi line comment
memset(&row->highlight[i], HL_MLCOMMENT, mce_len);
i = i+mce_len;
in_comment = 0;
prev_separator = 1;
continue;
}
else {
i++;
continue;
}
}
else if (!strncmp(&row->render[i], mcs, mcs_len)) {
// checking if we are at the beginning of a multi line comment
memset(&row->highlight[i], HL_MLCOMMENT, mcs_len);
i = i+mcs_len;
in_comment = 1; // setting in_comment to TRUE
continue;
}
}
if (E.syntax->flags && HL_HIGHLIGHT_STRINGS) {
if (in_string) {
row->highlight[i] = HL_STRING;
if (c == '\\' && i+1 < row->rsize) {
row->highlight[i+1] = HL_STRING;
i = i + 2;
continue;
}
if (c == in_string) {
in_string = 0;
}
i++;
prev_separator = 1;
continue;
}
else {
if (c=='"' || c=='\'') {
in_string = c;
row->highlight[i] = HL_STRING;
i++;
continue;
}
}
}
if (E.syntax->flags && HL_HIGHLIGHT_NUMBERS) {
if ((isdigit(c) &&
(prev_separator||prev_highlight==HL_NUMBER))
|| (c == '.' && prev_highlight == HL_NUMBER)) {
row->highlight[i] = HL_NUMBER;
i++;
prev_separator = 0;
continue;
}
}
if (prev_separator) {
int j;
for (j=0; keywords[j]; j++) {
int klen = strlen(keywords[j]);
int kw2 = keywords[j][klen-1] == '|';
if (kw2) {
klen--;
}
if (!strncmp(&row->render[i], keywords[j], klen) &&
is_separator(row->render[i+klen])) {
memset(&row->highlight[i], kw2 ? HL_KEYWORD2 :
HL_KEYWORD1, klen);
i += klen;
break;
}
}
if (keywords[j] != NULL) {
prev_separator = 0;
continue;
}
}
prev_separator = is_separator(c);
i++;
}
int changed = (row->hl_open_comment != in_comment);
// Setting whether the row ended as an unclosed multi line comment or not
row->hl_open_comment = in_comment;
if (changed && row->index + 1 < E.numrows) {
editorUpdateSyntax(&E.row[row->index + 1]);
}
}
int editorSyntaxToColor(int highlight) {
switch (highlight) {
case HL_COMMENT:
case HL_MLCOMMENT:
// single line comments & multi line comments coloring with cyan
return 36;
case HL_KEYWORD1:
// keywords coloring with yellow
return 33;
case HL_KEYWORD2:
// keywords coloring with green
return 32;
case HL_STRING:
// strings coloring with magenta
return 35;
case HL_NUMBER:
// digits coloring with red
return 31;
case HL_MATCH:
// matched string coloring with blue
return 34;
default:
return 37;
}
}
void editorSelectSyntaxHighlight() {
E.syntax = NULL;
if (E.filename == NULL) {
return;
}
/* strrchr() returns a pointer to the last occurance of
a character in a string
*/
char *ext = strrchr(E.filename, '.');
for(unsigned int j=0; j<HLDB_ENTRIES; j++) {
struct editorSyntax *s = &HLDB[j];
unsigned int i = 0;
while(s->filematch[i]) {
int is_ext = (s->filematch[i][0] == '.');
if ((is_ext && ext && !strcmp(ext, s->filematch[i])) ||
(!is_ext && strstr(E.filename, s->filematch[i]))) {
E.syntax = s;
for (int filerow = 0; filerow < E.numrows; filerow++) {
editorUpdateSyntax(&E.row[filerow]);
}
return;
}
i++;
}
}
}
/*** row operations ***/
int editorRowCxToRx (erow *row, int cx) {
int rx = 0;
for (int j=0; j<cx; j++) {
if (row->chars[j] == '\t') {
rx = rx + (CODIBLE_TAB_STOP - 1) - (rx % CODIBLE_TAB_STOP);
/* (rx % CODIBLE_TAB_STOP) = how many columns to the right
of the last tab stop
(CODIBLE_TAB_STOP - 1) = how many columns to the left
of the next tab stop
Added these with rx to get to the next tab stop
*/
}
rx++; // gets on the next tab stop
}
return rx;
}
int editorRowRxToCx(erow *row, int rx) {
int current_rx=0,cx;
for (cx = 0; cx < row->size; cx++) {
if (row->chars[cx] == '\t') {
current_rx += (CODIBLE_TAB_STOP-1)-(current_rx%CODIBLE_TAB_STOP);
}
current_rx++;
if (current_rx > rx) {
return cx;
}
}
return cx;
}
void editorUpdateRow(erow *row) {
int tabs = 0;
for (int j=0; j<row->size; j++) {
// counting tabs found in chars of the row
if (row->chars[j] == '\t') {
tabs++;
}
}
free(row->render);
/* the maximum number of characters needed
for a tab is 8
*/
row->render = malloc(row->size + tabs*(CODIBLE_TAB_STOP-1) + 1);
int index = 0;
for (int j=0; j<row->size; j++) {
/* if a tab is found, replace it with a space
until the tab stop location
*/
if (row->chars[j] == '\t') {
row->render[index++] = ' ';
// tab stop location can be divided by 8
while (index%CODIBLE_TAB_STOP != 0) {
row->render[index++] = ' ';
}
}
else {
row->render[index++] = row->chars[j];
}
}
row->render[index] = '\0';
row->rsize = index;
editorUpdateSyntax(row);
}
void editorInsertRow (int at, char *s, size_t len) {
if (at<0 || at>E.numrows) {
// validating the index
return;
}
E.row = realloc(E.row, sizeof(erow)*(E.numrows + 1));
memmove(&E.row[at+1], &E.row[at], sizeof(erow)*(E.numrows-at));
for (int j=at+1; j<=E.numrows; j++) {
/* updating index of the row because of the insertion
of the new row
*/
E.row[j].index++;
}
E.row[at].index = at;
E.row[at].size = len;
E.row[at].chars = malloc(len + 1);
memcpy(E.row[at].chars, s, len);
E.row[at].chars[len] = '\0';
/* Initializing the rendering size is 0
and the rendering string is NULL
*/
E.row[at].rsize = 0;
E.row[at].render = NULL;
E.row[at].highlight = NULL;
E.row[at].hl_open_comment = 0;
editorUpdateRow(&E.row[at]);
E.numrows++;
E.dirty++;
}
void editorFreeRow(erow *row) {
free(row->render);
free(row->chars);
free(row->highlight);
}
void editorDelRow(int at) {
if (at<0 || at>=E.numrows) {
return;
}
editorFreeRow(&E.row[at]);
memmove(&E.row[at], &E.row[at+1], sizeof(erow)*(E.numrows-at-1));
for (int j=at; j<E.numrows-1; j++) {
/* Updating index of the row because of the deletion
of an existing row
*/
E.row[j].index--;
}
E.numrows--;
E.dirty++;
}
void editorRowInsertChar(erow *row, int at, int c) {
if (at<0 || at>row->size) {
at = row->size;
}
row->chars = realloc(row->chars, row->size + 2);
/* memmove is safe to use than memcpy when source &
destination of an array overlaps with each other
*/
memmove(&row->chars[at+1], &row->chars[at], row->size - at+1);
row->size++;
row->chars[at] = c;
// updating render & rsize
editorUpdateRow(row);
E.dirty++;
}
void editorRowAppendString(erow *row, char *s, size_t len) {
row->chars = realloc(row->chars, row->size+len+1);
// appending the string in the row
memcpy(&row->chars[row->size], s, len);
// updating the size
row->size += len;
row->chars[row->size] = '\0';
editorUpdateRow(row);
E.dirty++;
}
void editorRowDelChar(erow *row, int at) {
if (at < 0 || at >= row->size) {
return;
}
// overwrite the deleted character with the next character
memmove(&row->chars[at], &row->chars[at+1], row->size - at);
// then decrement the size of the row
row->size--;
editorUpdateRow(row);
E.dirty++;
}
/*** editor operations ***/
void editorInsertChar (int c) {
if (E.cy == E.numrows) {
/* appending a blank row after the end of a line to take
the character from the user
*/
editorInsertRow(E.numrows,"", 0);
}
editorRowInsertChar(&E.row[E.cy], E.cx, c);
/* after taking the character, moving forward the cursor
to take the next character right after the previous one
*/
E.cx++;
}
void editorInsertNewLine() {
if (E.cx == 0) {
/* if the cursor at the beginning of a line, then
pressing Enter will create a blank line before
that line
*/
editorInsertRow(E.cy, "", 0);
}
else {
erow *row = &E.row[E.cy];
/* passing the characters of the right of cursor
to the new line
*/
editorInsertRow(E.cy+1, &row->chars[E.cx], row->size - E.cx);
row = &E.row[E.cy];
row->size = E.cx;
row->chars[row->size] = '\0';
editorUpdateRow(row);
}
E.cy++;
E.cx=0;
}
void editorDelChar() {
if (E.cy == E.numrows) {
/* return immediately if the cursor gets to
the end of the file
*/
return;
}
if (E.cx == 0 && E.cy == 0) {
/* return immediately if the cursor is at the
beginning of the first line
*/
return;
}
erow *row = &E.row[E.cy];
if (E.cx > 0) {
editorRowDelChar(row, E.cx - 1);
E.cx--;
}
else {
/* setting the cursor at the end of the previous row
before appending
*/
E.cx = E.row[E.cy-1].size;
editorRowAppendString(&E.row[E.cy-1], row->chars, row->size);
editorDelRow(E.cy);
E.cy--;
}
}
/*** file i/o ***/
char *editorRowsToString(int *buflen) {
int totallen=0;
// getting the total size to be copied
for (int j=0; j<E.numrows; j++) {
totallen = totallen + E.row[j].size + 1;
}
*buflen = totallen;
char *buf = malloc(totallen);
char *p = buf;
for (int j=0; j<E.numrows; j++) {
memcpy(p, E.row[j].chars, E.row[j].size);
p = p + E.row[j].size;
// appending the new line character after each row
*p = '\n';
p++;
}
return buf;
// the caller function will free the buf
}
void editorOpen(char *filename) {
free(E.filename);
E.filename = strdup(filename);
editorSelectSyntaxHighlight();
// taking a filename & opens it for reading by fopen()
FILE *fp = fopen(filename, "r");
if (!fp) {
die("fopen");
}
char *line = NULL;
size_t linecap = 0;
ssize_t len;
/* getting the line & len from getline() instead of hardcoded
getline returns the length of the line it reads
or -1 when it is the end of the file i.e. no more lines
to read
*/
while ((len = getline(&line, &linecap, fp)) != -1) {
while (len>0 && (line[len-1]=='\n' || line[len-1]=='\r')) {
/* stripping the newline '\n' & carriage return '\r'
from the line we consider. It's a one liner. so it will
be redundant to include newline or carriage return
*/
len--;
}
editorInsertRow(E.numrows, line, len);
}
free(line);
fclose(fp);
E.dirty = 0;
}
void editorSave() {
if (E.filename == NULL) {
E.filename = editorPrompt("Save as: %s (ESC to cancel)", NULL);
if (E.filename == NULL) {
editorSetStatusMessage("Save aborted");
return;
}
editorSelectSyntaxHighlight();
}
int len;
char *buf = editorRowsToString(&len);
/* opening E.filename
if it's new, then create a file. that's why O_CREAT used
if it exists, then open the file for read and write.
Thats's why O_RDWR is used.
OCREAT's permission here is 0644.
The 0644 permits that the owner can read and write
whenever they want
otherwise the user can only read
*/
int fd = open(E.filename, O_RDWR | O_CREAT, 0644);
if (fd != -1) {
if (ftruncate(fd, len) != -1) {
if (write(fd, buf, len) == len) {
close(fd);
free(buf);
E.dirty = 0;
editorSetStatusMessage("%d bytes written to disk", len);
return;
}
}
close(fd);
}
free(buf);
editorSetStatusMessage("Can't save !! I/O error: %s",
strerror(errno));
}
/*** find ***/
void editorFindCallBack(char *query, int key) {
/* last match is the index in the row that have
searched previous query
*/
static int last_match = -1;
// direction 1 means forward search,
// diredtion 2 means backward search
static int direction = 1;
static int saved_highlight_line;
static char *saved_highlight = NULL;
if (saved_highlight) {
memcpy(E.row[saved_highlight_line].highlight, saved_highlight,
E.row[saved_highlight_line].rsize);
free(saved_highlight);
saved_highlight = NULL;
}
/* stopping incremental search if the user pressed
ENTER or ESC key
*/
if (key == '\r' || key == '\x1b') {
/* resetting last_match and direction to get ready
for next search operation
*/
last_match = -1;
direction = 1;
return;
}
else if (key == ARROW_RIGHT || key == ARROW_DOWN) {
direction = 1;
}
else if (key == ARROW_LEFT || key == ARROW_UP) {
direction = -1;
}
else {
last_match = -1;
direction = 1;
}
if (last_match == -1) {
direction = 1;
}
// current is the current row we are searching
int current = last_match;
for (int i=0; i<E.numrows; i++) {
current = current + direction;
if (current == -1) {
current = E.numrows - 1;
}
else if (current == E.numrows) {
current = 0;
}
erow *row = &E.row[current];
// using strstr() to find if query is a substring of the current row
char *match = strstr(row->render, query);
if (match) {
last_match = current;
E.cy = current;
E.cx = editorRowRxToCx(row, match-row->render);
/* the matching line will always be on top by setting
the rowoff very bottom of the file
*/
E.rowoff = E.numrows;
saved_highlight_line = current;
saved_highlight = malloc(row->rsize);
memcpy(saved_highlight, row->highlight, row->rsize);
memset(&row->highlight[match - row->render],
HL_MATCH, strlen(query));
break;
}
}
}
void editorFind() {
// saving the cursors position before search
int saved_cx = E.cx;
int saved_cy = E.cy;
int saved_coloff = E.coloff;
int saved_rowoff = E.rowoff;
char *query = editorPrompt("Search: %s (Use ESC/Arrows/ENTER)", editorFindCallBack);
if (query) {
free(query);
}
else {
// restoring the cursor position before search
E.cx = saved_cx;
E.cy = saved_cy;
E.coloff = saved_coloff;
E.rowoff = saved_rowoff;
}
}
/*** append buffer ***/
struct abuf {
char *b;
int len;
};
#define ABUF_INIT {NULL, 0}
/* initially pointing to the empty buffer
worked as a constructor
*/
void abAppend(struct abuf *ab, const char *s, int len) {
char *new = realloc(ab->b, ab->len + len);
if (new == NULL) {
return;
}
memcpy(&new[ab->len],s,len);
// copy the string s at the end of the buffer
// updating pointer and length
ab->b = new;
ab->len += len;
}
void abFree(struct abuf *ab) {
free(ab->b);
}
/*** output ***/
void editorScroll() {
E.rx = 0;
if (E.cy < E.numrows) {
E.rx = editorRowCxToRx(&E.row[E.cy], E.cx);
}
if (E.cy < E.rowoff) {
// checking if the cursor is within the visible window
E.rowoff = E.cy;
}
if (E.cy >= E.rowoff + E.screenrows) {
/* if the cursor is out of visible window, then
scroll & show the remaining part within the
visible window
*/
E.rowoff = E.cy - E.screenrows + 1;
}
if (E.rx < E.coloff) {
// checking if the cursor is within the visible window
E.coloff = E.rx;
}
if (E.rx >= E.coloff + E.screencolumns) {
/* if the cursor is out of visible window, then
scroll & show the remaining part within the
visible window
*/
E.coloff = E.rx - E.screencolumns + 1;
}
}
void editorDrawRows(struct abuf *ab) {
/* putting '~' in front of each row which is not part of the
text being edited
*/
int y;
for (y=0; y<E.screenrows; y++) {
int filerow = y+E.rowoff;
if (filerow >= E.numrows) {
// Displaying the welcome message when no file is called
if (E.numrows==0 && y==E.screenrows/3) {
char welcome[80];
int welcomelen = snprintf(welcome,sizeof(welcome),
"Codible -- version %s", CODIBLE_VERSION);
// this will show the welcome message at 1/3 of the screen
if (welcomelen > E.screencolumns) {
welcomelen = E.screencolumns;
}
int padding = (E.screencolumns - welcomelen)/2;
// centering the welcome message
if (padding != 0) {
abAppend(ab, "~", 1);
// first character is the ~
padding--;
}
while (padding--) {
/* next spaces are filled with " " (spaces)
until the message character starts