From 8d7d364037b4993e5a2187ce5ee19782ab518f0a Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sat, 23 Apr 2016 13:16:55 +0200 Subject: [PATCH] Switch sh(1) to termios. sh(1) now restores reasonable terminal attributes. This is not really its problem, but as long as common Sortix programs don't always restore the terminal attributes on exit, this will work around the issue in practice. --- sh/editline.c | 247 +++++++++++++++++++++++++++++++------------------- sh/editline.h | 22 ++--- sh/sh.c | 5 +- sh/showline.c | 24 ++++- sh/showline.h | 6 +- 5 files changed, 194 insertions(+), 110 deletions(-) diff --git a/sh/editline.c b/sh/editline.c index a725e6b7..5ff9528a 100644 --- a/sh/editline.c +++ b/sh/editline.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen. + * Copyright (c) 2011, 2012, 2013, 2014, 2015, 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,15 +17,13 @@ * Read a line from the terminal. */ -#include -#include - #include #include #include #include #include #include +#include #include #include #include @@ -33,6 +31,8 @@ #include "editline.h" #include "showline.h" +#define CONTROL(x) (((x) - 64) & 127) + void edit_line_show(struct edit_line* edit_state) { size_t line_length = 0; @@ -270,31 +270,31 @@ void edit_line_type_codepoint(struct edit_line* edit_state, wchar_t wc) assert(edit_state->line_used <= edit_state->line_length); } -void line_edit_type_home(struct edit_line* edit_state) +void edit_line_type_home(struct edit_line* edit_state) { edit_state->line_offset = 0; } -void line_edit_type_left(struct edit_line* edit_state) +void edit_line_type_left(struct edit_line* edit_state) { if ( edit_state->line_offset == 0 ) return; edit_state->line_offset--; } -void line_edit_type_right(struct edit_line* edit_state) +void edit_line_type_right(struct edit_line* edit_state) { if ( edit_state->line_offset == edit_state->line_used ) return; edit_state->line_offset++; } -void line_edit_type_end(struct edit_line* edit_state) +void edit_line_type_end(struct edit_line* edit_state) { edit_state->line_offset = edit_state->line_used; } -void line_edit_type_backspace(struct edit_line* edit_state) +void edit_line_type_backspace(struct edit_line* edit_state) { if ( edit_state->line_offset == 0 ) return; @@ -304,7 +304,7 @@ void line_edit_type_backspace(struct edit_line* edit_state) edit_state->line[i] = edit_state->line[i+1]; } -void line_edit_type_previous_word(struct edit_line* edit_state) +void edit_line_type_previous_word(struct edit_line* edit_state) { while ( edit_state->line_offset && iswspace(edit_state->line[edit_state->line_offset-1]) ) @@ -314,7 +314,7 @@ void line_edit_type_previous_word(struct edit_line* edit_state) edit_state->line_offset--; } -void line_edit_type_next_word(struct edit_line* edit_state) +void edit_line_type_next_word(struct edit_line* edit_state) { while ( edit_state->line_offset != edit_state->line_used && iswspace(edit_state->line[edit_state->line_offset]) ) @@ -324,7 +324,7 @@ void line_edit_type_next_word(struct edit_line* edit_state) edit_state->line_offset++; } -void line_edit_type_delete(struct edit_line* edit_state) +void edit_line_type_delete(struct edit_line* edit_state) { if ( edit_state->line_offset == edit_state->line_used ) return; @@ -333,10 +333,10 @@ void line_edit_type_delete(struct edit_line* edit_state) edit_state->line[i] = edit_state->line[i+1]; } -void line_edit_type_eof_or_delete(struct edit_line* edit_state) +void edit_line_type_eof_or_delete(struct edit_line* edit_state) { if ( edit_state->line_used ) - return line_edit_type_delete(edit_state); + return edit_line_type_delete(edit_state); edit_state->editing = false; edit_state->eof_condition = true; if ( edit_state->trap_eof_opportunity ) @@ -353,13 +353,13 @@ void edit_line_type_interrupt(struct edit_line* edit_state) void edit_line_type_kill_after(struct edit_line* edit_state) { while ( edit_state->line_offset < edit_state->line_used ) - line_edit_type_delete(edit_state); + edit_line_type_delete(edit_state); } void edit_line_type_kill_before(struct edit_line* edit_state) { while ( edit_state->line_offset ) - line_edit_type_backspace(edit_state); + edit_line_type_backspace(edit_state); } void edit_line_type_clear(struct edit_line* edit_state) @@ -371,10 +371,10 @@ void edit_line_type_delete_word_before(struct edit_line* edit_state) { while ( edit_state->line_offset && iswspace(edit_state->line[edit_state->line_offset-1]) ) - line_edit_type_backspace(edit_state); + edit_line_type_backspace(edit_state); while ( edit_state->line_offset && !iswspace(edit_state->line[edit_state->line_offset-1]) ) - line_edit_type_backspace(edit_state); + edit_line_type_backspace(edit_state); } int edit_line_completion_sort(const void* a_ptr, const void* b_ptr) @@ -495,73 +495,8 @@ void edit_line_type_complete(struct edit_line* edit_state) free(partial); } -void edit_line_kbkey(struct edit_line* edit_state, int kbkey) -{ - if ( kbkey != KBKEY_TAB && kbkey != -KBKEY_TAB ) - edit_state->double_tab = false; - - if ( edit_state->left_control || edit_state->right_control ) - { - switch ( kbkey ) - { - case KBKEY_LEFT: line_edit_type_previous_word(edit_state); return; - case KBKEY_RIGHT: line_edit_type_next_word(edit_state); return; - }; - } - - switch ( kbkey ) - { - case KBKEY_HOME: line_edit_type_home(edit_state); return; - case KBKEY_LEFT: line_edit_type_left(edit_state); return; - case KBKEY_RIGHT: line_edit_type_right(edit_state); return; - case KBKEY_UP: edit_line_type_history_prev(edit_state); return; - case KBKEY_DOWN: edit_line_type_history_next(edit_state); return; - case KBKEY_END: line_edit_type_end(edit_state); return; - case KBKEY_BKSPC: line_edit_type_backspace(edit_state); return; - case KBKEY_DELETE: line_edit_type_delete(edit_state); return; - case KBKEY_TAB: edit_line_type_complete(edit_state); return; - case -KBKEY_LCTRL: edit_state->left_control = false; return; - case +KBKEY_LCTRL: edit_state->left_control = true; return; - case -KBKEY_RCTRL: edit_state->right_control = false; return; - case +KBKEY_RCTRL: edit_state->right_control = true; return; - }; -} - -void edit_line_codepoint(struct edit_line* edit_state, wchar_t wc) -{ - if ( (edit_state->left_control || edit_state->right_control) && - ((L'a' <= wc && wc <= L'z') || (L'A' <= wc && wc <= L'Z')) ) - { - if ( wc == L'a' || wc == L'A' ) - line_edit_type_home(edit_state); - if ( wc == L'b' || wc == L'B' ) - line_edit_type_left(edit_state); - if ( wc == L'c' || wc == L'C' ) - edit_line_type_interrupt(edit_state); - if ( wc == L'd' || wc == L'D' ) - line_edit_type_eof_or_delete(edit_state); - if ( wc == L'e' || wc == L'E' ) - line_edit_type_end(edit_state); - if ( wc == L'f' || wc == L'F' ) - line_edit_type_right(edit_state); - if ( wc == L'k' || wc == L'K' ) - edit_line_type_kill_after(edit_state); - if ( wc == L'l' || wc == L'L' ) - show_line_clear(&edit_state->show_state); - if ( wc == L'u' || wc == L'U' ) - edit_line_type_kill_before(edit_state); - if ( wc == L'w' || wc == L'W' ) - edit_line_type_delete_word_before(edit_state); - return; - } - - if ( wc == L'\b' || wc == 127 ) - return; - if ( wc == L'\t' ) - return; - - edit_line_type_codepoint(edit_state, wc); -} +#define SORTIX_LFLAGS (ISORTIX_KBKEY | ISORTIX_CHARS_DISABLE | ISORTIX_32BIT | \ + ISORTIX_NONBLOCK | ISORTIX_TERMMODE) void edit_line(struct edit_line* edit_state) { @@ -578,27 +513,153 @@ void edit_line(struct edit_line* edit_state) edit_state->history_offset = edit_state->history_used; edit_state->history_target = edit_state->history_used; - settermmode(edit_state->in_fd, TERMMODE_KBKEY | TERMMODE_UNICODE); + struct termios old_tio, tio; + tcgetattr(edit_state->in_fd, &old_tio); + + // TODO: There's no good way in Sortix for processes to restore the terminal + // attributes on exit, even if that exit is a crash. Restore default + // terminal attributes here to ensure programs run in the expected + // terminal environment, if transitional Sortix extensions are + // enabled. This ensures programs are run in a conforming environment + // even if a process using Sortix extensions don't exit cleanly. + if ( old_tio.c_lflag & SORTIX_LFLAGS ) + { + old_tio.c_lflag &= ~SORTIX_LFLAGS; + old_tio.c_lflag |= ECHO | ECHOE | ECHOK | ICANON | IEXTEN | ISIG; + old_tio.c_iflag |= ICRNL; + old_tio.c_oflag &= ~(OCRNL); + old_tio.c_oflag |= OPOST | ONLCR; + } + + memcpy(&tio, &old_tio, sizeof(tio)); + old_tio.c_lflag &= ~(ISORTIX_KBKEY | ISORTIX_CHARS_DISABLE | + ISORTIX_32BIT | ISORTIX_NONBLOCK | ISORTIX_TERMMODE); + tio.c_lflag &= ~(ISIG | ICANON | ECHO | IEXTEN); + + tcsetattr(edit_state->in_fd, TCSANOW, &tio); show_line_begin(&edit_state->show_state, edit_state->out_fd); + int escape = 0; + unsigned int params[16]; + size_t param_index = 0; + + mbstate_t ps = { 0 }; while ( edit_state->editing ) { edit_line_show(edit_state); - uint32_t codepoint; - if ( read(0, &codepoint, sizeof(codepoint)) != sizeof(codepoint) ) + char c; + if ( read(0, &c, sizeof(c)) != sizeof(c) ) { edit_state->eof_condition = true; edit_state->abort_editing = true; break; } - int kbkey; - if ( (kbkey = KBKEY_DECODE(codepoint)) ) - edit_line_kbkey(edit_state, kbkey); + if ( c != '\t' ) + edit_state->double_tab = false; + + if ( escape ) + { + if ( c == '[' ) + { + escape = 2; + } + else if ( escape == 1 && c == 'O' ) + { + escape = 3; + } + else if ( '0' <= c && c <= '9' ) + { + params[param_index] *= 10; + params[param_index] += c - '0'; + } + else if ( c == ';' ) + { + if ( param_index < 16 ) + ++param_index; + } + else if ( 64 <= c && c <= 126 ) + { + for ( size_t i = 0; i < 16; i++ ) + if ( params[i] == 0 ) + params[i] = 1; + switch ( c ) + { + case 'A': edit_line_type_history_prev(edit_state); break; + case 'B': edit_line_type_history_next(edit_state); break; + case 'C': + if ( (params[1] - 1) & (1 << 2) ) /* control */ + edit_line_type_next_word(edit_state); + else + edit_line_type_right(edit_state); + break; + case 'D': + if ( (params[1] - 1) & (1 << 2) ) /* control */ + edit_line_type_previous_word(edit_state); + else + edit_line_type_left(edit_state); + break; + case 'F': edit_line_type_end(edit_state); break; + case 'H': edit_line_type_home(edit_state); break; + case 'R': + { + unsigned int r = params[0] - 1; + unsigned int c = params[1] - 1; + show_line_wincurpos(&edit_state->show_state, r, c); + edit_line_show(edit_state); + } break; + case '~': + if ( params[0] == 3 ) + edit_line_type_delete(edit_state); + break; + } + escape = 0; + } + } + else if ( c == CONTROL('A') ) + edit_line_type_home(edit_state); + else if ( c == CONTROL('B') ) + edit_line_type_left(edit_state); + else if ( c == CONTROL('C') ) + edit_line_type_interrupt(edit_state); + else if ( c == CONTROL('D') ) + edit_line_type_eof_or_delete(edit_state); + else if ( c == CONTROL('E') ) + edit_line_type_end(edit_state); + else if ( c == CONTROL('F') ) + edit_line_type_right(edit_state); + else if ( c == CONTROL('I') ) + edit_line_type_complete(edit_state); + else if ( c == CONTROL('K') ) + edit_line_type_kill_after(edit_state); + else if ( c == CONTROL('L') ) + show_line_clear(&edit_state->show_state); + else if ( c == CONTROL('U') ) + edit_line_type_kill_before(edit_state); + else if ( c == CONTROL('W') ) + edit_line_type_delete_word_before(edit_state); + else if ( c == CONTROL('[') ) + { + param_index = 0; + memset(params, 0, sizeof(params)); + escape = 1; + } + else if ( c == 127 ) + edit_line_type_backspace(edit_state); else - edit_line_codepoint(edit_state, (wchar_t) codepoint); + { + wchar_t wc; + size_t amount = mbrtowc(&wc, &c, 1, &ps); + if ( amount == (size_t) -2 ) + continue; + if ( amount == (size_t) -1 ) + wc = 0xFFFD; /* REPLACEMENT CHARACTER */ + if ( amount == 0 ) + continue; + edit_line_type_codepoint(edit_state, wc); + } } if ( edit_state->abort_editing ) @@ -609,5 +670,5 @@ void edit_line(struct edit_line* edit_state) show_line_finish(&edit_state->show_state); } - settermmode(edit_state->in_fd, TERMMODE_NORMAL); + tcsetattr(edit_state->in_fd, TCSANOW, &old_tio); } diff --git a/sh/editline.h b/sh/editline.h index 9dc5cf1b..d299a9b8 100644 --- a/sh/editline.h +++ b/sh/editline.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen. + * Copyright (c) 2011, 2012, 2013, 2014, 2015, 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 @@ -65,15 +65,15 @@ void edit_line_type_history_save_current(struct edit_line* edit_state); void edit_line_type_history_prev(struct edit_line* edit_state); void edit_line_type_history_next(struct edit_line* edit_state); void edit_line_type_codepoint(struct edit_line* edit_state, wchar_t wc); -void line_edit_type_home(struct edit_line* edit_state); -void line_edit_type_left(struct edit_line* edit_state); -void line_edit_type_right(struct edit_line* edit_state); -void line_edit_type_end(struct edit_line* edit_state); -void line_edit_type_backspace(struct edit_line* edit_state); -void line_edit_type_previous_word(struct edit_line* edit_state); -void line_edit_type_next_word(struct edit_line* edit_state); -void line_edit_type_delete(struct edit_line* edit_state); -void line_edit_type_eof_or_delete(struct edit_line* edit_state); +void edit_line_type_home(struct edit_line* edit_state); +void edit_line_type_left(struct edit_line* edit_state); +void edit_line_type_right(struct edit_line* edit_state); +void edit_line_type_end(struct edit_line* edit_state); +void edit_line_type_backspace(struct edit_line* edit_state); +void edit_line_type_previous_word(struct edit_line* edit_state); +void edit_line_type_next_word(struct edit_line* edit_state); +void edit_line_type_delete(struct edit_line* edit_state); +void edit_line_type_eof_or_delete(struct edit_line* edit_state); void edit_line_type_interrupt(struct edit_line* edit_state); void edit_line_type_kill_after(struct edit_line* edit_state); void edit_line_type_kill_before(struct edit_line* edit_state); @@ -81,8 +81,6 @@ void edit_line_type_clear(struct edit_line* edit_state); void edit_line_type_delete_word_before(struct edit_line* edit_state); int edit_line_completion_sort(const void* a_ptr, const void* b_ptr); void edit_line_type_complete(struct edit_line* edit_state); -void edit_line_kbkey(struct edit_line* edit_state, int kbkey); -void edit_line_codepoint(struct edit_line* edit_state, wchar_t wc); void edit_line(struct edit_line* edit_state); #endif diff --git a/sh/sh.c b/sh/sh.c index 2e74331c..b3a36ff6 100644 --- a/sh/sh.c +++ b/sh/sh.c @@ -17,6 +17,7 @@ * Command language interpreter. */ +#include #include #include @@ -1005,9 +1006,9 @@ struct execute_result execute(char** tokens, if ( !strcmp(type, "<") ) fd = open(target, O_RDONLY | O_CLOEXEC); else if ( !strcmp(type, ">") ) - fd = open(target, O_WRONLY | O_CREATE | O_TRUNC | O_CLOEXEC, 0666); + fd = open(target, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666); else if ( !strcmp(type, ">>") ) - fd = open(target, O_WRONLY | O_CREATE | O_APPEND | O_CLOEXEC, 0666); + fd = open(target, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0666); if ( fd < 0 ) { diff --git a/sh/showline.c b/sh/showline.c index a570ddb1..0c47312b 100644 --- a/sh/showline.c +++ b/sh/showline.c @@ -82,9 +82,26 @@ void show_line_begin(struct show_line* show_state, int out_fd) show_state->out_fd = out_fd; show_state->current_line = NULL; show_state->current_cursor = 0; - tcgetwincurpos(out_fd, &show_state->wcp_start); - show_state->wcp_current = show_state->wcp_start; tcgetwinsize(show_state->out_fd, &show_state->ws); + if ( tcgetwincurpos(out_fd, &show_state->wcp_start) == 0 ) + show_state->wcp_current = show_state->wcp_start; + else + { + dprintf(show_state->out_fd, "\e[6n"); + show_state->wcp_pending = true; + } +} + +void show_line_wincurpos(struct show_line* show_state, + unsigned int r, + unsigned int c) +{ + if ( !show_state->wcp_pending ) + return; + show_state->wcp_start.wcp_row = r; + show_state->wcp_start.wcp_col = c; + show_state->wcp_current = show_state->wcp_start; + show_state->wcp_pending = false; } bool show_line_is_weird(const char* line) @@ -270,6 +287,9 @@ bool show_line_optimized(struct show_line* show_state, const char* line, size_t void show_line(struct show_line* show_state, const char* line, size_t cursor) { + if ( show_state->wcp_pending ) + return; + // TODO: We don't currently invalidate on SIGWINCH. struct winsize ws; tcgetwinsize(show_state->out_fd, &ws); diff --git a/sh/showline.h b/sh/showline.h index 4fec494b..3f52ae35 100644 --- a/sh/showline.h +++ b/sh/showline.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen. + * Copyright (c) 2011, 2012, 2013, 2014, 2015, 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 @@ -38,6 +38,7 @@ struct show_line char* current_line; size_t current_cursor; bool invalidated; + bool wcp_pending; }; struct wincurpos predict_cursor(struct cursor_predict* cursor_predict, @@ -49,6 +50,9 @@ bool predict_will_scroll(struct cursor_predict cursor_predict, struct winsize ws, wchar_t c); void show_line_begin(struct show_line* show_state, int out_fd); +void show_line_wincurpos(struct show_line* show_state, + unsigned int r, + unsigned int c); bool show_line_is_weird(const char* line); void show_line_change_cursor(struct show_line* show_state, struct wincurpos wcp); bool show_line_optimized(struct show_line* show_state, const char* line, size_t cursor);