From d4d16198c2924b1085258c0b6562b562c7df3c29 Mon Sep 17 00:00:00 2001 From: Tzafrir Cohen Date: Fri, 7 Sep 2012 15:14:04 +0300 Subject: geresh 0.6.3 --- editor.cc | 1103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1103 insertions(+) create mode 100644 editor.cc (limited to 'editor.cc') diff --git a/editor.cc b/editor.cc new file mode 100644 index 0000000..6ab707e --- /dev/null +++ b/editor.cc @@ -0,0 +1,1103 @@ +// Copyright (C) 2003 Mooffie +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + +#include + +#include +#include +#include // chdir +#include // WEXITSTATUS() +#include + +#include "editor.h" +#include "menus.h" +#include "scrollbar.h" +#include "io.h" +#include "pathnames.h" +#include "themes.h" +#include "utf8.h" // used in show_character_code() +#include "dbg.h" +#include "transtbl.h" +#include "helpbox.h" + +Editor *Editor::global_instance; // for SIGHUP + +#define LOAD_HISTORY 1 +#define SAVEAS_HISTORY 0 +#define SEARCH_HISTORY 2 +#define INSERT_HISTORY 1 +#define CHDIR_HISTORY 4 +#define WRITESELECTION_HISTORY 5 +#define SPELER_CMD_HISTORY 6 +#define SPELER_ENCODING_HISTORY 7 +#define EXTERNALEDITOR_HISTORY 8 + +// describe_key() - reads a key event from the keyboard, searches and then +// prints the description of the correspoding action. + +INTERACTIVE void Editor::describe_key() +{ + dialog.show_message(_("Describe key:")); + dialog.immediate_update(); + + Event evt; + get_next_event(evt, dialog.wnd); + + // first look for a corresponding action in Editor's action map. + // If none was found, look in EditBox's. + const char *action, *desc = NULL; + if ((action = get_event_action(evt))) + desc = get_action_description(action); + else { + if ((action = wedit.get_event_action(evt))) + desc = wedit.get_action_description(action); + } + dialog.show_message(desc ? _(desc) + : _("Sorry, no description for this key")); +} + +INTERACTIVE void Editor::refresh_and_center() +{ + wedit.center_line(); + refresh(); +} + +void Editor::refresh(bool soft) +{ + if (!soft) + clearok(curscr, TRUE); + if (spellerwnd) + spellerwnd->invalidate_view(); + if (scrollbar) + scrollbar->invalidate_view(); + if (menubar) + menubar->invalidate_view(); + wedit.invalidate_view(); + status.invalidate_view(); + dialog.invalidate_view(); + update_terminal(soft); +} + +// show_character_code() - prints the code value and the UTF-8 sequence +// of the character on which the cursor stands. + +INTERACTIVE void Editor::show_character_code() +{ + unichar ch = wedit.get_current_char(); + + // construct a nice utf-8 representation in utf8_c_sync + char utf8[6], utf8_c_syn[6*4 + 1]; + int nbytes = unicode_to_utf8(utf8, &ch, 1); + for (int i = 0; i < nbytes; i++) + sprintf(utf8_c_syn + i*4, "\\x%02X", (unsigned char)utf8[i]); + + dialog.show_message_fmt(_("Unicode value: U+%04lX (decimal: %ld), " + "UTF-8 sequence: %s"), + (unsigned long)ch, (unsigned long)ch, + utf8_c_syn); +} + +// show_character_info() - prints information from UnicodeData.txt +// about the characetr on which the cursor stands. + +INTERACTIVE void Editor::show_character_info() +{ +#define MAX_LINE_LEN 1024 + unichar ch = wedit.get_current_char(); + + FILE *fp; + if ((fp = fopen(get_cfg_filename(USER_UNICODE_DATA_FILE), "r")) != NULL + || (fp = fopen(get_cfg_filename(SYSTEM_UNICODE_DATA_FILE), "r")) != NULL) { + char line[MAX_LINE_LEN]; + while (fgets(line, MAX_LINE_LEN, fp)) { + if (strtol(line, NULL, 16) == (long)ch) { + line[strlen(line) - 1] = '\0'; // remove LF + dialog.show_message(line); + break; + } + } + fclose(fp); + } else { + set_last_error(errno); + show_file_io_error(_("Can't open %s: %s"), + get_cfg_filename(SYSTEM_UNICODE_DATA_FILE)); + } +#undef MAX_LINE_LEN +} + +Editor::Editor() + : dialog(this), + status(this, &wedit), + speller(*this, dialog) +{ + spellerwnd = NULL; + wedit.set_error_listener(this); + global_instance = this; + set_default_encoding(DEFAULT_FILE_ENCODING); + set_encoding(get_default_encoding()); + set_filename(""); + set_new(false); +#ifdef HAVE_CURS_SET + big_cursor = false; +#endif + + menubar = create_main_menubar(this, &wedit); + scrollbar = NULL; + scrollbar_pos = scrlbrNone; + + // The rest is only needed if we're in interactive mode, + // that is, not in "do_log2vis" mode. + if (terminal::is_interactive()) { + layout_windows(); + dialog.show_message_fmt(_("Welcome to %s version %s |" + "F1=Help, F9 or F10=Menu, ALT-X=Quit"), + PACKAGE, VERSION); + + TranslationTable tbl; + + if (tbl.load(get_cfg_filename(USER_TRANSTBL_FILE)) + || tbl.load(get_cfg_filename(SYSTEM_TRANSTBL_FILE))) + wedit.set_translation_table(tbl); + + if (tbl.load(get_cfg_filename(USER_REPRTBL_FILE)) + || tbl.load(get_cfg_filename(SYSTEM_REPRTBL_FILE))) + wedit.set_repr_table(tbl); + + if (tbl.load(get_cfg_filename(USER_ALTKBDTBL_FILE)) + || tbl.load(get_cfg_filename(SYSTEM_ALTKBDTBL_FILE))) { + wedit.set_alt_kbd_table(tbl); + } else { + show_file_io_error(_("Can't load Hebrew kbd emulation %s: %s"), + get_cfg_filename(SYSTEM_ALTKBDTBL_FILE)); + } + } +} + +// show_file_io_error() - convenience method to print I/O errors. + +void Editor::show_file_io_error(const char *msg, const char *filename) +{ + u8string errmsg; + errmsg.cformat(msg, filename, get_last_error()); + if (terminal::is_interactive()) + dialog.show_error_message(errmsg.c_str()); + else + fatal("%s\n", errmsg.c_str()); +} + +#ifdef HAVE_CURS_SET +INTERACTIVE void Editor::toggle_big_cursor() +{ + set_big_cursor(!is_big_cursor()); +} +#endif + +INTERACTIVE void Editor::toggle_cursor_position_report() +{ + status.toggle_cursor_position_report(); +} + +bool Editor::is_cursor_position_report() const +{ + return status.is_cursor_position_report(); +} + +// quit() - interactive command to quit the editor. it first makes sure the +// user don't want to save the changes. + +INTERACTIVE void Editor::quit() +{ + if (wedit.is_modified()) { + bool canceled; + bool save = dialog.ask_yes_or_no(_("Buffer was modified; save changes?"), + &canceled); + if (canceled) + return; + if (save) { + save_file(); + if (wedit.is_modified()) + return; + } + } + finished = true; // signal exec() +} + +// help() - interactive command to load and show the manual. + +void Editor::show_help_topic(const char *topic) +{ + HelpBox helpbox(this, wedit); + helpbox.layout_windows(); + if (helpbox.load_user_manual()) { + if (topic) + helpbox.jump_to_topic(topic); + helpbox.exec(); + refresh(); + } +} + +INTERACTIVE void Editor::help() +{ + show_help_topic(NULL); +} + +void Editor::set_scrollbar_pos(scrollbar_pos_t pos) +{ + if (terminal::is_interactive()) { + if (pos == scrlbrNone) { + delete scrollbar; + scrollbar = NULL; + } else { + if (!scrollbar) + scrollbar = new Scrollbar(); + wedit.sync_scrollbar(scrollbar); + } + scrollbar_pos = pos; + layout_windows(); + } +} + +INTERACTIVE void Editor::menu_set_scrollbar_none() +{ + set_scrollbar_pos(scrlbrNone); +} + +INTERACTIVE void Editor::menu_set_scrollbar_left() +{ + set_scrollbar_pos(scrlbrLeft); +} + +INTERACTIVE void Editor::menu_set_scrollbar_right() +{ + set_scrollbar_pos(scrlbrRight); +} + +INTERACTIVE void Editor::menu() +{ + if (menubar) + menubar->exec(); +} + +bool Editor::save_file(const char *filename, const char *specified_encoding) +{ + status.invalidate_view(); // encoding may change, so update the statusline + unichar offending_char; + if (!xsave_file(&wedit, filename, specified_encoding, + get_backup_suffix(), offending_char)) { + show_file_io_error(_("Saving %s failed: %s"), filename); + if (offending_char) + wedit.move_first_char(offending_char); + return false; + } else { + set_filename(filename); + set_encoding(specified_encoding); + set_new(false); + wedit.set_modified(false); + dialog.show_message(_("Saved OK")); + return true; + } +} + +bool Editor::write_selection_to_file(const char *filename, + const char *specified_encoding) +{ + unichar offending_char; + if (!xsave_file(&wedit, filename, specified_encoding, + get_backup_suffix(), offending_char, true)) { + show_file_io_error(_("Saving %s failed: %s"), filename); + if (offending_char) + wedit.move_first_char(offending_char); + return false; + } else { + dialog.show_message(_("Written OK")); + return true; + } +} + +INTERACTIVE void Editor::write_selection_to_file() +{ + u8string qry_filename, qry_encoding; + if (query_filename(wedit.has_selected_text() + ? _("Write selection to:") + : _("Write whole text to:"), + qry_filename, qry_encoding, WRITESELECTION_HISTORY)) + write_selection_to_file(qry_filename.c_str(), + qry_encoding.empty() ? get_encoding() : qry_encoding.c_str()); +} + +void Editor::set_encoding(const char *s) +{ + encoding = u8string(s); + status.invalidate_view(); +} + +bool Editor::load_file(const char *filename, const char *specified_encoding) +{ + bool is_new; + u8string effective_encoding; + + status.invalidate_view(); + set_filename(""); + + dialog.show_message(_("Loading...")); + dialog.immediate_update(); + if (!xload_file(&wedit, filename, specified_encoding, + get_default_encoding(), effective_encoding, is_new, true)) { + if (!effective_encoding.empty()) + set_encoding(effective_encoding.c_str()); + set_new(false); + show_file_io_error(_("Loading %s failed: %s"), filename); + return false; + } else { + if (scrollbar) + wedit.sync_scrollbar(scrollbar); + set_filename(filename); + if (!is_new) + set_encoding(effective_encoding.c_str()); + else + set_encoding(specified_encoding ? specified_encoding + : get_default_encoding()); + set_new(is_new); + dialog.show_message(_("Loaded OK")); + if (get_syntax_auto_detection()) + detect_syntax(); + else + wedit.set_syn_hlt(EditBox::synhltOff); + return true; + } +} + +// insert_file() - insert file at the cursor location. + +bool Editor::insert_file(const char *filename, const char *specified_encoding) +{ + bool dummy_is_new; + u8string dummy_effective_encoding; + status.invalidate_view(); + + if (!xload_file(&wedit, filename, specified_encoding, + get_default_encoding(), dummy_effective_encoding, + dummy_is_new, false)) { + show_file_io_error(_("Loading %s failed: %s"), filename); + return false; + } else { + dialog.show_message(_("Inserted OK")); + return true; + } +} + +INTERACTIVE void Editor::save_file_as() +{ + u8string qry_filename = get_filename(), qry_encoding; + if (query_filename(_("Save file as:"), qry_filename, qry_encoding, SAVEAS_HISTORY)) + save_file(qry_filename.c_str(), + qry_encoding.empty() ? get_encoding() : qry_encoding.c_str()); +} + +INTERACTIVE void Editor::save_file() +{ + if (is_untitled()) + save_file_as(); + else + save_file(get_filename(), get_encoding()); +} + +// emergency_save() - called by the SIGHUP handler to save the buffer. + +void Editor::emergency_save() +{ + if (wedit.is_modified()) { + u8string filename; + filename.cformat("%s.save", is_untitled() ? PACKAGE : get_filename()); + save_file(filename.c_str(), get_encoding()); + } +} + +INTERACTIVE void Editor::load_file() +{ + if (wedit.is_modified()) + if (!dialog.ask_yes_or_no(_("Buffer was modified; discard changes?"))) + return; + u8string qry_filename, qry_encoding; + if (query_filename(_("Open file:"), qry_filename, qry_encoding, LOAD_HISTORY)) + load_file(qry_filename.c_str(), + qry_encoding.empty() ? NULL : qry_encoding.c_str()); +} + +INTERACTIVE void Editor::insert_file() +{ + u8string qry_filename, qry_encoding; + if (query_filename(_("Insert file:"), qry_filename, qry_encoding, INSERT_HISTORY)) + insert_file(qry_filename.c_str(), + qry_encoding.empty() ? NULL : qry_encoding.c_str()); +} + +INTERACTIVE void Editor::change_directory() +{ + u8string qry_dirname, dummy; + if (query_filename(_("Change directory to:"), qry_dirname, dummy, + CHDIR_HISTORY, InputLine::cmpltDirectories)) { + if (chdir(qry_dirname.c_str()) == -1) { + set_last_error(errno); + show_file_io_error(_("Can't chdir to %s: %s"), qry_dirname.c_str()); + } + } +} + +void Editor::set_theme(const char *theme) +{ + ThemeError theme_error; + bool rslt; + if (!theme) + rslt = load_default_theme(theme_error); + else + rslt = load_theme(theme, theme_error); + if (!rslt) + show_file_io_error(_("Error: %s"), theme_error.format().c_str()); + refresh(); +} + +void Editor::set_default_theme() +{ + set_theme(NULL); +} + +const char *Editor::get_theme_name() +{ + return ::get_theme_name(); +} + +void Editor::menu_set_encoding(bool default_encoding) +{ + unistring answer = dialog.query( + _("Enter encoding name (do 'iconv -l' at the shell for a list):")); + if (!answer.empty()) { + if (default_encoding) + set_default_encoding(u8string(answer).c_str()); + else + set_encoding(u8string(answer).c_str()); + } +} + +// extract_encoding() - takes the filename that the user has entered, +// of the form "/path/to/filename -encoding", and returns the "encoding" +// part. it also removes it from the filename. + +static u8string extract_encoding(u8string &filename) +{ + u8string encoding; + int pos = filename.rindex(' '); + if (pos != -1 && (filename[pos + 1] == '-' || filename[pos + 1] == '+')) { + encoding.assign(filename, pos + 2, filename.size() - pos - 2); + // we've found it. now remove the encoding part from the filename itself + while (pos >= 0 && filename[pos] == ' ') + --pos; + filename.erase(filename.begin() + pos + 1, filename.end()); + } + return encoding; +} + +// query_filename() - prompt the user for a filename. + +bool Editor::query_filename(const char *label, u8string &qry_filename, + u8string &qry_encoding, int history_set, + InputLine::CompleteType complete) +{ + unistring uni_filename; + uni_filename.init_from_filename(qry_filename.c_str()); + // we feed dialog.query() not qry_filename but uni_filename. + // this extra conversion is needed because qry_filename may not be + // a valid UTF-8 string (this is possible since using UTF-8 for + // filenames is only a convention). + unistring answer = dialog.query(label, uni_filename, history_set, complete); + qry_filename.init_from_unichars(answer); + // extract encoding, but only if it's not a pipe. + if (!qry_filename.empty() && qry_filename[0] != '|' && qry_filename[0] != '!') + qry_encoding = extract_encoding(qry_filename); + else + qry_encoding = ""; + if (qry_filename == "~") + qry_filename = "~/"; + expand_tilde(qry_filename); + return !qry_filename.empty(); +} + +INTERACTIVE void Editor::change_tab_width() +{ + int num = dialog.get_number(_("Enter tab width:"), wedit.get_tab_width()); + if (num <= 80) // arbitrary sanity check + set_tab_width(num); +} + +INTERACTIVE void Editor::change_justification_column() +{ + int num = dialog.get_number(_("Enter justification column:"), + wedit.get_justification_column()); + set_justification_column(num); +} + +INTERACTIVE void Editor::change_scroll_step() +{ + int num = dialog.get_number(_("Enter scroll step:"), wedit.get_scroll_step()); + wedit.set_scroll_step(num); +} + +INTERACTIVE void Editor::insert_unicode_char() +{ + unistring answer = dialog.query( + _("Enter unicode hex value (or end in '.' for base 10):")); + if (answer.empty()) + return; + u8string numstr; + numstr.init_from_unichars(answer); + errno = 0; + char *endp; + unichar ch = strtol(numstr.c_str(), &endp, + strchr(numstr.c_str(), '.') ? 10 : 16); + if (!errno && (*endp == '.' || *endp == '\0')) + wedit.insert_char(ch); +} + +void Editor::log2vis(const char *options) +{ + wedit.log2vis(options); +} + +INTERACTIVE void Editor::toggle_arabic_shaping() +{ + terminal::do_arabic_shaping = !terminal::do_arabic_shaping; + wedit.reformat(); + if (terminal::is_interactive()) + refresh(); +} + +INTERACTIVE void Editor::toggle_graphical_boxes() +{ + terminal::graphical_boxes = !terminal::graphical_boxes; + if (terminal::is_interactive()) + refresh(); +} + +void Editor::detect_syntax() +{ +#define NMHAS(EXT) (strstr(get_filename(), EXT)) + if (NMHAS(".htm") || NMHAS(".HTM")) + wedit.set_syn_hlt(EditBox::synhltHTML); + else if (NMHAS(".letter") || NMHAS(".article") || NMHAS(".followup") || + NMHAS(".eml") || NMHAS("SLRN") || NMHAS("pico") || NMHAS("mutt")) + // the above strings were taken from Vim's cfg. + wedit.set_syn_hlt(EditBox::synhltEmail); + else + wedit.detect_syntax(); +#undef NMHAS +} + +void Editor::set_syntax_auto_detection(bool v) +{ + syntax_auto_detection = v; + if (v) + detect_syntax(); +} + +INTERACTIVE void Editor::toggle_syntax_auto_detection() +{ + set_syntax_auto_detection(!get_syntax_auto_detection()); +} + +INTERACTIVE void Editor::go_to_line() +{ + bool canceled; + int num = dialog.get_number(_("Go to line #:"), 0, &canceled); + if (!canceled) + go_to_line(num); +} + +void Editor::search_forward(const unistring &search) +{ + if (!wedit.search_forward(search)) + show_kbd_error(_("Not found")); + last_searched_string = search; +} + +INTERACTIVE void Editor::search_forward() +{ + bool alt_kbd = wedit.get_alt_kbd(); + unistring search = dialog.query(_("Search forward:"), + last_searched_string, SEARCH_HISTORY, + InputLine::cmpltOff, &alt_kbd); + wedit.set_alt_kbd(alt_kbd); + if (!search.empty()) + search_forward(search); +} + +INTERACTIVE void Editor::search_forward_next() +{ + if (!last_searched_string.empty()) + search_forward(last_searched_string); + else + search_forward(); +} + +u8string Editor::get_external_editor() +{ + if (!external_editor.empty()) + return external_editor; + + // User hasn't specified an editor, so we figure one out ourselves: + // First try $EDITOR, them gvim, then vim, then ... + if (getenv("EDITOR") && *getenv("EDITOR")) + return getenv("EDITOR"); + if (terminal::under_x11() && has_prog("gvim")) + return "gvim -f"; // `-f' = not to fork and detach + if (has_prog("vim")) + return "vim"; + if (has_prog("emacs")) + return "emacs"; + if (has_prog("pico")) + return "pico"; + return "vi"; +} + +void Editor::set_external_editor(const char *cmd) +{ + external_editor = cmd; +} + +INTERACTIVE void Editor::external_edit_prompt() +{ + external_edit(true); +} + +INTERACTIVE void Editor::external_edit_no_prompt() +{ + external_edit(false); +} + +// external_edit() launches an external editor to edit the file +// and then reload it. + +void Editor::external_edit(bool prompt) +{ + // Step 1: we may first need to save the file. + + if (is_untitled()) { + if (dialog.ask_yes_or_no(_("First I must save this buffer as a file; is it OK with you?"))) + save_file(); + } else if (wedit.is_modified()) { + if (dialog.ask_yes_or_no(_("Buffer was modified and I must save it first; is it OK with you?"))) + save_file(); + } + if (is_untitled() || wedit.is_modified()) + return; + + // Step 2: get the editor command + + cstring command = get_external_editor(); + if (prompt) { + unistring inpt = dialog.query(_("External editor:"), + command.c_str(), + EXTERNALEDITOR_HISTORY, + InputLine::cmpltAll); + command = u8string(inpt).trim(); + if (command.empty()) // user aborted + return; + set_external_editor(command.c_str()); + } + + // Step 3: write the cursor location into a temporary file, + // pointed to by $GERESH_CURSOR_FILE + + cstring cursor_file; + //cursor_file = tmpnam(NULL); // gives linker warning! + cursor_file.cformat("%s/geresh-%ld-cursor.tmp", + getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp", + (long)getpid()); + + Point cursor; + wedit.get_cursor_position(cursor); + + FILE *cursor_fh; + if ((cursor_fh = fopen(cursor_file.c_str(), "w")) != NULL) { + fprintf(cursor_fh, "%d %d\n%s\n", + cursor.para + 1, cursor.pos + 1, get_filename()); + fclose(cursor_fh); + + cstring nameval; + nameval.cformat("GERESH_CURSOR_FILE=%s", cursor_file.c_str()); + putenv(strdup(nameval.c_str())); + } + + // Step 4: adjust the editor command so the line number is passed. + + if (command.index("vim") != -1) { + // With vim we not only pass the column number in addition + // to the line number, but we also pass the necessary + // command to write the cursor position back into + // $GERESH_CURSOR_FILE. + cstring basefilename = get_filename(); + if (basefilename.index('/') != -1) + basefilename = basefilename.substr(basefilename.rindex('/')+1); + cstring col_command; + if (cursor.pos != 0) + col_command.cformat("-c \"normal %dl\"", cursor.pos); + command.cformat("%s -c %d -c \"normal 0\" %s " + "-c \"au BufUnload %s call system('echo '.line('.').' '.col('.').' >%s')\"", + command.c_str(), + cursor.para + 1, + col_command.c_str(), + basefilename.c_str(), + cursor_file.c_str()); + } + else if (command.index("vi") != -1 || + command.index("pico") != -1 || + command.index("nano") != -1 || + command.index("emacs") != -1 || + command.index("mined") != -1) + { + // The above editors are known to accept a "+line" parameter. + command.cformat("%s +%d", command.c_str(), cursor.para + 1); + } + + command += " \""; + command += get_filename(); + command += "\""; + + // Step 5: execute the editor. + + cstring msg; + msg.cformat("Executing: %s", command.c_str()); + dialog.show_message(msg.c_str()); + update_terminal(); + + endwin(); + int status = system(command.c_str()); + doupdate(); + + // Step 6: reload the file. + + // I can't just pass 'get_filename()' to load_file(), because + // get_filename() returns a pointer to a string which load_file() + // modifies (via set_filename()) ... + load_file(u8string(get_filename()).c_str(), + u8string(get_encoding()).c_str()); + + // Step 7: read the cursor position from $GERESH_CURSOR_FILE. + + if ((cursor_fh = fopen(cursor_file.c_str(), "r")) != NULL) { + int line_no, col_no; + fscanf(cursor_fh, "%d %d", &line_no, &col_no); + cursor.para = line_no - 1; + cursor.pos = col_no - 1; + wedit.set_cursor_position(cursor); + fclose(cursor_fh); + } else { + wedit.set_cursor_position(cursor); + } + unlink(cursor_file.c_str()); + + if (WIFEXITED(status) && WEXITSTATUS(status) == 127) { + msg.cformat(_("Error: command \"%s\" could not be found"), + get_external_editor().c_str()); + dialog.show_message(msg.c_str()); + } + + refresh_and_center(); +} + +// layout_windows() - is called every time the window changes its size. + +INTERACTIVE void Editor::layout_windows() +{ + int cols, rows; + getmaxyx(stdscr, rows, cols); + + int speller_height = 0; + + if (spellerwnd) { + // determine the height of the speller window + // based on the screen size. + if (rows <= 10) + speller_height = 2; + else if (rows <= 16) + speller_height = 4; + else if (rows <= 20) + speller_height = 6; + else if (rows <= 26) + speller_height = 8; + else if (rows <= 36) + speller_height = 11; + else + speller_height = 13; + + spellerwnd->resize(speller_height, cols, rows - 2 - speller_height, 0); + } + + if (menubar) + menubar->resize(1, cols, 0, 0); +#define MENUHEIGHT (menubar ? 1 : 0) + + if (scrollbar) + scrollbar->resize(rows - 2 - MENUHEIGHT - speller_height, 1, + MENUHEIGHT, (scrollbar_pos == scrlbrLeft) ? 0 : cols - 1); +#define SCROLLBARWIDTH (scrollbar ? 1 : 0) + + wedit.resize(rows - 2 - MENUHEIGHT - speller_height, + cols - SCROLLBARWIDTH, MENUHEIGHT, + (scrollbar_pos == scrlbrLeft) ? 1 : 0); + status.resize(1, cols, rows - 2, 0); + dialog.resize(1, cols, rows - 1, 0); + + update_terminal(); +} + +// update_terminal() - updates the screen. + +void Editor::update_terminal(bool soft) +{ + // for every widget that's dirty, call its update() method to update + // stdscr. If any was dirty, call doupdate() to update the physical + // screen. + + bool need_dorefresh = false; + + if (scrollbar && scrollbar->is_dirty()) { + scrollbar->update(); + need_dorefresh = true; + } + + if (menubar && menubar->is_dirty()) { + menubar->update(); + need_dorefresh = true; + } + + if (status.is_dirty()) { + status.update(); + need_dorefresh = true; + } + + if (dialog.is_dirty()) { + dialog.update(); + need_dorefresh = true; + } + + if (wedit.is_dirty()) { + wedit.update(); + need_dorefresh = true; + } else if (need_dorefresh) { + // make sure wedit gets the cursor even if some + // of the other widgets get drawn. + wedit.update_cursor(); + } + + if (spellerwnd) { + if (spellerwnd->is_dirty()) { + spellerwnd->update(); + need_dorefresh = true; + } else if (need_dorefresh) { + // spellerwnd always gets the cursor. + spellerwnd->update_cursor(); + } + } + + if (!soft && need_dorefresh) + doupdate(); +} + +// Editor::exec() - the main event loop. it reads events and either handles +// them or send them on to the editbox. + +void Editor::exec() +{ + finished = false; + while (!finished) { + Event evt; + update_terminal(); + get_next_event(evt, wedit.wnd); + dialog.clear_transient_message(); + if (!evt.is_literal()) + if (handle_event(evt)) + continue; + wedit.handle_event(evt); + if (scrollbar) + wedit.sync_scrollbar(scrollbar); + } +} + +// load_unload_speller() - interactively loads and unloads a speller +// process. When the user interactively loads a speller, he is asked to +// specify the speller-command and the speller-encoding. + +INTERACTIVE void Editor::load_unload_speller() +{ + if (speller.is_loaded()) { + speller.unload(); + } else { + // We're about to load the speller. query the user for cmd / encoding. + unistring cmd = dialog.query(_("Enter speller command:"), + get_speller_cmd(), SPELER_CMD_HISTORY, InputLine::cmpltAll); + if (cmd.empty()) + return; + if (cmd.index(u8string("-a")) == -1) { + if (!dialog.ask_yes_or_no( + _("There's no `-a' option in the command, proceed anyway?"))) + return; + } + unistring encoding = dialog.query(_("Enter speller encoding:"), + get_speller_encoding(), SPELER_ENCODING_HISTORY); + if (encoding.empty()) + return; + set_speller_cmd(u8string(cmd).c_str()); + set_speller_encoding(u8string(encoding).c_str()); + speller.load(get_speller_cmd(), get_speller_encoding()); + } + status.invalidate_view(); +} + +// adjust_speller_cmd() - if the user hasn't specified a speller command, +// figure it out ourselves. +// Prefer: multispell, then ispell, and finally aspell. +// Use the ISO-8859-8 encoding for Hebrew spell-checkers. + +void Editor::adjust_speller_cmd() +{ + bool no_cmd = (*get_speller_cmd() == '\0'); + bool no_encoding = (*get_speller_encoding() == '\0'); + + if (no_cmd) { + bool has_multispell = has_prog("multispell"); + bool has_ispell = has_prog("ispell"); + bool has_aspell = !has_ispell && has_prog("aspell"); // optimization: search aspell + // only if no ispell found. + if (has_multispell) { + u8string cmd; + if (has_ispell || has_aspell) { + cmd = "LC_ALL=C multispell -a -n"; + if (!has_ispell && has_aspell) + cmd += " --ispell-cmd=aspell"; + } else { + cmd = "LC_ALL=C hspell -a -n"; + } + set_speller_cmd(cmd.c_str()); + set_speller_encoding("ISO-8859-8"); + } else { + if (!has_ispell && has_aspell) + set_speller_cmd("aspell -a"); + else + set_speller_cmd("ispell -a"); + if (no_encoding) + set_speller_encoding("ISO-8859-1"); + } + } else if (no_encoding) { + if (strstr(get_speller_cmd(), "hspell") || strstr(get_speller_cmd(), "multispell")) + set_speller_encoding("ISO-8859-8"); + else + set_speller_encoding("ISO-8859-1"); + } +} + +INTERACTIVE void Editor::spell_check_all() +{ + spell_check(Speller::splRngAll); +} + +INTERACTIVE void Editor::spell_check_forward() +{ + spell_check(Speller::splRngForward); +} + +INTERACTIVE void Editor::spell_check_word() +{ + spell_check(Speller::splRngWord); +} + +void Editor::spell_check(Speller::splRng range) +{ + if (!spellerwnd) { + spellerwnd = new SpellerWnd(*this); + layout_windows(); + } + + // if the speller is not yet loaded, load it with the default + // parameters. + if (!speller.is_loaded()) { + speller.load(get_speller_cmd(), get_speller_encoding()); + status.invalidate_view(); + update_terminal(); // immediately update the status line + } + + if (speller.is_loaded()) + speller.spell_check(range, wedit, *spellerwnd); + + // The speller window exists during the spell check only. we + // delete it afterwards. + delete spellerwnd; + spellerwnd = NULL; + layout_windows(); +} + +// show_hint() is used to print the popdown menu hints. Screen is updated +// only after doupdate() + +void Editor::show_hint(const char *msg) +{ + dialog.show_message(msg); + dialog.update(); +} + +// show_kbd_error() - prints a transient error message and rings the bell. +// it is usually used for "keyboard" errors and the bell is supposed to +// catch the users attention. + +void Editor::show_kbd_error(const char *msg) +{ + // we don't use show_error_message() because we want the + // message to disappear at the next event. + dialog.show_message(msg); + Widget::signal_error(); +} + +void Editor::on_read_only_error(unichar ch) +{ + unistring quit_chars; + // when the buffer is in read-only mode, we let the user exit + // by pressing 'q'. + quit_chars.init_from_utf8(_("qQ")); + if (quit_chars.has_char(ch)) + quit(); + else + show_kbd_error(_("Buffer is read-only")); +} + +void Editor::on_no_selection_error() +{ + show_kbd_error(_("No text is selected")); +} + +void Editor::on_no_alt_kbd_error() +{ + show_kbd_error(_("No alternate keyboard (Hebrew) was loaded")); +} + +void Editor::on_no_translation_table_error() +{ + show_kbd_error(_("No translation table was loaded")); +} + +void Editor::on_cant_display_nsm_error() +{ + show_kbd_error(_("Terminal can't display non-spacing marks (like Hebrew points)")); +} + -- cgit v1.2.3