editor.cc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. // Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
  2. //
  3. // This program is free software; you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation; either version 2 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  16. #include <config.h>
  17. #include <stdlib.h>
  18. #include <errno.h>
  19. #include <unistd.h> // chdir
  20. #include <sys/wait.h> // WEXITSTATUS()
  21. #include <string.h>
  22. #include "editor.h"
  23. #include "menus.h"
  24. #include "scrollbar.h"
  25. #include "io.h"
  26. #include "pathnames.h"
  27. #include "themes.h"
  28. #include "utf8.h" // used in show_character_code()
  29. #include "dbg.h"
  30. #include "transtbl.h"
  31. #include "helpbox.h"
  32. Editor *Editor::global_instance; // for SIGHUP
  33. #define LOAD_HISTORY 1
  34. #define SAVEAS_HISTORY 0
  35. #define SEARCH_HISTORY 2
  36. #define INSERT_HISTORY 1
  37. #define CHDIR_HISTORY 4
  38. #define WRITESELECTION_HISTORY 5
  39. #define SPELER_CMD_HISTORY 6
  40. #define SPELER_ENCODING_HISTORY 7
  41. #define EXTERNALEDITOR_HISTORY 8
  42. // describe_key() - reads a key event from the keyboard, searches and then
  43. // prints the description of the correspoding action.
  44. INTERACTIVE void Editor::describe_key()
  45. {
  46. dialog.show_message(_("Describe key:"));
  47. dialog.immediate_update();
  48. Event evt;
  49. get_next_event(evt, dialog.wnd);
  50. // first look for a corresponding action in Editor's action map.
  51. // If none was found, look in EditBox's.
  52. const char *action, *desc = NULL;
  53. if ((action = get_event_action(evt)))
  54. desc = get_action_description(action);
  55. else {
  56. if ((action = wedit.get_event_action(evt)))
  57. desc = wedit.get_action_description(action);
  58. }
  59. dialog.show_message(desc ? _(desc)
  60. : _("Sorry, no description for this key"));
  61. }
  62. INTERACTIVE void Editor::refresh_and_center()
  63. {
  64. wedit.center_line();
  65. refresh();
  66. }
  67. void Editor::refresh(bool soft)
  68. {
  69. if (!soft)
  70. clearok(curscr, TRUE);
  71. if (spellerwnd)
  72. spellerwnd->invalidate_view();
  73. if (scrollbar)
  74. scrollbar->invalidate_view();
  75. if (menubar)
  76. menubar->invalidate_view();
  77. wedit.invalidate_view();
  78. status.invalidate_view();
  79. dialog.invalidate_view();
  80. update_terminal(soft);
  81. }
  82. // show_character_code() - prints the code value and the UTF-8 sequence
  83. // of the character on which the cursor stands.
  84. INTERACTIVE void Editor::show_character_code()
  85. {
  86. unichar ch = wedit.get_current_char();
  87. // construct a nice utf-8 representation in utf8_c_sync
  88. char utf8[6], utf8_c_syn[6*4 + 1];
  89. int nbytes = unicode_to_utf8(utf8, &ch, 1);
  90. for (int i = 0; i < nbytes; i++)
  91. sprintf(utf8_c_syn + i*4, "\\x%02X", (unsigned char)utf8[i]);
  92. dialog.show_message_fmt(_("Unicode value: U+%04lX (decimal: %ld), "
  93. "UTF-8 sequence: %s"),
  94. (unsigned long)ch, (unsigned long)ch,
  95. utf8_c_syn);
  96. }
  97. // show_character_info() - prints information from UnicodeData.txt
  98. // about the characetr on which the cursor stands.
  99. INTERACTIVE void Editor::show_character_info()
  100. {
  101. #define MAX_LINE_LEN 1024
  102. unichar ch = wedit.get_current_char();
  103. FILE *fp;
  104. if ((fp = fopen(get_cfg_filename(USER_UNICODE_DATA_FILE), "r")) != NULL
  105. || (fp = fopen(get_cfg_filename(SYSTEM_UNICODE_DATA_FILE), "r")) != NULL) {
  106. char line[MAX_LINE_LEN];
  107. while (fgets(line, MAX_LINE_LEN, fp)) {
  108. if (strtol(line, NULL, 16) == (long)ch) {
  109. line[strlen(line) - 1] = '\0'; // remove LF
  110. dialog.show_message(line);
  111. break;
  112. }
  113. }
  114. fclose(fp);
  115. } else {
  116. set_last_error(errno);
  117. show_file_io_error(_("Can't open %s: %s"),
  118. get_cfg_filename(SYSTEM_UNICODE_DATA_FILE));
  119. }
  120. #undef MAX_LINE_LEN
  121. }
  122. Editor::Editor()
  123. : dialog(this),
  124. status(this, &wedit),
  125. speller(*this, dialog)
  126. {
  127. spellerwnd = NULL;
  128. wedit.set_error_listener(this);
  129. global_instance = this;
  130. set_default_encoding(DEFAULT_FILE_ENCODING);
  131. set_encoding(get_default_encoding());
  132. set_filename("");
  133. set_new(false);
  134. #ifdef HAVE_CURS_SET
  135. big_cursor = false;
  136. #endif
  137. menubar = create_main_menubar(this, &wedit);
  138. scrollbar = NULL;
  139. scrollbar_pos = scrlbrNone;
  140. // The rest is only needed if we're in interactive mode,
  141. // that is, not in "do_log2vis" mode.
  142. if (terminal::is_interactive()) {
  143. layout_windows();
  144. dialog.show_message_fmt(_("Welcome to %s version %s |"
  145. "F1=Help, F9 or F10=Menu, ALT-X=Quit"),
  146. PACKAGE, VERSION);
  147. TranslationTable tbl;
  148. if (tbl.load(get_cfg_filename(USER_TRANSTBL_FILE))
  149. || tbl.load(get_cfg_filename(SYSTEM_TRANSTBL_FILE)))
  150. wedit.set_translation_table(tbl);
  151. if (tbl.load(get_cfg_filename(USER_REPRTBL_FILE))
  152. || tbl.load(get_cfg_filename(SYSTEM_REPRTBL_FILE)))
  153. wedit.set_repr_table(tbl);
  154. if (tbl.load(get_cfg_filename(USER_ALTKBDTBL_FILE))
  155. || tbl.load(get_cfg_filename(SYSTEM_ALTKBDTBL_FILE))) {
  156. wedit.set_alt_kbd_table(tbl);
  157. } else {
  158. show_file_io_error(_("Can't load Hebrew kbd emulation %s: %s"),
  159. get_cfg_filename(SYSTEM_ALTKBDTBL_FILE));
  160. }
  161. }
  162. }
  163. // show_file_io_error() - convenience method to print I/O errors.
  164. void Editor::show_file_io_error(const char *msg, const char *filename)
  165. {
  166. u8string errmsg;
  167. errmsg.cformat(msg, filename, get_last_error());
  168. if (terminal::is_interactive())
  169. dialog.show_error_message(errmsg.c_str());
  170. else
  171. fatal("%s\n", errmsg.c_str());
  172. }
  173. #ifdef HAVE_CURS_SET
  174. INTERACTIVE void Editor::toggle_big_cursor()
  175. {
  176. set_big_cursor(!is_big_cursor());
  177. }
  178. #endif
  179. INTERACTIVE void Editor::toggle_cursor_position_report()
  180. {
  181. status.toggle_cursor_position_report();
  182. }
  183. bool Editor::is_cursor_position_report() const
  184. {
  185. return status.is_cursor_position_report();
  186. }
  187. // quit() - interactive command to quit the editor. it first makes sure the
  188. // user don't want to save the changes.
  189. INTERACTIVE void Editor::quit()
  190. {
  191. if (wedit.is_modified()) {
  192. bool canceled;
  193. bool save = dialog.ask_yes_or_no(_("Buffer was modified; save changes?"),
  194. &canceled);
  195. if (canceled)
  196. return;
  197. if (save) {
  198. save_file();
  199. if (wedit.is_modified())
  200. return;
  201. }
  202. }
  203. finished = true; // signal exec()
  204. }
  205. // help() - interactive command to load and show the manual.
  206. void Editor::show_help_topic(const char *topic)
  207. {
  208. HelpBox helpbox(this, wedit);
  209. helpbox.layout_windows();
  210. if (helpbox.load_user_manual()) {
  211. if (topic)
  212. helpbox.jump_to_topic(topic);
  213. helpbox.exec();
  214. refresh();
  215. }
  216. }
  217. INTERACTIVE void Editor::help()
  218. {
  219. show_help_topic(NULL);
  220. }
  221. void Editor::set_scrollbar_pos(scrollbar_pos_t pos)
  222. {
  223. if (terminal::is_interactive()) {
  224. if (pos == scrlbrNone) {
  225. delete scrollbar;
  226. scrollbar = NULL;
  227. } else {
  228. if (!scrollbar)
  229. scrollbar = new Scrollbar();
  230. wedit.sync_scrollbar(scrollbar);
  231. }
  232. scrollbar_pos = pos;
  233. layout_windows();
  234. }
  235. }
  236. INTERACTIVE void Editor::menu_set_scrollbar_none()
  237. {
  238. set_scrollbar_pos(scrlbrNone);
  239. }
  240. INTERACTIVE void Editor::menu_set_scrollbar_left()
  241. {
  242. set_scrollbar_pos(scrlbrLeft);
  243. }
  244. INTERACTIVE void Editor::menu_set_scrollbar_right()
  245. {
  246. set_scrollbar_pos(scrlbrRight);
  247. }
  248. INTERACTIVE void Editor::menu()
  249. {
  250. if (menubar)
  251. menubar->exec();
  252. }
  253. bool Editor::save_file(const char *filename, const char *specified_encoding)
  254. {
  255. status.invalidate_view(); // encoding may change, so update the statusline
  256. unichar offending_char;
  257. if (!xsave_file(&wedit, filename, specified_encoding,
  258. get_backup_suffix(), offending_char)) {
  259. show_file_io_error(_("Saving %s failed: %s"), filename);
  260. if (offending_char)
  261. wedit.move_first_char(offending_char);
  262. return false;
  263. } else {
  264. set_filename(filename);
  265. set_encoding(specified_encoding);
  266. set_new(false);
  267. wedit.set_modified(false);
  268. dialog.show_message(_("Saved OK"));
  269. return true;
  270. }
  271. }
  272. bool Editor::write_selection_to_file(const char *filename,
  273. const char *specified_encoding)
  274. {
  275. unichar offending_char;
  276. if (!xsave_file(&wedit, filename, specified_encoding,
  277. get_backup_suffix(), offending_char, true)) {
  278. show_file_io_error(_("Saving %s failed: %s"), filename);
  279. if (offending_char)
  280. wedit.move_first_char(offending_char);
  281. return false;
  282. } else {
  283. dialog.show_message(_("Written OK"));
  284. return true;
  285. }
  286. }
  287. INTERACTIVE void Editor::write_selection_to_file()
  288. {
  289. u8string qry_filename, qry_encoding;
  290. if (query_filename(wedit.has_selected_text()
  291. ? _("Write selection to:")
  292. : _("Write whole text to:"),
  293. qry_filename, qry_encoding, WRITESELECTION_HISTORY))
  294. write_selection_to_file(qry_filename.c_str(),
  295. qry_encoding.empty() ? get_encoding() : qry_encoding.c_str());
  296. }
  297. void Editor::set_encoding(const char *s)
  298. {
  299. encoding = u8string(s);
  300. status.invalidate_view();
  301. }
  302. bool Editor::load_file(const char *filename, const char *specified_encoding)
  303. {
  304. bool is_new;
  305. u8string effective_encoding;
  306. status.invalidate_view();
  307. set_filename("");
  308. dialog.show_message(_("Loading..."));
  309. dialog.immediate_update();
  310. if (!xload_file(&wedit, filename, specified_encoding,
  311. get_default_encoding(), effective_encoding, is_new, true)) {
  312. if (!effective_encoding.empty())
  313. set_encoding(effective_encoding.c_str());
  314. set_new(false);
  315. show_file_io_error(_("Loading %s failed: %s"), filename);
  316. return false;
  317. } else {
  318. if (scrollbar)
  319. wedit.sync_scrollbar(scrollbar);
  320. set_filename(filename);
  321. if (!is_new)
  322. set_encoding(effective_encoding.c_str());
  323. else
  324. set_encoding(specified_encoding ? specified_encoding
  325. : get_default_encoding());
  326. set_new(is_new);
  327. dialog.show_message(_("Loaded OK"));
  328. if (get_syntax_auto_detection())
  329. detect_syntax();
  330. else
  331. wedit.set_syn_hlt(EditBox::synhltOff);
  332. return true;
  333. }
  334. }
  335. // insert_file() - insert file at the cursor location.
  336. bool Editor::insert_file(const char *filename, const char *specified_encoding)
  337. {
  338. bool dummy_is_new;
  339. u8string dummy_effective_encoding;
  340. status.invalidate_view();
  341. if (!xload_file(&wedit, filename, specified_encoding,
  342. get_default_encoding(), dummy_effective_encoding,
  343. dummy_is_new, false)) {
  344. show_file_io_error(_("Loading %s failed: %s"), filename);
  345. return false;
  346. } else {
  347. dialog.show_message(_("Inserted OK"));
  348. return true;
  349. }
  350. }
  351. INTERACTIVE void Editor::save_file_as()
  352. {
  353. u8string qry_filename = get_filename(), qry_encoding;
  354. if (query_filename(_("Save file as:"), qry_filename, qry_encoding, SAVEAS_HISTORY))
  355. save_file(qry_filename.c_str(),
  356. qry_encoding.empty() ? get_encoding() : qry_encoding.c_str());
  357. }
  358. INTERACTIVE void Editor::save_file()
  359. {
  360. if (is_untitled())
  361. save_file_as();
  362. else
  363. save_file(get_filename(), get_encoding());
  364. }
  365. // emergency_save() - called by the SIGHUP handler to save the buffer.
  366. void Editor::emergency_save()
  367. {
  368. if (wedit.is_modified()) {
  369. u8string filename;
  370. filename.cformat("%s.save", is_untitled() ? PACKAGE : get_filename());
  371. save_file(filename.c_str(), get_encoding());
  372. }
  373. }
  374. INTERACTIVE void Editor::load_file()
  375. {
  376. if (wedit.is_modified())
  377. if (!dialog.ask_yes_or_no(_("Buffer was modified; discard changes?")))
  378. return;
  379. u8string qry_filename, qry_encoding;
  380. if (query_filename(_("Open file:"), qry_filename, qry_encoding, LOAD_HISTORY))
  381. load_file(qry_filename.c_str(),
  382. qry_encoding.empty() ? NULL : qry_encoding.c_str());
  383. }
  384. INTERACTIVE void Editor::insert_file()
  385. {
  386. u8string qry_filename, qry_encoding;
  387. if (query_filename(_("Insert file:"), qry_filename, qry_encoding, INSERT_HISTORY))
  388. insert_file(qry_filename.c_str(),
  389. qry_encoding.empty() ? NULL : qry_encoding.c_str());
  390. }
  391. INTERACTIVE void Editor::change_directory()
  392. {
  393. u8string qry_dirname, dummy;
  394. if (query_filename(_("Change directory to:"), qry_dirname, dummy,
  395. CHDIR_HISTORY, InputLine::cmpltDirectories)) {
  396. if (chdir(qry_dirname.c_str()) == -1) {
  397. set_last_error(errno);
  398. show_file_io_error(_("Can't chdir to %s: %s"), qry_dirname.c_str());
  399. }
  400. }
  401. }
  402. void Editor::set_theme(const char *theme)
  403. {
  404. ThemeError theme_error;
  405. bool rslt;
  406. if (!theme)
  407. rslt = load_default_theme(theme_error);
  408. else
  409. rslt = load_theme(theme, theme_error);
  410. if (!rslt)
  411. show_file_io_error(_("Error: %s"), theme_error.format().c_str());
  412. refresh();
  413. }
  414. void Editor::set_default_theme()
  415. {
  416. set_theme(NULL);
  417. }
  418. const char *Editor::get_theme_name()
  419. {
  420. return ::get_theme_name();
  421. }
  422. void Editor::menu_set_encoding(bool default_encoding)
  423. {
  424. unistring answer = dialog.query(
  425. _("Enter encoding name (do 'iconv -l' at the shell for a list):"));
  426. if (!answer.empty()) {
  427. if (default_encoding)
  428. set_default_encoding(u8string(answer).c_str());
  429. else
  430. set_encoding(u8string(answer).c_str());
  431. }
  432. }
  433. // extract_encoding() - takes the filename that the user has entered,
  434. // of the form "/path/to/filename -encoding", and returns the "encoding"
  435. // part. it also removes it from the filename.
  436. static u8string extract_encoding(u8string &filename)
  437. {
  438. u8string encoding;
  439. int pos = filename.rindex(' ');
  440. if (pos != -1 && (filename[pos + 1] == '-' || filename[pos + 1] == '+')) {
  441. encoding.assign(filename, pos + 2, filename.size() - pos - 2);
  442. // we've found it. now remove the encoding part from the filename itself
  443. while (pos >= 0 && filename[pos] == ' ')
  444. --pos;
  445. filename.erase(filename.begin() + pos + 1, filename.end());
  446. }
  447. return encoding;
  448. }
  449. // query_filename() - prompt the user for a filename.
  450. bool Editor::query_filename(const char *label, u8string &qry_filename,
  451. u8string &qry_encoding, int history_set,
  452. InputLine::CompleteType complete)
  453. {
  454. unistring uni_filename;
  455. uni_filename.init_from_filename(qry_filename.c_str());
  456. // we feed dialog.query() not qry_filename but uni_filename.
  457. // this extra conversion is needed because qry_filename may not be
  458. // a valid UTF-8 string (this is possible since using UTF-8 for
  459. // filenames is only a convention).
  460. unistring answer = dialog.query(label, uni_filename, history_set, complete);
  461. qry_filename.init_from_unichars(answer);
  462. // extract encoding, but only if it's not a pipe.
  463. if (!qry_filename.empty() && qry_filename[0] != '|' && qry_filename[0] != '!')
  464. qry_encoding = extract_encoding(qry_filename);
  465. else
  466. qry_encoding = "";
  467. if (qry_filename == "~")
  468. qry_filename = "~/";
  469. expand_tilde(qry_filename);
  470. return !qry_filename.empty();
  471. }
  472. INTERACTIVE void Editor::change_tab_width()
  473. {
  474. int num = dialog.get_number(_("Enter tab width:"), wedit.get_tab_width());
  475. if (num <= 80) // arbitrary sanity check
  476. set_tab_width(num);
  477. }
  478. INTERACTIVE void Editor::change_justification_column()
  479. {
  480. int num = dialog.get_number(_("Enter justification column:"),
  481. wedit.get_justification_column());
  482. set_justification_column(num);
  483. }
  484. INTERACTIVE void Editor::change_scroll_step()
  485. {
  486. int num = dialog.get_number(_("Enter scroll step:"), wedit.get_scroll_step());
  487. wedit.set_scroll_step(num);
  488. }
  489. INTERACTIVE void Editor::insert_unicode_char()
  490. {
  491. unistring answer = dialog.query(
  492. _("Enter unicode hex value (or end in '.' for base 10):"));
  493. if (answer.empty())
  494. return;
  495. u8string numstr;
  496. numstr.init_from_unichars(answer);
  497. errno = 0;
  498. char *endp;
  499. unichar ch = strtol(numstr.c_str(), &endp,
  500. strchr(numstr.c_str(), '.') ? 10 : 16);
  501. if (!errno && (*endp == '.' || *endp == '\0'))
  502. wedit.insert_char(ch);
  503. }
  504. void Editor::log2vis(const char *options)
  505. {
  506. wedit.log2vis(options);
  507. }
  508. INTERACTIVE void Editor::toggle_arabic_shaping()
  509. {
  510. terminal::do_arabic_shaping = !terminal::do_arabic_shaping;
  511. wedit.reformat();
  512. if (terminal::is_interactive())
  513. refresh();
  514. }
  515. INTERACTIVE void Editor::toggle_graphical_boxes()
  516. {
  517. terminal::graphical_boxes = !terminal::graphical_boxes;
  518. if (terminal::is_interactive())
  519. refresh();
  520. }
  521. void Editor::detect_syntax()
  522. {
  523. #define NMHAS(EXT) (strstr(get_filename(), EXT))
  524. if (NMHAS(".htm") || NMHAS(".HTM"))
  525. wedit.set_syn_hlt(EditBox::synhltHTML);
  526. else if (NMHAS(".letter") || NMHAS(".article") || NMHAS(".followup") ||
  527. NMHAS(".eml") || NMHAS("SLRN") || NMHAS("pico") || NMHAS("mutt"))
  528. // the above strings were taken from Vim's cfg.
  529. wedit.set_syn_hlt(EditBox::synhltEmail);
  530. else
  531. wedit.detect_syntax();
  532. #undef NMHAS
  533. }
  534. void Editor::set_syntax_auto_detection(bool v)
  535. {
  536. syntax_auto_detection = v;
  537. if (v)
  538. detect_syntax();
  539. }
  540. INTERACTIVE void Editor::toggle_syntax_auto_detection()
  541. {
  542. set_syntax_auto_detection(!get_syntax_auto_detection());
  543. }
  544. INTERACTIVE void Editor::go_to_line()
  545. {
  546. bool canceled;
  547. int num = dialog.get_number(_("Go to line #:"), 0, &canceled);
  548. if (!canceled)
  549. go_to_line(num);
  550. }
  551. void Editor::search_forward(const unistring &search)
  552. {
  553. if (!wedit.search_forward(search))
  554. show_kbd_error(_("Not found"));
  555. last_searched_string = search;
  556. }
  557. INTERACTIVE void Editor::search_forward()
  558. {
  559. bool alt_kbd = wedit.get_alt_kbd();
  560. unistring search = dialog.query(_("Search forward:"),
  561. last_searched_string, SEARCH_HISTORY,
  562. InputLine::cmpltOff, &alt_kbd);
  563. wedit.set_alt_kbd(alt_kbd);
  564. if (!search.empty())
  565. search_forward(search);
  566. }
  567. INTERACTIVE void Editor::search_forward_next()
  568. {
  569. if (!last_searched_string.empty())
  570. search_forward(last_searched_string);
  571. else
  572. search_forward();
  573. }
  574. u8string Editor::get_external_editor()
  575. {
  576. if (!external_editor.empty())
  577. return external_editor;
  578. // User hasn't specified an editor, so we figure one out ourselves:
  579. // First try $EDITOR, them gvim, then vim, then ...
  580. if (getenv("EDITOR") && *getenv("EDITOR"))
  581. return getenv("EDITOR");
  582. if (terminal::under_x11() && has_prog("gvim"))
  583. return "gvim -f"; // `-f' = not to fork and detach
  584. if (has_prog("vim"))
  585. return "vim";
  586. if (has_prog("emacs"))
  587. return "emacs";
  588. if (has_prog("pico"))
  589. return "pico";
  590. return "vi";
  591. }
  592. void Editor::set_external_editor(const char *cmd)
  593. {
  594. external_editor = cmd;
  595. }
  596. INTERACTIVE void Editor::external_edit_prompt()
  597. {
  598. external_edit(true);
  599. }
  600. INTERACTIVE void Editor::external_edit_no_prompt()
  601. {
  602. external_edit(false);
  603. }
  604. // external_edit() launches an external editor to edit the file
  605. // and then reload it.
  606. void Editor::external_edit(bool prompt)
  607. {
  608. // Step 1: we may first need to save the file.
  609. if (is_untitled()) {
  610. if (dialog.ask_yes_or_no(_("First I must save this buffer as a file; is it OK with you?")))
  611. save_file();
  612. } else if (wedit.is_modified()) {
  613. if (dialog.ask_yes_or_no(_("Buffer was modified and I must save it first; is it OK with you?")))
  614. save_file();
  615. }
  616. if (is_untitled() || wedit.is_modified())
  617. return;
  618. // Step 2: get the editor command
  619. cstring command = get_external_editor();
  620. if (prompt) {
  621. unistring inpt = dialog.query(_("External editor:"),
  622. command.c_str(),
  623. EXTERNALEDITOR_HISTORY,
  624. InputLine::cmpltAll);
  625. command = u8string(inpt).trim();
  626. if (command.empty()) // user aborted
  627. return;
  628. set_external_editor(command.c_str());
  629. }
  630. // Step 3: write the cursor location into a temporary file,
  631. // pointed to by $GERESH_CURSOR_FILE
  632. cstring cursor_file;
  633. //cursor_file = tmpnam(NULL); // gives linker warning!
  634. cursor_file.cformat("%s/geresh-%ld-cursor.tmp",
  635. getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp",
  636. (long)getpid());
  637. Point cursor;
  638. wedit.get_cursor_position(cursor);
  639. FILE *cursor_fh;
  640. if ((cursor_fh = fopen(cursor_file.c_str(), "w")) != NULL) {
  641. fprintf(cursor_fh, "%d %d\n%s\n",
  642. cursor.para + 1, cursor.pos + 1, get_filename());
  643. fclose(cursor_fh);
  644. cstring nameval;
  645. nameval.cformat("GERESH_CURSOR_FILE=%s", cursor_file.c_str());
  646. putenv(strdup(nameval.c_str()));
  647. }
  648. // Step 4: adjust the editor command so the line number is passed.
  649. if (command.index("vim") != -1) {
  650. // With vim we not only pass the column number in addition
  651. // to the line number, but we also pass the necessary
  652. // command to write the cursor position back into
  653. // $GERESH_CURSOR_FILE.
  654. cstring basefilename = get_filename();
  655. if (basefilename.index('/') != -1)
  656. basefilename = basefilename.substr(basefilename.rindex('/')+1);
  657. cstring col_command;
  658. if (cursor.pos != 0)
  659. col_command.cformat("-c \"normal %dl\"", cursor.pos);
  660. command.cformat("%s -c %d -c \"normal 0\" %s "
  661. "-c \"au BufUnload %s call system('echo '.line('.').' '.col('.').' >%s')\"",
  662. command.c_str(),
  663. cursor.para + 1,
  664. col_command.c_str(),
  665. basefilename.c_str(),
  666. cursor_file.c_str());
  667. }
  668. else if (command.index("vi") != -1 ||
  669. command.index("pico") != -1 ||
  670. command.index("nano") != -1 ||
  671. command.index("emacs") != -1 ||
  672. command.index("mined") != -1)
  673. {
  674. // The above editors are known to accept a "+line" parameter.
  675. command.cformat("%s +%d", command.c_str(), cursor.para + 1);
  676. }
  677. command += " \"";
  678. command += get_filename();
  679. command += "\"";
  680. // Step 5: execute the editor.
  681. cstring msg;
  682. msg.cformat("Executing: %s", command.c_str());
  683. dialog.show_message(msg.c_str());
  684. update_terminal();
  685. endwin();
  686. int status = system(command.c_str());
  687. doupdate();
  688. // Step 6: reload the file.
  689. // I can't just pass 'get_filename()' to load_file(), because
  690. // get_filename() returns a pointer to a string which load_file()
  691. // modifies (via set_filename()) ...
  692. load_file(u8string(get_filename()).c_str(),
  693. u8string(get_encoding()).c_str());
  694. // Step 7: read the cursor position from $GERESH_CURSOR_FILE.
  695. if ((cursor_fh = fopen(cursor_file.c_str(), "r")) != NULL) {
  696. int line_no, col_no;
  697. fscanf(cursor_fh, "%d %d", &line_no, &col_no);
  698. cursor.para = line_no - 1;
  699. cursor.pos = col_no - 1;
  700. wedit.set_cursor_position(cursor);
  701. fclose(cursor_fh);
  702. } else {
  703. wedit.set_cursor_position(cursor);
  704. }
  705. unlink(cursor_file.c_str());
  706. if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
  707. msg.cformat(_("Error: command \"%s\" could not be found"),
  708. get_external_editor().c_str());
  709. dialog.show_message(msg.c_str());
  710. }
  711. refresh_and_center();
  712. }
  713. // layout_windows() - is called every time the window changes its size.
  714. INTERACTIVE void Editor::layout_windows()
  715. {
  716. int cols, rows;
  717. getmaxyx(stdscr, rows, cols);
  718. int speller_height = 0;
  719. if (spellerwnd) {
  720. // determine the height of the speller window
  721. // based on the screen size.
  722. if (rows <= 10)
  723. speller_height = 2;
  724. else if (rows <= 16)
  725. speller_height = 4;
  726. else if (rows <= 20)
  727. speller_height = 6;
  728. else if (rows <= 26)
  729. speller_height = 8;
  730. else if (rows <= 36)
  731. speller_height = 11;
  732. else
  733. speller_height = 13;
  734. spellerwnd->resize(speller_height, cols, rows - 2 - speller_height, 0);
  735. }
  736. if (menubar)
  737. menubar->resize(1, cols, 0, 0);
  738. #define MENUHEIGHT (menubar ? 1 : 0)
  739. if (scrollbar)
  740. scrollbar->resize(rows - 2 - MENUHEIGHT - speller_height, 1,
  741. MENUHEIGHT, (scrollbar_pos == scrlbrLeft) ? 0 : cols - 1);
  742. #define SCROLLBARWIDTH (scrollbar ? 1 : 0)
  743. wedit.resize(rows - 2 - MENUHEIGHT - speller_height,
  744. cols - SCROLLBARWIDTH, MENUHEIGHT,
  745. (scrollbar_pos == scrlbrLeft) ? 1 : 0);
  746. status.resize(1, cols, rows - 2, 0);
  747. dialog.resize(1, cols, rows - 1, 0);
  748. update_terminal();
  749. }
  750. // update_terminal() - updates the screen.
  751. void Editor::update_terminal(bool soft)
  752. {
  753. // for every widget that's dirty, call its update() method to update
  754. // stdscr. If any was dirty, call doupdate() to update the physical
  755. // screen.
  756. bool need_dorefresh = false;
  757. if (scrollbar && scrollbar->is_dirty()) {
  758. scrollbar->update();
  759. need_dorefresh = true;
  760. }
  761. if (menubar && menubar->is_dirty()) {
  762. menubar->update();
  763. need_dorefresh = true;
  764. }
  765. if (status.is_dirty()) {
  766. status.update();
  767. need_dorefresh = true;
  768. }
  769. if (dialog.is_dirty()) {
  770. dialog.update();
  771. need_dorefresh = true;
  772. }
  773. if (wedit.is_dirty()) {
  774. wedit.update();
  775. need_dorefresh = true;
  776. } else if (need_dorefresh) {
  777. // make sure wedit gets the cursor even if some
  778. // of the other widgets get drawn.
  779. wedit.update_cursor();
  780. }
  781. if (spellerwnd) {
  782. if (spellerwnd->is_dirty()) {
  783. spellerwnd->update();
  784. need_dorefresh = true;
  785. } else if (need_dorefresh) {
  786. // spellerwnd always gets the cursor.
  787. spellerwnd->update_cursor();
  788. }
  789. }
  790. if (!soft && need_dorefresh)
  791. doupdate();
  792. }
  793. // Editor::exec() - the main event loop. it reads events and either handles
  794. // them or send them on to the editbox.
  795. void Editor::exec()
  796. {
  797. finished = false;
  798. while (!finished) {
  799. Event evt;
  800. update_terminal();
  801. get_next_event(evt, wedit.wnd);
  802. dialog.clear_transient_message();
  803. if (!evt.is_literal())
  804. if (handle_event(evt))
  805. continue;
  806. wedit.handle_event(evt);
  807. if (scrollbar)
  808. wedit.sync_scrollbar(scrollbar);
  809. }
  810. }
  811. // load_unload_speller() - interactively loads and unloads a speller
  812. // process. When the user interactively loads a speller, he is asked to
  813. // specify the speller-command and the speller-encoding.
  814. INTERACTIVE void Editor::load_unload_speller()
  815. {
  816. if (speller.is_loaded()) {
  817. speller.unload();
  818. } else {
  819. // We're about to load the speller. query the user for cmd / encoding.
  820. unistring cmd = dialog.query(_("Enter speller command:"),
  821. get_speller_cmd(), SPELER_CMD_HISTORY, InputLine::cmpltAll);
  822. if (cmd.empty())
  823. return;
  824. if (cmd.index(u8string("-a")) == -1) {
  825. if (!dialog.ask_yes_or_no(
  826. _("There's no `-a' option in the command, proceed anyway?")))
  827. return;
  828. }
  829. unistring encoding = dialog.query(_("Enter speller encoding:"),
  830. get_speller_encoding(), SPELER_ENCODING_HISTORY);
  831. if (encoding.empty())
  832. return;
  833. set_speller_cmd(u8string(cmd).c_str());
  834. set_speller_encoding(u8string(encoding).c_str());
  835. speller.load(get_speller_cmd(), get_speller_encoding());
  836. }
  837. status.invalidate_view();
  838. }
  839. // adjust_speller_cmd() - if the user hasn't specified a speller command,
  840. // figure it out ourselves.
  841. // Prefer: multispell, then ispell, and finally aspell.
  842. // Use the ISO-8859-8 encoding for Hebrew spell-checkers.
  843. void Editor::adjust_speller_cmd()
  844. {
  845. bool no_cmd = (*get_speller_cmd() == '\0');
  846. bool no_encoding = (*get_speller_encoding() == '\0');
  847. if (no_cmd) {
  848. bool has_multispell = has_prog("multispell");
  849. bool has_ispell = has_prog("ispell");
  850. bool has_aspell = !has_ispell && has_prog("aspell"); // optimization: search aspell
  851. // only if no ispell found.
  852. if (has_multispell) {
  853. u8string cmd;
  854. if (has_ispell || has_aspell) {
  855. cmd = "LC_ALL=C multispell -a -n";
  856. if (!has_ispell && has_aspell)
  857. cmd += " --ispell-cmd=aspell";
  858. } else {
  859. cmd = "LC_ALL=C hspell -a -n";
  860. }
  861. set_speller_cmd(cmd.c_str());
  862. set_speller_encoding("ISO-8859-8");
  863. } else {
  864. if (!has_ispell && has_aspell)
  865. set_speller_cmd("aspell -a");
  866. else
  867. set_speller_cmd("ispell -a");
  868. if (no_encoding)
  869. set_speller_encoding("ISO-8859-1");
  870. }
  871. } else if (no_encoding) {
  872. if (strstr(get_speller_cmd(), "hspell") || strstr(get_speller_cmd(), "multispell"))
  873. set_speller_encoding("ISO-8859-8");
  874. else
  875. set_speller_encoding("ISO-8859-1");
  876. }
  877. }
  878. INTERACTIVE void Editor::spell_check_all()
  879. {
  880. spell_check(Speller::splRngAll);
  881. }
  882. INTERACTIVE void Editor::spell_check_forward()
  883. {
  884. spell_check(Speller::splRngForward);
  885. }
  886. INTERACTIVE void Editor::spell_check_word()
  887. {
  888. spell_check(Speller::splRngWord);
  889. }
  890. void Editor::spell_check(Speller::splRng range)
  891. {
  892. if (!spellerwnd) {
  893. spellerwnd = new SpellerWnd(*this);
  894. layout_windows();
  895. }
  896. // if the speller is not yet loaded, load it with the default
  897. // parameters.
  898. if (!speller.is_loaded()) {
  899. speller.load(get_speller_cmd(), get_speller_encoding());
  900. status.invalidate_view();
  901. update_terminal(); // immediately update the status line
  902. }
  903. if (speller.is_loaded())
  904. speller.spell_check(range, wedit, *spellerwnd);
  905. // The speller window exists during the spell check only. we
  906. // delete it afterwards.
  907. delete spellerwnd;
  908. spellerwnd = NULL;
  909. layout_windows();
  910. }
  911. // show_hint() is used to print the popdown menu hints. Screen is updated
  912. // only after doupdate()
  913. void Editor::show_hint(const char *msg)
  914. {
  915. dialog.show_message(msg);
  916. dialog.update();
  917. }
  918. // show_kbd_error() - prints a transient error message and rings the bell.
  919. // it is usually used for "keyboard" errors and the bell is supposed to
  920. // catch the users attention.
  921. void Editor::show_kbd_error(const char *msg)
  922. {
  923. // we don't use show_error_message() because we want the
  924. // message to disappear at the next event.
  925. dialog.show_message(msg);
  926. Widget::signal_error();
  927. }
  928. void Editor::on_read_only_error(unichar ch)
  929. {
  930. unistring quit_chars;
  931. // when the buffer is in read-only mode, we let the user exit
  932. // by pressing 'q'.
  933. quit_chars.init_from_utf8(_("qQ"));
  934. if (quit_chars.has_char(ch))
  935. quit();
  936. else
  937. show_kbd_error(_("Buffer is read-only"));
  938. }
  939. void Editor::on_no_selection_error()
  940. {
  941. show_kbd_error(_("No text is selected"));
  942. }
  943. void Editor::on_no_alt_kbd_error()
  944. {
  945. show_kbd_error(_("No alternate keyboard (Hebrew) was loaded"));
  946. }
  947. void Editor::on_no_translation_table_error()
  948. {
  949. show_kbd_error(_("No translation table was loaded"));
  950. }
  951. void Editor::on_cant_display_nsm_error()
  952. {
  953. show_kbd_error(_("Terminal can't display non-spacing marks (like Hebrew points)"));
  954. }