1 //////////////////////////////////////////////////////////////////////// 2 // 3 // Copyright (C) 2011-2021 The Octave Project Developers 4 // 5 // See the file COPYRIGHT.md in the top-level directory of this 6 // distribution or <https://octave.org/copyright/>. 7 // 8 // This file is part of Octave. 9 // 10 // Octave is free software: you can redistribute it and/or modify it 11 // under the terms of the GNU General Public License as published by 12 // the Free Software Foundation, either version 3 of the License, or 13 // (at your option) any later version. 14 // 15 // Octave is distributed in the hope that it will be useful, but 16 // WITHOUT ANY WARRANTY; without even the implied warranty of 17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 // GNU General Public License for more details. 19 // 20 // You should have received a copy of the GNU General Public License 21 // along with Octave; see the file COPYING. If not, see 22 // <https://www.gnu.org/licenses/>. 23 // 24 //////////////////////////////////////////////////////////////////////// 25 26 //! @file file-editor-tab.cc A single GUI file tab. 27 //! 28 //! This interfaces QsciScintilla with the rest of Octave. 29 30 #if defined (HAVE_CONFIG_H) 31 # include "config.h" 32 #endif 33 34 #if defined (HAVE_QSCINTILLA) 35 36 #include <QApplication> 37 #include <QCheckBox> 38 #include <QDateTime> 39 #include <QDesktopServices> 40 #include <QDialogButtonBox> 41 #include <QFileDialog> 42 #include <QInputDialog> 43 #include <QLabel> 44 #include <QMessageBox> 45 #include <QPrintDialog> 46 #include <QPushButton> 47 #include <QStyle> 48 #include <QTextBlock> 49 #include <QTextCodec> 50 #include <QTextStream> 51 #include <QVBoxLayout> 52 #if defined (HAVE_QSCI_QSCILEXEROCTAVE_H) 53 # define HAVE_LEXER_OCTAVE 1 54 # include <Qsci/qscilexeroctave.h> 55 #elif defined (HAVE_QSCI_QSCILEXERMATLAB_H) 56 # define HAVE_LEXER_MATLAB 1 57 # include <Qsci/qscilexermatlab.h> 58 #endif 59 #include <Qsci/qscilexerbash.h> 60 #include <Qsci/qscilexerbatch.h> 61 #include <Qsci/qscilexercpp.h> 62 #include <Qsci/qscilexerdiff.h> 63 #include <Qsci/qscilexerperl.h> 64 #include <Qsci/qsciprinter.h> 65 66 #include "file-editor-tab.h" 67 #include "file-editor.h" 68 #include "gui-preferences-cs.h" 69 #include "gui-preferences-ed.h" 70 #include "gui-preferences-global.h" 71 #include "marker.h" 72 #include "octave-qobject.h" 73 #include "octave-txt-lexer.h" 74 75 #include "cmd-edit.h" 76 #include "file-ops.h" 77 #include "localcharset-wrapper.h" 78 #include "uniconv-wrappers.h" 79 80 #include "bp-table.h" 81 #include "builtin-defun-decls.h" 82 #include "interpreter-private.h" 83 #include "interpreter.h" 84 #include "load-path.h" 85 #include "oct-map.h" 86 #include "ov-usr-fcn.h" 87 #include "qt-interpreter-events.h" 88 #include "symtab.h" 89 #include "unwind-prot.h" 90 #include "utils.h" 91 #include "version.h" 92 93 namespace octave 94 { 95 //! A file_editor_tab object consists of a text area and three left margins. 96 //! The first holds breakpoints, bookmarks, and the debug program counter. 97 //! The second holds line numbers. The third holds "fold" marks, to hide 98 //! sections of text. 99 100 // Make parent null for the file editor tab so that warning WindowModal 101 // messages don't affect grandparents. file_editor_tab(base_qobject & oct_qobj,const QString & directory_arg)102 file_editor_tab::file_editor_tab (base_qobject& oct_qobj, 103 const QString& directory_arg) 104 : m_octave_qobj (oct_qobj) 105 { 106 m_lexer_apis = nullptr; 107 m_is_octave_file = true; 108 m_lines_changed = false; 109 m_autoc_active = false; 110 111 m_ced = directory_arg; 112 113 m_file_name = ""; 114 m_file_system_watcher.setObjectName ("_qt_autotest_force_engine_poller"); 115 116 m_edit_area = new octave_qscintilla (this, m_octave_qobj); 117 m_line = 0; 118 m_col = 0; 119 120 m_bp_lines.clear (); // start with empty lists of breakpoints 121 m_bp_conditions.clear (); 122 m_bp_restore_count = 0; 123 124 m_breakpoint_info.remove_next = false; 125 m_breakpoint_info.remove_line = -1; 126 127 // Initialize last modification date to now 128 m_last_modified = QDateTime::currentDateTimeUtc(); 129 130 connect (m_edit_area, SIGNAL (cursorPositionChanged (int, int)), 131 this, SLOT (handle_cursor_moved (int,int))); 132 133 connect (m_edit_area, SIGNAL (SCN_CHARADDED (int)), 134 this, SLOT (handle_char_added (int))); 135 136 connect (m_edit_area, SIGNAL (SCN_DOUBLECLICK (int, int, int)), 137 this, SLOT (handle_double_click (int, int, int))); 138 139 connect (m_edit_area, SIGNAL (linesChanged ()), 140 this, SLOT (handle_lines_changed ())); 141 142 connect (m_edit_area, SIGNAL (context_menu_edit_signal (const QString&)), 143 this, SLOT (handle_context_menu_edit (const QString&))); 144 145 // create statusbar for row/col indicator and eol mode 146 m_status_bar = new QStatusBar (this); 147 148 // row- and col-indicator 149 m_row_indicator = new QLabel ("", this); 150 QFontMetrics fm = m_row_indicator->fontMetrics (); 151 m_row_indicator->setMinimumSize (4.5*fm.averageCharWidth (),0); 152 QLabel *row_label = new QLabel (tr ("line:"), this); 153 m_col_indicator = new QLabel ("", this); 154 m_col_indicator->setMinimumSize (4*fm.averageCharWidth (),0); 155 QLabel *col_label = new QLabel (tr ("col:"), this); 156 m_status_bar->addWidget (row_label, 0); 157 m_status_bar->addWidget (m_row_indicator, 0); 158 m_status_bar->addWidget (col_label, 0); 159 m_status_bar->addWidget (m_col_indicator, 0); 160 161 // status bar: encoding 162 QLabel *enc_label = new QLabel (tr ("encoding:"), this); 163 m_enc_indicator = new QLabel ("",this); 164 m_status_bar->addWidget (enc_label, 0); 165 m_status_bar->addWidget (m_enc_indicator, 0); 166 m_status_bar->addWidget (new QLabel (" ", this), 0); 167 168 // status bar: eol mode 169 QLabel *eol_label = new QLabel (tr ("eol:"), this); 170 m_eol_indicator = new QLabel ("",this); 171 m_status_bar->addWidget (eol_label, 0); 172 m_status_bar->addWidget (m_eol_indicator, 0); 173 m_status_bar->addWidget (new QLabel (" ", this), 0); 174 175 // symbols 176 m_edit_area->setMarginType (1, QsciScintilla::SymbolMargin); 177 m_edit_area->setMarginSensitivity (1, true); 178 m_edit_area->markerDefine (QsciScintilla::RightTriangle, marker::bookmark); 179 m_edit_area->setMarkerBackgroundColor (QColor (0,0,232), marker::bookmark); 180 m_edit_area->markerDefine (QsciScintilla::Circle, marker::breakpoint); 181 m_edit_area->setMarkerBackgroundColor (QColor (192,0,0), marker::breakpoint); 182 m_edit_area->markerDefine (QsciScintilla::Circle, marker::cond_break); 183 m_edit_area->setMarkerBackgroundColor (QColor (255,127,0), marker::cond_break); 184 m_edit_area->markerDefine (QsciScintilla::RightArrow, 185 marker::debugger_position); 186 m_edit_area->setMarkerBackgroundColor (QColor (255,255,0), 187 marker::debugger_position); 188 m_edit_area->markerDefine (QsciScintilla::RightArrow, 189 marker::unsure_debugger_position); 190 m_edit_area->setMarkerBackgroundColor (QColor (192,192,192), 191 marker::unsure_debugger_position); 192 193 connect (m_edit_area, SIGNAL (marginClicked (int, int, 194 Qt::KeyboardModifiers)), 195 this, SLOT (handle_margin_clicked (int, int, 196 Qt::KeyboardModifiers))); 197 198 connect (m_edit_area, SIGNAL (context_menu_break_condition_signal (int)), 199 this, SLOT (handle_context_menu_break_condition (int))); 200 201 // line numbers 202 m_edit_area->setMarginsForegroundColor (QColor (96, 96, 96)); 203 m_edit_area->setMarginsBackgroundColor (QColor (232, 232, 220)); 204 m_edit_area->setMarginType (2, QsciScintilla::TextMargin); 205 206 // other features 207 m_edit_area->setBraceMatching (QsciScintilla::StrictBraceMatch); 208 m_edit_area->setAutoIndent (true); 209 m_edit_area->setIndentationWidth (2); 210 m_edit_area->setIndentationsUseTabs (false); 211 212 m_edit_area->setUtf8 (true); 213 214 // auto completion 215 m_edit_area->SendScintilla (QsciScintillaBase::SCI_AUTOCSETCANCELATSTART, false); 216 217 QVBoxLayout *edit_area_layout = new QVBoxLayout (); 218 edit_area_layout->addWidget (m_edit_area); 219 edit_area_layout->addWidget (m_status_bar); 220 edit_area_layout->setMargin (0); 221 edit_area_layout->setSpacing (0); 222 setLayout (edit_area_layout); 223 224 // Any interpreter_event signal from a file_editor_tab_widget is 225 // handled the same as for the parent main_window object. 226 227 connect (m_edit_area, SIGNAL (interpreter_event (const fcn_callback&)), 228 this, SIGNAL (interpreter_event (const fcn_callback&))); 229 230 connect (m_edit_area, SIGNAL (interpreter_event (const meth_callback&)), 231 this, SIGNAL (interpreter_event (const meth_callback&))); 232 233 // connect modified signal 234 connect (m_edit_area, SIGNAL (modificationChanged (bool)), 235 this, SLOT (update_window_title (bool))); 236 237 connect (m_edit_area, SIGNAL (copyAvailable (bool)), 238 this, SLOT (handle_copy_available (bool))); 239 240 connect (&m_file_system_watcher, SIGNAL (fileChanged (const QString&)), 241 this, SLOT (file_has_changed (const QString&))); 242 243 connect (this, SIGNAL (maybe_remove_next (int)), 244 this, SLOT (handle_remove_next (int))); 245 246 connect (this, SIGNAL (dbstop_if (const QString&, int, const QString&)), 247 this, 248 SLOT (handle_dbstop_if (const QString&, int, const QString&))); 249 250 connect (this, SIGNAL (request_add_breakpoint (int, const QString&)), 251 this, SLOT (handle_request_add_breakpoint (int, const QString&))); 252 253 connect (this, SIGNAL (api_entries_added (void)), 254 this, SLOT (handle_api_entries_added (void))); 255 256 connect (this, SIGNAL (confirm_dbquit_and_save_signal (const QString&, const QString&, bool, bool)), 257 this, SLOT (confirm_dbquit_and_save (const QString&, const QString&, bool, bool))); 258 259 connect (this, SIGNAL (do_save_file_signal (const QString&, bool, bool)), 260 this, SLOT (do_save_file (const QString&, bool, bool))); 261 262 resource_manager& rmgr = m_octave_qobj.get_resource_manager (); 263 gui_settings *settings = rmgr.get_settings (); 264 if (settings) 265 notice_settings (settings, true); 266 267 // encoding, not updated with the settings 268 QString locale_enc_name = 269 QString ("SYSTEM (") + 270 QString (octave_locale_charset_wrapper ()).toUpper () + QString (")"); 271 m_encoding = settings->value (ed_default_enc.key, locale_enc_name).toString (); 272 m_enc_indicator->setText (m_encoding); 273 // no changes in encoding yet 274 m_new_encoding = m_encoding; 275 } 276 ~file_editor_tab(void)277 file_editor_tab::~file_editor_tab (void) 278 { 279 // Tell all connected markers to self-destruct. 280 emit remove_all_breakpoints (); 281 emit remove_all_positions (); 282 283 // Destroy lexer attached to m_edit_area, which is not the parent 284 // of lexer 285 QsciLexer *lexer = m_edit_area->lexer (); 286 if (lexer) 287 { 288 delete lexer; 289 m_edit_area->setLexer (nullptr); 290 } 291 } 292 set_encoding(const QString & new_encoding)293 void file_editor_tab::set_encoding (const QString& new_encoding) 294 { 295 if (new_encoding.isEmpty ()) 296 return; 297 298 m_encoding = new_encoding; 299 m_enc_indicator->setText (m_encoding); 300 if (! m_edit_area->text ().isEmpty ()) 301 set_modified (true); 302 } 303 closeEvent(QCloseEvent * e)304 void file_editor_tab::closeEvent (QCloseEvent *e) 305 { 306 int save_dialog = check_file_modified (true); 307 if ((save_dialog == QMessageBox::Cancel) || 308 (save_dialog == QMessageBox::Save)) 309 { 310 // Ignore close event if file is saved or user cancels 311 // closing this window. In case of saving, tab is closed after 312 // successful saving. 313 e->ignore (); 314 } 315 else 316 { 317 e->accept (); 318 emit tab_remove_request (); 319 } 320 } 321 set_current_directory(const QString & dir)322 void file_editor_tab::set_current_directory (const QString& dir) 323 { 324 m_ced = dir; 325 } 326 handle_context_menu_edit(const QString & word_at_cursor)327 void file_editor_tab::handle_context_menu_edit (const QString& word_at_cursor) 328 { 329 // Search for a subfunction in actual file (this is done first because 330 // Octave finds this function before others with the same name in the 331 // search path. 332 QRegExp rxfun1 ("^[\t ]*function[^=]+=[\t ]*" 333 + word_at_cursor + "[\t ]*\\([^\\)]*\\)[\t ]*$"); 334 QRegExp rxfun2 ("^[\t ]*function[\t ]+" 335 + word_at_cursor + "[\t ]*\\([^\\)]*\\)[\t ]*$"); 336 QRegExp rxfun3 ("^[\t ]*function[\t ]+" 337 + word_at_cursor + "[\t ]*$"); 338 QRegExp rxfun4 ("^[\t ]*function[^=]+=[\t ]*" 339 + word_at_cursor + "[\t ]*$"); 340 341 int pos_fct = -1; 342 QStringList lines = m_edit_area->text ().split ("\n"); 343 344 int line; 345 for (line = 0; line < lines.count (); line++) 346 { 347 if ((pos_fct = rxfun1.indexIn (lines.at (line))) != -1) 348 break; 349 if ((pos_fct = rxfun2.indexIn (lines.at (line))) != -1) 350 break; 351 if ((pos_fct = rxfun3.indexIn (lines.at (line))) != -1) 352 break; 353 if ((pos_fct = rxfun4.indexIn (lines.at (line))) != -1) 354 break; 355 } 356 357 if (pos_fct > -1) 358 { 359 // reg expr. found: it is an internal function 360 m_edit_area->setCursorPosition (line, pos_fct); 361 m_edit_area->SendScintilla (2232, line); // SCI_ENSUREVISIBLE 362 // SCI_VISIBLEFROMDOCLINE 363 int vis_line = m_edit_area->SendScintilla (2220, line); 364 m_edit_area->SendScintilla (2613, vis_line); // SCI_SETFIRSTVISIBLELINE 365 return; 366 } 367 368 emit edit_mfile_request (word_at_cursor, m_file_name, m_ced, -1); 369 } 370 371 // If "dbstop if ..." selected from context menu, create a conditional 372 // breakpoint. The default condition is (a) the existing condition if there 373 // is already a breakpoint, (b) any selected text, or (c) empty handle_context_menu_break_condition(int linenr)374 void file_editor_tab::handle_context_menu_break_condition (int linenr) 375 { 376 // Ensure editor line numbers match Octave core's line numbers. 377 // Give users the option to save modifications if necessary. 378 if (! unchanged_or_saved ()) 379 return; 380 381 QString cond; 382 383 // Search for previous condition. FIXME: is there a more direct way? 384 if (m_edit_area->markersAtLine (linenr) & (1 << marker::cond_break)) 385 { 386 emit report_marker_linenr (m_bp_lines, m_bp_conditions); 387 for (int i = 0; i < m_bp_lines.length (); i++) 388 if (m_bp_lines.value (i) == linenr) 389 { 390 cond = m_bp_conditions.value (i); 391 break; 392 } 393 m_bp_lines.clear (); 394 m_bp_conditions.clear (); 395 } 396 397 // If text selected by the mouse, default to that instead 398 // If both present, use the OR of them, to avoid accidental overwriting 399 // FIXME: If both are present, show old condition unselected and 400 // the selection (in edit area) selected (in the dialog). 401 if (m_edit_area->hasSelectedText ()) 402 { 403 if (cond == "") 404 cond = m_edit_area->selectedText (); 405 else 406 cond = '(' + cond + ") || (" + m_edit_area->selectedText () + ')'; 407 } 408 409 emit dbstop_if ("dbstop if", linenr+1, cond); 410 } 411 412 // Display dialog in GUI thread to get condition, then emit 413 // interpreter_event signal to check it in the interpreter thread. 414 // If the dialog returns a valid condition, then either emit a signal 415 // to add the breakpoint in the editor tab or a signal to display a 416 // new dialog. 417 handle_dbstop_if(const QString & prompt,int line,const QString & cond)418 void file_editor_tab::handle_dbstop_if (const QString& prompt, int line, 419 const QString& cond) 420 { 421 bool ok; 422 QString new_cond 423 = QInputDialog::getText (this, tr ("Breakpoint condition"), 424 prompt, QLineEdit::Normal, cond, &ok); 425 426 // If cancel, don't change breakpoint condition. 427 428 if (ok && ! new_cond.isEmpty ()) 429 { 430 emit interpreter_event 431 ([this, line, new_cond] (interpreter& interp) 432 { 433 // INTERPRETER THREAD 434 435 error_system& es = interp.get_error_system (); 436 437 unwind_protect frame; 438 439 // Prevent an error in the evaluation here from sending us 440 // into the debugger. 441 442 es.interpreter_try (frame); 443 444 bool eval_error = false; 445 std::string msg; 446 447 try 448 { 449 tree_evaluator& tw = interp.get_evaluator (); 450 bp_table& bptab = tw.get_bp_table (); 451 452 bptab.condition_valid (new_cond.toStdString ()); 453 454 // The condition seems OK, so set the conditional 455 // breakpoint. 456 457 emit request_add_breakpoint (line, new_cond); 458 } 459 catch (const execution_exception& e) 460 { 461 interp.recover_from_exception (); 462 463 msg = e.message (); 464 eval_error = true; 465 } 466 catch (const interrupt_exception&) 467 { 468 interp.recover_from_exception (); 469 470 msg = "evaluation interrupted"; 471 eval_error = true; 472 } 473 474 if (eval_error) 475 { 476 // Try again with a prompt that indicates the last 477 // attempt was an error. 478 479 QString new_prompt = (tr ("ERROR: ") 480 + QString::fromStdString (msg) 481 + "\n\ndbstop if"); 482 483 emit dbstop_if (new_prompt, line, ""); 484 } 485 }); 486 } 487 } 488 set_file_name(const QString & fileName)489 void file_editor_tab::set_file_name (const QString& fileName) 490 { 491 // update tracked file if we really have a file on disk 492 QStringList trackedFiles = m_file_system_watcher.files (); 493 if (! trackedFiles.isEmpty ()) 494 m_file_system_watcher.removePath (m_file_name); 495 if (! fileName.isEmpty () && QFile::exists (fileName)) 496 { 497 m_file_system_watcher.addPath (fileName); 498 m_last_modified = QFileInfo (fileName).lastModified ().toUTC (); 499 } 500 501 // update lexer and file name variable if file name changes 502 if (m_file_name != fileName) 503 { 504 m_file_name = fileName; 505 update_lexer (); 506 } 507 508 // update the file editor with current editing directory 509 emit editor_state_changed (m_copy_available, m_is_octave_file); 510 511 // add the new file to the most-recently-used list 512 emit mru_add_file (m_file_name, m_encoding); 513 } 514 515 // valid_file_name (file): checks whether "file" names a file. 516 // By default, "file" is empty; then m_file_name is checked valid_file_name(const QString & file)517 bool file_editor_tab::valid_file_name (const QString& file) 518 { 519 if (file.isEmpty ()) 520 { 521 if (m_file_name.isEmpty ()) 522 return false; 523 else 524 return true; 525 } 526 527 return true; 528 } 529 530 // We cannot create a breakpoint when the file is modified 531 // because the line number the editor is providing might 532 // not match what Octave core is interpreting in the 533 // file on disk. This function gives the user the option 534 // to save before creating the breakpoint. unchanged_or_saved(void)535 bool file_editor_tab::unchanged_or_saved (void) 536 { 537 bool retval = true; 538 if (m_edit_area->isModified () || ! valid_file_name ()) 539 { 540 int ans = QMessageBox::question (nullptr, tr ("Octave Editor"), 541 tr ("Cannot add breakpoint to modified or unnamed file.\n" 542 "Save and add breakpoint, or cancel?"), 543 QMessageBox::Save | QMessageBox::Cancel, QMessageBox::Save); 544 545 if (ans == QMessageBox::Save) 546 save_file (m_file_name, false); 547 else 548 retval = false; 549 } 550 551 return retval; 552 } 553 554 // Toggle a breakpoint at the editor_linenr or, if this was called by 555 // a click with CTRL pressed, toggle a bookmark at that point. handle_margin_clicked(int margin,int editor_linenr,Qt::KeyboardModifiers state)556 void file_editor_tab::handle_margin_clicked (int margin, int editor_linenr, 557 Qt::KeyboardModifiers state) 558 { 559 if (margin == 1) 560 { 561 unsigned int markers_mask = m_edit_area->markersAtLine (editor_linenr); 562 563 if (state & Qt::ControlModifier) 564 { 565 if (markers_mask & (1 << marker::bookmark)) 566 m_edit_area->markerDelete (editor_linenr, marker::bookmark); 567 else 568 m_edit_area->markerAdd (editor_linenr, marker::bookmark); 569 } 570 else 571 { 572 if (markers_mask & ((1 << marker::breakpoint) 573 | (1 << marker::cond_break))) 574 handle_request_remove_breakpoint (editor_linenr + 1); 575 else 576 { 577 if (unchanged_or_saved ()) 578 handle_request_add_breakpoint (editor_linenr + 1, ""); 579 } 580 } 581 } 582 } 583 584 update_lexer(void)585 void file_editor_tab::update_lexer (void) 586 { 587 // Create a new lexer 588 QsciLexer *lexer = nullptr; 589 590 m_is_octave_file = false; 591 592 // Find the required lexer from file extensions 593 if (m_file_name.endsWith (".m") 594 || m_file_name.endsWith ("octaverc")) 595 { 596 #if defined (HAVE_LEXER_OCTAVE) 597 lexer = new QsciLexerOctave (); 598 #elif defined (HAVE_LEXER_MATLAB) 599 lexer = new QsciLexerMatlab (); 600 #else 601 lexer = new octave_txt_lexer (); 602 #endif 603 m_is_octave_file = true; 604 } 605 606 if (! lexer) 607 { 608 if (m_file_name.endsWith (".c") 609 || m_file_name.endsWith (".cc") 610 || m_file_name.endsWith (".cpp") 611 || m_file_name.endsWith (".cxx") 612 || m_file_name.endsWith (".c++") 613 || m_file_name.endsWith (".h") 614 || m_file_name.endsWith (".hh") 615 || m_file_name.endsWith (".hpp") 616 || m_file_name.endsWith (".h++")) 617 { 618 lexer = new QsciLexerCPP (); 619 } 620 else if (m_file_name.endsWith (".pl")) 621 { 622 lexer = new QsciLexerPerl (); 623 } 624 else if (m_file_name.endsWith (".bat")) 625 { 626 lexer = new QsciLexerBatch (); 627 } 628 else if (m_file_name.endsWith (".diff")) 629 { 630 lexer = new QsciLexerDiff (); 631 } 632 else if (m_file_name.endsWith (".sh")) 633 { 634 lexer = new QsciLexerBash (); 635 } 636 else if (! valid_file_name ()) 637 { 638 // new, not yet named file: let us assume it is octave 639 #if defined (HAVE_LEXER_OCTAVE) 640 lexer = new QsciLexerOctave (); 641 m_is_octave_file = true; 642 #elif defined (HAVE_LEXER_MATLAB) 643 lexer = new QsciLexerMatlab (); 644 m_is_octave_file = true; 645 #else 646 lexer = new octave_txt_lexer (); 647 #endif 648 } 649 else 650 { 651 // other or no extension 652 lexer = new octave_txt_lexer (); 653 } 654 } 655 656 // Get any existing lexer 657 QsciLexer *old_lexer = m_edit_area->lexer (); 658 659 // If new file, no lexer, or lexer has changed, 660 // delete old one and set the newly created as current lexer 661 if (! old_lexer || ! valid_file_name () 662 || QString(old_lexer->lexer ()) != QString(lexer->lexer ())) 663 { 664 // Delete and set new lexer 665 if (old_lexer) 666 delete old_lexer; 667 m_edit_area->setLexer (lexer); 668 669 // Build information for auto completion (APIs) 670 m_lexer_apis = new QsciAPIs (lexer); 671 672 connect (this, SIGNAL (request_add_octave_apis (const QStringList&)), 673 this, SLOT (handle_add_octave_apis (const QStringList&))); 674 675 // Get the settings for this new lexer 676 update_lexer_settings (); 677 } 678 else 679 { 680 // Otherwise, delete the newly created lexer and 681 // use the old, existing one. 682 delete lexer; 683 } 684 } 685 686 687 // Update settings, which are lexer related and have to be updated 688 // when a) the lexer changes or b) the settings have changed. update_lexer_settings(void)689 void file_editor_tab::update_lexer_settings (void) 690 { 691 QsciLexer *lexer = m_edit_area->lexer (); 692 693 resource_manager& rmgr = m_octave_qobj.get_resource_manager (); 694 gui_settings *settings = rmgr.get_settings (); 695 696 if (m_lexer_apis) 697 { 698 m_lexer_apis->cancelPreparation (); // stop preparing if apis exists 699 700 bool update_apis = false; // flag, whether update of apis files 701 702 // Get path to prepared api info (cache). Temporarily set the 703 // application name to 'octave' instead of 'GNU Octave' name for 704 // not having blanks in the path. 705 QString tmp_app_name = QCoreApplication::applicationName (); 706 QCoreApplication::setApplicationName ("octave"); // Set new name 707 708 #if defined (HAVE_QSTANDARDPATHS) 709 QString local_data_path 710 = QStandardPaths::writableLocation (QStandardPaths::CacheLocation); 711 #else 712 QString local_data_path 713 = QDesktopServices::storageLocation (QDesktopServices::CacheLocation); 714 #endif 715 716 QCoreApplication::setApplicationName ("octave"); // Set temp. name 717 718 m_prep_apis_path 719 = local_data_path + "/" + QString (OCTAVE_VERSION) + "/qsci/"; 720 721 // get settings which infos are used for octave 722 bool octave_builtins 723 = settings->value (ed_code_completion_octave_builtins).toBool (); 724 bool octave_functions 725 = settings->value (ed_code_completion_octave_functions).toBool (); 726 727 QCoreApplication::setApplicationName (tmp_app_name); // Restore name 728 729 if (m_is_octave_file) 730 { 731 // Keywords and Builtins do not change, this information can be 732 // stored in prepared form in a file. Information on function are 733 // changing frequently, then if functions should also be auto- 734 // completed, the date of any existing file is checked. 735 736 // Keywords are always used 737 m_prep_apis_file = m_prep_apis_path + lexer->lexer () + "_k"; 738 739 // Builtins are only used if the user settings say so 740 if (octave_builtins) 741 m_prep_apis_file += 'b'; 742 743 if (octave_functions) 744 m_prep_apis_file += 'f'; 745 746 m_prep_apis_file += ".pap"; // final name of apis file 747 748 // check whether the APIs info needs to be prepared and saved 749 QFileInfo apis_file = QFileInfo (m_prep_apis_file); 750 751 // flag whether apis file needs update 752 update_apis = ! apis_file.exists (); 753 754 if (octave_functions) 755 { 756 // Functions may change frequently. Update the apis data 757 // if the file is older than a few minutes preventing from 758 // re-preparing data when the user opens several files. 759 QDateTime apis_time = apis_file.lastModified (); 760 if (QDateTime::currentDateTime () > apis_time.addSecs (180)) 761 update_apis = true; 762 } 763 764 } 765 else 766 { 767 // No octave file, just add extension. 768 m_prep_apis_file = m_prep_apis_path + lexer->lexer () + ".pap"; 769 } 770 771 // Make sure the apis file is usable, otherwise the gui might crash, 772 // e.g., in case of max. number of opened files 773 QFile f (m_prep_apis_file); 774 775 bool apis_usable = f.open (QIODevice::ReadOnly); 776 if (! apis_usable) 777 { 778 QDir ().mkpath (QFileInfo (f).absolutePath ()); 779 apis_usable = f.open (QIODevice::WriteOnly); 780 } 781 if (apis_usable) 782 f.close (); 783 784 if (apis_usable 785 && (update_apis || ! m_lexer_apis->loadPrepared (m_prep_apis_file))) 786 { 787 // either we have decided to update the apis file or 788 // no prepared info was loaded, prepare and save if possible 789 790 // create raw apis info 791 792 if (m_is_octave_file) 793 { 794 emit interpreter_event 795 ([this, octave_functions, octave_builtins] (interpreter& interp) 796 { 797 // INTERPRETER THREAD 798 799 QStringList api_entries; 800 801 octave_value_list tmp = Fiskeyword (); 802 const Cell ctmp = tmp(0).cell_value (); 803 for (octave_idx_type i = 0; i < ctmp.numel (); i++) 804 { 805 std::string kw = ctmp(i).string_value (); 806 api_entries.append (QString::fromStdString (kw)); 807 } 808 809 if (octave_builtins) 810 { 811 symbol_table& symtab = interp.get_symbol_table (); 812 813 string_vector bfl = symtab.built_in_function_names (); 814 815 for (octave_idx_type i = 0; i < bfl.numel (); i++) 816 api_entries.append (QString::fromStdString (bfl[i])); 817 } 818 819 820 if (octave_functions) 821 { 822 load_path& lp = interp.get_load_path (); 823 824 string_vector ffl = lp.fcn_names (); 825 string_vector afl = interp.autoloaded_functions (); 826 827 for (octave_idx_type i = 0; i < ffl.numel (); i++) 828 api_entries.append (QString::fromStdString (ffl[i])); 829 830 for (octave_idx_type i = 0; i < afl.numel (); i++) 831 api_entries.append (QString::fromStdString (afl[i])); 832 } 833 834 emit request_add_octave_apis (api_entries); 835 }); 836 } 837 else 838 { 839 for (int i = 1; i <= 3; i++) 840 { 841 // Get list, split, and add to API. 842 843 QString keyword = QString (lexer->keywords (i)); 844 845 QStringList keyword_list 846 = keyword.split (QRegExp (R"(\s+)")); 847 848 for (int j = 0; j < keyword_list.size (); j++) 849 m_lexer_apis->add (keyword_list.at (j)); 850 } 851 852 emit api_entries_added (); 853 } 854 } 855 } 856 857 lexer->readSettings (*settings); 858 859 m_edit_area->setCaretForegroundColor (lexer->color (0)); 860 m_edit_area->setIndentationGuidesForegroundColor (lexer->color (0)); 861 862 // set some colors depending on selected background color of the lexer 863 QColor bg = lexer->paper (0); 864 QColor fg = lexer->color (0); 865 866 int bh, bs, bv, fh, fs, fv, h, s, v; 867 bg.getHsv (&bh,&bs,&bv); 868 fg.getHsv (&fh,&fs,&fv); 869 870 // margin colors 871 h = bh; 872 s = bs/2; 873 v = bv + (fv - bv)/5; 874 875 bg.setHsv (h,s,v); 876 m_edit_area->setEdgeColor (bg); 877 878 v = bv + (fv - bv)/8; 879 bg.setHsv (h,s,v); 880 v = bv + (fv - bv)/4; 881 fg.setHsv (h,s,v); 882 883 m_edit_area->setMarkerForegroundColor (lexer->color (0)); 884 m_edit_area->setMarginsForegroundColor (lexer->color (0)); 885 m_edit_area->setMarginsBackgroundColor (bg); 886 m_edit_area->setFoldMarginColors (bg,fg); 887 888 // color indicator for highlighting all occurrences: 889 // applications highlight color with more transparency 890 QColor hg = QApplication::palette ().color (QPalette::Highlight); 891 m_edit_area->set_selection_marker_color (hg); 892 893 // fix line number width with respect to the font size of the lexer and 894 // set the line numbers font depending on the lexer's font 895 if (settings->value (ed_show_line_numbers).toBool ()) 896 { 897 // Line numbers width 898 auto_margin_width (); 899 900 // Line numbers font 901 QFont line_numbers_font = lexer->defaultFont (); 902 int font_size = line_numbers_font.pointSize (); 903 font_size = font_size 904 + settings->value (ed_line_numbers_size).toInt (); 905 if (font_size < 4) 906 font_size = 4; 907 line_numbers_font.setPointSize (font_size); 908 909 m_edit_area->setMarginsFont (line_numbers_font); 910 } 911 else 912 m_edit_area->setMarginWidth (2,0); 913 } 914 915 // function for adding entries to the octave lexer's APIs handle_add_octave_apis(const QStringList & api_entries)916 void file_editor_tab::handle_add_octave_apis (const QStringList& api_entries) 917 { 918 for (int idx = 0; idx < api_entries.size (); idx++) 919 m_lexer_apis->add (api_entries.at (idx)); 920 921 emit api_entries_added (); 922 } 923 handle_api_entries_added(void)924 void file_editor_tab::handle_api_entries_added (void) 925 { 926 // disconnect slot for saving prepared info if already connected 927 disconnect (m_lexer_apis, SIGNAL (apiPreparationFinished ()), 928 nullptr, nullptr); 929 930 // check whether path for prepared info exists or can be created 931 if (QDir ("/").mkpath (m_prep_apis_path)) 932 { 933 // path exists, apis info can be saved there 934 connect (m_lexer_apis, SIGNAL (apiPreparationFinished ()), 935 this, SLOT (save_apis_info ())); 936 } 937 938 m_lexer_apis->prepare (); // prepare apis info 939 } 940 save_apis_info(void)941 void file_editor_tab::save_apis_info (void) 942 { 943 m_lexer_apis->savePrepared (m_prep_apis_file); 944 } 945 946 // slot for fetab_set_focus: sets the focus to the current edit area set_focus(const QWidget * ID)947 void file_editor_tab::set_focus (const QWidget *ID) 948 { 949 if (ID != this) 950 return; 951 m_edit_area->setFocus (); 952 emit edit_area_changed (m_edit_area); // update the edit area in find dlg 953 } 954 context_help(const QWidget * ID,bool doc)955 void file_editor_tab::context_help (const QWidget *ID, bool doc) 956 { 957 if (ID != this) 958 return; 959 960 m_edit_area->context_help_doc (doc); 961 } 962 context_edit(const QWidget * ID)963 void file_editor_tab::context_edit (const QWidget *ID) 964 { 965 if (ID != this) 966 return; 967 968 m_edit_area->context_edit (); 969 } 970 save_file(const QWidget * ID)971 void file_editor_tab::save_file (const QWidget *ID) 972 { 973 if (ID != this) 974 return; 975 976 save_file (m_file_name); 977 } 978 save_file(const QWidget * ID,const QString & fileName,bool remove_on_success)979 void file_editor_tab::save_file (const QWidget *ID, const QString& fileName, 980 bool remove_on_success) 981 { 982 if (ID != this) 983 return; 984 985 save_file (fileName, remove_on_success); 986 } 987 save_file_as(const QWidget * ID)988 void file_editor_tab::save_file_as (const QWidget *ID) 989 { 990 if (ID != this) 991 return; 992 993 save_file_as (); 994 } 995 print_file(const QWidget * ID)996 void file_editor_tab::print_file (const QWidget *ID) 997 { 998 if (ID != this) 999 return; 1000 1001 QsciPrinter *printer = new QsciPrinter (QPrinter::HighResolution); 1002 1003 QPrintDialog printDlg (printer, this); 1004 1005 if (printDlg.exec () == QDialog::Accepted) 1006 printer->printRange (m_edit_area); 1007 1008 delete printer; 1009 } 1010 run_file(const QWidget * ID,bool step_into)1011 void file_editor_tab::run_file (const QWidget *ID, bool step_into) 1012 { 1013 if (ID != this) 1014 return; 1015 1016 if (m_edit_area->isModified () | ! valid_file_name ()) 1017 { 1018 save_file (m_file_name); // save file dialog 1019 1020 // Running a file is disabled for non-octave files. But when saving 1021 // a new file, an octave file is assumed but might actually saved 1022 // as another file or with an invalid file name. 1023 if (! (m_is_octave_file && valid_file_name ())) 1024 return; 1025 } 1026 1027 if (step_into) 1028 { 1029 // Get current first breakpoint and set breakpoint waiting for 1030 // the returned line number. Store whether to remove this breakpoint 1031 // afterwards. 1032 int first_bp_line 1033 = m_edit_area->markerFindNext (0, (1 << marker::breakpoint)) + 1; 1034 1035 // Set flag for storing the line number of the breakpoint 1036 m_breakpoint_info.remove_next = true; 1037 m_breakpoint_info.do_not_remove_line = first_bp_line; 1038 1039 // Add breakpoint, storing its line number 1040 handle_request_add_breakpoint (1, QString ()); 1041 } 1042 1043 QFileInfo info (m_file_name); 1044 emit run_file_signal (info); 1045 } 1046 context_run(const QWidget * ID)1047 void file_editor_tab::context_run (const QWidget *ID) 1048 { 1049 if (ID != this) 1050 return; 1051 1052 m_edit_area->context_run (); 1053 } 1054 toggle_bookmark(const QWidget * ID)1055 void file_editor_tab::toggle_bookmark (const QWidget *ID) 1056 { 1057 if (ID != this) 1058 return; 1059 1060 int line, cur; 1061 m_edit_area->getCursorPosition (&line, &cur); 1062 1063 if (m_edit_area->markersAtLine (line) & (1 << marker::bookmark)) 1064 m_edit_area->markerDelete (line, marker::bookmark); 1065 else 1066 m_edit_area->markerAdd (line, marker::bookmark); 1067 } 1068 1069 // Move the text cursor to the closest bookmark 1070 // after the current line. next_bookmark(const QWidget * ID)1071 void file_editor_tab::next_bookmark (const QWidget *ID) 1072 { 1073 if (ID != this) 1074 return; 1075 1076 int line, cur; 1077 m_edit_area->getCursorPosition (&line, &cur); 1078 1079 line++; // Find bookmark strictly after the current line. 1080 1081 int nextline = m_edit_area->markerFindNext (line, (1 << marker::bookmark)); 1082 1083 // Wrap. 1084 if (nextline == -1) 1085 nextline = m_edit_area->markerFindNext (1, (1 << marker::bookmark)); 1086 1087 m_edit_area->setCursorPosition (nextline, 0); 1088 } 1089 1090 // Move the text cursor to the closest bookmark 1091 // before the current line. previous_bookmark(const QWidget * ID)1092 void file_editor_tab::previous_bookmark (const QWidget *ID) 1093 { 1094 if (ID != this) 1095 return; 1096 1097 int line, cur; 1098 m_edit_area->getCursorPosition (&line, &cur); 1099 1100 line--; // Find bookmark strictly before the current line. 1101 1102 int prevline = m_edit_area->markerFindPrevious (line, (1 << marker::bookmark)); 1103 1104 // Wrap. Should use the last line of the file, not 1<<15 1105 if (prevline == -1) 1106 prevline = m_edit_area->markerFindPrevious (m_edit_area->lines (), 1107 (1 << marker::bookmark)); 1108 1109 m_edit_area->setCursorPosition (prevline, 0); 1110 } 1111 remove_bookmark(const QWidget * ID)1112 void file_editor_tab::remove_bookmark (const QWidget *ID) 1113 { 1114 if (ID != this) 1115 return; 1116 1117 m_edit_area->markerDeleteAll (marker::bookmark); 1118 } 1119 bp_info(const QString & fname,int l,const QString & cond)1120 file_editor_tab::bp_info::bp_info (const QString& fname, int l, 1121 const QString& cond) 1122 : line (l), file (fname.toStdString ()), condition (cond.toStdString ()) 1123 { 1124 QFileInfo file_info (fname); 1125 1126 QString q_dir = file_info.absolutePath (); 1127 QString q_function_name = file_info.fileName (); 1128 1129 // We have to cut off the suffix, because octave appends it. 1130 q_function_name.chop (file_info.suffix ().length () + 1); 1131 1132 dir = q_dir.toStdString (); 1133 function_name = q_function_name.toStdString (); 1134 1135 // Is the last component of DIR @foo? If so, strip it and prepend it 1136 // to the name of the function. 1137 1138 std::size_t pos = dir.rfind (sys::file_ops::dir_sep_chars ()); 1139 1140 if (pos != std::string::npos && pos < dir.length () - 1) 1141 { 1142 if (dir[pos+1] == '@') 1143 { 1144 function_name = sys::file_ops::concat (dir.substr (pos+1), function_name); 1145 1146 dir = dir.substr (0, pos); 1147 } 1148 } 1149 } 1150 handle_request_add_breakpoint(int line,const QString & condition)1151 void file_editor_tab::handle_request_add_breakpoint (int line, 1152 const QString& condition) 1153 { 1154 bp_info info (m_file_name, line, condition); 1155 1156 add_breakpoint_event (info); 1157 } 1158 handle_request_remove_breakpoint(int line)1159 void file_editor_tab::handle_request_remove_breakpoint (int line) 1160 { 1161 bp_info info (m_file_name, line); 1162 1163 emit interpreter_event 1164 ([info] (interpreter& interp) 1165 { 1166 // INTERPRETER THREAD 1167 1168 load_path& lp = interp.get_load_path (); 1169 1170 bp_table::intmap line_info; 1171 line_info[0] = info.line; 1172 1173 if (lp.contains_file_in_dir (info.file, info.dir)) 1174 { 1175 tree_evaluator& tw = interp.get_evaluator (); 1176 1177 bp_table& bptab = tw.get_bp_table (); 1178 1179 bptab.remove_breakpoint (info.function_name, line_info); 1180 } 1181 }); 1182 } 1183 toggle_breakpoint(const QWidget * ID)1184 void file_editor_tab::toggle_breakpoint (const QWidget *ID) 1185 { 1186 if (ID != this) 1187 return; 1188 1189 int editor_linenr, cur; 1190 m_edit_area->getCursorPosition (&editor_linenr, &cur); 1191 1192 if (m_edit_area->markersAtLine (editor_linenr) & (1 << marker::breakpoint)) 1193 request_remove_breakpoint_via_editor_linenr (editor_linenr); 1194 else 1195 { 1196 if (unchanged_or_saved ()) 1197 handle_request_add_breakpoint (editor_linenr + 1, ""); 1198 } 1199 } 1200 1201 // Move the text cursor to the closest breakpoint (conditional or unconditional) 1202 // after the current line. next_breakpoint(const QWidget * ID)1203 void file_editor_tab::next_breakpoint (const QWidget *ID) 1204 { 1205 if (ID != this) 1206 return; 1207 1208 int line, cur; 1209 m_edit_area->getCursorPosition (&line, &cur); 1210 1211 line++; // Find breakpoint strictly after the current line. 1212 1213 int nextline = m_edit_area->markerFindNext (line, (1 << marker::breakpoint)); 1214 int nextcond = m_edit_area->markerFindNext (line, (1 << marker::cond_break)); 1215 1216 // Check if the next conditional breakpoint is before next unconditional one. 1217 if (nextcond != -1 && (nextcond < nextline || nextline == -1)) 1218 nextline = nextcond; 1219 1220 m_edit_area->setCursorPosition (nextline, 0); 1221 } 1222 1223 // Move the text cursor to the closest breakpoint (conditional or unconditional) 1224 // before the current line. previous_breakpoint(const QWidget * ID)1225 void file_editor_tab::previous_breakpoint (const QWidget *ID) 1226 { 1227 if (ID != this) 1228 return; 1229 1230 int line, cur, prevline, prevcond; 1231 m_edit_area->getCursorPosition (&line, &cur); 1232 1233 line--; // Find breakpoint strictly before the current line. 1234 1235 prevline = m_edit_area->markerFindPrevious (line, (1 << marker::breakpoint)); 1236 prevcond = m_edit_area->markerFindPrevious (line, (1 << marker::cond_break)); 1237 1238 // Check if the prev conditional breakpoint is closer than the unconditional. 1239 if (prevcond != -1 && prevcond > prevline) 1240 prevline = prevcond; 1241 1242 m_edit_area->setCursorPosition (prevline, 0); 1243 } 1244 remove_all_breakpoints(const QWidget * ID)1245 void file_editor_tab::remove_all_breakpoints (const QWidget *ID) 1246 { 1247 if (ID != this) 1248 return; 1249 1250 bp_info info (m_file_name); 1251 1252 emit interpreter_event 1253 ([info] (interpreter& interp) 1254 { 1255 // INTERPRETER THREAD 1256 1257 load_path& lp = interp.get_load_path (); 1258 1259 if (lp.contains_file_in_dir (info.file, info.dir)) 1260 { 1261 tree_evaluator& tw = interp.get_evaluator (); 1262 1263 bp_table& bptab = tw.get_bp_table (); 1264 1265 bptab.remove_all_breakpoints_in_file (info.function_name, true); 1266 } 1267 }); 1268 } 1269 scintilla_command(const QWidget * ID,unsigned int sci_msg)1270 void file_editor_tab::scintilla_command (const QWidget *ID, 1271 unsigned int sci_msg) 1272 { 1273 if (ID != this) 1274 return; 1275 1276 m_edit_area->SendScintilla (sci_msg); 1277 } 1278 comment_selected_text(const QWidget * ID,bool input_str)1279 void file_editor_tab::comment_selected_text (const QWidget *ID, 1280 bool input_str) 1281 { 1282 if (ID != this) 1283 return; 1284 1285 do_comment_selected_text (true, input_str); 1286 } 1287 uncomment_selected_text(const QWidget * ID)1288 void file_editor_tab::uncomment_selected_text (const QWidget *ID) 1289 { 1290 if (ID != this) 1291 return; 1292 1293 do_comment_selected_text (false); 1294 } 1295 indent_selected_text(const QWidget * ID)1296 void file_editor_tab::indent_selected_text (const QWidget *ID) 1297 { 1298 if (ID != this) 1299 return; 1300 1301 do_indent_selected_text (true); 1302 } 1303 unindent_selected_text(const QWidget * ID)1304 void file_editor_tab::unindent_selected_text (const QWidget *ID) 1305 { 1306 if (ID != this) 1307 return; 1308 1309 do_indent_selected_text (false); 1310 } 1311 smart_indent_line_or_selected_text(const QWidget * ID)1312 void file_editor_tab::smart_indent_line_or_selected_text (const QWidget *ID) 1313 { 1314 if (ID != this) 1315 return; 1316 1317 do_smart_indent_line_or_selected_text (); 1318 } 1319 convert_eol(const QWidget * ID,QsciScintilla::EolMode eol_mode)1320 void file_editor_tab::convert_eol (const QWidget *ID, 1321 QsciScintilla::EolMode eol_mode) 1322 { 1323 if (ID != this) 1324 return; 1325 1326 m_edit_area->convertEols (eol_mode); 1327 m_edit_area->setEolMode (eol_mode); 1328 update_eol_indicator (); 1329 } 1330 zoom_in(const QWidget * ID)1331 void file_editor_tab::zoom_in (const QWidget *ID) 1332 { 1333 if (ID != this) 1334 return; 1335 1336 m_edit_area->zoomIn (1); 1337 auto_margin_width (); 1338 } 1339 zoom_out(const QWidget * ID)1340 void file_editor_tab::zoom_out (const QWidget *ID) 1341 { 1342 if (ID != this) 1343 return; 1344 1345 m_edit_area->zoomOut (1); 1346 auto_margin_width (); 1347 } 1348 zoom_normal(const QWidget * ID)1349 void file_editor_tab::zoom_normal (const QWidget *ID) 1350 { 1351 if (ID != this) 1352 return; 1353 1354 m_edit_area->zoomTo (0); 1355 auto_margin_width (); 1356 } 1357 add_breakpoint_event(const bp_info & info)1358 void file_editor_tab::add_breakpoint_event (const bp_info& info) 1359 { 1360 emit interpreter_event 1361 ([this, info] (interpreter& interp) 1362 { 1363 // INTERPRETER THREAD 1364 1365 // FIXME: note duplication with the code in 1366 // handle_context_menu_break_condition. 1367 1368 load_path& lp = interp.get_load_path (); 1369 1370 bp_table::intmap line_info; 1371 line_info[0] = info.line; 1372 1373 if (lp.contains_file_in_dir (info.file, info.dir)) 1374 { 1375 tree_evaluator& tw = interp.get_evaluator (); 1376 1377 bp_table& bptab = tw.get_bp_table (); 1378 1379 bp_table::intmap bpmap 1380 = bptab.add_breakpoint (info.function_name, "", line_info, 1381 info.condition); 1382 1383 if (! bpmap.empty ()) 1384 { 1385 bp_table::intmap::iterator bp_it = bpmap.begin (); 1386 1387 int remove_line = bp_it->second; 1388 1389 emit maybe_remove_next (remove_line); 1390 } 1391 } 1392 }); 1393 } 1394 handle_remove_next(int remove_line)1395 void file_editor_tab::handle_remove_next (int remove_line) 1396 { 1397 // Store some info breakpoint 1398 if (m_breakpoint_info.remove_next) 1399 { 1400 m_breakpoint_info.remove_line = remove_line; 1401 m_breakpoint_info.remove_next = false; 1402 } 1403 } 1404 goto_line(const QWidget * ID,int line)1405 void file_editor_tab::goto_line (const QWidget *ID, int line) 1406 { 1407 if (ID != this) 1408 return; 1409 1410 if (m_bp_restore_count > 0) 1411 { 1412 // This goto-line request is invoked by restoring a breakpoint during 1413 // saving the file, thus, do not go to the related line 1414 m_bp_restore_count--; 1415 return; 1416 } 1417 1418 if (line <= 0) // ask for desired line 1419 { 1420 bool ok = false; 1421 int index; 1422 m_edit_area->getCursorPosition (&line, &index); 1423 line = QInputDialog::getInt (m_edit_area, tr ("Goto line"), 1424 tr ("Line number"), line+1, 1, 1425 m_edit_area->lines (), 1, &ok); 1426 if (ok) 1427 m_edit_area->setCursorPosition (line-1, 0); 1428 } 1429 else // go to given line without dialog 1430 m_edit_area->setCursorPosition (line-1, 0); 1431 1432 center_current_line (false); // only center line if at top or bottom 1433 } 1434 move_match_brace(const QWidget * ID,bool select)1435 void file_editor_tab::move_match_brace (const QWidget *ID, bool select) 1436 { 1437 if (ID != this) 1438 return; 1439 1440 if (select) 1441 m_edit_area->selectToMatchingBrace (); 1442 else 1443 m_edit_area->moveToMatchingBrace (); 1444 } 1445 show_auto_completion(const QWidget * ID)1446 void file_editor_tab::show_auto_completion (const QWidget *ID) 1447 { 1448 if (ID != this) 1449 return; 1450 1451 m_autoc_active = true; 1452 1453 QsciScintilla::AutoCompletionSource s = m_edit_area->autoCompletionSource (); 1454 switch (s) 1455 { 1456 case QsciScintilla::AcsAll: 1457 m_edit_area->autoCompleteFromAll (); 1458 break; 1459 1460 case QsciScintilla::AcsAPIs: 1461 m_edit_area->autoCompleteFromAPIs (); 1462 break; 1463 1464 case QsciScintilla::AcsDocument: 1465 m_edit_area->autoCompleteFromDocument (); 1466 break; 1467 1468 case QsciScintilla::AcsNone: 1469 break; 1470 } 1471 } 1472 do_indent_selected_text(bool indent)1473 void file_editor_tab::do_indent_selected_text (bool indent) 1474 { 1475 // FIXME: 1476 m_edit_area->beginUndoAction (); 1477 1478 if (m_edit_area->hasSelectedText ()) 1479 { 1480 int lineFrom, lineTo, colFrom, colTo; 1481 m_edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo); 1482 1483 if (colTo == 0) // the beginning of last line is not selected 1484 lineTo--; // stop at line above 1485 1486 for (int i = lineFrom; i <= lineTo; i++) 1487 { 1488 if (indent) 1489 m_edit_area->indent (i); 1490 else 1491 m_edit_area->unindent (i); 1492 } 1493 //set selection on (un)indented section 1494 m_edit_area->setSelection (lineFrom, 0, lineTo, 1495 m_edit_area->text (lineTo).length ()-1); 1496 } 1497 else 1498 { 1499 int cpline, col; 1500 m_edit_area->getCursorPosition (&cpline, &col); 1501 if (indent) 1502 m_edit_area->indent (cpline); 1503 else 1504 m_edit_area->unindent (cpline); 1505 } 1506 1507 m_edit_area->endUndoAction (); 1508 } 1509 do_smart_indent_line_or_selected_text(void)1510 void file_editor_tab::do_smart_indent_line_or_selected_text (void) 1511 { 1512 m_edit_area->beginUndoAction (); 1513 1514 int lineFrom, lineTo; 1515 1516 if (m_edit_area->hasSelectedText ()) 1517 { 1518 int colFrom, colTo; 1519 m_edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo); 1520 1521 if (colTo == 0) // the beginning of last line is not selected 1522 lineTo--; // stop at line above 1523 } 1524 else 1525 { 1526 int col; 1527 m_edit_area->getCursorPosition (&lineFrom, &col); 1528 1529 lineTo = lineFrom; 1530 } 1531 1532 m_edit_area->smart_indent_line_or_selected_text (lineFrom, lineTo); 1533 1534 m_edit_area->endUndoAction (); 1535 } 1536 do_comment_selected_text(bool comment,bool input_str)1537 void file_editor_tab::do_comment_selected_text (bool comment, bool input_str) 1538 { 1539 QRegExp rxc; 1540 QString ws = "^(?:[ \\t]*)"; 1541 QStringList comment_str = m_edit_area->comment_string (comment); 1542 QString used_comment_str = comment_str.at (0); 1543 1544 if (comment) 1545 { 1546 if (input_str) 1547 { 1548 bool ok; 1549 resource_manager& rmgr = m_octave_qobj.get_resource_manager (); 1550 gui_settings *settings = rmgr.get_settings (); 1551 1552 used_comment_str 1553 = QInputDialog::getText (this, tr ("Comment selected text"), 1554 tr ("Comment string to use:\n"), 1555 QLineEdit::Normal, 1556 settings->value (ed_last_comment_str, comment_str.at (0)).toString (), 1557 &ok); 1558 1559 if ((! ok) || used_comment_str.isEmpty ()) 1560 return; // No input, do nothing 1561 else 1562 settings->setValue (ed_last_comment_str, used_comment_str); // Store last 1563 } 1564 } 1565 else 1566 { 1567 // Uncommenting (several strings possible) 1568 1569 // Sort strings according their length 1570 QStringList comment_str_sorted (comment_str.at (0)); 1571 bool inserted; 1572 1573 for (int i = 1; i < comment_str.length (); i++) 1574 { 1575 inserted = false; 1576 for (int j = 0; j < comment_str_sorted.length (); j++) 1577 { 1578 if (comment_str.at (i).length () > comment_str_sorted.at (j).length ()) 1579 { 1580 comment_str_sorted.insert (j, comment_str.at (i)); 1581 inserted = true; 1582 break; 1583 } 1584 } 1585 if (! inserted) 1586 comment_str_sorted << comment_str.at (i); 1587 } 1588 1589 // Create regular expression 1590 QString regexp; 1591 for (int i = 0; i < comment_str_sorted.length (); i++) 1592 { 1593 if (i > 0) 1594 regexp = regexp + QString ("|"); 1595 regexp = regexp + comment_str_sorted.at (i); 1596 } 1597 rxc = QRegExp (ws + "(" + regexp + ")"); 1598 } 1599 1600 // Do the commenting/uncommenting 1601 int len = 0, lenc = 0; 1602 m_edit_area->beginUndoAction (); 1603 1604 if (m_edit_area->hasSelectedText ()) 1605 { 1606 int lineFrom, lineTo, colFrom, colTo; 1607 int change_col_from = 1; 1608 int change_col_to = 1; 1609 bool removed; 1610 1611 m_edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo); 1612 1613 if (colTo == 0) // the beginning of last line is not selected 1614 lineTo--; // stop at line above 1615 1616 for (int i = lineFrom; i <= lineTo; i++) 1617 { 1618 if (comment) 1619 { 1620 m_edit_area->insertAt (used_comment_str, i, 0); 1621 } 1622 else 1623 { 1624 QString line (m_edit_area->text (i)); 1625 if ((removed = line.contains (rxc))) 1626 { 1627 len = rxc.matchedLength (); // complete length 1628 QString matched_text = rxc.capturedTexts ().at (0); 1629 lenc = matched_text.remove (QRegExp (ws)).length (); // only comment string 1630 m_edit_area->setSelection (i, len-lenc, i, len); 1631 m_edit_area->removeSelectedText (); 1632 } 1633 1634 // handle case, where the selection remains unchanged 1635 if (i == lineFrom && (colFrom < len-lenc || ! removed)) 1636 change_col_from = 0; // do not change start of selection 1637 if (i == lineTo && (colTo < len-lenc || ! removed)) 1638 change_col_to = 0; // do not change end of selection 1639 } 1640 } 1641 1642 // update the selection area 1643 if (comment) 1644 { 1645 colFrom = colFrom + lenc; // shift start position by comment length 1646 if (colTo > 0) 1647 colTo = colTo + lenc; // shift end position by comment length 1648 else 1649 lineTo++; // colTo == 0 , fully select previous line 1650 } 1651 else 1652 { 1653 if (colTo == 0) 1654 lineTo++; // colTo == 0 , fully select previous line 1655 colFrom = colFrom - change_col_from*lenc; 1656 colTo = colTo - change_col_to*lenc; 1657 } 1658 1659 // set updated selection area 1660 m_edit_area->setSelection (lineFrom, colFrom, lineTo, colTo); 1661 } 1662 else 1663 { 1664 int cpline, col; 1665 m_edit_area->getCursorPosition (&cpline, &col); 1666 if (comment) 1667 m_edit_area->insertAt (used_comment_str, cpline, 0); 1668 else 1669 { 1670 QString line (m_edit_area->text (cpline)); 1671 if (line.contains (rxc)) 1672 { 1673 len = rxc.matchedLength (); // complete length 1674 QString matched_text = rxc.capturedTexts ().at (0); 1675 lenc = matched_text.remove (QRegExp (ws)).length (); // only comment string 1676 m_edit_area->setSelection (cpline, len-lenc, cpline, len); 1677 m_edit_area->removeSelectedText (); 1678 } 1679 } 1680 } 1681 m_edit_area->endUndoAction (); 1682 } 1683 update_window_title(bool modified)1684 void file_editor_tab::update_window_title (bool modified) 1685 { 1686 QString title (""); 1687 QString tooltip (""); 1688 1689 if (! valid_file_name ()) 1690 title = tr ("<unnamed>"); 1691 else 1692 { 1693 if (m_long_title) 1694 title = m_file_name; 1695 else 1696 { 1697 QFileInfo file (m_file_name); 1698 title = file.fileName (); 1699 tooltip = m_file_name; 1700 } 1701 } 1702 1703 emit file_name_changed (title, tooltip, modified); 1704 } 1705 handle_copy_available(bool enableCopy)1706 void file_editor_tab::handle_copy_available (bool enableCopy) 1707 { 1708 m_copy_available = enableCopy; 1709 emit editor_state_changed (m_copy_available, m_is_octave_file); 1710 } 1711 1712 // show_dialog: shows a modal or non modal dialog depending on input arg show_dialog(QDialog * dlg,bool modal)1713 void file_editor_tab::show_dialog (QDialog *dlg, bool modal) 1714 { 1715 dlg->setAttribute (Qt::WA_DeleteOnClose); 1716 if (modal) 1717 dlg->exec (); 1718 else 1719 { 1720 dlg->setWindowModality (Qt::NonModal); 1721 dlg->show (); 1722 } 1723 } 1724 check_file_modified(bool remove)1725 int file_editor_tab::check_file_modified (bool remove) 1726 { 1727 int decision = QMessageBox::Yes; 1728 if (m_edit_area->isModified ()) 1729 { 1730 // File is modified but not saved, ask user what to do. The file 1731 // editor tab can't be made parent because it may be deleted depending 1732 // upon the response. Instead, change the m_edit_area to read only. 1733 QMessageBox::StandardButtons buttons = QMessageBox::Save | 1734 QMessageBox::Discard | 1735 QMessageBox::Cancel; 1736 1737 // For now, just a warning message about closing a tab that has been 1738 // modified seems sufficient. Exit-condition-specific messages could 1739 // be achieved by making 'available_actions' a function input string. 1740 QString available_actions = 1741 tr ("Do you want to cancel closing, save or discard the changes?"); 1742 1743 QString file; 1744 if (valid_file_name ()) 1745 file = m_file_name; 1746 else 1747 file = tr ("<unnamed>"); 1748 1749 QMessageBox *msgBox 1750 = new QMessageBox (QMessageBox::Warning, tr ("Octave Editor"), 1751 tr ("The file\n\n" 1752 " %1\n\n" 1753 "is about to be closed but has been modified. " 1754 "%2"). 1755 arg (file). arg (available_actions), 1756 buttons, qobject_cast<QWidget *> (parent ())); 1757 1758 msgBox->setDefaultButton (QMessageBox::Save); 1759 m_edit_area->setReadOnly (true); 1760 1761 decision = msgBox->exec (); // show_dialog (msgBox, true); 1762 1763 if (decision == QMessageBox::Cancel) 1764 m_edit_area->setReadOnly (false); 1765 else if (decision == QMessageBox::Save) 1766 save_file (m_file_name, remove, false); 1767 else 1768 emit tab_ready_to_close (); 1769 } 1770 else 1771 { 1772 emit tab_ready_to_close (); 1773 } 1774 1775 return decision; 1776 } 1777 set_modified(bool modified)1778 void file_editor_tab::set_modified (bool modified) 1779 { 1780 m_edit_area->setModified (modified); 1781 } 1782 recover_from_exit(void)1783 void file_editor_tab::recover_from_exit (void) 1784 { 1785 // reset the possibly still existing read only state 1786 m_edit_area->setReadOnly (false); 1787 1788 // if we are in this slot and the list of breakpoints is not empty, 1789 // then this tab was saved during an exit of the applications (not 1790 // restoring the breakpoints and not emptying the list) and the user 1791 // canceled this closing late on. 1792 check_restore_breakpoints (); 1793 } 1794 check_restore_breakpoints(void)1795 void file_editor_tab::check_restore_breakpoints (void) 1796 { 1797 if (! m_bp_lines.isEmpty ()) 1798 { 1799 // At least one breakpoint is present. 1800 // Get rid of breakpoints at old (now possibly invalid) linenumbers 1801 remove_all_breakpoints (this); 1802 1803 // and set breakpoints at the new linenumbers 1804 m_bp_restore_count = m_bp_lines.length (); 1805 for (int i = 0; i < m_bp_lines.length (); i++) 1806 handle_request_add_breakpoint (m_bp_lines.value (i) + 1, 1807 m_bp_conditions.value (i)); 1808 1809 // Keep the list of breakpoints empty, except after explicit requests. 1810 m_bp_lines.clear (); 1811 m_bp_conditions.clear (); 1812 } 1813 } 1814 load_file(const QString & fileName)1815 QString file_editor_tab::load_file (const QString& fileName) 1816 { 1817 // get the absolute path 1818 QFileInfo file_info = QFileInfo (fileName); 1819 QString file_to_load; 1820 if (file_info.exists ()) 1821 file_to_load = file_info.canonicalFilePath (); 1822 else 1823 file_to_load = fileName; 1824 QFile file (file_to_load); 1825 if (!file.open(QIODevice::ReadOnly)) 1826 return file.errorString (); 1827 1828 int col = 0, line = 0; 1829 if (fileName == m_file_name) 1830 { 1831 // We have to reload the current file, thus get current cursor position 1832 line = m_line; 1833 col = m_col; 1834 } 1835 1836 QApplication::setOverrideCursor (Qt::WaitCursor); 1837 1838 // read the file binary, decoding later 1839 const QByteArray text_data = file.readAll (); 1840 1841 // decode 1842 QTextCodec::ConverterState st; 1843 QTextCodec *codec = QTextCodec::codecForName (m_encoding.toLatin1 ()); 1844 if (codec == nullptr) 1845 codec = QTextCodec::codecForLocale (); 1846 1847 const QString text = codec->toUnicode(text_data.constData(), 1848 text_data.size(), &st); 1849 1850 // Decoding with invalid characters? 1851 if (st.invalidChars > 0) 1852 { 1853 // Set read only 1854 m_edit_area->setReadOnly (true); 1855 1856 // Message box for user decision 1857 QString msg = tr ("There were problems reading the file\n" 1858 "%1\n" 1859 "with the selected encoding %2.\n\n" 1860 "Modifying and saving the file might " 1861 "cause data loss!") 1862 .arg (file_to_load).arg (m_encoding); 1863 QMessageBox *msg_box = new QMessageBox (); 1864 msg_box->setIcon (QMessageBox::Warning); 1865 msg_box->setText (msg); 1866 msg_box->setWindowTitle (tr ("Octave Editor")); 1867 msg_box->addButton (tr ("&Edit anyway"), QMessageBox::YesRole); 1868 msg_box->addButton (tr ("Chan&ge encoding"), QMessageBox::AcceptRole); 1869 msg_box->addButton (tr ("&Close"), QMessageBox::RejectRole); 1870 1871 connect (msg_box, SIGNAL (buttonClicked (QAbstractButton *)), 1872 this, SLOT (handle_decode_warning_answer (QAbstractButton *))); 1873 1874 msg_box->setWindowModality (Qt::WindowModal); 1875 msg_box->setAttribute (Qt::WA_DeleteOnClose); 1876 msg_box->show (); 1877 } 1878 1879 m_edit_area->setText (text); 1880 m_edit_area->setEolMode (detect_eol_mode ()); 1881 1882 QApplication::restoreOverrideCursor (); 1883 1884 m_copy_available = false; // no selection yet available 1885 set_file_name (file_to_load); 1886 update_window_title (false); // window title (no modification) 1887 m_edit_area->setModified (false); // loaded file is not modified yet 1888 1889 update_eol_indicator (); 1890 1891 m_edit_area->setCursorPosition (line, col); 1892 1893 return QString (); 1894 } 1895 handle_decode_warning_answer(QAbstractButton * btn)1896 void file_editor_tab::handle_decode_warning_answer (QAbstractButton *btn) 1897 { 1898 QString txt = btn->text (); 1899 1900 if (txt == tr ("&Close")) 1901 { 1902 // Just close the file 1903 close (); 1904 return; 1905 } 1906 1907 if (txt == tr ("Chan&ge encoding")) 1908 { 1909 // Dialog for reloading the file with another encoding 1910 QDialog dlg; 1911 dlg.setWindowTitle (tr ("Select new default encoding")); 1912 1913 QLabel *text 1914 = new QLabel (tr ("Please select a new encoding\n" 1915 "for reloading the current file.\n\n" 1916 "This does not change the default encoding.\n")); 1917 1918 QComboBox *enc_combo = new QComboBox (); 1919 resource_manager& rmgr = m_octave_qobj.get_resource_manager (); 1920 rmgr.combo_encoding (enc_combo); 1921 m_new_encoding = enc_combo->currentText (); 1922 connect (enc_combo, SIGNAL (currentTextChanged (const QString&)), 1923 this, SLOT (handle_current_enc_changed (const QString&))); 1924 1925 QDialogButtonBox *buttons 1926 = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel, 1927 Qt::Horizontal); 1928 connect (buttons, SIGNAL (accepted ()), &dlg, SLOT (accept ())); 1929 connect (buttons, SIGNAL (rejected ()), &dlg, SLOT (reject ())); 1930 1931 QGridLayout *main_layout = new QGridLayout; 1932 main_layout->setSizeConstraint (QLayout::SetFixedSize); 1933 main_layout->addWidget (text, 0, 0); 1934 main_layout->addWidget (enc_combo, 1, 0); 1935 main_layout->addWidget (buttons, 2, 0); 1936 dlg.setLayout (main_layout); 1937 1938 int answer = dlg.exec (); 1939 1940 if (answer == QDialog::Accepted) 1941 { 1942 // Reload the file with new encoding but using the same tab 1943 QString reload_file_name = m_file_name; // store file name 1944 m_file_name = ""; // force reuse of this tab when opening a new file 1945 emit request_open_file (reload_file_name, m_new_encoding); 1946 } 1947 } 1948 1949 // Continue editing, set writable again 1950 m_edit_area->setReadOnly (false); 1951 } 1952 handle_current_enc_changed(const QString & enc)1953 void file_editor_tab::handle_current_enc_changed (const QString& enc) 1954 { 1955 m_new_encoding = enc; 1956 } 1957 detect_eol_mode(void)1958 QsciScintilla::EolMode file_editor_tab::detect_eol_mode (void) 1959 { 1960 QByteArray text = m_edit_area->text ().toLatin1 (); 1961 1962 QByteArray eol_lf = QByteArray (1,0x0a); 1963 QByteArray eol_cr = QByteArray (1,0x0d); 1964 QByteArray eol_crlf = eol_cr; 1965 eol_crlf.append (eol_lf); 1966 1967 int count_crlf = text.count (eol_crlf); 1968 int count_lf = text.count (eol_lf) - count_crlf; // isolated lf 1969 int count_cr = text.count (eol_cr) - count_crlf; // isolated cr 1970 1971 resource_manager& rmgr = m_octave_qobj.get_resource_manager (); 1972 gui_settings *settings = rmgr.get_settings (); 1973 QsciScintilla::EolMode eol_mode 1974 = static_cast<QsciScintilla::EolMode> (settings->value (ed_default_eol_mode).toInt ()); 1975 1976 int count_max = 0; 1977 1978 if (count_crlf > count_max) 1979 { 1980 eol_mode = QsciScintilla::EolWindows; 1981 count_max = count_crlf; 1982 } 1983 if (count_lf > count_max) 1984 { 1985 eol_mode = QsciScintilla::EolUnix; 1986 count_max = count_lf; 1987 } 1988 if (count_cr > count_max) 1989 { 1990 eol_mode = QsciScintilla::EolMac; 1991 } 1992 1993 return eol_mode; 1994 } 1995 update_eol_indicator(void)1996 void file_editor_tab::update_eol_indicator (void) 1997 { 1998 switch (m_edit_area->eolMode ()) 1999 { 2000 case QsciScintilla::EolWindows: 2001 m_eol_indicator->setText ("CRLF"); 2002 break; 2003 case QsciScintilla::EolMac: 2004 m_eol_indicator->setText ("CR"); 2005 break; 2006 case QsciScintilla::EolUnix: 2007 m_eol_indicator->setText ("LF"); 2008 break; 2009 } 2010 } 2011 update_breakpoints()2012 void file_editor_tab::update_breakpoints () 2013 { 2014 if (m_file_name.isEmpty ()) 2015 return; 2016 2017 // Create and queue the command object. 2018 2019 emit interpreter_event 2020 ([this] (interpreter& interp) 2021 { 2022 // INTERPRETER THREAD 2023 2024 octave_value_list argout = Fdbstatus (interp, ovl (), 1); 2025 2026 connect (this, SIGNAL (update_breakpoints_signal (const octave_value_list&)), 2027 this, SLOT (update_breakpoints_handler (const octave_value_list&)), 2028 Qt::QueuedConnection); 2029 2030 emit update_breakpoints_signal (argout); 2031 }); 2032 } 2033 update_breakpoints_handler(const octave_value_list & argout)2034 void file_editor_tab::update_breakpoints_handler (const octave_value_list& argout) 2035 { 2036 octave_map dbg = argout(0).map_value (); 2037 octave_idx_type n_dbg = dbg.numel (); 2038 2039 Cell file = dbg.contents ("file"); 2040 Cell line = dbg.contents ("line"); 2041 Cell cond = dbg.contents ("cond"); 2042 2043 for (octave_idx_type i = 0; i < n_dbg; i++) 2044 { 2045 if (file (i).string_value () == m_file_name.toStdString ()) 2046 do_breakpoint_marker (true, this, line (i).int_value (), 2047 QString::fromStdString (cond (i).string_value ())); 2048 } 2049 } 2050 new_file(const QString & commands)2051 void file_editor_tab::new_file (const QString& commands) 2052 { 2053 update_window_title (false); // window title (no modification) 2054 2055 resource_manager& rmgr = m_octave_qobj.get_resource_manager (); 2056 gui_settings *settings = rmgr.get_settings (); 2057 2058 // set the eol mode from the settings or depending on the OS if the entry is 2059 // missing in the settings 2060 m_edit_area->setEolMode (static_cast<QsciScintilla::EolMode> (settings->value (ed_default_eol_mode).toInt ())); 2061 2062 update_eol_indicator (); 2063 2064 update_lexer (); 2065 2066 m_edit_area->setText (commands); 2067 m_edit_area->setModified (!commands.isEmpty ()); 2068 } 2069 confirm_dbquit_and_save(const QString & file_to_save,const QString & base_name,bool remove_on_success,bool restore_breakpoints)2070 void file_editor_tab::confirm_dbquit_and_save (const QString& file_to_save, 2071 const QString& base_name, 2072 bool remove_on_success, 2073 bool restore_breakpoints) 2074 { 2075 int ans = QMessageBox::question (nullptr, tr ("Debug or Save"), 2076 tr ("This file is currently being executed.\n" 2077 "Quit debugging and save?"), 2078 QMessageBox::Save | QMessageBox::Cancel); 2079 2080 if (ans == QMessageBox::Save) 2081 { 2082 emit interpreter_event 2083 ([this, file_to_save, base_name, remove_on_success, restore_breakpoints] (interpreter& interp) 2084 { 2085 // INTERPRETER THREAD 2086 2087 tree_evaluator& tw = interp.get_evaluator (); 2088 2089 tw.dbquit (true); 2090 2091 command_editor::interrupt (true); 2092 2093 std::string std_base_name = base_name.toStdString (); 2094 2095 symbol_table& symtab = interp.get_symbol_table (); 2096 2097 symtab.clear_user_function (std_base_name); 2098 2099 emit do_save_file_signal (file_to_save, remove_on_success, 2100 restore_breakpoints); 2101 }); 2102 } 2103 } 2104 save_file(const QString & saveFileName,bool remove_on_success,bool restore_breakpoints)2105 void file_editor_tab::save_file (const QString& saveFileName, 2106 bool remove_on_success, 2107 bool restore_breakpoints) 2108 { 2109 // If it is a new file with no name, signal that saveFileAs 2110 // should be performed. 2111 if (! valid_file_name (saveFileName)) 2112 { 2113 save_file_as (remove_on_success); 2114 return; 2115 } 2116 2117 m_encoding = m_new_encoding; // consider a possible new encoding 2118 2119 // set the desired codec (if suitable for contents) 2120 QTextCodec *codec = check_valid_codec (); 2121 if (! codec) 2122 return; // No valid codec 2123 2124 // Get a list of breakpoint line numbers, before exiting debug mode 2125 // and clearing function in interpreter_event action below. 2126 2127 emit report_marker_linenr (m_bp_lines, m_bp_conditions); 2128 2129 // get the absolute path (if existing) 2130 QFileInfo file_info = QFileInfo (saveFileName); 2131 QString file_to_save; 2132 if (file_info.exists ()) 2133 { 2134 file_to_save = file_info.canonicalFilePath (); 2135 QString base_name = file_info.baseName (); 2136 2137 emit interpreter_event 2138 ([this, file_to_save, base_name, remove_on_success, restore_breakpoints] (interpreter& interp) 2139 { 2140 // INTERPRETER THREAD 2141 2142 // Force reloading of a file after it is saved. 2143 // This is needed to get the right line numbers for 2144 // breakpoints (bug #46632). 2145 2146 tree_evaluator& tw = interp.get_evaluator (); 2147 2148 symbol_table& symtab = interp.get_symbol_table (); 2149 2150 std::string std_base_name = base_name.toStdString (); 2151 2152 if (tw.in_debug_repl ()) 2153 { 2154 octave_value sym; 2155 try 2156 { 2157 sym = symtab.find_user_function (std_base_name); 2158 } 2159 catch (const execution_exception& e) 2160 { 2161 interp.recover_from_exception (); 2162 2163 // Ignore syntax error. It was in the old file on disk; 2164 // the user may have fixed it already. 2165 } 2166 2167 // Return early if this file is not loaded in the symbol table 2168 if (! sym.is_defined () || ! sym.is_user_code ()) 2169 { 2170 emit do_save_file_signal (file_to_save, remove_on_success, 2171 restore_breakpoints); 2172 return; 2173 } 2174 2175 octave_user_code *fcn = sym.user_code_value (); 2176 2177 std::string full_name = file_to_save.toStdString (); 2178 2179 if (sys::canonicalize_file_name (full_name) 2180 != sys::canonicalize_file_name (fcn->fcn_file_name ())) 2181 { 2182 emit do_save_file_signal (file_to_save, remove_on_success, 2183 restore_breakpoints); 2184 return; 2185 } 2186 2187 // If this file is loaded, check that we aren't currently 2188 // running it. 2189 // FIXME: is there a better way to get this info? 2190 2191 octave_idx_type curr_frame = -1; 2192 2193 octave_map stk = tw.backtrace (curr_frame, false); 2194 2195 Cell names = stk.contents ("name"); 2196 2197 for (octave_idx_type i = names.numel () - 1; i >= 0; i--) 2198 { 2199 if (names(i).string_value () == std_base_name) 2200 { 2201 emit confirm_dbquit_and_save_signal 2202 (file_to_save, base_name, remove_on_success, 2203 restore_breakpoints); 2204 return; 2205 } 2206 } 2207 } 2208 2209 symtab.clear_user_function (std_base_name); 2210 2211 emit do_save_file_signal (file_to_save, remove_on_success, 2212 restore_breakpoints); 2213 }); 2214 } 2215 else 2216 emit do_save_file_signal (saveFileName, remove_on_success, 2217 restore_breakpoints); 2218 } 2219 do_save_file(const QString & file_to_save,bool remove_on_success,bool restore_breakpoints)2220 void file_editor_tab::do_save_file (const QString& file_to_save, 2221 bool remove_on_success, 2222 bool restore_breakpoints) 2223 { 2224 QFile file (file_to_save); 2225 2226 // stop watching file 2227 QStringList trackedFiles = m_file_system_watcher.files (); 2228 if (trackedFiles.contains (file_to_save)) 2229 m_file_system_watcher.removePath (file_to_save); 2230 2231 // open the file for writing 2232 if (! file.open (QIODevice::WriteOnly)) 2233 { 2234 // Unsuccessful, begin watching file again if it was being 2235 // watched previously. 2236 if (trackedFiles.contains (file_to_save)) 2237 m_file_system_watcher.addPath (file_to_save); 2238 2239 // Create a NonModal message about error. 2240 QMessageBox *msgBox 2241 = new QMessageBox (QMessageBox::Critical, 2242 tr ("Octave Editor"), 2243 tr ("Could not open file %1 for write:\n%2."). 2244 arg (file_to_save).arg (file.errorString ()), 2245 QMessageBox::Ok, nullptr); 2246 show_dialog (msgBox, false); 2247 2248 return; 2249 } 2250 2251 // save the contents into the file 2252 2253 // write the file 2254 QTextStream out (&file); 2255 2256 // set the desired codec (if suitable for contents) 2257 QTextCodec *codec = check_valid_codec (); 2258 if (! codec) 2259 return; // No valid codec 2260 2261 out.setCodec (codec); 2262 2263 QApplication::setOverrideCursor (Qt::WaitCursor); 2264 out << m_edit_area->text (); 2265 out.flush (); 2266 QApplication::restoreOverrideCursor (); 2267 file.flush (); 2268 file.close (); 2269 2270 // file exists now 2271 QFileInfo file_info = QFileInfo (file); 2272 QString full_file_to_save = file_info.canonicalFilePath (); 2273 2274 // save filename after closing file as set_file_name starts watching again 2275 set_file_name (full_file_to_save); // make absolute 2276 2277 // set the window title to actual filename (not modified) 2278 update_window_title (false); 2279 2280 // file is save -> not modified, update encoding in statusbar 2281 m_edit_area->setModified (false); 2282 m_enc_indicator->setText (m_encoding); 2283 2284 emit tab_ready_to_close (); 2285 2286 if (remove_on_success) 2287 { 2288 emit tab_remove_request (); 2289 return; // Don't touch member variables after removal 2290 } 2291 2292 // Attempt to restore the breakpoints if that is desired. 2293 // This is only allowed if the tab is not closing since changing 2294 // breakpoints would reopen the tab in this case. 2295 if (restore_breakpoints) 2296 check_restore_breakpoints (); 2297 } 2298 save_file_as(bool remove_on_success)2299 void file_editor_tab::save_file_as (bool remove_on_success) 2300 { 2301 // Simply put up the file chooser dialog box with a slot connection 2302 // then return control to the system waiting for a file selection. 2303 2304 // reset m_new_encoding 2305 m_new_encoding = m_encoding; 2306 2307 // If the tab is removed in response to a QFileDialog signal, the tab 2308 // can't be a parent. 2309 QFileDialog *fileDialog; 2310 if (remove_on_success) 2311 { 2312 // If tab is closed, "this" cannot be parent in which case modality 2313 // has no effect. Disable editing instead. 2314 m_edit_area->setReadOnly (true); 2315 fileDialog = new QFileDialog (); 2316 } 2317 else 2318 fileDialog = new QFileDialog (this); 2319 2320 // add the possible filters and the default suffix 2321 QStringList filters; 2322 filters << tr ("Octave Files (*.m)") 2323 << tr ("All Files (*)"); 2324 fileDialog->setNameFilters (filters); 2325 fileDialog->setDefaultSuffix ("m"); 2326 2327 if (valid_file_name ()) 2328 { 2329 fileDialog->selectFile (m_file_name); 2330 QFileInfo file_info (m_file_name); 2331 if (file_info.suffix () != "m") 2332 { 2333 // it is not an octave file 2334 fileDialog->selectNameFilter (filters.at (1)); // "All Files" 2335 fileDialog->setDefaultSuffix (""); // no default suffix 2336 } 2337 } 2338 else 2339 { 2340 fileDialog->selectFile (""); 2341 fileDialog->setDirectory (m_ced); 2342 2343 // propose a name corresponding to the function name 2344 // if the new file contains a function 2345 QString fname = get_function_name (); 2346 if (! fname.isEmpty ()) 2347 fileDialog->selectFile (fname + ".m"); 2348 } 2349 2350 fileDialog->setAcceptMode (QFileDialog::AcceptSave); 2351 fileDialog->setViewMode (QFileDialog::Detail); 2352 fileDialog->setOption (QFileDialog::HideNameFilterDetails, false); 2353 2354 // FIXME: Remove, if for all common KDE versions (bug #54607) is resolved. 2355 resource_manager& rmgr = m_octave_qobj.get_resource_manager (); 2356 gui_settings *settings = rmgr.get_settings (); 2357 if (! settings->value (global_use_native_dialogs).toBool ()) 2358 { 2359 // Qt file dialogs 2360 fileDialog->setOption(QFileDialog::DontUseNativeDialog); 2361 } 2362 else 2363 { 2364 // Native file dialogs: Test for already existing files is done manually 2365 // since native file dialogs might not consider the automatically 2366 // appended default extension when checking if the file already exists 2367 fileDialog->setOption(QFileDialog::DontConfirmOverwrite); 2368 } 2369 2370 connect (fileDialog, SIGNAL (filterSelected (const QString&)), 2371 this, SLOT (handle_save_as_filter_selected (const QString&))); 2372 2373 if (remove_on_success) 2374 { 2375 connect (fileDialog, SIGNAL (fileSelected (const QString&)), 2376 this, SLOT (handle_save_file_as_answer_close (const QString&))); 2377 2378 connect (fileDialog, SIGNAL (rejected ()), 2379 this, SLOT (handle_save_file_as_answer_cancel ())); 2380 } 2381 else 2382 { 2383 connect (fileDialog, SIGNAL (fileSelected (const QString&)), 2384 this, SLOT (handle_save_file_as_answer (const QString&))); 2385 } 2386 2387 show_dialog (fileDialog, ! valid_file_name ()); 2388 } 2389 handle_save_as_filter_selected(const QString & filter)2390 void file_editor_tab::handle_save_as_filter_selected (const QString& filter) 2391 { 2392 // On some systems, the filterSelected signal is emitted without user 2393 // action and with an empty filter string when the file dialog is shown. 2394 // Just return in this case and do not remove the current default suffix. 2395 if (filter.isEmpty ()) 2396 return; 2397 2398 QFileDialog *file_dialog = qobject_cast<QFileDialog *> (sender ()); 2399 2400 QRegExp rx ("\\*\\.([^ ^\\)]*)[ \\)]"); // regexp for suffix in filter 2401 int index = rx.indexIn (filter,0); // get first suffix in filter 2402 2403 if (index > -1) 2404 file_dialog->setDefaultSuffix (rx.cap (1)); // found a suffix, set default 2405 else 2406 file_dialog->setDefaultSuffix (""); // not found, clear default 2407 } 2408 check_valid_identifier(QString file_name)2409 bool file_editor_tab::check_valid_identifier (QString file_name) 2410 { 2411 QFileInfo file = QFileInfo (file_name); 2412 QString base_name = file.baseName (); 2413 2414 if ((file.suffix () == "m") 2415 && (! valid_identifier (base_name.toStdString ()))) 2416 { 2417 int ans = QMessageBox::question (nullptr, tr ("Octave Editor"), 2418 tr ("\"%1\"\n" 2419 "is not a valid identifier.\n\n" 2420 "If you keep this filename, you will not be able to\n" 2421 "call your script using its name as an Octave command.\n\n" 2422 "Do you want to choose another name?").arg (base_name), 2423 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); 2424 2425 if (ans == QMessageBox::Yes) 2426 return true; 2427 } 2428 2429 return false; 2430 } 2431 check_valid_codec()2432 QTextCodec* file_editor_tab::check_valid_codec () 2433 { 2434 QTextCodec *codec = QTextCodec::codecForName (m_encoding.toLatin1 ()); 2435 2436 // "SYSTEM" is used as alias for the locale encoding. 2437 if ((! codec) && m_encoding.startsWith("SYSTEM")) 2438 codec = QTextCodec::codecForLocale (); 2439 2440 if (! codec) 2441 { 2442 QMessageBox::critical (nullptr, 2443 tr ("Octave Editor"), 2444 tr ("The current encoding %1\n" 2445 "can not be applied.\n\n" 2446 "Please select another one!").arg (m_encoding)); 2447 2448 return nullptr; 2449 } 2450 2451 QString editor_text = m_edit_area->text (); 2452 bool can_encode = codec->canEncode (editor_text); 2453 2454 // We cannot rely on QTextCodec::canEncode because it uses the 2455 // ConverterState of convertFromUnicode which isn't updated by some 2456 // implementations. 2457 if (can_encode) 2458 { 2459 QVector<uint> u32_str = editor_text.toUcs4 (); 2460 const uint32_t *src = reinterpret_cast<const uint32_t *> 2461 (u32_str.data ()); 2462 2463 std::size_t length; 2464 char *res_str = 2465 octave_u32_conv_to_encoding_strict (m_encoding.toStdString ().c_str (), 2466 src, u32_str.size (), &length); 2467 if (! res_str) 2468 { 2469 if (errno == EILSEQ) 2470 can_encode = false; 2471 } 2472 else 2473 ::free (static_cast<void *> (res_str)); 2474 } 2475 2476 if (! can_encode) 2477 { 2478 QMessageBox::StandardButton pressed_button 2479 = QMessageBox::critical (nullptr, 2480 tr ("Octave Editor"), 2481 tr ("The current editor contents can not be encoded\n" 2482 "with the selected encoding %1.\n" 2483 "Using it would result in data loss!\n\n" 2484 "Please select another one!").arg (m_encoding), 2485 QMessageBox::Cancel | QMessageBox::Ignore, 2486 QMessageBox::Cancel); 2487 2488 if (pressed_button == QMessageBox::Ignore) 2489 return codec; 2490 else 2491 return nullptr; 2492 } 2493 2494 return codec; 2495 } 2496 handle_save_file_as_answer(const QString & save_file_name)2497 void file_editor_tab::handle_save_file_as_answer (const QString& save_file_name) 2498 { 2499 QString saveFileName = save_file_name; 2500 QFileInfo file (saveFileName); 2501 QFileDialog *file_dialog = qobject_cast<QFileDialog *> (sender ()); 2502 2503 // Test if the file dialog should have added a default file 2504 // suffix, but the selected file still has no suffix (see Qt bug 2505 // https://bugreports.qt.io/browse/QTBUG-59401) 2506 if ((! file_dialog->defaultSuffix ().isEmpty ()) && file.suffix ().isEmpty ()) 2507 { 2508 saveFileName = saveFileName + "." + file_dialog->defaultSuffix (); 2509 } 2510 2511 file.setFile (saveFileName); 2512 2513 // If overwrite confirmation was not done by the file dialog (in case 2514 // of native file dialogs, see above), do it here 2515 if(file_dialog->testOption (QFileDialog::DontConfirmOverwrite) && file.exists ()) 2516 { 2517 int ans = QMessageBox::question (file_dialog, 2518 tr ("Octave Editor"), 2519 tr ("%1\n already exists\n" 2520 "Do you want to overwrite it?").arg (saveFileName), 2521 QMessageBox::Yes | QMessageBox::No); 2522 if (ans != QMessageBox::Yes) 2523 { 2524 // Try again, if edit area is read only, remove on success 2525 save_file_as (m_edit_area->isReadOnly ()); 2526 return; 2527 } 2528 } 2529 2530 if (m_save_as_desired_eol != m_edit_area->eolMode ()) 2531 convert_eol (this,m_save_as_desired_eol); 2532 2533 if (saveFileName == m_file_name) 2534 { 2535 save_file (saveFileName); 2536 } 2537 else 2538 { 2539 // Have editor check for conflict, do not delete tab after save. 2540 if (check_valid_identifier (saveFileName)) 2541 save_file_as (false); 2542 else 2543 emit editor_check_conflict_save (saveFileName, false); 2544 } 2545 } 2546 handle_save_file_as_answer_close(const QString & saveFileName)2547 void file_editor_tab::handle_save_file_as_answer_close (const QString& saveFileName) 2548 { 2549 if (m_save_as_desired_eol != m_edit_area->eolMode ()) 2550 { 2551 m_edit_area->setReadOnly (false); // was set to read-only in save_file_as 2552 convert_eol (this,m_save_as_desired_eol); 2553 m_edit_area->setReadOnly (true); // restore read-only mode 2554 } 2555 2556 // saveFileName == m_file_name can not happen, because we only can get here 2557 // when we close a tab and m_file_name is not a valid filename yet 2558 2559 // Have editor check for conflict, delete tab after save. 2560 if (check_valid_identifier (saveFileName)) 2561 save_file_as (true); 2562 else 2563 emit editor_check_conflict_save (saveFileName, true); 2564 } 2565 handle_save_file_as_answer_cancel(void)2566 void file_editor_tab::handle_save_file_as_answer_cancel (void) 2567 { 2568 // User canceled, allow editing again. 2569 m_edit_area->setReadOnly (false); 2570 } 2571 file_has_changed(const QString &,bool do_close)2572 void file_editor_tab::file_has_changed (const QString&, bool do_close) 2573 { 2574 bool file_exists = QFile::exists (m_file_name); 2575 2576 if (file_exists && ! do_close) 2577 { 2578 // Test if file is really modified or if just the timezone has 2579 // changed. In the latter, just return without doing anything. 2580 QDateTime modified = QFileInfo (m_file_name).lastModified ().toUTC (); 2581 2582 if (modified <= m_last_modified) 2583 return; 2584 2585 m_last_modified = modified; 2586 } 2587 2588 // Prevent popping up multiple message boxes when the file has 2589 // been changed multiple times by temporarily removing from the 2590 // file watcher. 2591 QStringList trackedFiles = m_file_system_watcher.files (); 2592 if (! trackedFiles.isEmpty ()) 2593 m_file_system_watcher.removePath (m_file_name); 2594 2595 if (file_exists && ! do_close) 2596 { 2597 2598 // The file is modified 2599 if (m_always_reload_changed_files) 2600 load_file (m_file_name); 2601 2602 else 2603 { 2604 // give editor and this tab the focus, 2605 // possibly making the editor visible if it is hidden 2606 emit set_focus_editor_signal (this); 2607 m_edit_area->setFocus (); 2608 2609 // Create a WindowModal message that blocks the edit area 2610 // by making m_edit_area parent. 2611 QMessageBox *msgBox 2612 = new QMessageBox (QMessageBox::Warning, 2613 tr ("Octave Editor"), 2614 tr ("It seems that \'%1\' has been modified by another application. Do you want to reload it?"). 2615 arg (m_file_name), 2616 QMessageBox::Yes | QMessageBox::No, this); 2617 2618 connect (msgBox, SIGNAL (finished (int)), 2619 this, SLOT (handle_file_reload_answer (int))); 2620 2621 msgBox->setWindowModality (Qt::WindowModal); 2622 msgBox->setAttribute (Qt::WA_DeleteOnClose); 2623 msgBox->show (); 2624 } 2625 } 2626 else 2627 { 2628 // If desired and if file is not modified, 2629 // close the file without any user interaction 2630 if (do_close && ! m_edit_area->isModified ()) 2631 { 2632 handle_file_resave_answer (QMessageBox::Cancel); 2633 return; 2634 } 2635 2636 // give editor and this tab the focus, 2637 // possibly making the editor visible if it is hidden 2638 emit set_focus_editor_signal (this); 2639 m_edit_area->setFocus (); 2640 2641 QString modified = ""; 2642 if (m_edit_area->isModified ()) 2643 modified = tr ("\n\nWarning: The contents in the editor is modified!"); 2644 2645 // Create a WindowModal message. The file editor tab can't be made 2646 // parent because it may be deleted depending upon the response. 2647 // Instead, change the m_edit_area to read only. 2648 QMessageBox *msgBox 2649 = new QMessageBox (QMessageBox::Warning, tr ("Octave Editor"), 2650 tr ("It seems that the file\n" 2651 "%1\n" 2652 "has been deleted or renamed. Do you want to save it now?%2"). 2653 arg (m_file_name).arg (modified), 2654 QMessageBox::Save | QMessageBox::Close, nullptr); 2655 2656 m_edit_area->setReadOnly (true); 2657 2658 connect (msgBox, SIGNAL (finished (int)), 2659 this, SLOT (handle_file_resave_answer (int))); 2660 2661 msgBox->setWindowModality (Qt::WindowModal); 2662 msgBox->setAttribute (Qt::WA_DeleteOnClose); 2663 msgBox->show (); 2664 } 2665 } 2666 notice_settings(const gui_settings * settings,bool init)2667 void file_editor_tab::notice_settings (const gui_settings *settings, bool init) 2668 { 2669 if (! settings) 2670 return; 2671 2672 if (! init) 2673 update_lexer_settings (); 2674 2675 // code folding 2676 if (settings->value (ed_code_folding).toBool ()) 2677 { 2678 m_edit_area->setMarginType (3, QsciScintilla::SymbolMargin); 2679 m_edit_area->setFolding (QsciScintilla::BoxedTreeFoldStyle, 3); 2680 } 2681 else 2682 { 2683 m_edit_area->setFolding (QsciScintilla::NoFoldStyle, 3); 2684 } 2685 2686 // status bar 2687 if (settings->value (ed_show_edit_status_bar).toBool ()) 2688 m_status_bar->show (); 2689 else 2690 m_status_bar->hide (); 2691 2692 //highlight current line color 2693 QColor setting_color = settings->value (ed_highlight_current_line_color).value<QColor> (); 2694 m_edit_area->setCaretLineBackgroundColor (setting_color); 2695 m_edit_area->setCaretLineVisible 2696 (settings->value (ed_highlight_current_line).toBool ()); 2697 2698 bool match_keywords = settings->value 2699 (ed_code_completion_keywords).toBool (); 2700 bool match_document = settings->value 2701 (ed_code_completion_document).toBool (); 2702 2703 QsciScintilla::AutoCompletionSource source = QsciScintilla::AcsNone; 2704 if (match_keywords) 2705 if (match_document) 2706 source = QsciScintilla::AcsAll; 2707 else 2708 source = QsciScintilla::AcsAPIs; 2709 else if (match_document) 2710 source = QsciScintilla::AcsDocument; 2711 m_edit_area->setAutoCompletionSource (source); 2712 2713 m_edit_area->setAutoCompletionReplaceWord 2714 (settings->value (ed_code_completion_replace).toBool ()); 2715 m_edit_area->setAutoCompletionCaseSensitivity 2716 (settings->value (ed_code_completion_case).toBool ()); 2717 2718 if (settings->value (ed_code_completion).toBool ()) 2719 m_edit_area->setAutoCompletionThreshold 2720 (settings->value (ed_code_completion_threshold).toInt ()); 2721 else 2722 m_edit_area->setAutoCompletionThreshold (-1); 2723 2724 if (settings->value (ed_show_white_space).toBool ()) 2725 if (settings->value (ed_show_white_space_indent).toBool ()) 2726 m_edit_area->setWhitespaceVisibility (QsciScintilla::WsVisibleAfterIndent); 2727 else 2728 m_edit_area->setWhitespaceVisibility (QsciScintilla::WsVisible); 2729 else 2730 m_edit_area->setWhitespaceVisibility (QsciScintilla::WsInvisible); 2731 2732 m_edit_area->setEolVisibility (settings->value (ed_show_eol_chars).toBool ()); 2733 2734 m_save_as_desired_eol = static_cast<QsciScintilla::EolMode> 2735 (settings->value (ed_default_eol_mode).toInt ()); 2736 2737 if (settings->value (ed_show_line_numbers).toBool ()) 2738 { 2739 m_edit_area->setMarginLineNumbers (2, true); 2740 auto_margin_width (); 2741 connect (m_edit_area, SIGNAL (linesChanged ()), 2742 this, SLOT (auto_margin_width ())); 2743 } 2744 else 2745 { 2746 m_edit_area->setMarginLineNumbers (2, false); 2747 disconnect (m_edit_area, SIGNAL (linesChanged ()), nullptr, nullptr); 2748 } 2749 2750 m_smart_indent = settings->value (ed_auto_indent).toBool (); 2751 m_edit_area->setAutoIndent (m_smart_indent); 2752 m_edit_area->setTabIndents 2753 (settings->value (ed_tab_indents_line).toBool ()); 2754 m_edit_area->setBackspaceUnindents 2755 (settings->value (ed_backspace_unindents_line).toBool ()); 2756 m_edit_area->setIndentationGuides 2757 (settings->value (ed_show_indent_guides).toBool ()); 2758 m_edit_area->setIndentationsUseTabs 2759 (settings->value (ed_indent_uses_tabs).toBool ()); 2760 m_edit_area->setIndentationWidth 2761 (settings->value (ed_indent_width).toInt ()); 2762 2763 m_edit_area->setTabWidth 2764 (settings->value (ed_tab_width).toInt ()); 2765 2766 m_ind_char_width = 1; 2767 if (m_edit_area->indentationsUseTabs ()) 2768 m_ind_char_width = m_edit_area->tabWidth (); 2769 2770 m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETHSCROLLBAR, 2771 settings->value (ed_show_hscroll_bar).toBool ()); 2772 m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETSCROLLWIDTH,-1); 2773 m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETSCROLLWIDTHTRACKING,true); 2774 2775 m_long_title = settings->value (ed_long_window_title).toBool (); 2776 update_window_title (m_edit_area->isModified ()); 2777 2778 m_auto_endif = settings->value (ed_auto_endif).toInt (); 2779 2780 // long line marker 2781 int line_length = settings->value (ed_long_line_column).toInt (); 2782 m_edit_area->setEdgeColumn (line_length); 2783 2784 if (settings->value (ed_long_line_marker).toBool ()) 2785 { 2786 if (settings->value (ed_long_line_marker_line).toBool ()) 2787 m_edit_area->setEdgeMode (QsciScintilla::EdgeLine); 2788 else 2789 { 2790 if (settings->value (ed_long_line_marker_background) 2791 .toBool ()) 2792 m_edit_area->setEdgeMode (QsciScintilla::EdgeBackground); 2793 else 2794 m_edit_area->setEdgeMode (QsciScintilla::EdgeLine); 2795 } 2796 } 2797 else 2798 m_edit_area->setEdgeMode (QsciScintilla::EdgeNone); 2799 2800 // line wrapping and breaking 2801 m_edit_area->setWrapVisualFlags (QsciScintilla::WrapFlagByBorder); 2802 m_edit_area->setWrapIndentMode (QsciScintilla::WrapIndentSame); 2803 2804 if (settings->value (ed_wrap_lines).toBool ()) 2805 m_edit_area->setWrapMode (QsciScintilla::WrapWord); 2806 else 2807 m_edit_area->setWrapMode (QsciScintilla::WrapNone); 2808 2809 if (settings->value (ed_break_lines).toBool ()) 2810 m_line_break = line_length; 2811 else 2812 m_line_break = 0; 2813 2814 m_line_break_comments = 2815 settings->value (ed_break_lines_comments).toBool (); 2816 2817 // highlight all occurrences of a word selected by a double click 2818 m_highlight_all_occurrences = 2819 settings->value (ed_highlight_all_occurrences).toBool (); 2820 2821 // reload changed files 2822 m_always_reload_changed_files = 2823 settings->value (ed_always_reload_changed_files).toBool (); 2824 2825 // Set cursor blinking depending on the settings. 2826 // QScintilla ignores the application global settings, so some special 2827 // handling is required 2828 bool cursor_blinking; 2829 2830 if (settings->contains (global_cursor_blinking.key)) 2831 cursor_blinking = settings->value (global_cursor_blinking).toBool (); 2832 else 2833 cursor_blinking = settings->value (cs_cursor_blinking).toBool (); 2834 2835 if (cursor_blinking) 2836 m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETCARETPERIOD,500); 2837 else 2838 m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETCARETPERIOD,0); 2839 2840 } 2841 2842 auto_margin_width(void)2843 void file_editor_tab::auto_margin_width (void) 2844 { 2845 m_edit_area->setMarginWidth (2, "1" + QString::number (m_edit_area->lines ())); 2846 } 2847 2848 // the following close request was changed from a signal slot into a 2849 // normal function because we need the return value from close whether 2850 // the tab really was closed (for canceling exiting octave). 2851 // When emitting a signal, only the return value from the last slot 2852 // goes back to the sender conditional_close(void)2853 bool file_editor_tab::conditional_close (void) 2854 { 2855 return close (); 2856 } 2857 change_editor_state(const QWidget * ID)2858 void file_editor_tab::change_editor_state (const QWidget *ID) 2859 { 2860 if (ID != this) 2861 return; 2862 2863 emit editor_state_changed (m_copy_available, m_is_octave_file); 2864 } 2865 handle_file_reload_answer(int decision)2866 void file_editor_tab::handle_file_reload_answer (int decision) 2867 { 2868 if (decision == QMessageBox::Yes) 2869 { 2870 // reload: file is readded to the file watcher in set_file_name () 2871 load_file (m_file_name); 2872 } 2873 else 2874 { 2875 // do not reload: readd to the file watcher 2876 m_file_system_watcher.addPath (m_file_name); 2877 } 2878 } 2879 handle_file_resave_answer(int decision)2880 void file_editor_tab::handle_file_resave_answer (int decision) 2881 { 2882 // check decision of user in dialog 2883 if (decision == QMessageBox::Save) 2884 { 2885 save_file (m_file_name); // readds file to watcher in set_file_name () 2886 m_edit_area->setReadOnly (false); // delete read only flag 2887 } 2888 else 2889 { 2890 // Definitely close the file. 2891 // Set modified to false to prevent the dialog box when the close event 2892 // is posted. If the user cancels the close in this dialog the tab is 2893 // left open with a non-existing file. 2894 m_edit_area->setModified (false); 2895 close (); 2896 } 2897 } 2898 insert_debugger_pointer(const QWidget * ID,int line)2899 void file_editor_tab::insert_debugger_pointer (const QWidget *ID, int line) 2900 { 2901 if (ID != this || ID == nullptr) 2902 return; 2903 2904 emit remove_all_positions (); // debugger_position, unsure_debugger_position 2905 2906 if (line > 0) 2907 { 2908 marker *dp; 2909 2910 if (m_edit_area->isModified ()) 2911 { 2912 // The best that can be done if the editor contents have been 2913 // modified is to see if there is a match with the original 2914 // line number of any existing breakpoints. We can put a normal 2915 // debugger pointer at that breakpoint position. Otherwise, it 2916 // isn't certain whether the original line number and current line 2917 // number match. 2918 int editor_linenr = -1; 2919 marker *dummy; 2920 emit find_translated_line_number (line, editor_linenr, dummy); 2921 if (editor_linenr != -1) 2922 { 2923 // Match with an existing breakpoint. 2924 dp = new marker (m_edit_area, line, 2925 marker::debugger_position, editor_linenr); 2926 } 2927 else 2928 { 2929 int original_linenr = -1; 2930 editor_linenr = -1; 2931 emit find_linenr_just_before (line, original_linenr, editor_linenr); 2932 if (original_linenr >= 0) 2933 { 2934 // Make a guess by using an offset from the breakpoint. 2935 int linenr_guess = editor_linenr + line - original_linenr; 2936 dp = new marker (m_edit_area, line, 2937 marker::unsure_debugger_position, 2938 linenr_guess); 2939 } 2940 else 2941 { 2942 // Can't make a very good guess, so just use the debugger 2943 // line number. 2944 dp = new marker (m_edit_area, line, 2945 marker::unsure_debugger_position); 2946 } 2947 } 2948 } 2949 else 2950 { 2951 dp = new marker (m_edit_area, line, marker::debugger_position); 2952 2953 // In case of a not modified file we might have to remove 2954 // a breakpoint here if we have stepped into the file 2955 if (line == m_breakpoint_info.remove_line) 2956 { 2957 m_breakpoint_info.remove_line = -1; 2958 if (line != m_breakpoint_info.do_not_remove_line) 2959 handle_request_remove_breakpoint (line); 2960 } 2961 } 2962 2963 connect (this, SIGNAL (remove_position_via_debugger_linenr (int)), 2964 dp, SLOT (handle_remove_via_original_linenr (int))); 2965 connect (this, SIGNAL (remove_all_positions (void)), 2966 dp, SLOT (handle_remove (void))); 2967 2968 center_current_line (false); 2969 } 2970 } 2971 delete_debugger_pointer(const QWidget * ID,int line)2972 void file_editor_tab::delete_debugger_pointer (const QWidget *ID, int line) 2973 { 2974 if (ID != this || ID == nullptr) 2975 return; 2976 2977 if (line > 0) 2978 emit remove_position_via_debugger_linenr (line); 2979 } 2980 do_breakpoint_marker(bool insert,const QWidget * ID,int line,const QString & cond)2981 void file_editor_tab::do_breakpoint_marker (bool insert, 2982 const QWidget *ID, int line, 2983 const QString& cond) 2984 { 2985 if (ID != this || ID == nullptr) 2986 return; 2987 2988 if (line > 0) 2989 { 2990 if (insert) 2991 { 2992 int editor_linenr = -1; 2993 marker *bp = nullptr; 2994 2995 // If comes back indicating a non-zero breakpoint marker, 2996 // reuse it if possible 2997 emit find_translated_line_number (line, editor_linenr, bp); 2998 if (bp != nullptr) 2999 { 3000 if ((cond == "") != (bp->get_cond () == "")) 3001 { 3002 // can only reuse conditional bp as conditional 3003 emit remove_breakpoint_via_debugger_linenr (line); 3004 bp = nullptr; 3005 } 3006 else 3007 bp->set_cond (cond); 3008 } 3009 3010 if (bp == nullptr) 3011 { 3012 bp = new marker (m_edit_area, line, 3013 cond == "" ? marker::breakpoint 3014 : marker::cond_break, cond); 3015 3016 connect (this, SIGNAL (remove_breakpoint_via_debugger_linenr 3017 (int)), 3018 bp, SLOT (handle_remove_via_original_linenr (int))); 3019 connect (this, SIGNAL (request_remove_breakpoint_via_editor_linenr 3020 (int)), 3021 bp, SLOT (handle_request_remove_via_editor_linenr 3022 (int))); 3023 connect (this, SIGNAL (remove_all_breakpoints (void)), 3024 bp, SLOT (handle_remove (void))); 3025 connect (this, SIGNAL (find_translated_line_number (int, int&, 3026 marker*&)), 3027 bp, SLOT (handle_find_translation (int, int&, 3028 marker*&))); 3029 connect (this, SIGNAL (find_linenr_just_before (int, int&, int&)), 3030 bp, SLOT (handle_find_just_before (int, int&, int&))); 3031 connect (this, SIGNAL (report_marker_linenr (QIntList&, 3032 QStringList&)), 3033 bp, SLOT (handle_report_editor_linenr (QIntList&, 3034 QStringList&))); 3035 connect (bp, SIGNAL (request_remove (int)), 3036 this, SLOT (handle_request_remove_breakpoint (int))); 3037 } 3038 } 3039 else 3040 emit remove_breakpoint_via_debugger_linenr (line); 3041 } 3042 } 3043 center_current_line(bool always)3044 void file_editor_tab::center_current_line (bool always) 3045 { 3046 long int visible_lines 3047 = m_edit_area->SendScintilla (QsciScintillaBase::SCI_LINESONSCREEN); 3048 3049 if (visible_lines > 2) 3050 { 3051 int line, index; 3052 m_edit_area->getCursorPosition (&line, &index); 3053 // compensate for "folding": 3054 // step 1: expand the current line, if it was folded 3055 m_edit_area->SendScintilla (2232, line); // SCI_ENSUREVISIBLE 3056 3057 // step 2: map file line num to "visible" one // SCI_VISIBLEFROMDOCLINE 3058 int vis_line = m_edit_area->SendScintilla (2220, line); 3059 3060 int first_line = m_edit_area->firstVisibleLine (); 3061 3062 if (always || vis_line == first_line 3063 || vis_line > first_line + visible_lines - 2) 3064 { 3065 first_line += (vis_line - first_line - (visible_lines - 1) / 2); 3066 m_edit_area->SendScintilla (2613, first_line); // SCI_SETFIRSTVISIBLELINE 3067 } 3068 } 3069 } 3070 handle_lines_changed(void)3071 void file_editor_tab::handle_lines_changed (void) 3072 { 3073 // the related signal is emitted before cursor-move-signal! 3074 m_lines_changed = true; 3075 } 3076 handle_cursor_moved(int line,int col)3077 void file_editor_tab::handle_cursor_moved (int line, int col) 3078 { 3079 // Cursor has moved, first check wether an autocompletion list 3080 // is active or if it was closed. Scintilla provides signals for 3081 // completed or cancelled lists, but not for list that where hidden 3082 // due to a new character not matching anymore with the list entries 3083 if (m_edit_area->SendScintilla (QsciScintillaBase::SCI_AUTOCACTIVE)) 3084 m_autoc_active = true; 3085 else if (m_autoc_active) 3086 { 3087 m_autoc_active = false; 3088 emit autoc_closed (); // Tell editor about closed list 3089 } 3090 3091 // Lines changed? Take care of indentation 3092 if (m_lines_changed) // cursor moved and lines have changed 3093 { 3094 m_lines_changed = false; 3095 if (m_is_octave_file && line == m_line+1 && col < m_col) 3096 { 3097 // Obviously, we have a newline here 3098 if (m_smart_indent || m_auto_endif) 3099 m_edit_area->smart_indent (m_smart_indent, m_auto_endif, 3100 m_line, m_ind_char_width); 3101 } 3102 } 3103 3104 // Update line and column indicator in the status bar 3105 m_line = line; 3106 m_col = col; 3107 m_row_indicator->setNum (line+1); 3108 m_col_indicator->setNum (col+1); 3109 } 3110 3111 // Slot that is entered each time a new character was typed. 3112 // It is used for handling line breaking if this is desired. 3113 // The related signal is emitted after the signal for a moved cursor 3114 // such that m_col and m_line can not be used for current position. handle_char_added(int)3115 void file_editor_tab::handle_char_added (int) 3116 { 3117 if (m_line_break) 3118 { 3119 // If line breaking is desired, get the current line and column. 3120 // For taking the tab width into consideration, use own function 3121 int line, col, pos; 3122 m_edit_area->get_current_position (&pos, &line, &col); 3123 3124 // immediately return if line has not reached the max. line length 3125 if (col <= m_line_break) 3126 return; 3127 3128 // If line breaking is only desired in comments, 3129 // return if not in a comment 3130 int style_comment = octave_qscintilla::ST_NONE; 3131 if (m_line_break_comments) 3132 { 3133 // line breaking only in comments, check for comment style 3134 style_comment = m_edit_area->is_style_comment (); 3135 if (! style_comment) 3136 return; // no comment, return 3137 } 3138 3139 // Here we go for breaking the current line by inserting a newline. 3140 // For determining the position of a specific column, we have to get 3141 // the column from the QScintilla function without taking tab lengths 3142 // into account, since the calculation from line/col to position 3143 // ignores this, too. 3144 m_edit_area->getCursorPosition (&line, &col); 3145 int c = 0; 3146 int col_space = col; 3147 int indentation = m_edit_area->indentation (line); 3148 3149 // Search the first occurrence of space or tab backwards starting from 3150 // the current column (col_space). 3151 while (c != ' ' && c != '\t' && col_space > indentation) 3152 { 3153 pos = m_edit_area->positionFromLineIndex (line, col_space--); 3154 c = m_edit_area->SendScintilla (QsciScintillaBase::SCI_GETCHARAT, pos); 3155 } 3156 3157 // If a space or tab was found, break at this char, 3158 // otherwise break at cursor position 3159 int col_newline = col - 1; 3160 if (c == ' ' || c == '\t') 3161 col_newline = col_space + 1; 3162 3163 // Insert a newline char for breaking the line possibly followed 3164 // by a line comment string 3165 QString newline = QString ("\n"); 3166 style_comment = m_edit_area->is_style_comment (); 3167 if (style_comment == octave_qscintilla::ST_LINE_COMMENT) 3168 newline = newline + m_edit_area->comment_string ().at (0); 3169 m_edit_area->insertAt (newline, line, col_newline); 3170 3171 // Automatically indent the new line to the indentation of previous line 3172 // and set the cursor position to the end of the indentation. 3173 m_edit_area->setIndentation (line + 1, indentation); 3174 m_edit_area->SendScintilla (QsciScintillaBase::SCI_LINEEND); 3175 } 3176 } 3177 3178 // Slot handling a double click into the text area handle_double_click(int,int,int modifier)3179 void file_editor_tab::handle_double_click (int, int, int modifier) 3180 { 3181 if (! modifier) 3182 { 3183 // double clicks without modifier 3184 // clear any existing indicators of this type 3185 m_edit_area->clear_selection_markers (); 3186 3187 if (m_highlight_all_occurrences) 3188 { 3189 // Clear any previous selection. 3190 m_edit_area->set_word_selection (); 3191 3192 // highlighting of all occurrences of the clicked word is enabled 3193 3194 // get the resulting cursor position 3195 // (required if click was beyond a line ending) 3196 int line, col; 3197 m_edit_area->getCursorPosition (&line, &col); 3198 3199 // get the word at the cursor (if any) 3200 QString word = m_edit_area->wordAtLineIndex (line, col); 3201 word = word.trimmed (); 3202 3203 if (! word.isEmpty ()) 3204 { 3205 // word is not empty, so find all occurrences of the word 3206 3207 // remember first visible line and x-offset for restoring the view afterwards 3208 int first_line = m_edit_area->firstVisibleLine (); 3209 int x_offset = m_edit_area->SendScintilla(QsciScintillaBase::SCI_GETXOFFSET); 3210 3211 // search for first occurrence of the detected word 3212 bool find_result_available 3213 = m_edit_area->findFirst (word, 3214 false, // no regexp 3215 true, // case sensitive 3216 true, // whole words only 3217 false, // do not wrap 3218 true, // forward 3219 0,0, // from the beginning 3220 false 3221 #if defined (HAVE_QSCI_VERSION_2_6_0) 3222 , true 3223 #endif 3224 ); 3225 3226 // loop over all occurrences and set the related indicator 3227 int oline, ocol; 3228 int wlen = word.length (); 3229 3230 while (find_result_available) 3231 { 3232 // get cursor position after having found an occurrence 3233 m_edit_area->getCursorPosition (&oline, &ocol); 3234 // mark the selection 3235 m_edit_area->show_selection_markers (oline, ocol-wlen, oline, ocol); 3236 3237 // find next occurrence 3238 find_result_available = m_edit_area->findNext (); 3239 } 3240 3241 // restore the visible area of the file, the cursor position, 3242 // and the selection 3243 m_edit_area->setFirstVisibleLine (first_line); 3244 m_edit_area->SendScintilla(QsciScintillaBase::SCI_SETXOFFSET, x_offset); 3245 m_edit_area->setCursorPosition (line, col); 3246 m_edit_area->setSelection (line, col - wlen, line, col); 3247 m_edit_area->set_word_selection (word); 3248 } 3249 } 3250 } 3251 } 3252 get_function_name(void)3253 QString file_editor_tab::get_function_name (void) 3254 { 3255 QRegExp rxfun1 ("^[\t ]*function[^=]+=([^\\(]+)\\([^\\)]*\\)[\t ]*$"); 3256 QRegExp rxfun2 ("^[\t ]*function[\t ]+([^\\(]+)\\([^\\)]*\\)[\t ]*$"); 3257 QRegExp rxfun3 ("^[\t ]*function[^=]+=[\t ]*([^\\s]+)[\t ]*$"); 3258 QRegExp rxfun4 ("^[\t ]*function[\t ]+([^\\s]+)[\t ]*$"); 3259 QRegExp rxfun5 ("^[\t ]*classdef[\t ]+([^\\s]+)[\t ]*$"); 3260 3261 QStringList lines = m_edit_area->text ().split ("\n"); 3262 3263 for (int i = 0; i < lines.count (); i++) 3264 { 3265 if (rxfun1.indexIn (lines.at (i)) != -1) 3266 return rxfun1.cap (1).remove (QRegExp ("[ \t]*")); 3267 else if (rxfun2.indexIn (lines.at (i)) != -1) 3268 return rxfun2.cap (1).remove (QRegExp ("[ \t]*")); 3269 else if (rxfun3.indexIn (lines.at (i)) != -1) 3270 return rxfun3.cap (1).remove (QRegExp ("[ \t]*")); 3271 else if (rxfun4.indexIn (lines.at (i)) != -1) 3272 return rxfun4.cap (1).remove (QRegExp ("[ \t]*")); 3273 else if (rxfun5.indexIn (lines.at (i)) != -1) 3274 return rxfun5.cap (1).remove (QRegExp ("[ \t]*")); 3275 } 3276 3277 return QString (); 3278 } 3279 } 3280 3281 #endif 3282