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