#include #include #include #include #include #include #include #include #define ctrl(x) ((x) & 0x1f) int index_to_start = 0; WINDOW* mainwin; WINDOW* statusbar; typedef struct Buffer_struct Buffer; struct Buffer_struct { char* text; char* start; char* end; int size; int gap_size; }; Buffer* new_buffer(int size) { Buffer* buffer = malloc(sizeof(Buffer)); buffer->size = size; buffer->text = malloc(buffer->size); buffer->gap_size = size; buffer->start = buffer->text; buffer->end = buffer->start + buffer->gap_size; return buffer; } void buffer_grow(Buffer* buffer) { int old_size = buffer->size; /* I am making use of the fact that, whenever this function is called, the strlen of the string must equal the size of the buffer (since the gap size is zero) */ int start_offset = buffer->start - buffer->text; buffer->size += 10; buffer->text = realloc(buffer->text,buffer->size); buffer->start = buffer->text + start_offset; buffer->gap_size = 10; for (int i= (old_size - start_offset) - 1; i >= 0; i--) { *(buffer->start + i + buffer->gap_size) = *(buffer->start + i); *(buffer->start + i) = 0; } buffer->end = buffer->start + buffer->gap_size; } void buffer_insert(char ch, Buffer* buffer) { *(buffer->start) = ch; buffer->start++; buffer->gap_size--; if (buffer->gap_size == 0) { buffer_grow(buffer); } } void buffer_delete_front(Buffer* buffer) { if (buffer->start != buffer->text) { buffer->start--; buffer->gap_size++; } } void buffer_delete_back(Buffer* buffer) { if (buffer->end != buffer->text + buffer->size) { buffer->end++; buffer->gap_size++; } } void buffer_right(Buffer* buffer) { if (buffer->end != buffer->text + buffer->size) { char c = *(buffer->end); buffer->start++; buffer->end++; *(buffer->start -1) = c; } } void right_key_handler(Buffer* buffer) { if ((*(buffer->end) != '\n') || (getcury(mainwin) + 1 < getmaxy(mainwin))) { buffer_right(buffer); } } void buffer_left(Buffer* buffer) { if (buffer->start != buffer->text) { char c = *(buffer->start - 1); buffer->start--; buffer->end--; *(buffer->end) = c; *(buffer->start) = 0; } } void left_key_handler(Buffer* buffer) { if ((getcurx(mainwin) > 0) || (getcury(mainwin) > 0)) { buffer_left(buffer); } } void scroll_page_down_handler(Buffer* buffer) { while (*(buffer->text + index_to_start) != '\n') { index_to_start++; } index_to_start++; } void page_down_handler(Buffer* buffer) { if ((getcury(mainwin) + 1) == getmaxy(mainwin)) { scroll_page_down_handler(buffer); } buffer_right(buffer); /* I must advance the cursor at least once, so this hardcoded statement is fine. */ while ((*(buffer->start - 1) != '\n') && (buffer->end != (buffer->text + buffer->size))) { buffer_right(buffer); } /* You would think that I need to call 'buffer_right' once more, to advance the cursor onto the next line. In fact, if you think about it, the place where the cursor (the rectangle) is, is actually the character _after_ the gap. Therefore, by advancing the start to the newline character, the character after the gap (i.e. the cursor) will automatically be moved to the next line. */ } void scroll_page_up_handler(Buffer* buffer) { for (int i=0;i<2;i++) { /* we need to encounter two 'newlines' before we stop */ while((*(buffer->text + index_to_start) != '\n') && (index_to_start >= 0)) { index_to_start--; } } index_to_start++; } void page_up_handler(Buffer* buffer) { while((*(buffer->start - 1) != '\n') && (buffer->text != buffer->start)) { buffer_left(buffer); } buffer_left(buffer); while((*(buffer->start - 1) != '\n') && (buffer->text != buffer->start)) { buffer_left(buffer); } if ((getcury(mainwin) == 0) && (index_to_start > 0)) { scroll_page_up_handler(buffer); } } void keypress_handler(char key, Buffer* buffer) { buffer_insert(key,buffer); if ((getcury(mainwin) + 1) == getmaxy(mainwin)) { scroll_page_down_handler(buffer); } } int is_file(char* path) { struct stat st; if (stat(path, &st) < 0) { return -1; } return S_ISREG(st.st_mode); } void save_text_helper(Buffer* buffer,char* filename) { FILE* file = fopen(filename,"w"); int i = 0; while (i < buffer->size) { if ((buffer->start - buffer->text) == i) { /* If we have encountered the start of the gap */ i += buffer->gap_size; } if (i >= buffer->size) { break; } fputc(*(buffer->text + i),file); i++; } } void init_curses() { initscr(); mainwin = newwin(getmaxy(stdscr)-1,getmaxx(stdscr),0,0); statusbar = newwin(1,getmaxx(stdscr),getmaxy(stdscr)-1,0); wattrset(statusbar,A_REVERSE); mvwhline(statusbar,0,0,' ',getmaxx(statusbar)); mvwprintw(statusbar,0,0,"Welcome to Editor!"); wrefresh(statusbar); noecho(); keypad(stdscr,TRUE); keypad(mainwin,TRUE); raw(); } void end_ncurses() { endwin(); exit(130); } int main(int argc, char** argv) { Buffer* buffer = new_buffer(10); FILE* logfile = fopen("logfile.txt","w"); if (argc == 2) { int num_of_chars = 0; if (access(argv[1],F_OK) == 0) { /* If the file exists */ FILE* file = fopen(argv[1],"r"); char c; while ((c = fgetc(file)) != EOF) { buffer_insert(c,buffer); num_of_chars++; } while (num_of_chars > 0) { buffer_left(buffer); num_of_chars--; } } } init_curses(); int ch; int y, x; while (true) { wclear(mainwin); int i=index_to_start; if ((buffer->start != buffer->text) || (buffer->gap_size != buffer->size)) { /* We don't want to print the string, if the gap starts at the first index of the string, and continues till the end */ while (i < buffer->size) { if ((buffer->start - buffer->text) == i) { /* If we have encountered the start of the gap */ getyx(mainwin,y,x); i += buffer->gap_size; } if (i >= buffer->size) { break; } fprintf(logfile,"At line %d of %d\n",getcury(mainwin),getmaxy(mainwin)); if (*(buffer->text + i) == '\n' && ((getcury(mainwin) + 1) == getmaxy(mainwin))) { fprintf(logfile,"Read newline\n"); break; } waddch(mainwin,*(buffer->text + i)); i++; } wmove(mainwin,y,x); wrefresh(mainwin); } ch = wgetch(mainwin); switch(ch) { case KEY_BACKSPACE: buffer_delete_front(buffer); break; case KEY_DC: buffer_delete_back(buffer); break; case KEY_LEFT: left_key_handler(buffer); break; case KEY_RIGHT: right_key_handler(buffer); break; case 10: /* Enter key */ keypress_handler('\n',buffer); /* Why handle this separately? Because, by default, curses seems to send '\r\n', which is technically two characters. I should probably add some code to deal with this scenario in the 'insert' method (instead of creating an exception here), but that's a problem for another day. */ break; case KEY_NPAGE: page_down_handler(buffer); break; case KEY_PPAGE: page_up_handler(buffer); break; case ctrl(KEY_NPAGE): abort(); case ctrl('s'): if (argc == 2) { save_text_helper(buffer,argv[1]); } else if (argc == 1) { save_text_helper(buffer,NULL); } break; case ctrl('c'): end_ncurses(); break; default: keypress_handler(ch,buffer); } } endwin(); }