diff --git a/editor/Makefile b/editor/Makefile index 48d6f674..d06f8404 100644 --- a/editor/Makefile +++ b/editor/Makefile @@ -1,4 +1,3 @@ -SOFTWARE_MEANT_FOR_SORTIX=1 include ../build-aux/platform.mak include ../build-aux/compiler.mak include ../build-aux/version.mak @@ -10,6 +9,10 @@ CFLAGS?=$(OPTLEVEL) CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\" CFLAGS:=$(CFLAGS) -Wall -Wextra +ifeq ($(HOST_IS_SORTIX),0) + CPPFLAGS+=-D_GNU_SOURCE +endif + BINARY=editor OBJS=\ diff --git a/editor/command.c b/editor/command.c index 706004e2..883d6bf0 100644 --- a/editor/command.c +++ b/editor/command.c @@ -814,6 +814,11 @@ void editor_type_paste(struct editor* editor) } } +void editor_type_suspend(struct editor* editor) +{ + editor->suspend_requested = true; +} + void editor_type_character(struct editor* editor, wchar_t c) { if ( editor->control ) @@ -832,6 +837,7 @@ void editor_type_character(struct editor* editor, wchar_t c) editor_type_save(editor); break; case L'v': editor_type_paste(editor); break; case L'x': editor_type_cut(editor); break; + case L'z': editor_type_suspend(editor); break; } return; } diff --git a/editor/command.h b/editor/command.h index 19c0b1c5..9eaff5e7 100644 --- a/editor/command.h +++ b/editor/command.h @@ -72,6 +72,7 @@ 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_suspend(struct editor* editor); void editor_type_character(struct editor* editor, wchar_t c); #endif diff --git a/editor/editor.c b/editor/editor.c index e50a25de..2aab3f28 100644 --- a/editor/editor.c +++ b/editor/editor.c @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include #include @@ -91,7 +91,7 @@ void editor_load_config_path(struct editor* editor, const char* path) if ( !fp ) { if ( errno != ENOENT ) - error(0, errno, "%s", path); + warn("%s", path); return; } char* line = NULL; @@ -106,7 +106,7 @@ void editor_load_config_path(struct editor* editor, const char* path) editor_modal_command_config(editor, line); } if ( ferror(fp) ) - error(0, errno, "getline: %s", path); + warn("getline: %s", path); fclose(fp); } @@ -119,7 +119,7 @@ void editor_load_config(struct editor* editor) char* path; if ( asprintf(&path, "%s/.editor", home) < 0 ) { - error(0, errno, "malloc"); + warn("malloc"); return; } editor_load_config_path(editor, path); @@ -275,9 +275,9 @@ int main(int argc, char* argv[]) setlocale(LC_ALL, ""); if ( !isatty(0) ) - error(1, errno, "standard input"); + err(1, "standard input"); if ( !isatty(1) ) - error(1, errno, "standard output"); + err(1, "standard output"); struct editor editor; initialize_editor(&editor); @@ -285,7 +285,7 @@ int main(int argc, char* argv[]) editor_load_config(&editor); if ( 2 <= argc && !editor_load_file(&editor, argv[1]) ) - error(1, errno, "`%s'", argv[1]); + err(1, "`%s'", argv[1]); struct editor_input editor_input; editor_input_begin(&editor_input); @@ -297,6 +297,14 @@ int main(int argc, char* argv[]) while ( editor.mode != MODE_QUIT ) { + if ( editor.suspend_requested ) + { + editor_input_suspend(&editor_input); + editor.suspend_requested = false; + reset_terminal_state(stdout, &stdout_state); + fflush(stdout); + } + struct terminal_state output_state; make_terminal_state(stdout, &output_state); editor_colorize(&editor); diff --git a/editor/editor.h b/editor/editor.h index ee8d5406..78114fb0 100644 --- a/editor/editor.h +++ b/editor/editor.h @@ -86,6 +86,7 @@ struct editor bool modal_error; enum language highlight_source; bool line_numbering; + bool suspend_requested; }; __attribute__((unused)) diff --git a/editor/input.c b/editor/input.c index d5ab2fc2..8c1b7dae 100644 --- a/editor/input.c +++ b/editor/input.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2014 Jonas 'Sortie' Termansen. + * Copyright (c) 2013, 2014, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,19 +17,88 @@ #if defined(__sortix__) #include -#include #endif +#include +#include +#include #include #include #include +#include +#include +#include #include +#include #include "command.h" #include "editor.h" #include "input.h" #include "modal.h" +struct terminal_sequence +{ + const char* sequence; + int kbkey; + bool control; + bool shift; +}; + +// TODO: The terminal doesn't deliver shift-pageup and shift-pagedown events. +// TODO: The terminal doesn't deliver shift-home and shift-end events. + +struct terminal_sequence terminal_sequences[] = +{ + { "\e[1;2A", KBKEY_UP, false, true }, + { "\e[1;2B", KBKEY_DOWN, false, true }, + { "\e[1;2C", KBKEY_RIGHT, false, true }, + { "\e[1;2D", KBKEY_LEFT, false, true }, + { "\e[1;2F", KBKEY_END, false, true }, + { "\e[1;2H", KBKEY_HOME, false, true }, + { "\e[1;2~", KBKEY_HOME, false, true }, + { "\e[1;5A", KBKEY_UP, true, false }, + { "\e[1;5B", KBKEY_DOWN, true, false }, + { "\e[1;5C", KBKEY_RIGHT, true, false }, + { "\e[1;5D", KBKEY_LEFT, true, false }, + { "\e[1;5F", KBKEY_END, true, false }, + { "\e[1;5H", KBKEY_HOME, true, false }, + { "\e[1;5~", KBKEY_HOME, true, false }, + { "\e[1;6A", KBKEY_UP, true, true }, + { "\e[1;6B", KBKEY_DOWN, true, true }, + { "\e[1;6C", KBKEY_RIGHT, true, true }, + { "\e[1;6D", KBKEY_LEFT, true, true }, + { "\e[1;6F", KBKEY_END, true, true }, + { "\e[1;6H", KBKEY_HOME, true, true }, + { "\e[1;6~", KBKEY_HOME, true, true }, + { "\e[1~", KBKEY_HOME, false, false }, + { "\e[3;2~", KBKEY_DELETE, false, true }, + { "\e[3;5~", KBKEY_DELETE, true, false }, + { "\e[3;6~", KBKEY_DELETE, true, true }, + { "\e[3~", KBKEY_DELETE, false, false }, + { "\e[4;2~", KBKEY_END, false, true }, + { "\e[4;5~", KBKEY_END, true, false }, + { "\e[4;6~", KBKEY_END, true, true }, + { "\e[4~", KBKEY_END, false, false }, + { "\e[5;2~", KBKEY_PGUP, false, true }, + { "\e[5;5~", KBKEY_PGUP, true, false }, + { "\e[5;6~", KBKEY_PGUP, true, true }, + { "\e[5~", KBKEY_PGUP, false, false }, + { "\e[6;2~", KBKEY_PGDOWN, false, true }, + { "\e[6;5~", KBKEY_PGDOWN, true, false }, + { "\e[6;6~", KBKEY_PGDOWN, true, true }, + { "\e[6~", KBKEY_PGDOWN, false, false }, + { "\e[A", KBKEY_UP, false, false }, + { "\e[B", KBKEY_DOWN, false, false }, + { "\e[C", KBKEY_RIGHT, false, false }, + { "\e[D", KBKEY_LEFT, false, false }, + { "\e[F", KBKEY_END, false, false }, + { "\e[H", KBKEY_HOME, false, false }, + { "\e:", KBKEY_ESC, false, false }, + { "\eOF", KBKEY_END, false, false }, + { "\eOH", KBKEY_HOME, false, false }, + { "\x7F", KBKEY_BKSPC, false, false }, +}; + void editor_codepoint(struct editor* editor, uint32_t codepoint) { wchar_t c = (wchar_t) codepoint; @@ -43,7 +112,6 @@ void editor_codepoint(struct editor* editor, uint32_t codepoint) editor_modal_character(editor, c); } -#if defined(__sortix__) void editor_type_kbkey(struct editor* editor, int kbkey) { if ( kbkey < 0 ) @@ -131,67 +199,232 @@ void editor_modal_kbkey(struct editor* editor, int kbkey) 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); } + +void editor_emulate_kbkey(struct editor* editor, + int kbkey, + bool control, + bool shift) +{ + editor->control = control; + editor->lshift = shift; + editor->rshift = false; + editor->shift = shift; + + editor_kbkey(editor, kbkey); + editor_kbkey(editor, -kbkey); + + editor->control = false; + editor->lshift = false; + editor->rshift = false; + editor->shift = false; +} + +void editor_emulate_control_letter(struct editor* editor, uint32_t c) +{ +#if !defined(__sortix__) + if ( c == 'Z' ) + { + raise(SIGSTOP); + } #endif + editor->control = true; + editor_codepoint(editor, c); + editor->control = false; +} + 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 + memset(editor_input, 0, sizeof(*editor_input)); + + tcgetattr(0, &editor_input->saved_termios); + struct termios tcattr; + memcpy(&tcattr, &editor_input->saved_termios, sizeof(struct termios)); + tcattr.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); + tcattr.c_iflag |= ICRNL; + tcattr.c_cc[VMIN] = 1; + tcattr.c_cc[VTIME] = 0; + tcsetattr(0, TCSADRAIN, &tcattr); + if ( getenv("TERM") && strcmp(getenv("TERM"), "sortix") != 0 ) + { + printf("\e[?1049h"); + fflush(stdout); + } } 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; - int kbkey; - if ( (kbkey = KBKEY_DECODE(input)) ) - editor_kbkey(editor, kbkey); + bool was_ambiguous_escape = editor_input->ambiguous_escape; + editor_input->ambiguous_escape = false; + + if ( was_ambiguous_escape ) + fcntl(0, F_SETFL, fcntl(0, F_GETFL, (void*) NULL) | O_NONBLOCK); + + unsigned char uc; + ssize_t amount_read = read(0, &uc, sizeof(uc)); + + if ( was_ambiguous_escape ) + fcntl(0, F_SETFL, fcntl(0, F_GETFL, (void*) NULL) &~ O_NONBLOCK); + + if ( amount_read != sizeof(uc) ) + { + if ( was_ambiguous_escape && + (errno == EWOULDBLOCK || errno == EAGAIN) ) + uc = ':'; + else + return; + } + +#if 0 + if ( 1 <= uc && uc <= 26 ) + fprintf(stderr, "Input: ^%c\n", 'A' + uc - 1); + else if ( uc == '\e' ) + fprintf(stderr, "Input: ^[\n"); else - editor_codepoint(editor, input); -#else - (void) editor_input; - (void) editor; + fprintf(stderr, "Input: '%c'\n", uc); #endif +#if 0 + fputc(uc, stderr); +#endif + + if ( editor_input->termseq_used < MAX_TERMSEQ_SIZE ) + editor_input->termseq[editor_input->termseq_used++] = (char) uc; + + size_t num_seqs = sizeof(terminal_sequences) / sizeof(terminal_sequences[0]); + + while ( editor_input->termseq_seen < editor_input->termseq_used ) + { + size_t match = 0; + size_t match_size = 0; + bool full_match = false; + bool partial_match = false; + + for ( size_t i = 0; i < num_seqs; i++ ) + { + struct terminal_sequence* terminal_sequence = &terminal_sequences[i]; + const char* sequence = terminal_sequence->sequence; + + bool potential_partial_match = false; + for ( size_t n = 0; n < editor_input->termseq_used; n++ ) + { + if ( sequence[n] != editor_input->termseq[n] ) + { + potential_partial_match = false; + break; + } + + if ( sequence[n+1] == '\0' ) + { + potential_partial_match = false; + full_match = true; + match = i; + match_size = n + 1; + break; + } + + potential_partial_match = true; + } + + if ( potential_partial_match ) + partial_match = true; + } + + if ( full_match ) + { + editor_emulate_kbkey(editor, + terminal_sequences[match].kbkey, + terminal_sequences[match].control, + terminal_sequences[match].shift); + memmove(editor_input->termseq, + editor_input->termseq + match_size, + editor_input->termseq_used - match_size); + editor_input->termseq_used -= match_size; + editor_input->termseq_seen = 0; + continue; + } + + if ( partial_match ) + { + editor_input->termseq_seen = editor_input->termseq_used; + + // HACK: We can't reliably tell an actual escape press apart from + // the beginning of an escape sequence. However, we could use + // timing to get close to the truth, through the assumption + // that a following non-blocking read will fail only if this + // was a single escape press. + if ( editor_input->termseq_used == 1 && + editor_input->termseq[0] == '\e' ) + { + editor_input->ambiguous_escape = true; + return editor_input_process(editor_input, editor); + } + + continue; + } + + char input = editor_input->termseq[0]; + + if ( 1 <= input && input <= 26 && input != '\t' && input != '\n' ) + { + editor_emulate_control_letter(editor, L'A' + input - 1); + } + else + { + wchar_t wc; + size_t amount = mbrtowc(&wc, &input, 1, &editor_input->ps); + if ( amount == (size_t) -1 ) + memset(&editor_input->ps, 0, sizeof(editor_input->ps)); + if ( amount == (size_t) 1 ) + editor_codepoint(editor, (uint32_t) wc); + } + + memmove(editor_input->termseq, + editor_input->termseq + 1, + editor_input->termseq_used - 1); + editor_input->termseq_used--; + editor_input->termseq_seen = 0; + } } void editor_input_end(struct editor_input* editor_input) { -#if defined(__sortix__) - settermmode(0, editor_input->saved_termmode); -#else + if ( getenv("TERM") && strcmp(getenv("TERM"), "sortix") != 0 ) + { + printf("\e[?1049l"); + fflush(stdout); + } + tcsetattr(0, TCSADRAIN, &editor_input->saved_termios); +} + +void editor_input_suspend(struct editor_input* editor_input) +{ (void) editor_input; + +#if !defined(__sortix__) + struct termios current_termios; + + if ( getenv("TERM") && strcmp(getenv("TERM"), "sortix") != 0 ) + { + printf("\e[?1049l"); + fflush(stdout); + } + + tcgetattr(0, ¤t_termios); + + raise(SIGSTOP); + + tcsetattr(0, TCSADRAIN, ¤t_termios); + + if ( getenv("TERM") && strcmp(getenv("TERM"), "sortix") != 0 ) + { + printf("\e[?1049h"); + fflush(stdout); + } #endif } diff --git a/editor/input.h b/editor/input.h index c3f9b699..c2662e83 100644 --- a/editor/input.h +++ b/editor/input.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2014 Jonas 'Sortie' Termansen. + * Copyright (c) 2013, 2014, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,18 +20,41 @@ #ifndef EDITOR_INPUT_H #define EDITOR_INPUT_H +#include +#include + +#if !defined(__sortix__) +#define KBKEY_ESC 0x01 +#define KBKEY_BKSPC 0x0E +#define KBKEY_HOME (0x80 + 0x47) +#define KBKEY_UP (0x80 + 0x48) +#define KBKEY_PGUP (0x80 + 0x49) +#define KBKEY_LEFT (0x80 + 0x4B) +#define KBKEY_RIGHT (0x80 + 0x4D) +#define KBKEY_END (0x80 + 0x4F) +#define KBKEY_DOWN (0x80 + 0x50) +#define KBKEY_PGDOWN (0x80 + 0x51) +#define KBKEY_DELETE (0x80 + 0x53) +#endif + struct editor; +#define MAX_TERMSEQ_SIZE 16 + struct editor_input { -#if defined(__sortix__) - unsigned int saved_termmode; -#endif + struct termios saved_termios; + mbstate_t ps; + char termseq[MAX_TERMSEQ_SIZE]; + size_t termseq_used; + size_t termseq_seen; + bool ambiguous_escape; }; 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); +void editor_input_suspend(struct editor_input* editor_input); #endif diff --git a/editor/terminal.c b/editor/terminal.c index 479d9b72..ba694213 100644 --- a/editor/terminal.c +++ b/editor/terminal.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2014 Jonas 'Sortie' Termansen. + * Copyright (c) 2013, 2014, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,8 @@ * Terminal handling. */ +#include + #include #include #include @@ -37,9 +39,19 @@ void update_terminal_color(FILE* fp, uint8_t desired_color, 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_fg == 7 ) + fprintf(fp, "\e[39m"); + else + 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) ); + { + if ( desired_bg == 0 ) // Transparent background colors and such. + fprintf(fp, "\e[49m"); + else + fprintf(fp, "\e[%im", desired_bg + (desired_bg < 8 ? 40 : 100-8)); + } current->color = desired_color; } @@ -109,7 +121,7 @@ void make_terminal_state(FILE* fp, struct terminal_state* state) memset(state, 0, sizeof(*state)); struct winsize terminal_size; - tcgetwinsize(fileno(fp), &terminal_size); + ioctl(fileno(fp), TIOCGWINSZ, &terminal_size); state->width = (int) terminal_size.ws_col; state->height = (int) terminal_size.ws_row; size_t data_length = state->width * state->height;