// 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 All these macros need to have a true value and not just be defined */ // // NOTE: The best way to debug this file when the behavior of "aspell // check" differs from the behavior of the orignal Aspell is to // compare against terminos.cc in the old Aspell and look for // differences. If everthing looks the same then it might be // a bug in CheckerString. // #include "settings.h" #ifdef CURSES_NON_POSIX #define CURSES_ONLY 1 #endif #include #include "asc_ctype.hpp" #include "check_funs.hpp" StackPtr state; const char * last_prompt = 0; StackPtr word_choices; StackPtr menu_choices; #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 #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 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 void do_nothing(int) {} void layout_screen(); #ifdef CURSES_ONLY inline void handle_last_signal() {} #else 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(); } 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(); } void suspend_handler(int) { last_signal = SIGTSTP; } void continue_handler(int) { restore_state(); signal(SIGTSTP, suspend_handler); signal(SIGCONT, continue_handler), last_signal = 0; } void resize_handler(int ) { last_signal = SIGWINCH; } void resize() { save_state(); restore_state(); last_signal = 0; } void suspend() { save_state(); signal(SIGTSTP, SIG_DFL); raise(SIGTSTP); last_signal = 0; } inline void handle_last_signal() { switch (last_signal) { case SIGWINCH: resize(); signal(SIGWINCH, resize_handler); break; case SIGTSTP: suspend(); break; } } #endif 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 } void get_line(String & line) { #if HAVE_LIBCURSES if (use_curses) { 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(); c = wgetch(choice_w); 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 ((c == KEY_LEFT || c == control('b')) && begin_x < x) { wmove(choice_w, y,x-1); } else if ((c == KEY_RIGHT || c == control('f')) && x < end_x) { wmove(choice_w, y,x+1); } else if (c == KEY_HOME || c == control('a')) { wmove(choice_w, y, begin_x); } else if (c == KEY_END || c == control('e')) { wmove(choice_w, y, end_x); } else if ((c == KEY_BACKSPACE || c == control('h') || c == '\x7F') && begin_x < x) { wmove(choice_w, y,x-1); wdelch(choice_w); --end_x; } else if (c == KEY_DC || c == control('d')) { wdelch(choice_w); --end_x; } else if (c == KEY_EOL || c == control('k')) { wclrtoeol(choice_w); end_x = x; } else if (x < max_x && 32 <= c && c != '\x7F' && c < 256) { winsch(choice_w, c); wmove(choice_w, y, x+1); ++end_x; } wrefresh(choice_w); } for (int i = begin_x; i < end_x; ++i) { line += mvwinch(choice_w, 0, i) & A_CHARTEXT; } 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 } } 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 (c == KEY_BREAK) c = 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 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); } } 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() { CheckerString::Iterator word_begin = state->word_begin(); CheckerString::Iterator word_end = state->word_end(); #if HAVE_LIBCURSES if (use_curses && text_w) { int height, width; werase(text_w); getmaxyx(text_w,height,width); assert(height > 0 && width > 0); CheckerString::Iterator i = word_begin; // // backup height/3 lines // int l = height/3; while (!i.off_end()) { if (*i == '\n') { --l; if (l == 0) break; } --i; } ++i; int last_space_pos = 0; CheckerString::Iterator last_space = i; while (l != 0) new_line(l,height); int y, x; l = -1; int attr = A_NORMAL; while (!i.off_end()) { getyx(text_w,y,x); if (x == width-1 || *i == '\n') { if (*i != '\n') { if (dist(last_space, i) < width/3) { wmove(text_w, y, last_space_pos); wclrtoeol(text_w); i = last_space; ++i; } wmove(text_w, y, width-1); waddch(text_w,'\\'); } last_space = i; last_space_pos = 0; if (l == 0) break; new_line(l,y,height); } else { if (asc_isspace(*i)) { getyx(text_w,y,last_space_pos); last_space = i; } } if (i == word_begin) { attr = A_REVERSE; l = height*2/3; } else if (i == word_end) { attr = A_NORMAL; } if (*i != '\n') waddch(text_w, (unsigned char)*i | attr); ++i; } while (l != 0) { new_line(l,height); } wnoutrefresh(text_w); } else if (use_curses && !text_w) { // do nothing } else #endif { CheckerString::Iterator i, line_begin, line_end; for (line_begin = word_begin; !line_begin.off_end() && *line_begin != '\n'; --line_begin); ++line_begin; for (line_end = word_end; !line_end.off_end() && *line_end != '\n'; ++line_end); for(i = line_begin; i != word_begin; ++i) putchar(*i); putchar('*'); for(; i != word_end; ++i) putchar(*i); putchar('*'); for(; i != line_end; ++i) putchar(*i); putchar('\n'); } } template static void print_truncate(O *out, const char * word, int width) { int i; for (i = 0; i < width-1 && word[i]; ++i) put(out,word[i]); if (i == width-1) { if (word[i] == '\0') put(out,' '); else if (word[i+1] == '\0') put(out,word[i]); else put(out,'$'); ++i; } for (;i < width; ++i) put(out,' '); } template static void display_menu(O * out, const Choices * choices, int width) { if (width <= 11) return; Choices::const_iterator i = choices->begin(); while (i != choices->end()) { put(out,i->choice); put(out,") "); print_truncate(out, i->desc, width/2 - 4); put(out,' '); ++i; if (i != choices->end()) { put(out,i->choice); put(out,") "); print_truncate(out, i->desc, width/2 - 4); ++i; } new_line(out); } } 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 new_line(FILE * out) {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 new_line(WINDOW * w) { int y,x; getyx(w,y,x); wmove(w,y+1,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); 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, "Enter", "", "Accept Changes"} , {0, "Backspace", "Control-H", "Delete the previous character"} , {"kcub1", "Left", "Control-B", "Move Back 1 space"} , {"kcuf1", "Right", "Control-F", "Move Forward 1 space"} , {"khome", "Home", "Control-A", "Move to the beginning of the line"} , {"kend" , "End", "Control-E", "Move to the end of the line"} , {"kdch1", "Delete", "Control-D", "Delete the next charcter"} , {0, "", "Control-K", "Kill all characters to the EOL"} , {0, "", "Control-C", "Abort This Operation"} }; scrollok(menu_w,false); werase(menu_w); for (int i = 0; i != 9; ++i) { wmove(menu_w, i, 0); int w = width; int fun_key_desc_width = 12; int control_key_desc_width = 12; if (w < fun_key_desc_width) fun_key_desc_width = w; if (menu_items[i].capname == 0 || tigetstr(const_cast(menu_items[i].capname)) != 0) print_truncate(menu_w, menu_items[i].fun_key, fun_key_desc_width); else print_truncate(menu_w, "", fun_key_desc_width); w -= fun_key_desc_width; if (w < control_key_desc_width) control_key_desc_width = w; print_truncate(menu_w, menu_items[i].control_key, control_key_desc_width); w -= control_key_desc_width; print_truncate(menu_w, 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); 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); } }