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.
This commit is contained in:
Jonas 'Sortie' Termansen 2016-04-23 13:16:55 +02:00
parent f6cde2d7a6
commit 8d7d364037
5 changed files with 194 additions and 110 deletions

View File

@ -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 <sys/keycodes.h>
#include <sys/termmode.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>
@ -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);
}

View File

@ -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

View File

@ -17,6 +17,7 @@
* Command language interpreter.
*/
#include <sys/stat.h>
#include <sys/wait.h>
#include <assert.h>
@ -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 )
{

View File

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

View File

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