diff --git a/Makefile b/Makefile index af15a0b4..9195e3da 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ libpthread \ dispd \ bench \ carray \ +editor \ ext \ games \ mbr \ diff --git a/doc/user-guide b/doc/user-guide index 6f6754dc..e64e4718 100644 --- a/doc/user-guide +++ b/doc/user-guide @@ -311,14 +311,18 @@ Editing Files You can use the `editor` program to edit files. The editor itself is fairly simple to use. It currently supports these keyboard commands: -* `Ctrl-Q` - Exit -* `Ctrl-O` - Open a file -* `Ctrl-S` - Save a file +* `Ctrl-C` - Copy * `Ctrl-I` - Go to line -* `ESC tabsize ` - change tab size +* `Ctrl-K` - Cut +* `Ctrl-O` - Open a file +* `Ctrl-Q` - Exit +* `Ctrl-S` - Save a file +* `Ctrl-V` - Paste +* `Ctrl-X` - Cut +* `ESC language ` - enable syntax highlighting * `ESC margin ` - add right margin at column * `ESC popen ` - open command output -* `ESC language ` - enable syntax highlighting +* `ESC tabsize ` - change tab size It is not currently possible to port third party editors because the terminal implementation is not standards-compliant enough and is seriously lacking. diff --git a/editor/.gitignore b/editor/.gitignore new file mode 100644 index 00000000..3626c154 --- /dev/null +++ b/editor/.gitignore @@ -0,0 +1,2 @@ +editor +*.o diff --git a/editor/Makefile b/editor/Makefile new file mode 100644 index 00000000..64bd74ea --- /dev/null +++ b/editor/Makefile @@ -0,0 +1,41 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/version.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=$(DEFAULT_OPTLEVEL) +CXXFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\" +CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti + +BINARY=editor + +OBJS=\ +command.o \ +cursor.o \ +display.o \ +editor.o \ +highlight.o \ +input.o \ +modal.o \ +multibyte.o \ +terminal.o \ + +all: $(BINARY) + +.PHONY: all install clean + +$(BINARY): $(OBJS) + $(CXX) $(OBJS) -o $(BINARY) $(CXXFLAGS) $(LIBS) + +%.o: %.c++ + $(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(BINARY) $(DESTDIR)$(BINDIR) + +clean: + rm -f $(BINARY) $(OBJS) *.o diff --git a/editor/command.c++ b/editor/command.c++ new file mode 100644 index 00000000..4ee512ef --- /dev/null +++ b/editor/command.c++ @@ -0,0 +1,734 @@ +/******************************************************************************* + + 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 . + + command.c++ + Editor commands. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include +#include +#include +#include + +#include "command.h++" +#include "cursor.h++" +#include "display.h++" +#include "editor.h++" +#include "multibyte.h++" +#include "terminal.h++" + +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); +} diff --git a/editor/command.h++ b/editor/command.h++ new file mode 100644 index 00000000..59e7bdc7 --- /dev/null +++ b/editor/command.h++ @@ -0,0 +1,70 @@ +/******************************************************************************* + + 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 . + + command.h++ + Editor commands. + +*******************************************************************************/ + +#ifndef EDITOR_COMMAND_HXX +#define EDITOR_COMMAND_HXX + +#include + +struct editor; + +void editor_type_newline(struct editor* editor); +void editor_type_combine_with_last(struct editor* editor); +void editor_type_backspace(struct editor* editor); +void editor_type_combine_with_next(struct editor* editor); +void editor_type_delete(struct editor* editor); +void editor_type_delete_selection(struct editor* editor); +void editor_type_left(struct editor* editor); +void editor_type_select_left(struct editor* editor); +void editor_type_right(struct editor* editor); +void editor_type_select_right(struct editor* editor); +void editor_type_up(struct editor* editor); +void editor_type_select_up(struct editor* editor); +void editor_type_down(struct editor* editor); +void editor_type_select_down(struct editor* editor); +void editor_skip_leading(struct editor* editor); +void editor_select_skip_leading(struct editor* editor); +void editor_type_home(struct editor* editor); +void editor_type_select_home(struct editor* editor); +void editor_skip_ending(struct editor* editor); +void editor_select_skip_ending(struct editor* editor); +void editor_type_end(struct editor* editor); +void editor_type_select_end(struct editor* editor); +void editor_type_page_up(struct editor* editor); +void editor_type_select_page_up(struct editor* editor); +void editor_type_page_down(struct editor* editor); +void editor_type_select_page_down(struct editor* editor); +void editor_type_edit(struct editor* editor); +void editor_type_goto_line(struct editor* editor); +void editor_type_save(struct editor* editor); +void editor_type_save_as(struct editor* editor); +void editor_type_open(struct editor* editor); +void editor_type_open_as(struct editor* editor); +void editor_type_quit(struct editor* editor); +void editor_type_command(struct editor* editor); +void editor_type_raw_character(struct editor* editor, wchar_t c); +void editor_type_copy(struct editor* editor); +void editor_type_cut(struct editor* editor); +void editor_type_paste(struct editor* editor); +void editor_type_character(struct editor* editor, wchar_t c); + +#endif diff --git a/editor/cursor.c++ b/editor/cursor.c++ new file mode 100644 index 00000000..0ff24d9a --- /dev/null +++ b/editor/cursor.c++ @@ -0,0 +1,132 @@ +/******************************************************************************* + + 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 . + + command.c++ + Editor cursor. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include + +#include "cursor.h++" +#include "display.h++" +#include "editor.h++" + +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); +} diff --git a/editor/cursor.h++ b/editor/cursor.h++ new file mode 100644 index 00000000..5841f493 --- /dev/null +++ b/editor/cursor.h++ @@ -0,0 +1,46 @@ +/******************************************************************************* + + 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 . + + command.h++ + Editor cursor. + +*******************************************************************************/ + +#ifndef EDITOR_CURSOR_HXX +#define EDITOR_CURSOR_HXX + +#include + +struct editor; + +size_t editor_select_column_set(struct editor* editor, size_t x); +size_t editor_select_row_set(struct editor* editor, size_t y); +void editor_select_set(struct editor* editor, size_t y, size_t x); +size_t editor_select_column_dec(struct editor* editor); +size_t editor_select_column_inc(struct editor* editor); +size_t editor_select_row_dec(struct editor* editor); +size_t editor_select_row_inc(struct editor* editor); + +size_t editor_cursor_column_set(struct editor* editor, size_t x); +size_t editor_cursor_row_set(struct editor* editor, size_t y); +void editor_cursor_set(struct editor* editor, size_t y, size_t x); +size_t editor_cursor_column_dec(struct editor* editor); +size_t editor_cursor_column_inc(struct editor* editor); +size_t editor_cursor_row_dec(struct editor* editor); +size_t editor_cursor_row_inc(struct editor* editor); + +#endif diff --git a/editor/display.c++ b/editor/display.c++ new file mode 100644 index 00000000..5b280b83 --- /dev/null +++ b/editor/display.c++ @@ -0,0 +1,233 @@ +/******************************************************************************* + + 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 . + + display.c++ + Display handling. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include "display.h++" +#include "editor.h++" +#include "multibyte.h++" +#include "terminal.h++" + +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 = 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; +} + +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; +} diff --git a/editor/display.h++ b/editor/display.h++ new file mode 100644 index 00000000..3d903eb8 --- /dev/null +++ b/editor/display.h++ @@ -0,0 +1,44 @@ +/******************************************************************************* + + 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 . + + display.h++ + Display handling. + +*******************************************************************************/ + +#ifndef EDITOR_DISPLAY_HXX +#define EDITOR_DISPLAY_HXX + +#include +#include + +struct editor; +struct terminal_state; + +struct display_char +{ + wchar_t character; + uint8_t color; +}; + +size_t displayed_string_length(const wchar_t* str, size_t len, size_t tabsize); +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); +void render_editor(struct editor* editor, struct terminal_state* state); + +#endif diff --git a/editor/editor.c++ b/editor/editor.c++ new file mode 100644 index 00000000..c73d240d --- /dev/null +++ b/editor/editor.c++ @@ -0,0 +1,276 @@ +/******************************************************************************* + + 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.c++ + Editor. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "command.h++" +#include "cursor.h++" +#include "display.h++" +#include "editor.h++" +#include "highlight.h++" +#include "input.h++" +#include "terminal.h++" + +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; +} + +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 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; +} + +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]); + + struct editor_input editor_input; + editor_input_begin(&editor_input); + + 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); + + editor_input_process(&editor_input, &editor); + } + + reset_terminal_state(stdout, &stdout_state); + free_terminal_state(&stdout_state); + fflush(stdout); + + editor_input_end(&editor_input); + + return 0; +} diff --git a/editor/editor.h++ b/editor/editor.h++ new file mode 100644 index 00000000..709657f5 --- /dev/null +++ b/editor/editor.h++ @@ -0,0 +1,102 @@ +/******************************************************************************* + + 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.h++ + Editor. + +*******************************************************************************/ + +#ifndef EDITOR_EDITOR_HXX +#define EDITOR_EDITOR_HXX + +#include +#include +#include + +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; +}; + +__attribute__((unused)) +static inline bool editor_has_selection(struct editor* editor) +{ + return !(editor->cursor_row == editor->select_row && + editor->cursor_column == editor->select_column); +} + +void initialize_editor(struct editor* editor); +void editor_reset_contents(struct editor* editor); +bool editor_load_file_contents(struct editor* editor, FILE* fp); +bool editor_load_file(struct editor* editor, const char* path); +bool editor_load_popen(struct editor* editor, const char* cmd); +bool editor_save_file(struct editor* editor, const char* path); + +#endif diff --git a/editor/highlight.c++ b/editor/highlight.c++ new file mode 100644 index 00000000..c1c19aa0 --- /dev/null +++ b/editor/highlight.c++ @@ -0,0 +1,495 @@ +/******************************************************************************* + + 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 . + + highlight.c++ + Syntax highlighting. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include +#include +#include + +#include "editor.h++" +#include "highlight.h++" + +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; +} + +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; + } + } +} diff --git a/editor/highlight.h++ b/editor/highlight.h++ new file mode 100644 index 00000000..b6c41ec3 --- /dev/null +++ b/editor/highlight.h++ @@ -0,0 +1,31 @@ +/******************************************************************************* + + 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 . + + highlight.h++ + Syntax highlighting. + +*******************************************************************************/ + +#ifndef EDITOR_HIGHLIGHT_HXX +#define EDITOR_HIGHLIGHT_HXX + +struct editor; + +bool should_highlight_path(const char* path); +void editor_colorize(struct editor* editor); + +#endif diff --git a/editor/input.c++ b/editor/input.c++ new file mode 100644 index 00000000..b6edd588 --- /dev/null +++ b/editor/input.c++ @@ -0,0 +1,196 @@ +/******************************************************************************* + + 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 . + + input.c++ + Keyboard input. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#if defined(__sortix__) +#include +#include +#endif + +#include +#include +#include + +#include "command.h++" +#include "editor.h++" +#include "input.h++" +#include "modal.h++" + +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); +} + +#if defined(__sortix__) +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_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_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); +} +#endif + +void editor_input_begin(struct editor_input* editor_input) +{ +#if defined(__sortix__) + gettermmode(0, &editor_input->saved_termmode); + settermmode(0, TERMMODE_KBKEY | TERMMODE_UNICODE); +#else + (void) editor_input; +#endif +} + +void editor_input_process(struct editor_input* editor_input, + struct editor* editor) +{ +#if defined(__sortix__) + (void) editor_input; + uint32_t input; + if ( read(0, &input, sizeof(input)) != sizeof(input) ) + return; + if ( int kbkey = KBKEY_DECODE(input) ) + editor_kbkey(editor, kbkey); + else + editor_codepoint(editor, input); +#else + (void) editor_input; + (void) editor; +#endif +} + +void editor_input_end(struct editor_input* editor_input) +{ +#if defined(__sortix__) + settermmode(0, editor_input->saved_termmode); +#else + (void) editor_input; +#endif +} diff --git a/editor/input.h++ b/editor/input.h++ new file mode 100644 index 00000000..2adaa3db --- /dev/null +++ b/editor/input.h++ @@ -0,0 +1,40 @@ +/******************************************************************************* + + 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 . + + input.h++ + Keyboard input. + +*******************************************************************************/ + +#ifndef EDITOR_INPUT_HXX +#define EDITOR_INPUT_HXX + +struct editor; + +struct editor_input +{ +#if defined(__sortix__) + unsigned int saved_termmode; +#endif +}; + +void editor_input_begin(struct editor_input* editor_input); +void editor_input_process(struct editor_input* editor_input, + struct editor* editor); +void editor_input_end(struct editor_input* editor_input); + +#endif diff --git a/editor/modal.c++ b/editor/modal.c++ new file mode 100644 index 00000000..afe9a94f --- /dev/null +++ b/editor/modal.c++ @@ -0,0 +1,304 @@ +/******************************************************************************* + + 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 . + + modal.c++ + Modal commands. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include +#include +#include +#include + +#include "command.h++" +#include "cursor.h++" +#include "editor.h++" +#include "modal.h++" +#include "multibyte.h++" + +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_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; +} diff --git a/editor/modal.h++ b/editor/modal.h++ new file mode 100644 index 00000000..ed8e1869 --- /dev/null +++ b/editor/modal.h++ @@ -0,0 +1,48 @@ +/******************************************************************************* + + 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 . + + modal.h++ + Modal commands. + +*******************************************************************************/ + +#ifndef EDITOR_MODAL_HXX +#define EDITOR_MODAL_HXX + +struct editor; + +void editor_modal_left(struct editor* editor); +void editor_modal_right(struct editor* editor); +void editor_modal_home(struct editor* editor); +void editor_modal_end(struct editor* editor); +void editor_modal_backspace(struct editor* editor); +void editor_modal_delete(struct editor* editor); + +void editor_modal_load(struct editor* editor, const char* path); +void editor_modal_save(struct editor* editor, const char* path); +void editor_modal_ask_quit(struct editor* editor, const char* answer); +void editor_modal_goto_line(struct editor* editor, const char* linestr); +void editor_modal_margin(struct editor* editor, const char* marginstr); +void editor_modal_popen(struct editor* editor, const char* cmd); +void editor_modal_tabsize(struct editor* editor, const char* tabsizestr); +void editor_modal_language(struct editor* editor, const char* language); + +bool is_modal_command(const char* cmd, const char* candidate, const char** rest); +void editor_modal_command(struct editor* editor, const char* cmd); +void editor_modal_character(struct editor* editor, wchar_t c); + +#endif diff --git a/editor/multibyte.c++ b/editor/multibyte.c++ new file mode 100644 index 00000000..2f113685 --- /dev/null +++ b/editor/multibyte.c++ @@ -0,0 +1,161 @@ +/******************************************************************************* + + 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 . + + multibyte.c++ + Conversion from multibyte strings to wide strings. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include +#include +#include + +#include "multibyte.h++" + +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; +} diff --git a/editor/multibyte.h++ b/editor/multibyte.h++ new file mode 100644 index 00000000..10a4f966 --- /dev/null +++ b/editor/multibyte.h++ @@ -0,0 +1,31 @@ +/******************************************************************************* + + 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 . + + multibyte.h++ + Conversion from multibyte strings to wide strings. + +*******************************************************************************/ + +#ifndef EDITOR_MULTIBYTE_HXX +#define EDITOR_MULTIBYTE_HXX + +#include + +wchar_t* convert_mbs_to_wcs(const char* mbs); +char* convert_wcs_to_mbs(const wchar_t* wcs); + +#endif diff --git a/editor/terminal.c++ b/editor/terminal.c++ new file mode 100644 index 00000000..43bbfc0d --- /dev/null +++ b/editor/terminal.c++ @@ -0,0 +1,142 @@ +/******************************************************************************* + + 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 . + + terminal.c++ + Terminal handling. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "terminal.h++" + +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); +} diff --git a/editor/terminal.h++ b/editor/terminal.h++ new file mode 100644 index 00000000..acc60643 --- /dev/null +++ b/editor/terminal.h++ @@ -0,0 +1,104 @@ +/******************************************************************************* + + 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 . + + terminal.h++ + Terminal handling. + +*******************************************************************************/ + +#ifndef EDITOR_TERMINAL_HXX +#define EDITOR_TERMINAL_HXX + +#include +#include +#include + +struct terminal_datum +{ + wchar_t character; + uint8_t vgacolor; +}; + +__attribute__((unused)) +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; +}; + +__attribute__((unused)) +static inline +bool is_row_column_lt(size_t ra, size_t ca, size_t rb, size_t cb) +{ + return ra < rb || (ra == rb && ca < cb); +} + +__attribute__((unused)) +static inline +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); +} + +__attribute__((unused)) +static inline +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; +} + +__attribute__((unused)) +static inline +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); +void update_terminal_cursor(FILE* fp, int x, int y, + struct terminal_state* current); +void update_terminal_entry(FILE* fp, struct terminal_datum entry, int x, int y, + struct terminal_state* current); +void update_terminal(FILE* fp, + struct terminal_state* desired, + struct terminal_state* current); +void make_terminal_state(FILE* fp, struct terminal_state* state); +void free_terminal_state(struct terminal_state* state); +void reset_terminal_state(FILE* fp, struct terminal_state* state); + + +#endif diff --git a/utils/.gitignore b/utils/.gitignore index 2c1719d7..e5689b80 100644 --- a/utils/.gitignore +++ b/utils/.gitignore @@ -13,7 +13,6 @@ date dirname du echo -editor env expr false diff --git a/utils/Makefile b/utils/Makefile index 0e88bd63..26acda7e 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -25,7 +25,6 @@ date \ dirname \ du \ echo \ -editor \ env \ expr \ false \ diff --git a/utils/editor.cpp b/utils/editor.cpp deleted file mode 100644 index 28e22761..00000000 --- a/utils/editor.cpp +++ /dev/null @@ -1,2472 +0,0 @@ -/******************************************************************************* - - 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; -}