// This file is part of The New Aspell // Copyright (C) 2002 by Kevin Atkinson under the GNU LGPL license // version 2.0 or 2.1. You should have received a copy of the LGPL // license along with this library if you did not you can find // it at http://www.gnu.org/. /* Low level terminal interface. This file will possible use the following macros: POSIX_TERMIOS HAVE_LIBCURSES CURSES_INCLUDE_STANDARD CURSES_INCLUDE_WORKAROUND_1 CURSES_ONLY HAVE_GETCH HAVE_MBLEN All these macros need to have a true value and not just be defined */ #include "settings.h" #ifdef DEFINE_XOPEN_SOURCE_EXTENDED # define _XOPEN_SOURCE_EXTENDED 1 #endif #ifdef CURSES_NON_POSIX #define CURSES_ONLY 1 #endif #include #include "asc_ctype.hpp" #include "check_funs.hpp" #include "convert.hpp" #include "config.hpp" #include "gettext.h" using namespace acommon; StackPtr state; const char * last_prompt = 0; StackPtr word_choices; StackPtr menu_choices; Conv null_conv; extern Conv dconv; extern Conv uiconv; #if POSIX_TERMIOS // Posix headers #include #include static termios new_attributes; static termios saved_attributes; #elif HAVE_GETCH extern "C" {int getch();} #endif #if HAVE_LIBCURSES #include CURSES_HEADER #if CURSES_INCLUDE_STANDARD #include #elif CURSES_INCLUDE_WORKAROUND_1 // including on solaris causes problems extern "C" {char * tigetstr(char * capname);} #endif enum MenuText {StdMenu, ReplMenu}; static MenuText menu_text = StdMenu; static bool use_curses = true; static WINDOW * text_w = 0; static WINDOW * menu_w = 0; static WINDOW * choice_w = 0; //static int beg_x = -1; static int end_x = -1; static int max_x; static char * choice_text; static int choice_text_size; static int cur_x = 0; static int cur_y = 0; static volatile int last_signal = 0; #ifdef CURSES_ONLY char * tigetstr(char *) {return "";} #else static SCREEN * term; #endif #endif // HAVE_LIBCURSES #ifdef HAVE_MBLEN # include #else # define mblen(x,y) (*(x) ? 1 : 0) #endif static void cleanup (void) { #if HAVE_LIBCURSES if (use_curses) { endwin(); } else #endif { #if POSIX_TERMIOS tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes); #endif } } #if HAVE_LIBCURSES //static void do_nothing(int) {} static void layout_screen(); #ifdef CURSES_ONLY inline void handle_last_signal() {} #else static void save_state() { getyx(choice_w,cur_y,cur_x); choice_text_size = COLS; choice_text = new char[choice_text_size]; for (int i = 0; i != choice_text_size; ++i) { choice_text[i] = mvwinch(choice_w, 0, i) & A_CHARTEXT; } endwin(); } static void restore_state() { delscreen(term); term = newterm(0,stdout,stdin); set_term(term); layout_screen(); display_menu(); display_misspelled_word(); wmove(choice_w,0,0); if (COLS <= choice_text_size) choice_text_size = COLS - 1; for (int i=0; i < choice_text_size; ++i) waddch(choice_w,choice_text[i]); delete[] choice_text; max_x = COLS - 1; if (cur_x > max_x) cur_x = max_x; if (end_x > max_x-1) end_x = max_x; wmove(choice_w,cur_y,cur_x); wnoutrefresh(choice_w); doupdate(); } static void suspend_handler(int) { last_signal = SIGTSTP; } static void continue_handler(int) { restore_state(); signal(SIGTSTP, suspend_handler); signal(SIGCONT, continue_handler), last_signal = 0; } static void resize_handler(int ) { last_signal = SIGWINCH; } static void resize() { save_state(); restore_state(); last_signal = 0; } static void suspend() { save_state(); signal(SIGTSTP, SIG_DFL); raise(SIGTSTP); last_signal = 0; } static inline void handle_last_signal() { switch (last_signal) { case SIGWINCH: resize(); signal(SIGWINCH, resize_handler); break; case SIGTSTP: suspend(); break; } } #endif static void layout_screen() { text_w = 0; menu_w = 0; choice_w = 0; nonl(); noecho(); halfdelay(1); keypad(stdscr, true); clear(); int height, width; getmaxyx(stdscr, height, width); int text_height = height - MENU_HEIGHT - 3; if (text_height >= 1) { text_w = newwin(text_height, width, 0, 0); scrollok(text_w,false); move(text_height, 0); hline((unsigned char)' '|A_REVERSE, width); menu_w = newwin(MENU_HEIGHT, width, text_height+1, 0); scrollok(menu_w,false); } if (height >= 2) { move(height-2,0); hline((unsigned char)' '|A_REVERSE, width); } choice_w = newwin(1, width, height-1, 0); keypad(choice_w,true); scrollok(menu_w,true); wnoutrefresh(stdscr); } #endif void begin_check() { // // // #if HAVE_LIBCURSES #if CURSES_ONLY use_curses=true; initscr(); #else term = newterm(0,stdout,stdin); if (term == 0) { use_curses = false; } else { set_term(term); if ((tigetstr(const_cast("cup") /*move*/) != 0 || (tigetstr(const_cast("cuf1") /*right*/) != 0 && tigetstr(const_cast("cub1") /*left*/) != 0 && tigetstr(const_cast("cuu1") /*up */) != 0 && tigetstr(const_cast("cud1") /*down*/) != 0)) && (tigetstr(const_cast("rev")) != 0)) { use_curses = true; } else { use_curses = false; endwin(); delscreen(term); } } if (use_curses) { signal(SIGWINCH, resize_handler); signal(SIGTSTP, suspend_handler); signal(SIGCONT, continue_handler); } #endif if (use_curses) { layout_screen(); atexit(cleanup); } #endif #if POSIX_TERMIOS || (HAVE_LIBCURSES && !CURSES_ONLY) if (!isatty (STDIN_FILENO)) { puts(_("Error: Stdin not a terminal.")); exit (-1); } // // Save the terminal attributes so we can restore them later. // tcgetattr (STDIN_FILENO, &saved_attributes); atexit(cleanup); // // Set up the terminal to read in a line character at a time // tcgetattr (STDIN_FILENO, &new_attributes); #if POSIX_TERMIOS new_attributes.c_lflag &= ~(ICANON); // Clear ICANON new_attributes.c_cc[VMIN] = 1; new_attributes.c_cc[VTIME] = 0; #endif // FIXME correctly test for _POSIX_VDISABLE; new_attributes.c_cc[VINTR] = _POSIX_VDISABLE; tcsetattr (STDIN_FILENO, TCSAFLUSH, &new_attributes); #endif } #ifdef HAVE_WIDE_CURSES # define IS_KEY(k) (res == KEY_CODE_YES && c == KEY_##k) # define NOT_KEY (res != KEY_CODE_YES) #else # define IS_KEY(k) (c == KEY_##k) # define NOT_KEY (c < 256) #endif void get_line(String & line) { #if HAVE_LIBCURSES if (use_curses) { // FIXME: This won't work correctly when combing characters are // involved. menu_text = ReplMenu; display_menu(); wnoutrefresh(choice_w); doupdate(); line.resize(0); int c; noecho(); int begin_x; {int junk; getyx(choice_w, junk, begin_x);} int max_x = COLS - 1; int end_x = begin_x; while (true) { handle_last_signal(); #ifdef HAVE_WIDE_CURSES wint_t wi = 0; int res = wget_wch(choice_w, &wi); c = wi; #else c = wgetch(choice_w); #endif if (c == ERR) continue; if (c == '\r' || c == '\n' || c == KEY_ENTER) break; if (c == control('c') || c == KEY_BREAK) { end_x = begin_x; break; } int y,x; getyx(choice_w,y,x); if ((IS_KEY(LEFT) || c == control('b')) && begin_x < x) { wmove(choice_w, y,x-1); } else if ((IS_KEY(RIGHT) || c == control('f')) && x < end_x) { wmove(choice_w, y,x+1); } else if (IS_KEY(HOME) || c == control('a')) { wmove(choice_w, y, begin_x); } else if (IS_KEY(END) || c == control('e')) { wmove(choice_w, y, end_x); } else if ((IS_KEY(BACKSPACE) || c == control('h') || c == '\x7F') && begin_x < x) { wmove(choice_w, y,x-1); wdelch(choice_w); --end_x; } else if (IS_KEY(DC) || c == control('d')) { wdelch(choice_w); --end_x; } else if (IS_KEY(EOL) || c == control('k')) { wclrtoeol(choice_w); end_x = x; } else if (x < max_x && 32 <= c && c != '\x7F' && NOT_KEY /*c < 256*/) { #ifdef HAVE_WIDE_CURSES wchar_t wc = c; wins_nwstr(choice_w, &wc, 1); #else winsch(choice_w, c); #endif wmove(choice_w, y, x+1); ++end_x; } wrefresh(choice_w); } #ifdef HAVE_WIDE_CURSES Vector wstr; for (int i = begin_x; i < end_x; ++i) { cchar_t cc; attr_t att; short cp; mvwin_wch(choice_w, 0, i, &cc); size_t s = getcchar(&cc, 0, &att, &cp, 0); wstr.resize(s+1); // +1 to allow room for the null character getcchar(&cc, wstr.data(), &att, &cp, 0); s = wcstombs(0, wstr.data(), 0); if (s != (size_t)-1) { int pos = line.alloc(s); // this always leaves space for the // null character wcstombs(line.data(pos), wstr.data(), s); } else { line += '?'; } } #else for (int i = begin_x; i < end_x; ++i) { line += mvwinch(choice_w, 0, i) & A_CHARTEXT; } #endif menu_text = StdMenu; display_menu(); doupdate(); } else #endif { #if POSIX_TERMIOS tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes); #endif line.resize(0); char c; while ((c = getchar()) != '\n') line += c; #if POSIX_TERMIOS tcsetattr (STDIN_FILENO, TCSANOW, &new_attributes); #endif } if (uiconv.conv) { line = uiconv(line); } } #undef IS_KEY #undef NOT_KEY void get_choice(int & c) { #if HAVE_LIBCURSES if (use_curses) { doupdate(); int c0; do { handle_last_signal(); c0 = wgetch(choice_w); } while (c0 == ERR); if (c0 == KEY_BREAK) c0 = control('c'); if (1 <= c0 && c0 < 128) { c = static_cast(c0); waddch(choice_w,c); wrefresh(choice_w); } else { c = 0; } } else #endif { #if POSIX_TERMIOS int c0 = 0; read (STDIN_FILENO, &c0, 1); c = c0; putchar('\n'); #elif HAVE_GETCH c = getch(); putchar(c); putchar('\n'); #else c = getchar(); char d = c; while (d != '\n') d = getchar(); putchar('\n'); #endif } } #if HAVE_LIBCURSES static void new_line(int & l, int y, int height) { --l; if (y == height - 1) { wmove(text_w, 0, 0); wdeleteln(text_w); wmove(text_w, height-1, 0); } else { wmove(text_w,y+1,0); } } static void new_line(int & l, int height) { int y,x; getyx(text_w,y,x); new_line(l,y,height); } #endif void display_misspelled_word() { const char * word_begin = state->word_begin(); const char * word_end = state->word_end(); LineIterator cur_line = state->cur_line(); #if HAVE_LIBCURSES if (use_curses && text_w) { int height, width; werase(text_w); getmaxyx(text_w,height,width); assert(height > 0 && width > 0); LineIterator i = cur_line; // // backup height/3 lines // int l = height/3; while (!i.off_end() && l > 0) { --l; --i; } if (i.off_end()) ++i; while (l != 0) new_line(l,height); l = -1; const char * j = i->begin(); while (!i.off_end()) { int y,x; int y0,x0; getyx(text_w, y, x); x0 = x; y0 = y; wattrset(text_w,A_NORMAL); int last_space_pos = 0; const char * last_space = j; int prev_glyph_pos = 0; const char * prev_glyph = j; // NOTE: Combining characters after a character at the very end of // a line will cause an unnecessary word wrap. Unfortunately I // do not know how to avoid it as they is no portable way to // find out if the next character will combine with the // previous. // We check that: // - we haven't reached the end of the text // - we haven't reached the end of the line // - curse haven't jumped to the next screen line // - curse haven't reached the end of the screen while (j < i->end() && *j != '\n' && (y0 == y && x >= x0) && x0 < width - 1) { if (asc_isspace(*j)) { last_space_pos = x; last_space = j; } if (j == word_begin) { wattrset(text_w,A_REVERSE); l = height*2/3; } else if (j == word_end) { wattrset(text_w,A_NORMAL); } int len = mblen(j, MB_CUR_MAX); int res = OK; if (len > 0) { res = waddnstr(text_w, const_cast(j), len); } else { waddch(text_w, ' '); len = 1; } j += len; x0 = x; getyx(text_w, y, x); if (x != x0 || res == ERR) { prev_glyph_pos = x0; prev_glyph = j - len; } } y = y0; if (j == i->end() || *j == '\n') { ++i; j = i->begin(); } else { if (width - last_space_pos < width/3) { wmove(text_w, y, last_space_pos); wclrtoeol(text_w); j = last_space + 1; } else { wmove(text_w, y, prev_glyph_pos); wclrtoeol(text_w); j = prev_glyph; } wmove(text_w, y, width-1); wattrset(text_w,A_NORMAL); waddch(text_w,'\\'); } if (l == 0) break; new_line(l,y,height); } while (l != 0) new_line(l,height); wnoutrefresh(text_w); } else if (use_curses && !text_w) { // do nothing } else #endif { int pre = word_begin - cur_line->begin(); int post = cur_line->end() - word_end; if (pre) fwrite(cur_line->begin(), pre, 1, stdout); putchar('*'); fwrite(word_begin, word_end - word_begin, 1, stdout); putchar('*'); if (post) fwrite(word_end, post, 1, stdout); } } static inline void put (FILE * out, char c) {putc(c, out);} static inline void put (FILE * out, const char * s) {fputs(s, out);} static inline void put (FILE * out, const char * s, int size) {fwrite(s, size, 1, out);} static void print_truncate(FILE * out, const char * word, int width) { int i; int len = 0; for (i = 0; i < width-1 && *word; word += len, ++i) { len = mblen(word, MB_CUR_MAX); if (len > 0) { put(out, word, len); } else { put(out, ' '); len = 1; } } if (i == width-1) { if (word == '\0') put(out,' '); else if (word[len] == '\0') put(out, word, len); else put(out,'$'); ++i; } for (;i < width; ++i) put(out,' '); } static void display_menu(FILE * out, const Choices * choices, int width, Conv & conv = null_conv) { if (width <= 11) return; Choices::const_iterator i = choices->begin(); while (i != choices->end()) { put(out,i->choice); put(out,") "); print_truncate(out, conv(i->desc), width/2 - 4); put(out,' '); ++i; if (i != choices->end()) { put(out,i->choice); put(out,") "); print_truncate(out, conv(i->desc), width/2 - 4); ++i; } putc('\n', out); } } #if HAVE_LIBCURSES static inline void put (WINDOW * w, char c) { waddch(w,static_cast(c)); } static inline void put (WINDOW * w, const char * c) { waddstr(w,const_cast(c)); } static inline void put (WINDOW * w, const char * c, int size) { waddnstr(w,const_cast(c), size); } static void print_truncate(WINDOW * out, const char * word, int width) { int len = 0; int y,x; int y0; getyx(out, y, x); int stop = x + width - 1; while (x <= stop && *word) { len = mblen(word, MB_CUR_MAX); if (len <= 0) len = 1; put(out, word, len); word += len; y0 = y; getyx(out, y, x); if (y != y0) {y = y0; x = stop + 1; break;} } for (; x <= stop; ++x) put(out, ' '); if (x > stop && *word) { wmove(out, y, stop); put(out,'$'); } } static void display_menu(WINDOW * out, const Choices * choices, int width, Conv & conv = null_conv) { if (width <= 11) return; Choices::const_iterator i = choices->begin(); int y,x; getyx(out, y, x); while (i != choices->end()) { wclrtoeol(out); put(out,i->choice); put(out,") "); print_truncate(out, conv(i->desc), width/2 - 4); ++i; if (i != choices->end()) { wmove(out, y, width/2); put(out,i->choice); put(out,") "); print_truncate(out, conv(i->desc), width/2 - 4); ++i; } ++y; wmove(out, y, 0); } } #endif void display_menu() { #if HAVE_LIBCURSES if (use_curses && menu_w) { if (menu_text == StdMenu) { scrollok(menu_w,false); int height,width; getmaxyx(menu_w,height,width); werase(menu_w); wmove(menu_w,0,0); display_menu(menu_w, word_choices.get(), width, dconv); wmove(menu_w,5,0); display_menu(menu_w, menu_choices.get(), width); wnoutrefresh(menu_w); } else { //ostream str; int height,width; getmaxyx(menu_w,height,width); struct MenuLine { const char * capname; const char * fun_key; const char * control_key; const char * desc; }; static MenuLine menu_items[9] = { {0, /* TRANSLATORS: This is a literal Key.*/ N_("Enter"), "", N_("Accept Changes")}, {0, /* TRANSLATORS: This is a literal Key. */ N_("Backspace"), /* TRANSLATORS: This is a literal Key. */ N_("Control-H"), N_("Delete the previous character")}, {"kcub1", /* TRANSLATORS: This is a literal Key. */ N_("Left"), /* TRANSLATORS: This is a literal Key. */ N_("Control-B"), N_("Move Back one space")}, {"kcuf1", /* TRANSLATORS: This is a literal Key. */ N_("Right"), /* TRANSLATORS: This is a literal Key. */ N_("Control-F"), N_("Move Forward one space")}, {"khome", /* TRANSLATORS: This is a literal Key. */ N_("Home"), /* TRANSLATORS: This is a literal Key. */ N_("Control-A"), N_("Move to the beginning of the line")}, {"kend" , /* TRANSLATORS: This is a literal Key. */ N_("End"), /* TRANSLATORS: This is a literal Key. */ N_("Control-E"), N_("Move to the end of the line")}, {"kdch1", /* TRANSLATORS: This is a literal Key. */ N_("Delete"), /* TRANSLATORS: This is a literal Key. */ N_("Control-D"), N_("Delete the next character")}, {0, "", /* TRANSLATORS: This is a literal Key. */ N_("Control-K"), N_("Kill all characters to the EOL")}, {0, "", /* TRANSLATORS: This is a literal Key. */ N_("Control-C"), N_("Abort This Operation")}, }; scrollok(menu_w,false); werase(menu_w); int beg = 0,end = 0; int y,x,x0; for (int i = 0; i != 9; ++i) { wmove(menu_w, i, beg); if (menu_items[i].capname == 0 || tigetstr(const_cast(menu_items[i].capname)) != 0) { getyx(menu_w, y, x0); put(menu_w, gt_(menu_items[i].fun_key)); getyx(menu_w, y, x); if (x < beg || y != i) end = width; else if (x > end) end = x; } } beg = end + 2; if (beg < width) for (int i = 0; i != 9; ++i) { wmove(menu_w, i, beg); put(menu_w, gt_(menu_items[i].control_key)); getyx(menu_w, y, x); if (x < beg || y != i) end = width; else if (x > end) end = x; } beg = end + 2; int w = width - beg; if (w > 1) for (int i = 0; i != 9; ++i) { wmove(menu_w, i, beg); print_truncate(menu_w, gt_(menu_items[i].desc), w); } wnoutrefresh(menu_w); } } else if (use_curses && !menu_w) { // do nothing } else #endif { display_menu(stdout, word_choices.get(), 80, dconv); display_menu(stdout, menu_choices.get(), 80); } } void prompt(const char * prompt) { last_prompt = prompt; #if HAVE_LIBCURSES if (use_curses) { werase(choice_w); waddstr(choice_w, const_cast(prompt)); wnoutrefresh(choice_w); } else #endif { fputs(prompt, stdout); fflush(stdout); } } void error(const char * error) { #if HAVE_LIBCURSES if (use_curses) { werase(choice_w); waddstr(choice_w, const_cast(error)); wnoutrefresh(choice_w); } else #endif { fputs(error, stdout); fputs(last_prompt, stdout); fflush(stdout); } }