/******************************************************************************* Copyright(C) Jonas 'Sortie' Termansen 2013, 2014. 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 Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . editor.cpp 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 wchar_t* convert_mbs_to_wcs(const char* mbs) { if ( !mbs ) mbs = ""; size_t mbs_offset = 0; size_t mbs_length = strlen(mbs) + 1; mbstate_t ps; // Determinal the length of the resulting string. size_t wcs_length = 0; memset(&ps, 0, sizeof(ps)); while ( true ) { wchar_t wc; size_t count = mbrtowc(&wc, mbs + mbs_offset, mbs_length - mbs_offset, &ps); if ( count == (size_t) 0 ) break; wcs_length++; if ( count == (size_t) -1 ) { memset(&ps, 0, sizeof(ps)); mbs_offset++; // Attempt to recover. continue; } if ( count == (size_t) -2 ) break; mbs_offset += count; } wchar_t* result = (wchar_t*) malloc(sizeof(wchar_t) * (wcs_length + 1)); if ( !result ) return NULL; // Create the resulting string. mbs_offset = 0; size_t wcs_offset = 0; memset(&ps, 0, sizeof(ps)); while ( true ) { wchar_t wc; size_t count = mbrtowc(&wc, mbs + mbs_offset, mbs_length - mbs_offset, &ps); if ( count == (size_t) 0 ) break; assert(mbs_offset < wcs_length); if ( count == (size_t) -1 ) { memset(&ps, 0, sizeof(ps)); result[wcs_offset++] = L'�'; mbs_offset++; // Attempt to recover. continue; } if ( count == (size_t) -2 ) { result[wcs_offset++] = L'�'; break; } result[wcs_offset++] = wc; mbs_offset += count; } result[wcs_offset] = L'\0'; return result; } char* convert_wcs_to_mbs(const wchar_t* wcs) { const char* replacement_mb = "�"; size_t replacement_mblen = strlen(replacement_mb); if ( !wcs ) wcs = L""; mbstate_t ps; // Determinal the length of the resulting string. size_t wcs_offset = 0; size_t mbs_length = 0; memset(&ps, 0, sizeof(ps)); while ( true ) { wchar_t wc = wcs[wcs_offset++]; char mb[MB_CUR_MAX]; size_t count = wcrtomb(mb, wc, &ps); if ( count == (size_t) -1 ) { memset(&ps, 0, sizeof(ps)); mbs_length += replacement_mblen; continue; } mbs_length += count; if ( wc == L'\0' ) break; } char* result = (char*) malloc(sizeof(char) * mbs_length); if ( !result ) return NULL; // Create the resulting string. wcs_offset = 0; size_t mbs_offset = 0; memset(&ps, 0, sizeof(ps)); while ( true ) { wchar_t wc = wcs[wcs_offset++]; char mb[MB_CUR_MAX]; size_t count = wcrtomb(mb, wc, &ps); if ( count == (size_t) -1 ) { memset(&ps, 0, sizeof(ps)); assert(replacement_mblen <= mbs_length - mbs_offset); memcpy(result + mbs_offset, replacement_mb, sizeof(char) * replacement_mblen); mbs_offset += replacement_mblen; continue; } assert(count <= mbs_length - mbs_offset); memcpy(result + mbs_offset, mb, sizeof(char) * count); mbs_offset += count; if ( wc == L'\0' ) break; } return result; } struct terminal_datum { wchar_t character; uint8_t vgacolor; }; static inline struct terminal_datum make_terminal_datum(wchar_t c, uint8_t cl) { struct terminal_datum result = { c, cl }; return result; } struct terminal_state { int width; int height; int cursor_x; int cursor_y; uint8_t color; struct terminal_datum* data; }; size_t displayed_string_length(const wchar_t* str, size_t len, size_t tabsize) { size_t ret_len = 0; for ( size_t i = 0; i < len; i++ ) if ( str[i] == L'\t' ) do ret_len++; while ( ret_len % tabsize ); else ret_len++; return ret_len; } struct display_char { wchar_t character; uint8_t color; }; struct display_char* expand_tabs(const wchar_t* 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++ ) { uint8_t color = i < colors_len ? colors[i] : 7; if ( str[i] == L'\t' ) do ret[j++] = { L' ', color}; while ( j % tabsize ); else ret[j++] = { str[i], color }; } ret[ret_len] = { L'\0', 0 }; if ( ret_len_ptr ) *ret_len_ptr = ret_len; return ret; } bool is_row_column_lt(size_t ra, size_t ca, size_t rb, size_t cb) { return ra < rb || (ra == rb && ca < cb); } bool is_row_column_le(size_t ra, size_t ca, size_t rb, size_t cb) { return (ra == rb && ca == cb) || is_row_column_lt(ra, ca, rb, cb); } 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; } 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; } 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, struct terminal_datum entry, int x, int y, struct terminal_state* current) { assert(entry.character != L'\0'); size_t index = y * current->width + x; struct terminal_datum current_entry = current->data[index]; if ( entry.character == current_entry.character && entry.vgacolor == current_entry.vgacolor ) return; update_terminal_cursor(fp, x, y, current); update_terminal_color(fp, entry.vgacolor, current); mbstate_t ps; memset(&ps, 0, sizeof(ps)); char mb[MB_CUR_MAX]; size_t count = wcrtomb(mb, entry.character, &ps); if ( count == (size_t) -1 ) fputs("�", fp); else for ( size_t i = 0; i < count; i++ ) fputc(mb[i], fp); current->data[index] = entry; if ( ++current->cursor_x == current->width ) { current->cursor_x = 0; current->cursor_y++; } } 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 index = y * desired->width + x; struct terminal_datum desired_entry = desired->data[index]; update_terminal_entry(fp, desired_entry, x, y, current); } } update_terminal_cursor(fp, desired->cursor_x, desired->cursor_y, current); update_terminal_color(fp, desired->color, current); } void make_terminal_state(FILE* fp, struct terminal_state* state) { memset(state, 0, sizeof(*state)); 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_length = state->width * state->height; size_t data_size = sizeof(struct terminal_datum) * data_length; state->data = (struct terminal_datum*) malloc(data_size); for ( size_t i = 0; i < data_length; i++ ) state->data[i].character = L' ', state->data[i].vgacolor = 0; } void free_terminal_state(struct terminal_state* state) { free(state->data); } 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; 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, make_terminal_datum(L' ', 0x07), x, y, state); update_terminal_cursor(fp, 0, 0, state); } struct line { wchar_t* 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; wchar_t* modal; size_t modal_used; size_t modal_length; size_t modal_cursor; wchar_t* 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) { memset(editor, 0, sizeof(*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] = make_terminal_datum(L' ', 0x70); // Render the name of the program. const wchar_t* header_start = editor->dirty ? L" editor *" : L" editor "; size_t header_start_len = wcslen(header_start); for ( size_t i = 0; i < header_start_len; i++ ) if ( i < (size_t) state->width) state->data[i].character = 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"; wchar_t* wcs_file_name = convert_mbs_to_wcs(file_name); size_t wcs_file_name_len = wcslen(wcs_file_name); for ( size_t i = 0; i < wcs_file_name_len; i++ ) if ( header_start_len+i < (size_t) state->width) state->data[header_start_len+i].character = wcs_file_name[i]; free(wcs_file_name); // 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++ ) { size_t line_index = page_y_offset + y; struct terminal_datum* 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 : L"", 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++ ) { 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; wchar_t c = is_blank ? L' ' : chars[x].character; uint8_t color = (is_blank ? 7 : chars[x].color); data_line[x] = selected && is_blank && at_margin ? make_terminal_datum(L'|', 0x41) : selected ? make_terminal_datum(c, 0x47) : is_blank && at_margin ? make_terminal_datum(L'|', 0x01) : make_terminal_datum(c, color); } delete[] expanded; } // 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; 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: "; struct terminal_datum* data_line = state->data + (state->height - 1) * state->width; wchar_t* wcs_msg = convert_mbs_to_wcs(msg); size_t wcs_msg_len = wcslen(wcs_msg); for ( size_t i = 0; i < wcs_msg_len; i++ ) if ( i < (size_t) state->width) data_line[i] = make_terminal_datum(wcs_msg[i], 0x70); free(wcs_msg); if ( (size_t) state->width <= wcs_msg_len ) return; size_t modal_viewport_width = state->width - wcs_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; struct terminal_datum* modal_viewport_data = data_line + wcs_msg_len; const wchar_t* 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++ ) { wchar_t c = x < modal_chars_length ? modal_chars[x] : L' '; uint16_t color = editor->modal_error ? 0x17 : 0x70; uint16_t tab_color = editor->modal_error ? 0x12 : 0x71; if ( c == L'\t' ) modal_viewport_data[x] = make_terminal_datum(L'>', tab_color); else modal_viewport_data[x] = make_terminal_datum(c, color); } state->cursor_x = wcs_msg_len + modal_viewport_cursor; state->cursor_y = state->height - 1; state->color = 0x70; } size_t recognize_constant(const wchar_t* string, size_t string_length) { bool hex = false; size_t result = 0; if ( result < string_length && string[result] == L'0' ) { result++; if ( result < string_length && (string[result] == L'x' || string[result] == L'X') ) { result++; hex = true; } } bool floating = false; bool exponent = false; while ( result < string_length ) { if ( (L'0' <= string[result] && string[result] <= L'9') || (hex && L'a' <= string[result] && string[result] <= L'f') || (hex && L'A' <= string[result] && string[result] <= L'F') ) { result++; continue; } if ( string[result] == L'.' ) { if ( hex || floating ) return 0; floating = true; result++; continue; } if ( !hex && (string[result] == L'e' || string[result] == L'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'l' || string[result] == L'L') ) result++; else if ( result < string_length && (string[result] == L'f' || string[result] == L'F') ) result++; } else { if ( result < string_length && (string[result] == L'u' || string[result] == L'U') ) result++; if ( result < string_length && (string[result] == L'l' || string[result] == L'L') ) result++; if ( result < string_length && (string[result] == L'l' || string[result] == L'L') ) result++; } return result; } 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; } if ( !editor->highlight_source ) return; 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; } editor->color_lines[i].length = editor->lines[i].used; } enum { 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; bool escaped = false; size_t fixed_state = 0; size_t multi_expiration = 0; for ( size_t y = 0; y < editor->lines_used; y++ ) { struct line* line = &editor->lines[y]; for ( size_t x = 0; x < line->used; x++ ) { wchar_t pc = x ? line->data[x-1] : '\0'; wchar_t c = line->data[x]; wchar_t nc = x+1 < line->used ? line->data[x+1] : L'\0'; uint8_t color = 7; // The character makes you leave this state. if ( !fixed_state && (state == STATE_KEYWORD || state == STATE_TYPE || state == STATE_NUMBER ) ) state = STATE_INIT; // The character makes you enter a new state. if ( !fixed_state && state == STATE_INIT && c == L'#' ) state = STATE_PREPROCESSOR; // TODO: Detect NULL as a value. if ( !fixed_state && state == STATE_INIT && !(x && (iswalnum(pc) || pc == L'_')) ) { size_t number_length = recognize_constant(line->data + x, line->used - x); if ( number_length ) { state = STATE_NUMBER; fixed_state = number_length; } } if ( !fixed_state && state == STATE_INIT && c == L'\'' ) state = STATE_SINGLE_QUOTE, fixed_state = 1, escaped = false; if ( !fixed_state && state == STATE_INIT && c == L'"' ) state = STATE_DOUBLE_QUOTE, fixed_state = 1, escaped = false; if ( !fixed_state && (state == STATE_INIT || state == STATE_PREPROCESSOR) ) { if ( c == L'/' && nc == L'/' ) state = STATE_LINE_COMMENT, fixed_state = 2; else if ( c == L'/' && nc == L'*' ) { prev_state = state; multi_expiration = 0; state = STATE_MULTI_LINE_COMMENT; fixed_state = 2; } } if ( !fixed_state && state == STATE_INIT ) { const wchar_t* keywords[] = { L"alignas", L"alignof", L"and", L"and_eq", L"asm", L"bitand", L"bitor", L"break", L"case", L"catch", L"class", L"compl", L"const_cast", L"constexpr", L"continue", L"decltype", L"default", L"delete", L"do", L"dynamic_cast", L"else", L"enum", L"false", L"final", L"for", L"friend", L"goto", L"if", L"namespace", L"new", L"not", L"not_eq", L"nullptr", L"operator", L"or", L"or_eq", L"override", L"private", L"protected", L"public", L"reinterpret_cast", L"return", L"sizeof", L"static_assert", L"static_cast", L"struct", L"switch", L"template", L"this", L"thread_local", L"throw", L"true", L"try", L"typedef", L"typeid", L"typename", L"union", L"using", L"virtual", L"while", L"xor", L"xor_eq", }; bool cannot_be_keyword = x && (iswalnum(pc) || pc == L'_'); for ( size_t i = 0; !cannot_be_keyword && i < sizeof(keywords) / sizeof(keywords[0]); i++ ) { const wchar_t* keyword = keywords[i]; if ( c != keyword[0] ) continue; size_t keyword_length = wcslen(keyword); if ( (x - line->used) < keyword_length ) continue; if ( wcsncmp(line->data + x, keyword, keyword_length) != 0 ) continue; if ( keyword_length < line->used - x ) { wchar_t wc = line->data[x + keyword_length]; if ( iswalnum(wc) || wc == L'_' ) continue; } state = STATE_KEYWORD; fixed_state = keyword_length; } } if ( !fixed_state && state == STATE_INIT ) { const wchar_t* types[] = { L"auto", L"blkcnt_t", L"blksize_t", L"bool", L"char", L"char16_t", L"char32_t", L"clockid_t", L"clock_t", L"const", L"dev_t", L"double", L"explicit", L"extern", L"FILE", L"float", L"fpos_t", L"fsblkcnt_t", L"fsfilcnt_t", L"gid_t", L"id_t", L"inline", L"ino_t", L"int", L"int16_t", L"int32_t", L"int64_t", L"int8_t", L"intmax_t", L"intptr_t", L"locale_t", L"long", L"mode_t", L"mutable", L"nlink_t", L"noexcept", L"off_t", L"pid_t", L"ptrdiff_t", L"register", L"restrict", L"short", L"signed", L"size_t", L"ssize_t", L"static", L"suseconds_t", L"thread_local", L"timer_t", L"time_t", L"trace_t", L"uid_t", L"uint16_t", L"uint32_t", L"uint64_t", L"uint8_t", L"uintmax_t", L"uintptr_t", L"unsigned", L"useconds_t", L"va_list", L"void", L"volatile", L"wchar_t", }; bool cannot_be_type = x && (iswalnum(pc) || pc == L'_'); for ( size_t i = 0; !cannot_be_type && i < sizeof(types) / sizeof(types[0]); i++ ) { const wchar_t* type = types[i]; if ( c != type[0] ) continue; size_t type_length = wcslen(type); if ( (x - line->used) < type_length ) continue; if ( wcsncmp(line->data + x, type, type_length) != 0 ) continue; if ( (x - line->used) != type_length && (iswalnum(line->data[x+type_length]) || line->data[x+type_length] == L'_') ) 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 == L'\'' ) state = STATE_INIT, fixed_state = 1; if ( state == STATE_DOUBLE_QUOTE && !escaped && c == L'"' ) state = STATE_INIT, fixed_state = 1; } if ( (state == STATE_SINGLE_QUOTE || state == STATE_DOUBLE_QUOTE) ) { if ( !escaped && c == L'\\' ) 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 == L'*' && nc == L'/' ) multi_expiration = 1; } if ( state == STATE_PREPROCESSOR ) escaped = c == L'\\' && !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 wchar_t[keep_length]; keep_line->used = keep_length; keep_line->length = keep_length; memcpy(keep_line->data, old_line.data + 0, sizeof(wchar_t) * keep_length); move_line->data = new wchar_t[move_length]; move_line->used = move_length; move_line->length = move_length; memcpy(move_line->data, old_line.data + keep_length, sizeof(wchar_t) * 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]; wchar_t* keep_line_data = keep_line->data; wchar_t* gone_line_data = gone_line->data; size_t new_length = keep_line->used + gone_line->used; wchar_t* new_data = new wchar_t[new_length]; memcpy(new_data, keep_line_data, sizeof(wchar_t) * keep_line->used); memcpy(new_data + keep_line->used, gone_line_data, sizeof(wchar_t) * 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]; wchar_t* keep_line_data = keep_line->data; wchar_t* gone_line_data = gone_line->data; size_t new_length = keep_line->used + gone_line->used; wchar_t* new_data = new wchar_t[new_length]; memcpy(new_data, keep_line_data, sizeof(wchar_t) * keep_line->used); memcpy(new_data + keep_line->used, gone_line_data, sizeof(wchar_t) * 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 ( !iswspace(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 ( !iswspace(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 ( !iswspace(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 ( !iswspace(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 = convert_mbs_to_wcs(editor->current_file_name); editor->modal_used = wcslen(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 = convert_mbs_to_wcs(editor->current_file_name); editor->modal_used = wcslen(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, wchar_t 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; wchar_t* new_data = new wchar_t[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++; } } editor->clipboard = new wchar_t[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, sizeof(wchar_t) * (end_column - column)); offset += end_column - column; column = end_column; } else { memcpy(editor->clipboard + offset, line->data, sizeof(wchar_t) * line->used); editor->clipboard[offset + line->used] = L'\n'; offset += line->used + 1 /*newline*/; column = 0; row++; } } editor->clipboard[length] = L'\0'; } void editor_type_cut(struct editor* editor) { if ( editor->cursor_row == editor->select_row && editor->cursor_column == editor->select_column ) return; editor_type_copy(editor); editor_type_delete_selection(editor); } 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] == L'\n' ) editor_type_newline(editor); else editor_type_raw_character(editor, editor->clipboard[i]); } } void editor_type_character(struct editor* editor, wchar_t c) { if ( editor->control ) { switch ( towlower(c) ) { case L'c': editor_type_copy(editor); break; case L'i': editor_type_goto_line(editor); break; case L'k': editor_type_cut(editor); break; case L'o': editor->shift ? editor_type_open_as(editor) : editor_type_open(editor); break; case L'q': editor_type_quit(editor); break; case L's': editor->shift ? editor_type_save_as(editor) : editor_type_save(editor); break; case L'v': editor_type_paste(editor); break; case L'x': editor_type_cut(editor); break; } return; } if ( editor_has_selection(editor) ) editor_type_delete_selection(editor); if ( c == L'\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); mbstate_t ps; memset(&ps, 0, sizeof(ps)); bool last_newline = false; int ic; while ( (ic = fgetc(fp)) != EOF ) { if ( last_newline ) { editor_type_newline(editor); last_newline = false; } char c = (char) ic; wchar_t wc; size_t count = mbrtowc(&wc, &c, 1, &ps); if ( count == (size_t) 0 ) continue; if ( count == (size_t) -1 ) { memset(&ps, 0, sizeof(ps)); wc = L'�'; } if ( count == (size_t) -2 ) continue; assert(wc != L'\0'); if ( !(last_newline = wc == L'\n') ) editor_type_raw_character(editor, wc); } if ( !mbsinit(&ps) ) editor_type_raw_character(editor, L'�'); editor_cursor_set(editor, 0, 0); return true; } bool should_highlight_path(const char* path) { 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; } bool editor_load_file(struct editor* editor, const char* path) { if ( FILE* fp = fopen(path, "r") ) { 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; mbstate_t ps; memset(&ps, 0, sizeof(ps)); for ( size_t i = 0; i < editor->lines_used; i++ ) { char mb[MB_CUR_MAX]; for ( size_t n = 0; n < editor->lines[i].used; n++ ) { mbstate_t saved_ps = ps; wchar_t wc = editor->lines[i].data[n]; size_t count = wcrtomb(mb, wc, &ps); if ( count == (size_t) -1 ) { ps = saved_ps; count = wcrtomb(mb, L'�', &ps); assert(count != (size_t) -1); } fwrite(mb, sizeof(char), count, fp); } size_t count = wcrtomb(mb, L'\n', &ps); assert(count != (size_t) -1); fwrite(mb, sizeof(char), count, fp); } 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, wchar_t c) { if ( editor->control ) { switch ( towlower(c) ) { case L'c': editor_type_edit(editor); break; } return; } editor->modal_error = false; if ( c == L'\n' ) { if ( !editor->modal ) editor->modal = (wchar_t*) malloc(sizeof(wchar_t) * 1); editor->modal[editor->modal_used] = L'\0'; char* param = convert_wcs_to_mbs(editor->modal); 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; wchar_t* new_data = (wchar_t*) malloc(sizeof(wchar_t) * (new_length + 1)); for ( size_t i = 0; i < editor->modal_used; i++ ) new_data[i] = editor->modal[i]; free(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) { wchar_t c = (wchar_t) codepoint; if ( c == L'\b' || c == 127 /* delete */ ) 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[]) { setlocale(LC_ALL, ""); if ( !isatty(0) ) error(1, errno, "standard input"); if ( !isatty(1) ) error(1, errno, "standard output"); 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 ) { 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); } reset_terminal_state(stdout, &stdout_state); free_terminal_state(&stdout_state); fflush(stdout); settermmode(0, old_termmode); return 0; }