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);