diff --git a/utils/editor.cpp b/utils/editor.cpp index 25c9e1cd..69b30126 100644 --- a/utils/editor.cpp +++ b/utils/editor.cpp @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2013. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free @@ -16,457 +16,2253 @@ this program. If not, see . editor.cpp - A simple and hacked together text editor. + A simple text editor. *******************************************************************************/ +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + #include #include -#include -#include -#include -#include -#include +#include + +#include +#include #include #include +#include +#include +#include +#include +#include #include +#include -const int MODE_QUIT = 1; -const int MODE_TEXT = 2; -const int MODE_CONFIRM_QUIT = 3; -const int MODE_SAVE = 4; -const int MODE_LOAD = 5; - -const unsigned WIDTH = 80; -const unsigned HEIGHT = 24; -char buffers[HEIGHT+1][WIDTH+1]; - -unsigned cursorx = 0; -unsigned cursory = 0; -unsigned numlines = 1; - -char filename[256]; - -bool bufferchanged = false; - -void clearbuffers() +struct terminal_state { - for ( unsigned y = 0; y < HEIGHT + 1; y++ ) + int width; + int height; + int cursor_x; + int cursor_y; + uint8_t color; + uint16_t* data; +}; + +char* strdup_safe(const char* str) +{ + return strdup(str ? str : ""); +} + +size_t displayed_string_length(const char* str, size_t len, size_t tabsize) +{ + size_t ret_len = 0; + for ( size_t i = 0; i < len; i++ ) + if ( str[i] == '\t' ) + do ret_len++; + while ( ret_len % tabsize ); + else + ret_len++; + return ret_len; +} + +struct display_char +{ + char character; + uint8_t color; +}; + +struct display_char* expand_tabs(const char* str, size_t len, uint8_t* colors, + size_t colors_len, size_t* ret_len_ptr, + size_t tabsize) +{ + size_t ret_len = displayed_string_length(str, len, tabsize); + struct display_char* ret = new struct display_char[ret_len+1]; + for ( size_t i = 0, j = 0; i < len; i++ ) { - for ( unsigned x = 0; x < WIDTH + 1; x++ ) { buffers[y][x] = 0; } + uint8_t color = i < colors_len ? colors[i] : 7; + if ( str[i] == '\t' ) + do ret[j++] = { ' ', color}; + while ( j % tabsize ); + else + ret[j++] = { str[i], color }; } - - bufferchanged = true; + ret[ret_len] = { '\0', 0 }; + if ( ret_len_ptr ) + *ret_len_ptr = ret_len; + return ret; } -void cursorto(unsigned x, unsigned y) +bool is_row_column_lt(size_t ra, size_t ca, size_t rb, size_t cb) { - printf("\e[%u;%uH", y+1+1, x+1); - fflush(stdout); + return ra < rb || (ra == rb && ca < cb); } -char* readline(int fd) +bool is_row_column_le(size_t ra, size_t ca, size_t rb, size_t cb) { - unsigned oldtermmode; - if ( gettermmode(fd, &oldtermmode) ) { return NULL; } + return (ra == rb && ca == cb) || is_row_column_lt(ra, ca, rb, cb); +} - unsigned termmode = TERMMODE_UNICODE - | TERMMODE_SIGNAL - | TERMMODE_UTF8 - | TERMMODE_LINEBUFFER - | TERMMODE_ECHO; - if ( settermmode(fd, termmode) ) { return NULL; } +void row_column_smallest(size_t ra, size_t ca, size_t rb, size_t cb, + size_t* row, size_t* column) +{ + if ( is_row_column_lt(ra, ca, rb, cb) ) + *row = ra, *column = ca; + else + *row = rb, *column = cb; +} - size_t lineused = 0; - size_t linelength = 32UL; - char* line = new char[linelength + 1]; - line[0] = '\0'; +void row_column_biggest(size_t ra, size_t ca, size_t rb, size_t cb, + size_t* row, size_t* column) +{ + if ( is_row_column_lt(ra, ca, rb, cb) ) + *row = rb, *column = cb; + else + *row = ra, *column = ca; +} - while ( true ) +void update_terminal_color(FILE* fp, uint8_t desired_color, + struct terminal_state* current) +{ + uint8_t desired_fg = (desired_color >> 0) % 16; + uint8_t desired_bg = (desired_color >> 4) % 16; + uint8_t current_fg = (current->color >> 0) % 16; + uint8_t current_bg = (current->color >> 4) % 16; + if ( desired_fg != current_fg ) + fprintf(fp, "\e[%im", desired_fg + (desired_fg < 8 ? 30 : 90-8) ); + if ( desired_bg != current_bg ) + fprintf(fp, "\e[%im", desired_bg + (desired_bg < 8 ? 40 : 100-8) ); + current->color = desired_color; +} + +void update_terminal_cursor(FILE* fp, int x, int y, + struct terminal_state* current) +{ + if ( current->cursor_x == x && current->cursor_y == y ) + return; + fprintf(fp, "\e[%i;%iH", y + 1, x + 1); + current->cursor_x = x; + current->cursor_y = y; +} + +void update_terminal_entry(FILE* fp, uint16_t entry, int x, int y, + struct terminal_state* current) +{ + size_t index = y * current->width + x; + uint16_t current_entry = current->data[index]; + if ( entry == current_entry ) + return; + update_terminal_cursor(fp, x, y, current); + uint8_t color = entry >> 8; + update_terminal_color(fp, color, current); + fputc((char) (entry & 0xFF), fp); + current->data[index] = entry; + if ( ++current->cursor_x == current->width ) { - char c; - ssize_t numbytes = read(fd, &c, sizeof(c)); - if ( numbytes < 0 ) { delete[] line; line = NULL; break; } - if ( !numbytes ) { break; } - if ( c == '\n' ) { break; } + current->cursor_x = 0; + current->cursor_y++; + } +} - if ( lineused == linelength ) +void update_terminal(FILE* fp, + struct terminal_state* desired, + struct terminal_state* current) +{ + // TODO: If terminal size has changed! + for ( int y = 0; y < current->height; y++ ) + { + for ( int x = 0; x < current->width; x++ ) { - size_t newlinelength = 2 * linelength; - char* newline = new char[newlinelength]; - if ( !newline ) { delete[] line; line = NULL; break; } - memcpy(newline, line, lineused * sizeof(*line)); - delete[] line; - line = newline; - linelength = newlinelength; + size_t index = y * desired->width + x; + uint16_t desired_entry = desired->data[index]; + update_terminal_entry(fp, desired_entry, x, y, current); } - - line[lineused++] = c; - line[lineused] = '\0'; } - - if ( settermmode(fd, oldtermmode) ) { delete[] line; line = NULL; } - return line; + update_terminal_cursor(fp, desired->cursor_x, desired->cursor_y, current); + update_terminal_color(fp, desired->color, current); } -void drawtextmode() +void make_terminal_state(FILE* fp, struct terminal_state* state) { - const char* printfilename = ( strlen(filename) > 0 ) ? filename : "New Buffer"; - printf("\e[30m\e[47m\e[2J\e[H"); - printf("Text Editor\t\tfile: %s\n", printfilename); - printf("\e[37m\e[40m\e[0J"); - for ( unsigned y = 0; y < HEIGHT; y++ ) - { - printf("%s", buffers[y]); - if ( y < HEIGHT-1 ) { printf("\n"); } - } + memset(state, 0, sizeof(*state)); - fflush(stdout); - cursorto(cursorx, cursory); + struct winsize terminal_size; + tcgetwinsize(fileno(fp), &terminal_size); + state->width = (int) terminal_size.ws_col; + state->height = (int) terminal_size.ws_row; + size_t data_size = sizeof(uint16_t) * state->width * state->height; + state->data = (uint16_t*) malloc(data_size); + for ( size_t i = 0; i < data_size / sizeof(uint16_t); i++ ) + state->data[i] = 0x0000 | ' '; } -unsigned textmode() +void free_terminal_state(struct terminal_state* state) { - drawtextmode(); + free(state->data); +} - bool ctrl = false; - unsigned dectrlmode = 0; +void reset_terminal_state(FILE* fp, struct terminal_state* state) +{ + fprintf(fp, "\e[H"); + fprintf(fp, "\e[m"); + fprintf(fp, "\e[2J"); + state->cursor_x = 0; + state->cursor_y = 0; - int oldcursorx = -1; - int oldcursory = -1; - while ( true ) + update_terminal_color(fp, 0x07, state); + for ( int y = 0; y < state->height; y++ ) + for ( int x = 0; x < state->width; x++ ) + update_terminal_entry(fp, 0x0700 | ' ', x, y, state); + + update_terminal_cursor(fp, 0, 0, state); +} + +struct line +{ + char* data; + size_t used; + size_t length; +}; + +struct color_line +{ + uint8_t* data; + size_t length; +}; + +enum editor_mode +{ + MODE_QUIT, + MODE_EDIT, + MODE_LOAD, + MODE_SAVE, + MODE_ASK_QUIT, + MODE_GOTO_LINE, + MODE_COMMAND, +}; + +struct editor +{ + char* current_file_name; + struct line* lines; + size_t lines_used; + size_t lines_length; + struct color_line* color_lines; + size_t color_lines_used; + size_t color_lines_length; + size_t cursor_column; + size_t cursor_row; + size_t select_column; + size_t select_row; + size_t viewport_width; + size_t viewport_height; + size_t page_x_offset; + size_t page_y_offset; + char* modal; + size_t modal_used; + size_t modal_length; + size_t modal_cursor; + char* clipboard; + size_t tabsize; + size_t margin; + enum editor_mode mode; + bool control; + bool shift; + bool lshift; + bool rshift; + bool dirty; + bool modal_error; + bool highlight_source; +}; + +void initialize_editor(struct editor* editor) +{ + editor->current_file_name = NULL; + editor->lines = NULL; + editor->lines_used = 0; + editor->lines_length = 0; + editor->cursor_column = 0; + editor->cursor_row = 0; + editor->select_column = 0; + editor->select_row = 0; + editor->page_x_offset = 0; + editor->page_y_offset = 0; + editor->modal = NULL; + editor->modal_used = 0; + editor->modal_length = 0; + editor->modal_cursor = 0; + editor->clipboard = NULL; + editor->tabsize = 8; + editor->margin = SIZE_MAX; + editor->mode = MODE_EDIT; + editor->control = false; + editor->shift = false; + editor->lshift = false; + editor->rshift = false; + editor->dirty = false; + editor->modal_error = false; + editor->highlight_source = false; + + editor->lines_used = 1; + editor->lines_length = 1; + editor->lines = new struct line[editor->lines_length]; + editor->lines[0].data = NULL; + editor->lines[0].used = 0; + editor->lines[0].length = 0; + + editor->color_lines_used = 0; + editor->color_lines_length = 0; + editor->color_lines = NULL; +} + +bool editor_has_selection(struct editor* editor) +{ + return !(editor->cursor_row == editor->select_row && + editor->cursor_column == editor->select_column); +} + +void render_editor(struct editor* editor, struct terminal_state* state) +{ + if ( state->height < 1 ) + return; + + // Create the header title bar. + for ( int x = 0; x < state->width; x++ ) + state->data[0 * state->width + x] = 0x7000; + + // Render the name of the program. + const char* header_start = editor->dirty ? " editor *" + : " editor "; + size_t header_start_len = strlen(header_start); + + for ( size_t i = 0; i < header_start_len; i++ ) + if ( i < (size_t) state->width) + state->data[i] |= (unsigned char) header_start[i]; + + // Render the name of the currently open file. + const char* file_name = editor->current_file_name; + if ( !file_name ) + file_name = "New File"; + size_t file_name_len = strlen(file_name); + + for ( size_t i = 0; i < file_name_len; i++ ) + if ( header_start_len+i < (size_t) state->width) + state->data[header_start_len+i] |= (unsigned char) file_name[i]; + + // Calculate the dimensions of the viewport. + size_t viewport_top = 1; + editor->viewport_width = (size_t) state->width; + editor->viewport_height = (size_t) state->height - viewport_top; + if ( !editor->viewport_height ) + return; + + // Decide which page of the file to render and the cursor position on it. + struct line* current_line = &editor->lines[editor->cursor_row]; + size_t cursor_x = displayed_string_length(current_line->data, + editor->cursor_column, + editor->tabsize); + size_t cursor_y = editor->cursor_row; + struct line* select_line = &editor->lines[editor->select_row]; + size_t select_x = displayed_string_length(select_line->data, + editor->select_column, + editor->tabsize); + size_t select_y = editor->select_row; + + size_t page_x_offset = editor->page_x_offset; + size_t page_y_offset = editor->page_y_offset; + + bool has_selection = !(editor->cursor_row == editor->select_row && + editor->cursor_column == editor->select_column); + size_t viewport_select_x = select_x - page_x_offset; + size_t viewport_select_y = select_y - page_y_offset; + + // Render this page of text. + for ( size_t y = 0; y < editor->viewport_height; y++ ) { - if ( oldcursorx != (int) cursorx || oldcursory != (int) cursory ) + size_t line_index = page_y_offset + y; + uint16_t* data_line = state->data + (viewport_top + y) * state->width; + struct line* line = line_index < editor->lines_used ? + &editor->lines[line_index] : NULL; + struct color_line* color_line = line_index < editor->color_lines_used ? + &editor->color_lines[line_index] : NULL; + size_t expanded_len; + struct display_char* expanded + = expand_tabs(line ? line->data : "", + line ? line->used : 0, + color_line ? color_line->data : NULL, + color_line ? color_line->length : 0, + &expanded_len, + editor->tabsize); + const struct display_char* chars = expanded; + size_t chars_length = expanded_len; + if ( chars_length < page_x_offset ) + chars = NULL, chars_length = 0; + else + chars += page_x_offset, chars_length -= page_x_offset; + for ( size_t x = 0; x < editor->viewport_width; x++ ) { - cursorto(cursorx, cursory); - oldcursorx = cursorx; - oldcursory = cursory; + size_t column_index = page_x_offset + x; + bool selected = (is_row_column_lt(cursor_y, cursor_x, select_y, select_x) && + is_row_column_le(cursor_y, cursor_x, line_index, column_index) && + is_row_column_lt(line_index, column_index, select_y, select_x)) || + (is_row_column_lt(select_y, select_x, cursor_y, cursor_x) && + is_row_column_le(select_y, select_x, line_index, column_index) && + is_row_column_lt(line_index, column_index, cursor_y, cursor_x)); + bool at_margin = column_index == editor->margin; + bool is_blank = chars_length <= x; + char c = is_blank ? ' ' : chars[x].character; + uint8_t color = (is_blank ? 7 : chars[x].color); + data_line[x] = selected && is_blank && at_margin ? 0x4100 | '|' : + selected ? 0x4700 | (unsigned char) c : + is_blank && at_margin ? 0x0100 | '|' : + color << 8 | (unsigned char) c; } + delete[] expanded; + } - unsigned termmode = TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_SIGNAL; - if ( settermmode(0, termmode) ) { error(1, errno, "settermmode"); } + // Set the rest of the terminal state. + state->cursor_x = has_selection ? + editor->viewport_width : viewport_select_x; + state->cursor_y = has_selection ? + editor->viewport_height : viewport_select_y + viewport_top; + state->color = 0x07; - uint32_t codepoint = 0; - ssize_t numbytes = read(0, &codepoint, sizeof(codepoint)); - if ( !numbytes ) { break; } - if ( numbytes < 0 ) { error(1, errno, "read stdin"); } - if ( numbytes < (ssize_t) sizeof(codepoint) ) { - printf("unexpectedly got %zi bytes\n", numbytes); - printf("bytes: %x\n", codepoint); + if ( editor->mode == MODE_EDIT ) + return; + const char* msg = ""; + if ( editor->mode == MODE_SAVE ) + msg = "File Name to Write: "; + if ( editor->mode == MODE_LOAD ) + msg = "File Name to Read: ";; + if ( editor->mode == MODE_ASK_QUIT ) + msg = "Exit without saving changes? (Y/N): "; + if ( editor->mode == MODE_GOTO_LINE ) + msg = "Go to line: "; + if ( editor->mode == MODE_COMMAND ) + msg = "Enter miscellaneous command: "; + size_t msg_len = strlen(msg); -fprintf(stderr, "bad stdin data\n"); exit(1); } - if ( !codepoint ) { continue; } - int kbkey = KBKEY_DECODE(codepoint); - if ( kbkey ) + uint16_t* data_line = state->data + (state->height - 1) * state->width; + for ( size_t i = 0; i < msg_len; i++ ) + if ( i < (size_t) state->width) + data_line[i] = 0x7000 | (unsigned char) msg[i]; + + if ( (size_t) state->width <= msg_len ) + return; + + size_t modal_viewport_width = state->width - msg_len; + size_t modal_viewport_cursor = editor->modal_cursor % modal_viewport_width; + size_t modal_viewport_page = editor->modal_cursor / modal_viewport_width; + size_t modal_viewport_offset = modal_viewport_page * modal_viewport_width; + + uint16_t* modal_viewport_data = data_line + msg_len; + + const char* modal_chars = editor->modal; + size_t modal_chars_length = editor->modal_used; + if ( modal_chars_length < modal_viewport_offset ) + modal_chars = NULL, + modal_chars_length = 0; + else + modal_chars += modal_viewport_offset, + modal_chars_length -= modal_viewport_offset; + + for ( size_t x = 0; x < modal_viewport_width; x++ ) + { + char c = x < modal_chars_length ? modal_chars[x] : ' '; + uint16_t color = editor->modal_error ? 0x1700 : 0x7000; + uint16_t tab_color = editor->modal_error ? 0x1200 : 0x7100; + if ( c == '\t' ) + modal_viewport_data[x] = tab_color | (unsigned char) '>'; + else + modal_viewport_data[x] = color | (unsigned char) c; + } + + state->cursor_x = msg_len + modal_viewport_cursor; + state->cursor_y = state->height - 1; + state->color = 0x70; +} + +size_t recognize_constant(const char* string, size_t string_length) +{ + bool hex = false; + size_t result = 0; + if ( result < string_length && string[result] == '0' ) + { + result++; + if ( result < string_length && (string[result] == 'x' || + string[result] == 'X') ) { - if ( kbkey == -KBKEY_LCTRL && dectrlmode ) { return dectrlmode; } - int abskbkey = (kbkey < 0) ? -kbkey : kbkey; - if ( abskbkey == KBKEY_LCTRL ) { ctrl = (0 < kbkey); continue; } - switch ( kbkey ) - { - case -KBKEY_ESC: - return MODE_CONFIRM_QUIT; - break; - case KBKEY_UP: - if ( cursory ) { cursory--; } - break; - case KBKEY_DOWN: - if ( cursory < numlines-1 ) { cursory++; } - break; - case KBKEY_LEFT: - if ( cursorx ) { cursorx--; } - break; - case KBKEY_RIGHT: - if ( cursorx < WIDTH-1 ) { cursorx++; } - break; - case KBKEY_O: - if ( ctrl ) { dectrlmode = MODE_SAVE; } - break; - case KBKEY_R: - if ( ctrl ) { dectrlmode = MODE_LOAD; } - break; - case KBKEY_X: - if ( ctrl ) { dectrlmode = MODE_CONFIRM_QUIT; } - break; - } + result++; + hex = true; + } + } + bool floating = false; + bool exponent = false; + while ( result < string_length ) + { + if ( ('0' <= string[result] && string[result] <= '9') || + (hex && 'a' <= string[result] && string[result] <= 'f') || + (hex && 'A' <= string[result] && string[result] <= 'F') ) + { + result++; continue; } - - if ( ctrl ) { continue; } - - switch ( codepoint ) + if ( string[result] == '.' ) { - case '\n': - cursorx = 0; - if ( cursory < HEIGHT-1 ) { numlines++; cursory++; } - break; - case '\b': - if ( cursorx ) - { - cursorx--; + if ( hex || floating ) + return 0; + floating = true; + result++; + continue; + } + if ( !hex && (string[result] == 'e' || string[result] == 'E') ) + { + if ( !result ) + return 0; + if ( exponent ) + return 0; + floating = true; + result++; + continue; + } + break; + } + if ( result == (hex ? 2 : 0) ) + return 0; + if ( floating ) + { + if ( result < string_length && (string[result] == 'l' || + string[result] == 'L') ) + result++; + else if ( result < string_length && (string[result] == 'f' || + string[result] == 'F') ) + result++; + } + else + { + if ( result < string_length && (string[result] == 'u' || + string[result] == 'U') ) + result++; + if ( result < string_length && (string[result] == 'l' || + string[result] == 'L') ) + result++; + if ( result < string_length && (string[result] == 'l' || + string[result] == 'L') ) + result++; + } + return result; +} - for ( unsigned x = cursorx; x < WIDTH; x++ ) - { - buffers[cursory][x] = buffers[cursory][x+1]; - bufferchanged = true; - } - printf("\e[2K\r%s", buffers[cursory]); - fflush(stdout); - } - else if ( 0 < cursory && strlen(buffers[cursory]) == 0 ) - { - for ( unsigned y = cursory; y < numlines; y++ ) - { - for ( unsigned x = 0; x < WIDTH; x++ ) - { - buffers[y][x] = buffers[y+1][x]; - } - } +void editor_colorize(struct editor* editor) +{ + if ( editor->color_lines_length != editor->lines_used || + !editor->highlight_source ) + { + for ( size_t i = 0; i < editor->color_lines_used; i++ ) + delete[] editor->color_lines[i].data; + delete[] editor->color_lines; + editor->color_lines_used = 0; + editor->color_lines_length = 0; + editor->color_lines = NULL; + } - numlines--; - cursory--; - cursorx = strlen(buffers[cursory]); + if ( !editor->highlight_source ) + return; - drawtextmode(); - } - break; + if ( !editor->color_lines ) + { + if ( !(editor->color_lines = new struct color_line[editor->lines_used]) ) + return; + editor->color_lines_used = editor->lines_used; + editor->color_lines_length = editor->lines_used; + for ( size_t i = 0; i < editor->lines_used; i++ ) + editor->color_lines[i].data = NULL, + editor->color_lines[i].length = 0; + } + + for ( size_t i = 0; i < editor->lines_used; i++ ) + { + if ( editor->color_lines[i].length == editor->lines[i].used ) + continue; + + if ( !(editor->color_lines[i].data = new uint8_t[editor->lines[i].used]) ) + { + for ( size_t n = 0; n < i; i++ ) + delete[] editor->color_lines[n].data; + delete[] editor->color_lines; + editor->color_lines_used = 0; + editor->color_lines_length = 0; + editor->color_lines = NULL; + return; } - size_t linelen = strlen(buffers[cursory]); - if ( linelen < cursorx ) { cursorx = linelen; } - - if ( WIDTH <= cursorx ) { continue; } - if ( codepoint >= 0x80 ) { continue; } - if ( codepoint == '\t' ) { continue; } - if ( codepoint == '\b' ) { continue; } - if ( codepoint == '\n' ) { continue; } - if ( codepoint == '\r' ) { continue; } - - char msg[2]; - msg[0] = codepoint; - msg[1] = 0; - printf("%s", msg); - buffers[cursory][cursorx++] = codepoint; - fflush(stdout); - bufferchanged = true; - if ( WIDTH <= cursorx ) { cursorx = WIDTH-1; } + editor->color_lines[i].length = editor->lines[i].used; } - return MODE_QUIT; -} - -unsigned confirmquit() -{ - if ( !bufferchanged ) { return MODE_QUIT; } - - printf("\e37m\e40m\e[2J\e[H"); - printf("There are unsaved changes: Are you sure you want to quit? (Y/N)\n"); - - if ( settermmode(0, TERMMODE_KBKEY | TERMMODE_SIGNAL | TERMMODE_ECHO) ) + enum { - error(1, errno, "settermmode"); - } + STATE_INIT, + STATE_LINE_COMMENT, + STATE_MULTI_LINE_COMMENT, + STATE_PREPROCESSOR, + STATE_SINGLE_QUOTE, + STATE_DOUBLE_QUOTE, + STATE_NUMBER, + STATE_KEYWORD, + STATE_TYPE, + } state = STATE_INIT, prev_state = STATE_INIT; - while ( true ) + bool escaped = false; + size_t fixed_state = 0; + size_t multi_expiration = 0; + for ( size_t y = 0; y < editor->lines_used; y++ ) { - uint32_t codepoint; - ssize_t numbytes = read(0, &codepoint, sizeof(codepoint)); - if ( !numbytes ) { exit(0); } - if ( numbytes < 0 ) { error(1, errno, "read stdin"); } - if ( numbytes < (ssize_t) sizeof(codepoint) ) { fprintf(stderr, "bad stdin data\n"); exit(1); } - if ( !codepoint ) { continue; } - - int kbkey = KBKEY_DECODE(codepoint); - if ( !kbkey ) { continue; } - if ( 0 < kbkey ) { continue; } - - switch ( kbkey ) + struct line* line = &editor->lines[y]; + for ( size_t x = 0; x < line->used; x++ ) { - case -KBKEY_ESC: - return MODE_QUIT; - break; - case -KBKEY_N: - return MODE_TEXT; - case -KBKEY_Y: - return MODE_QUIT; - default: - printf("Would you like to quit? N for No, Y for Yes\n"); - } - } -} + char pc = x ? line->data[x-1] : '\0'; + char c = line->data[x]; + char nc = x+1 < line->used ? line->data[x+1] : '\0'; + uint8_t color = 7; -bool savetofile(const char* path) -{ - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0777); - if ( fd < 0 ) { error(0, errno, "%s", path); return false; } + // The character makes you leave this state. - for ( unsigned y = 0; y < numlines; y++ ) - { - size_t len = strlen(buffers[y]); - buffers[y][len] = '\n'; - bool result = writeall(fd, buffers[y], len+1) == len+1; - buffers[y][len] = 0; - if ( !result ) { error(0, errno, "write: %s", path); close(fd); return false; } - } + if ( !fixed_state && (state == STATE_KEYWORD || + state == STATE_TYPE || + state == STATE_NUMBER ) ) + state = STATE_INIT; - if ( close(fd) ) { error(0, errno, "close: %s", path); return false; } - strcpy(filename, path); - return true; -} + // The character makes you enter a new state. -int savemode() -{ - printf("\e37m\e40m\e[2J\e[H"); - printf("Please enter the filename you wish to save the text to and press " - "enter. Type an empty filename to abort.\n\n"); + if ( !fixed_state && state == STATE_INIT && c == '#' ) + state = STATE_PREPROCESSOR; - char* storage = NULL; + // TODO: Detect NULL as a value. - do - { - delete[] storage; - printf("File to Write: "); - fflush(stdout); - storage = readline(0); - if ( !storage ) { error(1, errno, "readline"); } - if ( !storage[0] ) { delete[] storage; return MODE_TEXT; } - } while ( !savetofile(storage) ); - delete[] storage; - - bufferchanged = false; - - printf("Succesfully saved\n"); - sleep(1); - return MODE_TEXT; -} - -bool loadfromfile(const char* path) -{ - int fd = open(path, O_RDONLY, 0777); - if ( fd < 0 ) { error(0, errno, "%s", path); return false; } - - clearbuffers(); - - const size_t BUFFER_SIZE = 256; - char buffer[BUFFER_SIZE]; - - bool done = false; - while ( !done ) - { - ssize_t bytesread = read(fd, buffer, BUFFER_SIZE); - if ( bytesread < 0 ) { error(0, errno, "read: %s", path); close(fd); return false; } - if ( bytesread == 0 ) { break; } - for ( ssize_t i = 0; i < bytesread; i++ ) - { - if ( buffer[i] == '\n' ) + if ( !fixed_state && state == STATE_INIT && + !(x && (isalnum(pc) || pc == '_')) ) { - if ( HEIGHT-1 <= ++cursory ) { done = true; break; } - cursorx = 0; - continue; + size_t number_length = recognize_constant(line->data + x, + line->used - x); + if ( number_length ) + { + state = STATE_NUMBER; + fixed_state = number_length; + } } - if ( WIDTH <= cursorx ) { continue; } - buffers[cursory][cursorx++] = buffer[i]; + if ( !fixed_state && state == STATE_INIT && c == '\'' ) + state = STATE_SINGLE_QUOTE, fixed_state = 1, escaped = false; + + if ( !fixed_state && state == STATE_INIT && c == '"' ) + state = STATE_DOUBLE_QUOTE, fixed_state = 1, escaped = false; + + if ( !fixed_state && (state == STATE_INIT || + state == STATE_PREPROCESSOR) ) + { + if ( c == '/' && nc == '/' ) + state = STATE_LINE_COMMENT, fixed_state = 2; + else if ( c == '/' && nc == '*' ) + { + prev_state = state; + multi_expiration = 0; + state = STATE_MULTI_LINE_COMMENT; + fixed_state = 2; + } + } + + if ( !fixed_state && state == STATE_INIT ) + { + const char* keywords[] = + { + "alignas", + "alignof", + "and", + "and_eq", + "asm", + "bitand", + "bitor", + "break", + "case", + "catch", + "class", + "compl", + "const_cast", + "constexpr", + "continue", + "decltype", + "default", + "delete", + "do", + "dynamic_cast", + "else", + "enum", + "false", + "final", + "for", + "friend", + "goto", + "if", + "namespace", + "new", + "not", + "not_eq", + "nullptr", + "operator", + "or", + "or_eq", + "override", + "private", + "protected", + "public", + "reinterpret_cast", + "return", + "sizeof", + "static_assert", + "static_cast", + "struct", + "switch", + "template", + "this", + "thread_local", + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "using", + "virtual", + "while", + "xor", + "xor_eq", + }; + + bool cannot_be_keyword = x && (isalnum(pc) || pc == '_'); + for ( size_t i = 0; + !cannot_be_keyword && i < sizeof(keywords) / sizeof(keywords[0]); + i++ ) + { + const char* keyword = keywords[i]; + if ( c != keyword[0] ) + continue; + size_t keyword_length = strlen(keyword); + if ( (x - line->used) < keyword_length ) + continue; + if ( strncmp(line->data + x, keyword, keyword_length) != 0 ) + continue; + if ( (x - line->used) != keyword_length && + (isalnum(line->data[x+keyword_length]) || + line->data[x+keyword_length] == '_') ) + continue; + state = STATE_KEYWORD; + fixed_state = keyword_length; + } + } + + if ( !fixed_state && state == STATE_INIT ) + { + const char* types[] = + { + "auto", + "blkcnt_t", + "blksize_t", + "bool", + "char", + "char16_t", + "char32_t", + "clockid_t", + "clock_t", + "const", + "dev_t", + "double", + "explicit", + "extern", + "FILE", + "float", + "fpos_t", + "fsblkcnt_t", + "fsfilcnt_t", + "gid_t", + "id_t", + "inline", + "ino_t", + "int", + "int16_t", + "int32_t", + "int64_t", + "int8_t", + "intmax_t", + "intptr_t", + "locale_t", + "long", + "mode_t", + "mutable", + "nlink_t", + "noexcept", + "off_t", + "pid_t", + "ptrdiff_t", + "register", + "restrict", + "short", + "signed", + "size_t", + "ssize_t", + "static", + "suseconds_t", + "thread_local", + "timer_t", + "time_t", + "trace_t", + "uid_t", + "uint16_t", + "uint32_t", + "uint64_t", + "uint8_t", + "uintmax_t", + "uintptr_t", + "unsigned", + "useconds_t", + "va_list", + "void", + "volatile", + "wchar_t", + }; + + bool cannot_be_type = x && (isalnum(pc) || pc == '_'); + for ( size_t i = 0; + !cannot_be_type && i < sizeof(types) / sizeof(types[0]); + i++ ) + { + const char* type = types[i]; + if ( c != type[0] ) + continue; + size_t type_length = strlen(type); + if ( (x - line->used) < type_length ) + continue; + if ( strncmp(line->data + x, type, type_length) != 0 ) + continue; + if ( (x - line->used) != type_length && + (isalnum(line->data[x+type_length]) || + line->data[x+type_length] == '_') ) + continue; + state = STATE_TYPE; + fixed_state = type_length; + } + } + + // The current state uses a non-default color. + + if ( state == STATE_SINGLE_QUOTE || + state == STATE_DOUBLE_QUOTE || + state == STATE_NUMBER ) + color = 5; + + if ( state == STATE_PREPROCESSOR ) + color = 3; + + if ( state == STATE_LINE_COMMENT || + state == STATE_MULTI_LINE_COMMENT ) + color = 6; + + if ( state == STATE_KEYWORD ) + color = 1; + + if ( state == STATE_TYPE ) + color = 2; + + // The character is the last character in this state. + + if ( !fixed_state ) + { + if ( state == STATE_SINGLE_QUOTE && !escaped && c == '\'' ) + state = STATE_INIT, fixed_state = 1; + if ( state == STATE_DOUBLE_QUOTE && !escaped && c == '"' ) + state = STATE_INIT, fixed_state = 1; + } + + if ( (state == STATE_SINGLE_QUOTE || state == STATE_DOUBLE_QUOTE) ) + { + if ( !escaped && c == '\\' ) + escaped = true; + else if ( escaped ) + escaped = false; + } + + if ( !fixed_state && state == STATE_MULTI_LINE_COMMENT ) + { + if ( multi_expiration == 1 ) + state = prev_state, multi_expiration = 0; + else if ( c == '*' && nc == '/' ) + multi_expiration = 1; + } + + if ( state == STATE_PREPROCESSOR ) + escaped = c == '\\' && !nc; + + editor->color_lines[y].data[x] = color; + + if ( fixed_state ) + fixed_state--; + } + + if ( state == STATE_LINE_COMMENT || + state == STATE_PREPROCESSOR || + state == STATE_SINGLE_QUOTE || + state == STATE_DOUBLE_QUOTE ) + { + if ( state == STATE_PREPROCESSOR && escaped ) + escaped = false; + else + state = STATE_INIT; + } + } +} + +size_t editor_select_column_set(struct editor* editor, size_t x) +{ + if ( editor->viewport_width ) + { + struct line* line = &editor->lines[editor->select_row]; + size_t rx = displayed_string_length(line->data, x, editor->tabsize); + if ( rx < editor->page_x_offset ) + editor->page_x_offset = rx; + if ( editor->page_x_offset + editor->viewport_width <= rx ) + editor->page_x_offset = rx + 1- editor->viewport_width; + } + return editor->select_column = x; +} + +size_t editor_select_row_set(struct editor* editor, size_t y) +{ + if ( editor->viewport_height ) + { + if ( y < editor->page_y_offset ) + editor->page_y_offset = y; + if ( editor->page_y_offset + editor->viewport_height <= y ) + editor->page_y_offset = y + 1- editor->viewport_height; + } + return editor->select_row = y; +} + +void editor_select_set(struct editor* editor, size_t y, size_t x) +{ + editor_select_column_set(editor, x); + editor_select_row_set(editor, y); +} + +size_t editor_select_column_dec(struct editor* editor) +{ + assert(editor->select_column); + return editor_select_column_set(editor, editor->select_column-1); +} + +size_t editor_select_column_inc(struct editor* editor) +{ + // TODO: Assert line doesn't overflow! + return editor_select_column_set(editor, editor->select_column+1); +} + +size_t editor_select_row_dec(struct editor* editor) +{ + assert(editor->select_row); + return editor_select_row_set(editor, editor->select_row-1); +} + +size_t editor_select_row_inc(struct editor* editor) +{ + // TODO: Assert line doesn't overflow! + return editor_select_row_set(editor, editor->select_row+1); +} + +size_t editor_cursor_column_set(struct editor* editor, size_t x) +{ + editor_select_column_set(editor, x); + editor_select_row_set(editor, editor->cursor_row); + return editor->cursor_column = x; +} + +size_t editor_cursor_row_set(struct editor* editor, size_t y) +{ + editor_select_column_set(editor, editor->cursor_column); + editor_select_row_set(editor, y); + return editor->cursor_row = y; +} + +void editor_cursor_set(struct editor* editor, size_t y, size_t x) +{ + editor_cursor_column_set(editor, x); + editor_cursor_row_set(editor, y); +} + +size_t editor_cursor_column_dec(struct editor* editor) +{ + assert(editor->cursor_column); + return editor_cursor_column_set(editor, editor->cursor_column-1); +} + +size_t editor_cursor_column_inc(struct editor* editor) +{ + // TODO: Assert line doesn't overflow! + return editor_cursor_column_set(editor, editor->cursor_column+1); +} + +size_t editor_cursor_row_dec(struct editor* editor) +{ + assert(editor->cursor_row); + return editor_cursor_row_set(editor, editor->cursor_row-1); +} + +size_t editor_cursor_row_inc(struct editor* editor) +{ + // TODO: Assert line doesn't overflow! + return editor_cursor_row_set(editor, editor->cursor_row+1); +} + +void editor_type_newline(struct editor* editor) +{ + editor->dirty = true; + + if ( editor->lines_used == editor->lines_length ) + { + size_t new_length = editor->lines_length ? 2 * editor->lines_length : 8; + struct line* new_lines = new struct line[new_length]; + for ( size_t i = 0; i < editor->lines_used; i++ ) + new_lines[i] = editor->lines[i]; + delete[] editor->lines; + editor->lines = new_lines; + editor->lines_length = new_length; + } + + for ( size_t i = editor->lines_used-1; editor->cursor_row < i; i-- ) + editor->lines[i+1] = editor->lines[i]; + editor->lines_used++; + + struct line old_line = editor->lines[editor->cursor_row]; + + size_t keep_length = editor->cursor_column; + size_t move_length = old_line.used - keep_length; + + struct line* keep_line = &editor->lines[editor->cursor_row]; + struct line* move_line = &editor->lines[editor->cursor_row+1]; + + keep_line->data = new char[keep_length]; + keep_line->used = keep_length; + keep_line->length = keep_length; + memcpy(keep_line->data, old_line.data + 0, keep_length); + + move_line->data = new char[move_length]; + move_line->used = move_length; + move_line->length = move_length; + memcpy(move_line->data, old_line.data + keep_length, move_length); + + editor_cursor_set(editor, editor->cursor_row+1, 0); + + delete[] old_line.data; +} + +void editor_type_delete_selection(struct editor* editor); + +void editor_type_combine_with_last(struct editor* editor) +{ + if ( !editor->cursor_row ) + return; + + editor->dirty = true; + + struct line* keep_line = &editor->lines[editor->cursor_row-1]; + struct line* gone_line = &editor->lines[editor->cursor_row]; + + char* keep_line_data = keep_line->data; + char* gone_line_data = gone_line->data; + + size_t new_length = keep_line->used + gone_line->used; + char* new_data = new char[new_length]; + + memcpy(new_data, keep_line_data, keep_line->used); + memcpy(new_data + keep_line->used, gone_line_data, gone_line->used); + + editor_cursor_set(editor, editor->cursor_row-1, keep_line->used); + + keep_line->data = new_data; + keep_line->used = new_length; + keep_line->length = new_length; + + editor->lines_used--; + for ( size_t i = editor->cursor_row + 1; i < editor->lines_used; i++ ) + editor->lines[i] = editor->lines[i+1]; + + free(keep_line_data); + free(gone_line_data); +} + +void editor_type_backspace(struct editor* editor) +{ + if ( !(editor->select_row == editor->cursor_row && + editor->select_column == editor->cursor_column) ) + { + editor_type_delete_selection(editor); + return; + } + + struct line* current_line = &editor->lines[editor->cursor_row]; + + if ( !editor->cursor_column ) + { + editor_type_combine_with_last(editor); + return; + } + + editor->dirty = true; + + current_line->used--; + for ( size_t i = editor_cursor_column_dec(editor); i < current_line->used; i++ ) + current_line->data[i] = current_line->data[i+1]; +} + +void editor_type_combine_with_next(struct editor* editor) +{ + if ( editor->cursor_row + 1 == editor->lines_used ) + return; + + editor->dirty = true; + + struct line* keep_line = &editor->lines[editor->cursor_row]; + struct line* gone_line = &editor->lines[editor->cursor_row+1]; + + char* keep_line_data = keep_line->data; + char* gone_line_data = gone_line->data; + + size_t new_length = keep_line->used + gone_line->used; + char* new_data = new char[new_length]; + + memcpy(new_data, keep_line_data, keep_line->used); + memcpy(new_data + keep_line->used, gone_line_data, gone_line->used); + + editor_cursor_column_set(editor, keep_line->used); + + keep_line->data = new_data; + keep_line->used = new_length; + keep_line->length = new_length; + + editor->lines_used--; + for ( size_t i = editor->cursor_row + 1; i < editor->lines_used; i++ ) + editor->lines[i] = editor->lines[i+1]; + + free(keep_line_data); + free(gone_line_data); +} + +void editor_type_delete(struct editor* editor) +{ + if ( !(editor->select_row == editor->cursor_row && + editor->select_column == editor->cursor_column) ) + { + editor_type_delete_selection(editor); + return; + } + + struct line* current_line = &editor->lines[editor->cursor_row]; + + if ( editor->cursor_column == current_line->used ) + { + editor_type_combine_with_next(editor); + return; + } + + editor->dirty = true; + + current_line->used--; + for ( size_t i = editor->cursor_column; i < current_line->used; i++ ) + current_line->data[i] = current_line->data[i+1]; +} + +void editor_type_delete_selection(struct editor* editor) +{ + if ( is_row_column_lt(editor->select_row, editor->select_column, + editor->cursor_row, editor->cursor_column) ) + { + size_t tmp; + tmp = editor->select_row; + editor->select_row = editor->cursor_row; + editor->cursor_row = tmp; + tmp = editor->select_column; + editor->select_column = editor->cursor_column; + editor->cursor_column = tmp; + } + + size_t desired_row = editor->cursor_row; + size_t desired_column = editor->cursor_column; + + editor->cursor_row = editor->select_row; + editor->cursor_column = editor->select_column; + + while ( !(editor->cursor_row == desired_row && + editor->cursor_column == desired_column) ) + editor_type_backspace(editor); +} + +void editor_type_left(struct editor* editor) +{ + if ( editor_has_selection(editor) ) + { + size_t column, row; + row_column_smallest(editor->cursor_row, editor->cursor_column, + editor->select_row, editor->select_column, + &column, &row); + editor_cursor_set(editor, column, row); + return; + } + if ( editor->cursor_column ) + editor_cursor_column_dec(editor); + else if ( editor->cursor_row ) + { + editor_cursor_row_dec(editor); + editor_cursor_column_set(editor, editor->lines[editor->cursor_row].used); + } +} + +void editor_type_select_left(struct editor* editor) +{ + if ( editor->select_column ) + editor_select_column_dec(editor); + else if ( editor->select_row ) + { + editor_select_row_dec(editor); + editor_select_column_set(editor, editor->lines[editor->select_row].used); + } +} + +void editor_type_right(struct editor* editor) +{ + if ( editor_has_selection(editor) ) + { + size_t column, row; + row_column_biggest(editor->cursor_row, editor->cursor_column, + editor->select_row, editor->select_column, + &column, &row); + editor_cursor_set(editor, column, row); + return; + } + struct line* current_line = &editor->lines[editor->cursor_row]; + if ( editor->cursor_column != current_line->used ) + editor_cursor_column_inc(editor); + else if ( editor->cursor_row+1 != editor->lines_used ) + editor_cursor_row_inc(editor), + editor_cursor_column_set(editor, 0); +} + +void editor_type_select_right(struct editor* editor) +{ + struct line* current_line = &editor->lines[editor->select_row]; + if ( editor->select_column != current_line->used ) + editor_select_column_inc(editor); + else if ( editor->select_row+1 != editor->lines_used ) + editor_select_row_inc(editor), + editor_select_column_set(editor, 0); +} + +void editor_type_up(struct editor* editor) +{ + if ( editor_has_selection(editor) ) + { + size_t column, row; + row_column_smallest(editor->cursor_row, editor->cursor_column, + editor->select_row, editor->select_column, + &column, &row); + editor_cursor_set(editor, column, row); + } + if ( !editor->cursor_row ) + { + editor_cursor_column_set(editor, 0); + return; + } + size_t new_line_len = editor->lines[editor_cursor_row_dec(editor)].used; + if ( new_line_len < editor->cursor_column ) + editor_cursor_column_set(editor, new_line_len); +} + +void editor_type_select_up(struct editor* editor) +{ + if ( !editor->select_row ) + { + editor_select_column_set(editor, 0); + return; + } + size_t new_line_len = editor->lines[editor_select_row_dec(editor)].used; + if ( new_line_len < editor->select_column ) + editor_select_column_set(editor, new_line_len); +} + +void editor_type_down(struct editor* editor) +{ + if ( editor_has_selection(editor) ) + { + size_t column, row; + row_column_biggest(editor->cursor_row, editor->cursor_column, + editor->select_row, editor->select_column, + &column, &row); + editor_cursor_set(editor, column, row); + } + if ( editor->cursor_row+1 == editor->lines_used ) + { + editor_cursor_column_set(editor, editor->lines[editor->cursor_row].used); + return; + } + size_t new_line_len = editor->lines[editor_cursor_row_inc(editor)].used; + if ( new_line_len < editor->cursor_column ) + editor_cursor_column_set(editor, new_line_len); +} + +void editor_type_select_down(struct editor* editor) +{ + if ( editor->select_row+1 == editor->lines_used ) + { + editor_select_column_set(editor, editor->lines[editor->select_row].used); + return; + } + size_t new_line_len = editor->lines[editor_select_row_inc(editor)].used; + if ( new_line_len < editor->select_column ) + editor_select_column_set(editor, new_line_len); +} + +void editor_skip_leading(struct editor* editor) +{ + struct line* current_line = &editor->lines[editor->cursor_row]; + for ( editor_cursor_column_set(editor, 0); + editor->cursor_column < current_line->used; + editor_cursor_column_inc(editor) ) + if ( !isspace(current_line->data[editor->cursor_column]) ) + break; +} + +void editor_select_skip_leading(struct editor* editor) +{ + struct line* current_line = &editor->lines[editor->select_row]; + for ( editor_select_column_set(editor, 0); + editor->select_column < current_line->used; + editor_select_column_inc(editor) ) + if ( !isspace(current_line->data[editor->select_column]) ) + break; +} + +void editor_type_home(struct editor* editor) +{ + if ( editor_has_selection(editor) ) + { + size_t column, row; + row_column_smallest(editor->cursor_row, editor->cursor_column, + editor->select_row, editor->select_column, + &column, &row); + editor_cursor_set(editor, column, row); + } + if ( !editor->cursor_column ) + { + editor_skip_leading(editor); + return; + } + editor_cursor_column_set(editor, 0); +} + +void editor_type_select_home(struct editor* editor) +{ + if ( !editor->select_column ) + { + editor_select_skip_leading(editor); + return; + } + editor_select_column_set(editor, 0); +} + +void editor_skip_ending(struct editor* editor) +{ + struct line* current_line = &editor->lines[editor->cursor_row]; + for ( editor_cursor_column_set(editor, current_line->used); + editor->cursor_column; + editor_cursor_column_dec(editor) ) + if ( !isspace(current_line->data[editor->cursor_column-1]) ) + break; +} + +void editor_select_skip_ending(struct editor* editor) +{ + struct line* current_line = &editor->lines[editor->select_row]; + for ( editor_select_column_set(editor, current_line->used); + editor->select_column; + editor_select_column_dec(editor) ) + if ( !isspace(current_line->data[editor->select_column-1]) ) + break; +} + +void editor_type_end(struct editor* editor) +{ + if ( editor_has_selection(editor) ) + { + size_t column, row; + row_column_biggest(editor->cursor_row, editor->cursor_column, + editor->select_row, editor->select_column, + &column, &row); + editor_cursor_set(editor, column, row); + } + struct line* current_line = &editor->lines[editor->cursor_row]; + if ( editor->cursor_column == current_line->used ) + { + editor_skip_ending(editor); + return; + } + editor_cursor_column_set(editor, current_line->used); +} + +void editor_type_select_end(struct editor* editor) +{ + struct line* current_line = &editor->lines[editor->select_row]; + if ( editor->select_column == current_line->used ) + { + editor_select_skip_ending(editor); + return; + } + editor_select_column_set(editor, current_line->used); +} + +void editor_type_page_up(struct editor* editor) +{ + if ( editor_has_selection(editor) ) + { + size_t column, row; + row_column_smallest(editor->cursor_row, editor->cursor_column, + editor->select_row, editor->select_column, + &column, &row); + editor_cursor_set(editor, column, row); + } + if ( editor->cursor_row < editor->viewport_height ) + { + editor_cursor_set(editor, 0, 0); + return; + } + size_t new_line = editor->cursor_row - editor->viewport_height; + editor_cursor_row_set(editor, new_line); + size_t new_line_len = editor->lines[new_line].used; + if ( new_line_len < editor->cursor_column ) + editor_cursor_column_set(editor, new_line_len); +} + +void editor_type_select_page_up(struct editor* editor) +{ + if ( editor->select_row < editor->viewport_height ) + { + editor_select_set(editor, 0, 0); + return; + } + size_t new_line = editor->select_row - editor->viewport_height; + editor_select_row_set(editor, new_line); + size_t new_line_len = editor->lines[new_line].used; + if ( new_line_len < editor->select_column ) + editor_select_column_set(editor, new_line_len); +} + +void editor_type_page_down(struct editor* editor) +{ + if ( editor_has_selection(editor) ) + { + size_t column, row; + row_column_biggest(editor->cursor_row, editor->cursor_column, + editor->select_row, editor->select_column, + &column, &row); + editor_cursor_set(editor, column, row); + } + size_t new_line = editor->cursor_row + editor->viewport_height; + if ( editor->lines_used <= new_line ) + { + editor_cursor_row_set(editor, editor->lines_used - 1); + editor_cursor_column_set(editor, editor->lines[editor->cursor_row].used); + return; + } + editor_cursor_row_set(editor, new_line); + size_t new_line_len = editor->lines[new_line].used; + if ( new_line_len < editor->cursor_column ) + editor_cursor_column_set(editor, new_line_len); +} + +void editor_type_select_page_down(struct editor* editor) +{ + size_t new_line = editor->select_row + editor->viewport_height; + if ( editor->lines_used <= new_line ) + { + editor_select_row_set(editor, editor->lines_used - 1); + editor_select_column_set(editor, editor->lines[editor->select_row].used); + return; + } + editor_select_row_set(editor, new_line); + size_t new_line_len = editor->lines[new_line].used; + if ( new_line_len < editor->select_column ) + editor_select_column_set(editor, new_line_len); +} + +void editor_type_edit(struct editor* editor) +{ + editor->mode = MODE_EDIT; +} + +void editor_type_goto_line(struct editor* editor) +{ + editor->mode = MODE_GOTO_LINE; + editor->modal_used = 0; + editor->modal_cursor = 0; + editor->modal_error = false; +} + +void editor_type_save(struct editor* editor) +{ + editor->mode = MODE_SAVE; + + free(editor->modal); + editor->modal = strdup_safe(editor->current_file_name); + editor->modal_used = strlen(editor->modal); + editor->modal_length = editor->modal_used+1; + editor->modal_cursor = editor->modal_used; + editor->modal_error = false; +} + +void editor_type_save_as(struct editor* editor) +{ + editor->mode = MODE_SAVE; + editor->modal_used = 0; + editor->modal_cursor = 0; + editor->modal_error = false; +} + +void editor_type_open(struct editor* editor) +{ + editor->mode = MODE_LOAD; + editor->modal_used = 0; + editor->modal_cursor = 0; + editor->modal_error = false; +} + +void editor_type_open_as(struct editor* editor) +{ + editor->mode = MODE_LOAD; + + free(editor->modal); + editor->modal = strdup_safe(editor->current_file_name); + editor->modal_used = strlen(editor->modal); + editor->modal_length = editor->modal_used+1; + editor->modal_cursor = editor->modal_used; + editor->modal_error = false; +} + +void editor_type_quit(struct editor* editor) +{ + editor->mode = editor->dirty ? MODE_ASK_QUIT : MODE_QUIT; + editor->modal_cursor = 0; + editor->modal_used = 0; + editor->modal_error = false; +} + +void editor_type_command(struct editor* editor) +{ + editor->mode = MODE_COMMAND; + editor->modal_cursor = 0; + editor->modal_used = 0; + editor->modal_error = false; +} + +void editor_type_raw_character(struct editor* editor, char c) +{ + struct line* current_line = &editor->lines[editor->cursor_row]; + + if ( current_line->used == current_line->length ) + { + size_t new_length = current_line->length ? 2 * current_line->length : 8; + char* new_data = new char[new_length]; + for ( size_t i = 0; i < current_line->used; i++ ) + new_data[i] = current_line->data[i]; + delete[] current_line->data; + current_line->data = new_data; + current_line->length = new_length; + } + + editor->dirty = true; + + for ( size_t i = current_line->used; editor->cursor_column < i; i-- ) + current_line->data[i] = current_line->data[i-1]; + current_line->used++; + current_line->data[editor_cursor_column_inc(editor)-1] = c; +} + +void editor_type_copy(struct editor* editor) +{ + if ( editor->cursor_row == editor->select_row && + editor->cursor_column == editor->select_column ) + return; + + delete[] editor->clipboard; + + size_t start_row; + size_t start_column; + size_t end_row; + size_t end_column; + if ( is_row_column_lt(editor->select_row, editor->select_column, + editor->cursor_row, editor->cursor_column) ) + { + start_row = editor->select_row; + start_column = editor->select_column; + end_row = editor->cursor_row; + end_column = editor->cursor_column; + } + else + { + start_row = editor->cursor_row; + start_column = editor->cursor_column; + end_row = editor->select_row; + end_column = editor->select_column; + } + + size_t length = 0; + for ( size_t row = start_row, column = start_column; + is_row_column_lt(row, column, end_row, end_column); ) + { + if ( row == end_row ) + { + length += end_column - column; + column = end_column; + } + else + { + length += editor->lines[row].used + 1 /*newline*/; + column = 0; + row++; } } - numlines = cursory + 1; + editor->clipboard = new char[length + 1]; + size_t offset = 0; + for ( size_t row = start_row, column = start_column; + is_row_column_lt(row, column, end_row, end_column); ) + { + struct line* line = &editor->lines[row]; + if ( row == end_row ) + { + memcpy(editor->clipboard + offset, line->data + column, end_column - column); + offset += end_column - column; + column = end_column; + } + else + { + memcpy(editor->clipboard + offset, line->data, line->used); + editor->clipboard[offset + line->used] = '\n'; + offset += line->used + 1 /*newline*/; + column = 0; + row++; + } + } + editor->clipboard[length] = '\0'; +} - close(fd); +void editor_type_cut(struct editor* editor) +{ + if ( editor->cursor_row == editor->select_row && + editor->cursor_column == editor->select_column ) + return; - cursorx = 0; - cursory = 0; + editor_type_copy(editor); + editor_type_delete_selection(editor); +} - strcpy(filename, path); +void editor_type_paste(struct editor* editor) +{ + if ( editor->cursor_row == editor->select_row && + editor->cursor_column == editor->select_column ) + editor_type_delete_selection(editor); + + for ( size_t i = 0; editor->clipboard && editor->clipboard[i]; i++ ) + { + if ( editor->clipboard[i] == '\n' ) + editor_type_newline(editor); + else + editor_type_raw_character(editor, editor->clipboard[i]); + } +} + +void editor_type_character(struct editor* editor, char c) +{ + if ( editor->control ) + { + switch ( tolower(c) ) + { + case 'c': editor_type_copy(editor); break; + case 'i': editor_type_goto_line(editor); break; + case 'k': editor_type_cut(editor); break; + case 'o': editor->shift ? + editor_type_open_as(editor) : + editor_type_open(editor); break; + case 'q': editor_type_quit(editor); break; + case 's': editor->shift ? + editor_type_save_as(editor) : + editor_type_save(editor); break; + case 'v': editor_type_paste(editor); break; + case 'x': editor_type_cut(editor); break; + } + return; + } + + if ( editor_has_selection(editor) ) + editor_type_delete_selection(editor); + + if ( c == '\n' ) { editor_type_newline(editor); return; } + + editor_type_raw_character(editor, c); +} + +void editor_type_kbkey(struct editor* editor, int kbkey) +{ + if ( kbkey < 0 ) + return; + + if ( kbkey == KBKEY_ESC ) + { + editor_type_command(editor); + return; + } + + if ( editor->control && editor->shift ) + { + switch ( kbkey ) + { + } + } + else if ( editor->control && !editor->shift ) + { + switch ( kbkey ) + { + } + } + else if ( !editor->control && editor->shift ) + { + switch ( kbkey ) + { + case KBKEY_LEFT: editor_type_select_left(editor); break; + case KBKEY_RIGHT: editor_type_select_right(editor); break; + case KBKEY_UP: editor_type_select_up(editor); break; + case KBKEY_DOWN: editor_type_select_down(editor); break; + case KBKEY_HOME: editor_type_select_home(editor); break; + case KBKEY_END: editor_type_select_end(editor); break; + case KBKEY_PGUP: editor_type_select_page_up(editor); break; + case KBKEY_PGDOWN: editor_type_select_page_down(editor); break; + case KBKEY_BKSPC: editor_type_backspace(editor); break; + case KBKEY_DELETE: editor_type_delete(editor); break; + } + } + else if ( !editor->control && !editor->shift ) + { + switch ( kbkey ) + { + case KBKEY_LEFT: editor_type_left(editor); break; + case KBKEY_RIGHT: editor_type_right(editor); break; + case KBKEY_UP: editor_type_up(editor); break; + case KBKEY_DOWN: editor_type_down(editor); break; + case KBKEY_HOME: editor_type_home(editor); break; + case KBKEY_END: editor_type_end(editor); break; + case KBKEY_PGUP: editor_type_page_up(editor); break; + case KBKEY_PGDOWN: editor_type_page_down(editor); break; + case KBKEY_BKSPC: editor_type_backspace(editor); break; + case KBKEY_DELETE: editor_type_delete(editor); break; + } + } +} + +void editor_modal_left(struct editor* editor) +{ + if ( editor->modal_cursor ) + editor->modal_cursor--; +} + +void editor_modal_right(struct editor* editor) +{ + if ( editor->modal_cursor != editor->modal_used ) + editor->modal_cursor++; +} + +void editor_modal_home(struct editor* editor) +{ + editor->modal_cursor = 0; +} + +void editor_modal_end(struct editor* editor) +{ + editor->modal_cursor = editor->modal_used; +} + +void editor_modal_backspace(struct editor* editor) +{ + if ( !editor->modal_cursor ) + return; + + editor->modal_error = false; + + editor->modal_used--; + for ( size_t i = --editor->modal_cursor; i < editor->modal_used; i++ ) + editor->modal[i] = editor->modal[i+1]; +} + +void editor_modal_delete(struct editor* editor) +{ + if ( editor->modal_cursor == editor->modal_used ) + return; + + editor->modal_error = false; + + editor->modal_used--; + for ( size_t i = editor->modal_cursor; i < editor->modal_used; i++ ) + editor->modal[i] = editor->modal[i+1]; +} + +void editor_reset_contents(struct editor* editor) +{ + for ( size_t i = 0; i < editor->lines_used; i++ ) + delete[] editor->lines[i].data; + delete[] editor->lines; + + editor->lines_used = 1; + editor->lines_length = 1; + editor->lines = new struct line[editor->lines_length]; + editor->lines[0].data = NULL; + editor->lines[0].used = 0; + editor->lines[0].length = 0; + editor->highlight_source = false; + editor_cursor_set(editor, 0, 0); +} + +bool editor_load_file_contents(struct editor* editor, FILE* fp) +{ + struct stat st; + if ( fstat(fileno(fp), &st) != 0 || S_ISDIR(st.st_mode) ) + return errno = EISDIR, false; + + free(editor->current_file_name); + editor->current_file_name = NULL; + + editor_reset_contents(editor); + + bool last_newline = false; + int ic; + while ( (ic = fgetc(fp)) != EOF ) + { + if ( last_newline ) + editor_type_newline(editor); + if ( ic < 128 && !(last_newline = ic == '\n') ) + editor_type_raw_character(editor, (char) ic); + } + + editor_cursor_set(editor, 0, 0); return true; } -int loadmode() +bool should_highlight_path(const char* path) { - printf("\e37m\e40m\e[2J\e[H"); - printf("Please enter the filename you wish to load text from and press " - "enter. Type an empty filename to abort.\n\n"); - - char* storage = NULL; - - do - { - delete[] storage; - printf("File to Load: "); - fflush(stdout); - storage = readline(0); - if ( !storage ) { error(1, errno, "readline"); } - if ( !storage[0] ) { delete[] storage; return MODE_TEXT; } - } while ( !loadfromfile(storage) ); - delete[] storage; - - bufferchanged = false; - - return MODE_TEXT; + size_t path_length = strlen(path); + if ( 2 <= path_length && + (!strcmp(path+path_length-2, ".c") || + !strcmp(path+path_length-2, ".h")) ) + return true; + if ( 4 <= path_length && + (!strcmp(path+path_length-4, ".c++") || + !strcmp(path+path_length-4, ".h++") || + !strcmp(path+path_length-4, ".cxx") || + !strcmp(path+path_length-4, ".hxx") || + !strcmp(path+path_length-4, ".cpp") || + !strcmp(path+path_length-4, ".hpp")) ) + return true; + return false; } -void run() +bool editor_load_file(struct editor* editor, const char* path) { - bufferchanged = false; - - unsigned mode = MODE_TEXT; - while ( mode != (unsigned) MODE_QUIT ) + if ( FILE* fp = fopen(path, "r") ) { - switch ( mode ) - { - case MODE_TEXT: - mode = textmode(); - break; - case MODE_CONFIRM_QUIT: - mode = confirmquit(); - break; - case MODE_SAVE: - mode = savemode(); - break; - case MODE_LOAD: - mode = loadmode(); - break; - default: - printf("Application Bug: Unknown Mode\n"); - sleep(1); - mode = MODE_TEXT; - break; - } + bool success = editor_load_file_contents(editor, fp); + fclose(fp); + if ( !success ) + return false; + editor->dirty = false; + } + else if ( errno == ENOENT ) + { + editor_reset_contents(editor); + editor->dirty = true; + } + else + return false; + + editor->current_file_name = strdup(path); + editor->highlight_source = should_highlight_path(path); + + return true; +} + +bool editor_load_popen(struct editor* editor, const char* cmd) +{ + FILE* fp = popen(cmd, "r"); + if ( !fp ) + return false; + bool success = editor_load_file_contents(editor, fp); + pclose(fp); + + if ( !success ) + return false; + + editor->current_file_name = NULL; + editor->dirty = true; + + return true; +} + +bool editor_save_file(struct editor* editor, const char* path) +{ + FILE* fp = fopen(path, "w"); + if ( !fp ) + return false; + + for ( size_t i = 0; i < editor->lines_used; i++ ) + { + fwrite(editor->lines[i].data, sizeof(char), editor->lines[i].used, fp); + fputc('\n', fp); } - printf("\e[37m\e[40m\e[2J\e[H"); - fflush(stdout); + editor->current_file_name = strdup(path); + editor->dirty = false; + editor->highlight_source = should_highlight_path(path); + + return fclose(fp) != EOF; +} + +void editor_modal_load(struct editor* editor, const char* path) +{ + if ( editor_load_file(editor, path) ) + editor_type_edit(editor); + else + editor->modal_error = true; +} + +void editor_modal_save(struct editor* editor, const char* path) +{ + if ( editor_save_file(editor, path) ) + editor_type_edit(editor); + else + editor->modal_error = true; +} + +void editor_modal_ask_quit(struct editor* editor, const char* answer) +{ + if ( tolower(answer[0]) == 'y' ) + editor->mode = MODE_QUIT; + else if ( tolower(answer[0]) == 'n' || !answer[0] ) + editor_type_edit(editor); + else + editor->modal_error = true; +} + +void editor_modal_goto_line(struct editor* editor, const char* linestr) +{ + if ( linestr[0] ) + { + bool go_back = false, go_forward = false; + if ( linestr[0] == '+' ) + linestr++, go_forward = true; + else if ( linestr[0] == '-' ) + linestr++, go_back = true; + if ( !linestr[0] ) { editor->modal_error = true; return; } + const char* linestr_end; + unsigned long line = strtoul(linestr, (char**) &linestr_end, 0); + if ( *linestr_end ) { editor->modal_error = true; return; } + if ( go_back ) + { + if ( editor->cursor_row < line ) + { + editor->modal_error = true; + return; + } + editor_cursor_row_set(editor, editor->cursor_row - line); + } + else if ( go_forward ) + { + if ( editor->lines_used - (editor->cursor_row+1) < line ) + { + editor->modal_error = true; + return; + } + editor_cursor_row_set(editor, editor->cursor_row + line); + } + else + { + if ( editor->lines_used+1 <= line ) + { + editor->modal_error = true; + return; + } + editor_cursor_row_set(editor, line ? line - 1 : 0); + } + editor_cursor_column_set(editor, 0); + } + editor_type_edit(editor); +} + +void editor_modal_margin(struct editor* editor, const char* marginstr) +{ + if ( !marginstr[0] ) + editor->margin = SIZE_MAX; + else + { + char* end_ptr; + unsigned long margin = strtoul(marginstr, &end_ptr, 0); + if ( *end_ptr ) { editor->modal_error = true; return; } + editor->margin = margin; + } + editor_type_edit(editor); +} + +void editor_modal_popen(struct editor* editor, const char* cmd) +{ + if ( cmd[0] && editor_load_popen(editor, cmd) ) + editor_type_edit(editor); + else + editor->modal_error = true; +} + +void editor_modal_tabsize(struct editor* editor, const char* tabsizestr) +{ + if ( !tabsizestr[0] ) + editor->tabsize = 8; + else + { + char* end_ptr; + unsigned long tabsize = strtoul(tabsizestr, &end_ptr, 0); + if ( !tabsize || *end_ptr || 256 < tabsize ) + { + editor->modal_error = true; + return; + } + editor->tabsize = tabsize; + } + editor_type_edit(editor); +} + +void editor_modal_language(struct editor* editor, const char* language) +{ + if ( !language[0] || !strcmp(language, "none") ) + { + editor->highlight_source = false; + return; + } + if ( !strcmp(language, "c") || !strcmp(language, "c++") ) + { + editor->highlight_source = true; + return; + } + editor->modal_error = true; + editor_type_edit(editor); +} + +bool is_modal_command(const char* cmd, const char* candidate, const char** rest) +{ + size_t candidate_len = strlen(candidate); + if ( strncmp(cmd, candidate, candidate_len) == 0 && + (!cmd[candidate_len] || isspace(cmd[candidate_len])) ) + { + *rest = cmd + candidate_len; + while ( **rest && isspace(**rest) ) + (*rest)++; + return true; + } + return false; +} + +void editor_modal_command(struct editor* editor, const char* cmd) +{ + while ( *cmd && isspace(*cmd) ) + cmd++; + if ( cmd[0] == ':' ) + cmd++; + if ( !cmd[0] ) { editor_type_edit(editor); return; } + + if ( !strcmp(cmd, "q") || !strcmp(cmd, "exit") || !strcmp(cmd, "quit") ) + editor_type_quit(editor); + else if ( !strcmp(cmd, "q!") ) + editor->dirty = false, editor_type_quit(editor); + else if ( !strcmp(cmd, "w") ) + editor_type_save(editor); + else if ( !strcmp(cmd, "wq") || !strcmp(cmd, "wq!") ) + editor->dirty ? editor_type_save(editor) + : editor_type_quit(editor); + else if ( is_modal_command(cmd, "margin", &cmd) ) + editor_modal_margin(editor, cmd); + else if ( is_modal_command(cmd, "popen", &cmd) ) + editor_modal_popen(editor, cmd); + else if ( is_modal_command(cmd, "tabsize", &cmd) ) + editor_modal_tabsize(editor, cmd); + else if ( is_modal_command(cmd, "language", &cmd) ) + editor_modal_language(editor, cmd); + else + editor->modal_error = true; +} + +void editor_modal_character(struct editor* editor, char c) +{ + if ( editor->control ) + { + switch ( tolower(c) ) + { + case 'c': editor_type_edit(editor); break; + } + return; + } + + editor->modal_error = false; + + if ( c == '\n' ) + { + char* param = new char[editor->modal_used+1]; + memcpy(param, editor->modal, editor->modal_used); + param[editor->modal_used] = '\0'; + switch ( editor->mode ) + { + case MODE_LOAD: editor_modal_load(editor, param); break; + case MODE_SAVE: editor_modal_save(editor, param); break; + case MODE_ASK_QUIT: editor_modal_ask_quit(editor, param); break; + case MODE_GOTO_LINE: editor_modal_goto_line(editor, param); break; + case MODE_COMMAND: editor_modal_command(editor, param); break; + default: break; + } + free(param); + return; + } + + if ( editor->modal_used == editor->modal_length ) + { + size_t new_length = editor->modal_length ? 2 * editor->modal_length : 8; + char* new_data = new char[new_length]; + for ( size_t i = 0; i < editor->modal_used; i++ ) + new_data[i] = editor->modal[i]; + delete[] editor->modal; + editor->modal = new_data; + editor->modal_length = new_length; + } + + for ( size_t i = editor->modal_used; editor->modal_cursor < i; i-- ) + editor->modal[i] = editor->modal[i-1]; + editor->modal_used++; + editor->modal[editor->modal_cursor++] = c; +} + +void editor_modal_kbkey(struct editor* editor, int kbkey) +{ + if ( editor->control ) + return; + + if ( kbkey < 0 ) + return; + + switch ( kbkey ) + { + case KBKEY_LEFT: editor_modal_left(editor); break; + case KBKEY_RIGHT: editor_modal_right(editor); break; + case KBKEY_HOME: editor_modal_home(editor); break; + case KBKEY_END: editor_modal_end(editor); break; + case KBKEY_BKSPC: editor_modal_backspace(editor); break; + case KBKEY_DELETE: editor_modal_delete(editor); break; + case KBKEY_ESC: editor_type_edit(editor); break; + } +} + +void editor_codepoint(struct editor* editor, uint32_t codepoint) +{ + if ( 128 <= codepoint ) + return; + + char c = (char) codepoint; + + if ( c == '\b' || c == 127 ) + return; + + if ( editor->mode == MODE_EDIT ) + editor_type_character(editor, c); + else + editor_modal_character(editor, c); +} + +void editor_kbkey(struct editor* editor, int kbkey) +{ + int abskbkey = kbkey < 0 ? -kbkey : kbkey; + + if ( abskbkey == KBKEY_LCTRL ) + { + editor->control = 0 <= kbkey; + return; + } + if ( abskbkey == KBKEY_LSHIFT ) + { + editor->lshift = 0 <= kbkey; + editor->shift = editor->lshift || editor->rshift; + return; + } + if ( abskbkey == KBKEY_RSHIFT ) + { + editor->rshift = 0 <= kbkey; + editor->shift = editor->lshift || editor->rshift; + return; + } + + if ( editor->mode == MODE_EDIT ) + editor_type_kbkey(editor, kbkey); + else + editor_modal_kbkey(editor, kbkey); } int main(int argc, char* argv[]) { - if ( !isatty(1) ) { error(1, errno, "stdout must be a tty"); return 1; } - struct winsize ws; - if ( tcgetwinsize(1, &ws) != 0 ) - error(1, errno, "tcgetwinsize"); - if ( ws.ws_col != 80 || ws.ws_row != 25 ) + struct editor editor; + initialize_editor(&editor); + + if ( 2 <= argc && !editor_load_file(&editor, argv[1]) ) + error(1, errno, "`%s'", argv[1]); + + unsigned old_termmode; + gettermmode(0, &old_termmode); + settermmode(0, TERMMODE_KBKEY | TERMMODE_UNICODE); + + struct terminal_state stdout_state; + make_terminal_state(stdout, &stdout_state); + reset_terminal_state(stdout, &stdout_state); + fflush(stdout); + + while ( editor.mode != MODE_QUIT ) { - fprintf(stderr, "Sorry, this application only works with 80x25 " - "terminal resolutions, please fix it. :)\n"); - exit(1); + struct terminal_state output_state; + make_terminal_state(stdout, &output_state); + editor_colorize(&editor); + render_editor(&editor, &output_state); + update_terminal(stdout, &output_state, &stdout_state); + free_terminal_state(&output_state); + fflush(stdout); + + uint32_t input; + if ( read(0, &input, sizeof(input)) != sizeof(input) ) + break; + if ( int kbkey = KBKEY_DECODE(input) ) + editor_kbkey(&editor, kbkey); + else + editor_codepoint(&editor, input); } - if ( argc < 2 ) - { - clearbuffers(); - strcpy(filename, ""); - } - else - { - strcpy(filename, argv[1]); - if ( !loadfromfile(filename) ) { clearbuffers(); } - cursorx = 0; - cursory = 0; - } + reset_terminal_state(stdout, &stdout_state); + free_terminal_state(&stdout_state); + fflush(stdout); - run(); + settermmode(0, old_termmode); return 0; }