/* * Copyright (c) 2013, 2014 Jonas 'Sortie' Termansen. * Copyright (c) 2021 Juhani 'nortti' Krekelä. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * display.c * Display handling. */ #include #include #include #include #include #include "display.h" #include "editor.h" #include "multibyte.h" #include "terminal.h" size_t editor_display_column_of_line_offset(struct editor* editor, const struct line* line, size_t offset) { if ( line->used < offset ) offset = line->used; return displayed_string_length(line->data, offset, editor->tabsize); } size_t editor_line_offset_of_display_column(struct editor* editor, const struct line* line, size_t column) { size_t current_column = 0; for ( size_t offset = 0; offset < line->used; offset++ ) { if ( column <= current_column ) return offset; wchar_t wc = line->data[offset]; if ( wc == L'\t' ) { size_t old_column = current_column; do current_column++; while ( current_column % editor->tabsize != 0 ); if ( column <= current_column ) { size_t dist_to_old = column - old_column; size_t dist_to_cur = current_column - column; if ( dist_to_old < dist_to_cur ) return offset; return offset + 1; } continue; } current_column++; } return line->used; } 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* 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 = (struct display_char*) malloc(sizeof(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++] = (struct display_char) { L' ', color}; while ( j % tabsize ); else ret[j++] = (struct display_char) { str[i], color }; } ret[ret_len] = (struct display_char) { L'\0', 0 }; if ( ret_len_ptr ) *ret_len_ptr = ret_len; return ret; } 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); size_t line_number_width = 1; for ( size_t tmp = editor->lines_used; 10 <= tmp; tmp /= 10 ) line_number_width++; if ( !editor->line_numbering ) line_number_width = 0; // Calculate the dimensions of the viewport. size_t viewport_top = 1; size_t viewport_left = line_number_width; editor->viewport_width = (size_t) state->width; if ( editor->viewport_width < viewport_left ) editor->viewport_width = 0; else editor->viewport_width -= viewport_left; editor->viewport_height = (size_t) state->height; if ( editor->viewport_height < viewport_top ) editor->viewport_height = 0; else editor->viewport_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* raw_data_line = state->data + (viewport_top + y) * state->width; struct terminal_datum* data_line = raw_data_line + viewport_left; 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 < line_number_width; x++ ) raw_data_line[x] = make_terminal_datum(L' ', 0x70); if ( editor->line_numbering && line_index < editor->lines_used ) { char line_number[sizeof(size_t) * 3]; snprintf(line_number, sizeof(line_number), "%zu", line_index + 1); size_t length = strlen(line_number); size_t offset = line_number_width - length; for ( size_t i = 0; i < length; i++ ) raw_data_line[offset + i].character = btowc(line_number[i]); } 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); } free(expanded); } // Set the rest of the terminal state. if ( has_selection ) { state->cursor_x = state->width; state->cursor_y = state->height; } else { state->cursor_x = viewport_left + viewport_select_x; state->cursor_y = viewport_top + viewport_select_y; } state->color = 0x07; if ( editor->mode == MODE_EDIT ) return; const char* msg = ""; if ( editor->mode == MODE_SAVE || editor->mode == MODE_SAVE_LOAD || editor->mode == MODE_SAVE_QUIT ) msg = "File Name to Write: "; if ( editor->mode == MODE_LOAD ) msg = "File Name to Read: "; if ( editor->mode == MODE_ASK_LOAD ) msg = "Save modified file? (Y/N): "; if ( editor->mode == MODE_ASK_QUIT ) msg = "Save modified file? (Y/N): "; if ( editor->mode == MODE_GOTO_LINE ) msg = "Go to line: "; if ( editor->mode == MODE_COMMAND ) msg = "Enter miscellaneous command: "; if ( editor->mode == MODE_SEARCH ) msg = "Search: "; 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; }