GNU Octave  6.2.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
file-editor-tab.cc
Go to the documentation of this file.
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.
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 
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 
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,
186  m_edit_area->setMarkerBackgroundColor (QColor (255,255,0),
188  m_edit_area->markerDefine (QsciScintilla::RightArrow,
190  m_edit_area->setMarkerBackgroundColor (QColor (192,192,192),
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 
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
275  }
276 
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 
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 
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 
322  void file_editor_tab::set_current_directory (const QString& dir)
323  {
324  m_ced = dir;
325  }
326 
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
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  {
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 
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 
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
510 
511  // add the new file to the most-recently-used list
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
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.
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.
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 
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
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.
690  {
691  QsciLexer *lexer = m_edit_area->lexer ();
692 
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 
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
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);
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
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
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 
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 
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
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 
955  void file_editor_tab::context_help (const QWidget *ID, bool doc)
956  {
957  if (ID != this)
958  return;
959 
961  }
962 
964  {
965  if (ID != this)
966  return;
967 
969  }
970 
972  {
973  if (ID != this)
974  return;
975 
977  }
978 
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 
989  {
990  if (ID != this)
991  return;
992 
993  save_file_as ();
994  }
995 
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 
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  if (! valid_file_name ())
1020  return; // still invalid filename: "save as" was cancelled
1021  }
1022 
1023  if (step_into)
1024  {
1025  // Get current first breakpoint and set breakpoint waiting for
1026  // the returned line number. Store whether to remove this breakpoint
1027  // afterwards.
1028  int first_bp_line
1029  = m_edit_area->markerFindNext (0, (1 << marker::breakpoint)) + 1;
1030 
1031  // Set flag for storing the line number of the breakpoint
1033  m_breakpoint_info.do_not_remove_line = first_bp_line;
1034 
1035  // Add breakpoint, storing its line number
1036  handle_request_add_breakpoint (1, QString ());
1037  }
1038 
1039  QFileInfo info (m_file_name);
1040  emit run_file_signal (info);
1041  }
1042 
1044  {
1045  if (ID != this)
1046  return;
1047 
1049  }
1050 
1052  {
1053  if (ID != this)
1054  return;
1055 
1056  int line, cur;
1057  m_edit_area->getCursorPosition (&line, &cur);
1058 
1059  if (m_edit_area->markersAtLine (line) & (1 << marker::bookmark))
1060  m_edit_area->markerDelete (line, marker::bookmark);
1061  else
1062  m_edit_area->markerAdd (line, marker::bookmark);
1063  }
1064 
1065  // Move the text cursor to the closest bookmark
1066  // after the current line.
1068  {
1069  if (ID != this)
1070  return;
1071 
1072  int line, cur;
1073  m_edit_area->getCursorPosition (&line, &cur);
1074 
1075  line++; // Find bookmark strictly after the current line.
1076 
1077  int nextline = m_edit_area->markerFindNext (line, (1 << marker::bookmark));
1078 
1079  // Wrap.
1080  if (nextline == -1)
1081  nextline = m_edit_area->markerFindNext (1, (1 << marker::bookmark));
1082 
1083  m_edit_area->setCursorPosition (nextline, 0);
1084  }
1085 
1086  // Move the text cursor to the closest bookmark
1087  // before the current line.
1089  {
1090  if (ID != this)
1091  return;
1092 
1093  int line, cur;
1094  m_edit_area->getCursorPosition (&line, &cur);
1095 
1096  line--; // Find bookmark strictly before the current line.
1097 
1098  int prevline = m_edit_area->markerFindPrevious (line, (1 << marker::bookmark));
1099 
1100  // Wrap. Should use the last line of the file, not 1<<15
1101  if (prevline == -1)
1102  prevline = m_edit_area->markerFindPrevious (m_edit_area->lines (),
1103  (1 << marker::bookmark));
1104 
1105  m_edit_area->setCursorPosition (prevline, 0);
1106  }
1107 
1109  {
1110  if (ID != this)
1111  return;
1112 
1113  m_edit_area->markerDeleteAll (marker::bookmark);
1114  }
1115 
1116  file_editor_tab::bp_info::bp_info (const QString& fname, int l,
1117  const QString& cond)
1118  : line (l), file (fname.toStdString ()), condition (cond.toStdString ())
1119  {
1120  QFileInfo file_info (fname);
1121 
1122  QString q_dir = file_info.absolutePath ();
1123  QString q_function_name = file_info.fileName ();
1124 
1125  // We have to cut off the suffix, because octave appends it.
1126  q_function_name.chop (file_info.suffix ().length () + 1);
1127 
1128  dir = q_dir.toStdString ();
1129  function_name = q_function_name.toStdString ();
1130 
1131  // Is the last component of DIR @foo? If so, strip it and prepend it
1132  // to the name of the function.
1133 
1134  size_t pos = dir.rfind (sys::file_ops::dir_sep_chars ());
1135 
1136  if (pos != std::string::npos && pos < dir.length () - 1)
1137  {
1138  if (dir[pos+1] == '@')
1139  {
1141 
1142  dir = dir.substr (0, pos);
1143  }
1144  }
1145  }
1146 
1148  const QString& condition)
1149  {
1150  bp_info info (m_file_name, line, condition);
1151 
1152  add_breakpoint_event (info);
1153  }
1154 
1156  {
1157  bp_info info (m_file_name, line);
1158 
1159  emit interpreter_event
1160  ([info] (interpreter& interp)
1161  {
1162  // INTERPRETER THREAD
1163 
1164  load_path& lp = interp.get_load_path ();
1165 
1166  bp_table::intmap line_info;
1167  line_info[0] = info.line;
1168 
1169  if (lp.contains_file_in_dir (info.file, info.dir))
1170  {
1171  tree_evaluator& tw = interp.get_evaluator ();
1172 
1173  bp_table& bptab = tw.get_bp_table ();
1174 
1175  bptab.remove_breakpoint (info.function_name, line_info);
1176  }
1177  });
1178  }
1179 
1181  {
1182  if (ID != this)
1183  return;
1184 
1185  int editor_linenr, cur;
1186  m_edit_area->getCursorPosition (&editor_linenr, &cur);
1187 
1188  if (m_edit_area->markersAtLine (editor_linenr) & (1 << marker::breakpoint))
1190  else
1191  {
1192  if (unchanged_or_saved ())
1193  handle_request_add_breakpoint (editor_linenr + 1, "");
1194  }
1195  }
1196 
1197  // Move the text cursor to the closest breakpoint (conditional or unconditional)
1198  // after the current line.
1200  {
1201  if (ID != this)
1202  return;
1203 
1204  int line, cur;
1205  m_edit_area->getCursorPosition (&line, &cur);
1206 
1207  line++; // Find breakpoint strictly after the current line.
1208 
1209  int nextline = m_edit_area->markerFindNext (line, (1 << marker::breakpoint));
1210  int nextcond = m_edit_area->markerFindNext (line, (1 << marker::cond_break));
1211 
1212  // Check if the next conditional breakpoint is before next unconditional one.
1213  if (nextcond != -1 && (nextcond < nextline || nextline == -1))
1214  nextline = nextcond;
1215 
1216  m_edit_area->setCursorPosition (nextline, 0);
1217  }
1218 
1219  // Move the text cursor to the closest breakpoint (conditional or unconditional)
1220  // before the current line.
1222  {
1223  if (ID != this)
1224  return;
1225 
1226  int line, cur, prevline, prevcond;
1227  m_edit_area->getCursorPosition (&line, &cur);
1228 
1229  line--; // Find breakpoint strictly before the current line.
1230 
1231  prevline = m_edit_area->markerFindPrevious (line, (1 << marker::breakpoint));
1232  prevcond = m_edit_area->markerFindPrevious (line, (1 << marker::cond_break));
1233 
1234  // Check if the prev conditional breakpoint is closer than the unconditional.
1235  if (prevcond != -1 && prevcond > prevline)
1236  prevline = prevcond;
1237 
1238  m_edit_area->setCursorPosition (prevline, 0);
1239  }
1240 
1242  {
1243  if (ID != this)
1244  return;
1245 
1246  bp_info info (m_file_name);
1247 
1248  emit interpreter_event
1249  ([info] (interpreter& interp)
1250  {
1251  // INTERPRETER THREAD
1252 
1253  load_path& lp = interp.get_load_path ();
1254 
1255  if (lp.contains_file_in_dir (info.file, info.dir))
1256  {
1257  tree_evaluator& tw = interp.get_evaluator ();
1258 
1259  bp_table& bptab = tw.get_bp_table ();
1260 
1261  bptab.remove_all_breakpoints_in_file (info.function_name, true);
1262  }
1263  });
1264  }
1265 
1267  unsigned int sci_msg)
1268  {
1269  if (ID != this)
1270  return;
1271 
1272  m_edit_area->SendScintilla (sci_msg);
1273  }
1274 
1276  bool input_str)
1277  {
1278  if (ID != this)
1279  return;
1280 
1281  do_comment_selected_text (true, input_str);
1282  }
1283 
1285  {
1286  if (ID != this)
1287  return;
1288 
1289  do_comment_selected_text (false);
1290  }
1291 
1293  {
1294  if (ID != this)
1295  return;
1296 
1297  do_indent_selected_text (true);
1298  }
1299 
1301  {
1302  if (ID != this)
1303  return;
1304 
1305  do_indent_selected_text (false);
1306  }
1307 
1309  {
1310  if (ID != this)
1311  return;
1312 
1314  }
1315 
1317  QsciScintilla::EolMode eol_mode)
1318  {
1319  if (ID != this)
1320  return;
1321 
1322  m_edit_area->convertEols (eol_mode);
1323  m_edit_area->setEolMode (eol_mode);
1325  }
1326 
1328  {
1329  if (ID != this)
1330  return;
1331 
1332  m_edit_area->zoomIn (1);
1333  auto_margin_width ();
1334  }
1335 
1337  {
1338  if (ID != this)
1339  return;
1340 
1341  m_edit_area->zoomOut (1);
1342  auto_margin_width ();
1343  }
1344 
1346  {
1347  if (ID != this)
1348  return;
1349 
1350  m_edit_area->zoomTo (0);
1351  auto_margin_width ();
1352  }
1353 
1355  {
1356  emit interpreter_event
1357  ([this, info] (interpreter& interp)
1358  {
1359  // INTERPRETER THREAD
1360 
1361  // FIXME: note duplication with the code in
1362  // handle_context_menu_break_condition.
1363 
1364  load_path& lp = interp.get_load_path ();
1365 
1366  bp_table::intmap line_info;
1367  line_info[0] = info.line;
1368 
1369  if (lp.contains_file_in_dir (info.file, info.dir))
1370  {
1371  tree_evaluator& tw = interp.get_evaluator ();
1372 
1373  bp_table& bptab = tw.get_bp_table ();
1374 
1375  bp_table::intmap bpmap
1376  = bptab.add_breakpoint (info.function_name, "", line_info,
1377  info.condition);
1378 
1379  if (! bpmap.empty ())
1380  {
1381  bp_table::intmap::iterator bp_it = bpmap.begin ();
1382 
1383  int remove_line = bp_it->second;
1384 
1385  emit maybe_remove_next (remove_line);
1386  }
1387  }
1388  });
1389  }
1390 
1392  {
1393  // Store some info breakpoint
1395  {
1396  m_breakpoint_info.remove_line = remove_line;
1398  }
1399  }
1400 
1402  {
1403  if (ID != this)
1404  return;
1405 
1406  if (m_bp_restore_count > 0)
1407  {
1408  // This goto-line request is invoked by restoring a breakpoint during
1409  // saving the file, thus, do not go to the related line
1411  return;
1412  }
1413 
1414  if (line <= 0) // ask for desired line
1415  {
1416  bool ok = false;
1417  int index;
1418  m_edit_area->getCursorPosition (&line, &index);
1419  line = QInputDialog::getInt (m_edit_area, tr ("Goto line"),
1420  tr ("Line number"), line+1, 1,
1421  m_edit_area->lines (), 1, &ok);
1422  if (ok)
1423  m_edit_area->setCursorPosition (line-1, 0);
1424  }
1425  else // go to given line without dialog
1426  m_edit_area->setCursorPosition (line-1, 0);
1427 
1428  center_current_line (false); // only center line if at top or bottom
1429  }
1430 
1431  void file_editor_tab::move_match_brace (const QWidget *ID, bool select)
1432  {
1433  if (ID != this)
1434  return;
1435 
1436  if (select)
1437  m_edit_area->selectToMatchingBrace ();
1438  else
1439  m_edit_area->moveToMatchingBrace ();
1440  }
1441 
1443  {
1444  if (ID != this)
1445  return;
1446 
1447  m_autoc_active = true;
1448 
1449  QsciScintilla::AutoCompletionSource s = m_edit_area->autoCompletionSource ();
1450  switch (s)
1451  {
1452  case QsciScintilla::AcsAll:
1453  m_edit_area->autoCompleteFromAll ();
1454  break;
1455 
1456  case QsciScintilla::AcsAPIs:
1457  m_edit_area->autoCompleteFromAPIs ();
1458  break;
1459 
1460  case QsciScintilla::AcsDocument:
1461  m_edit_area->autoCompleteFromDocument ();
1462  break;
1463 
1464  case QsciScintilla::AcsNone:
1465  break;
1466  }
1467  }
1468 
1470  {
1471  // FIXME:
1472  m_edit_area->beginUndoAction ();
1473 
1474  if (m_edit_area->hasSelectedText ())
1475  {
1476  int lineFrom, lineTo, colFrom, colTo;
1477  m_edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo);
1478 
1479  if (colTo == 0) // the beginning of last line is not selected
1480  lineTo--; // stop at line above
1481 
1482  for (int i = lineFrom; i <= lineTo; i++)
1483  {
1484  if (indent)
1485  m_edit_area->indent (i);
1486  else
1487  m_edit_area->unindent (i);
1488  }
1489  //set selection on (un)indented section
1490  m_edit_area->setSelection (lineFrom, 0, lineTo,
1491  m_edit_area->text (lineTo).length ()-1);
1492  }
1493  else
1494  {
1495  int cpline, col;
1496  m_edit_area->getCursorPosition (&cpline, &col);
1497  if (indent)
1498  m_edit_area->indent (cpline);
1499  else
1500  m_edit_area->unindent (cpline);
1501  }
1502 
1503  m_edit_area->endUndoAction ();
1504  }
1505 
1507  {
1508  m_edit_area->beginUndoAction ();
1509 
1510  int lineFrom, lineTo;
1511 
1512  if (m_edit_area->hasSelectedText ())
1513  {
1514  int colFrom, colTo;
1515  m_edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo);
1516 
1517  if (colTo == 0) // the beginning of last line is not selected
1518  lineTo--; // stop at line above
1519  }
1520  else
1521  {
1522  int col;
1523  m_edit_area->getCursorPosition (&lineFrom, &col);
1524 
1525  lineTo = lineFrom;
1526  }
1527 
1528  m_edit_area->smart_indent_line_or_selected_text (lineFrom, lineTo);
1529 
1530  m_edit_area->endUndoAction ();
1531  }
1532 
1533  void file_editor_tab::do_comment_selected_text (bool comment, bool input_str)
1534  {
1535  QRegExp rxc;
1536  QString ws = "^(?:[ \\t]*)";
1537  QStringList comment_str = m_edit_area->comment_string (comment);
1538  QString used_comment_str = comment_str.at (0);
1539 
1540  if (comment)
1541  {
1542  if (input_str)
1543  {
1544  bool ok;
1546  gui_settings *settings = rmgr.get_settings ();
1547 
1548  used_comment_str
1549  = QInputDialog::getText (this, tr ("Comment selected text"),
1550  tr ("Comment string to use:\n"),
1551  QLineEdit::Normal,
1552  settings->value (ed_last_comment_str, comment_str.at (0)).toString (),
1553  &ok);
1554 
1555  if ((! ok) || used_comment_str.isEmpty ())
1556  return; // No input, do nothing
1557  else
1558  settings->setValue (ed_last_comment_str, used_comment_str); // Store last
1559  }
1560  }
1561  else
1562  {
1563  // Uncommenting (several strings possible)
1564 
1565  // Sort strings according their length
1566  QStringList comment_str_sorted (comment_str.at (0));
1567  bool inserted;
1568 
1569  for (int i = 1; i < comment_str.length (); i++)
1570  {
1571  inserted = false;
1572  for (int j = 0; j < comment_str_sorted.length (); j++)
1573  {
1574  if (comment_str.at (i).length () > comment_str_sorted.at (j).length ())
1575  {
1576  comment_str_sorted.insert (j, comment_str.at (i));
1577  inserted = true;
1578  break;
1579  }
1580  }
1581  if (! inserted)
1582  comment_str_sorted << comment_str.at (i);
1583  }
1584 
1585  // Create regular expression
1586  QString regexp;
1587  for (int i = 0; i < comment_str_sorted.length (); i++)
1588  {
1589  if (i > 0)
1590  regexp = regexp + QString ("|");
1591  regexp = regexp + comment_str_sorted.at (i);
1592  }
1593  rxc = QRegExp (ws + "(" + regexp + ")");
1594  }
1595 
1596  // Do the commenting/uncommenting
1597  int len = 0, lenc = 0;
1598  m_edit_area->beginUndoAction ();
1599 
1600  if (m_edit_area->hasSelectedText ())
1601  {
1602  int lineFrom, lineTo, colFrom, colTo;
1603  int change_col_from = 1;
1604  int change_col_to = 1;
1605  bool removed;
1606 
1607  m_edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo);
1608 
1609  if (colTo == 0) // the beginning of last line is not selected
1610  lineTo--; // stop at line above
1611 
1612  for (int i = lineFrom; i <= lineTo; i++)
1613  {
1614  if (comment)
1615  {
1616  m_edit_area->insertAt (used_comment_str, i, 0);
1617  }
1618  else
1619  {
1620  QString line (m_edit_area->text (i));
1621  if ((removed = line.contains (rxc)))
1622  {
1623  len = rxc.matchedLength (); // complete length
1624  QString matched_text = rxc.capturedTexts ().at (0);
1625  lenc = matched_text.remove (QRegExp (ws)).length (); // only comment string
1626  m_edit_area->setSelection (i, len-lenc, i, len);
1627  m_edit_area->removeSelectedText ();
1628  }
1629 
1630  // handle case, where the selection remains unchanged
1631  if (i == lineFrom && (colFrom < len-lenc || ! removed))
1632  change_col_from = 0; // do not change start of selection
1633  if (i == lineTo && (colTo < len-lenc || ! removed))
1634  change_col_to = 0; // do not change end of selection
1635  }
1636  }
1637 
1638  // update the selection area
1639  if (comment)
1640  {
1641  colFrom = colFrom + lenc; // shift start position by comment length
1642  if (colTo > 0)
1643  colTo = colTo + lenc; // shift end position by comment length
1644  else
1645  lineTo++; // colTo == 0 , fully select previous line
1646  }
1647  else
1648  {
1649  if (colTo == 0)
1650  lineTo++; // colTo == 0 , fully select previous line
1651  colFrom = colFrom - change_col_from*lenc;
1652  colTo = colTo - change_col_to*lenc;
1653  }
1654 
1655  // set updated selection area
1656  m_edit_area->setSelection (lineFrom, colFrom, lineTo, colTo);
1657  }
1658  else
1659  {
1660  int cpline, col;
1661  m_edit_area->getCursorPosition (&cpline, &col);
1662  if (comment)
1663  m_edit_area->insertAt (used_comment_str, cpline, 0);
1664  else
1665  {
1666  QString line (m_edit_area->text (cpline));
1667  if (line.contains (rxc))
1668  {
1669  len = rxc.matchedLength (); // complete length
1670  QString matched_text = rxc.capturedTexts ().at (0);
1671  lenc = matched_text.remove (QRegExp (ws)).length (); // only comment string
1672  m_edit_area->setSelection (cpline, len-lenc, cpline, len);
1673  m_edit_area->removeSelectedText ();
1674  }
1675  }
1676  }
1677  m_edit_area->endUndoAction ();
1678  }
1679 
1681  {
1682  QString title ("");
1683  QString tooltip ("");
1684 
1685  if (! valid_file_name ())
1686  title = tr ("<unnamed>");
1687  else
1688  {
1689  if (m_long_title)
1690  title = m_file_name;
1691  else
1692  {
1693  QFileInfo file (m_file_name);
1694  title = file.fileName ();
1695  tooltip = m_file_name;
1696  }
1697  }
1698 
1699  emit file_name_changed (title, tooltip, modified);
1700  }
1701 
1703  {
1704  m_copy_available = enableCopy;
1706  }
1707 
1708  // show_dialog: shows a modal or non modal dialog depending on input arg
1709  void file_editor_tab::show_dialog (QDialog *dlg, bool modal)
1710  {
1711  dlg->setAttribute (Qt::WA_DeleteOnClose);
1712  if (modal)
1713  dlg->exec ();
1714  else
1715  {
1716  dlg->setWindowModality (Qt::NonModal);
1717  dlg->show ();
1718  }
1719  }
1720 
1722  {
1723  int decision = QMessageBox::Yes;
1724  if (m_edit_area->isModified ())
1725  {
1726  // File is modified but not saved, ask user what to do. The file
1727  // editor tab can't be made parent because it may be deleted depending
1728  // upon the response. Instead, change the m_edit_area to read only.
1729  QMessageBox::StandardButtons buttons = QMessageBox::Save |
1730  QMessageBox::Discard |
1731  QMessageBox::Cancel;
1732 
1733  // For now, just a warning message about closing a tab that has been
1734  // modified seems sufficient. Exit-condition-specific messages could
1735  // be achieved by making 'available_actions' a function input string.
1736  QString available_actions =
1737  tr ("Do you want to cancel closing, save or discard the changes?");
1738 
1739  QString file;
1740  if (valid_file_name ())
1741  file = m_file_name;
1742  else
1743  file = tr ("<unnamed>");
1744 
1745  QMessageBox *msgBox
1746  = new QMessageBox (QMessageBox::Warning, tr ("Octave Editor"),
1747  tr ("The file\n\n"
1748  " %1\n\n"
1749  "is about to be closed but has been modified. "
1750  "%2").
1751  arg (file). arg (available_actions),
1752  buttons, qobject_cast<QWidget *> (parent ()));
1753 
1754  msgBox->setDefaultButton (QMessageBox::Save);
1755  m_edit_area->setReadOnly (true);
1756 
1757  decision = msgBox->exec (); // show_dialog (msgBox, true);
1758 
1759  if (decision == QMessageBox::Cancel)
1760  m_edit_area->setReadOnly (false);
1761  else if (decision == QMessageBox::Save)
1762  save_file (m_file_name, remove, false);
1763  else
1764  emit tab_ready_to_close ();
1765  }
1766  else
1767  {
1768  emit tab_ready_to_close ();
1769  }
1770 
1771  return decision;
1772  }
1773 
1774  void file_editor_tab::set_modified (bool modified)
1775  {
1776  m_edit_area->setModified (modified);
1777  }
1778 
1780  {
1781  // reset the possibly still existing read only state
1782  m_edit_area->setReadOnly (false);
1783 
1784  // if we are in this slot and the list of breakpoints is not empty,
1785  // then this tab was saved during an exit of the applications (not
1786  // restoring the breakpoints and not emptying the list) and the user
1787  // canceled this closing late on.
1789  }
1790 
1792  {
1793  if (! m_bp_lines.isEmpty ())
1794  {
1795  // At least one breakpoint is present.
1796  // Get rid of breakpoints at old (now possibly invalid) linenumbers
1797  remove_all_breakpoints (this);
1798 
1799  // and set breakpoints at the new linenumbers
1800  m_bp_restore_count = m_bp_lines.length ();
1801  for (int i = 0; i < m_bp_lines.length (); i++)
1803  m_bp_conditions.value (i));
1804 
1805  // Keep the list of breakpoints empty, except after explicit requests.
1806  m_bp_lines.clear ();
1807  m_bp_conditions.clear ();
1808  }
1809  }
1810 
1811  QString file_editor_tab::load_file (const QString& fileName)
1812  {
1813  // get the absolute path
1814  QFileInfo file_info = QFileInfo (fileName);
1815  QString file_to_load;
1816  if (file_info.exists ())
1817  file_to_load = file_info.canonicalFilePath ();
1818  else
1819  file_to_load = fileName;
1820  QFile file (file_to_load);
1821  if (!file.open(QIODevice::ReadOnly))
1822  return file.errorString ();
1823 
1824  int col = 0, line = 0;
1825  if (fileName == m_file_name)
1826  {
1827  // We have to reload the current file, thus get current cursor position
1828  line = m_line;
1829  col = m_col;
1830  }
1831 
1832  QApplication::setOverrideCursor (Qt::WaitCursor);
1833 
1834  // read the file binary, decoding later
1835  const QByteArray text_data = file.readAll ();
1836 
1837  // decode
1838  QTextCodec::ConverterState st;
1839  QTextCodec *codec = QTextCodec::codecForName (m_encoding.toLatin1 ());
1840  if (codec == nullptr)
1841  codec = QTextCodec::codecForLocale ();
1842 
1843  const QString text = codec->toUnicode(text_data.constData(),
1844  text_data.size(), &st);
1845 
1846  // Decoding with invalid characters?
1847  if (st.invalidChars > 0)
1848  {
1849  // Set read only
1850  m_edit_area->setReadOnly (true);
1851 
1852  // Message box for user decision
1853  QString msg = tr ("There were problems reading the file\n"
1854  "%1\n"
1855  "with the selected encoding %2.\n\n"
1856  "Modifying and saving the file might "
1857  "cause data loss!")
1858  .arg (file_to_load).arg (m_encoding);
1859  QMessageBox *msg_box = new QMessageBox ();
1860  msg_box->setIcon (QMessageBox::Warning);
1861  msg_box->setText (msg);
1862  msg_box->setWindowTitle (tr ("Octave Editor"));
1863  msg_box->addButton (tr ("&Edit anyway"), QMessageBox::YesRole);
1864  msg_box->addButton (tr ("Chan&ge encoding"), QMessageBox::AcceptRole);
1865  msg_box->addButton (tr ("&Close"), QMessageBox::RejectRole);
1866 
1867  connect (msg_box, SIGNAL (buttonClicked (QAbstractButton *)),
1868  this, SLOT (handle_decode_warning_answer (QAbstractButton *)));
1869 
1870  msg_box->setWindowModality (Qt::WindowModal);
1871  msg_box->setAttribute (Qt::WA_DeleteOnClose);
1872  msg_box->show ();
1873  }
1874 
1875  m_edit_area->setText (text);
1876  m_edit_area->setEolMode (detect_eol_mode ());
1877 
1878  QApplication::restoreOverrideCursor ();
1879 
1880  m_copy_available = false; // no selection yet available
1881  set_file_name (file_to_load);
1882  update_window_title (false); // window title (no modification)
1883  m_edit_area->setModified (false); // loaded file is not modified yet
1884 
1886 
1887  m_edit_area->setCursorPosition (line, col);
1888 
1889  return QString ();
1890  }
1891 
1893  {
1894  QString txt = btn->text ();
1895 
1896  if (txt == tr ("&Close"))
1897  {
1898  // Just close the file
1899  close ();
1900  return;
1901  }
1902 
1903  if (txt == tr ("Chan&ge encoding"))
1904  {
1905  // Dialog for reloading the file with another encoding
1906  QDialog dlg;
1907  dlg.setWindowTitle (tr ("Select new default encoding"));
1908 
1909  QLabel *text
1910  = new QLabel (tr ("Please select a new encoding\n"
1911  "for reloading the current file.\n\n"
1912  "This does not change the default encoding.\n"));
1913 
1914  QComboBox *enc_combo = new QComboBox ();
1916  rmgr.combo_encoding (enc_combo);
1917  m_new_encoding = enc_combo->currentText ();
1918  connect (enc_combo, SIGNAL (currentTextChanged (const QString&)),
1919  this, SLOT (handle_current_enc_changed (const QString&)));
1920 
1921  QDialogButtonBox *buttons
1922  = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
1923  Qt::Horizontal);
1924  connect (buttons, SIGNAL (accepted ()), &dlg, SLOT (accept ()));
1925  connect (buttons, SIGNAL (rejected ()), &dlg, SLOT (reject ()));
1926 
1927  QGridLayout *main_layout = new QGridLayout;
1928  main_layout->setSizeConstraint (QLayout::SetFixedSize);
1929  main_layout->addWidget (text, 0, 0);
1930  main_layout->addWidget (enc_combo, 1, 0);
1931  main_layout->addWidget (buttons, 2, 0);
1932  dlg.setLayout (main_layout);
1933 
1934  int answer = dlg.exec ();
1935 
1936  if (answer == QDialog::Accepted)
1937  {
1938  // Reload the file with new encoding but using the same tab
1939  QString reload_file_name = m_file_name; // store file name
1940  m_file_name = ""; // force reuse of this tab when opening a new file
1941  emit request_open_file (reload_file_name, m_new_encoding);
1942  }
1943  }
1944 
1945  // Continue editing, set writable again
1946  m_edit_area->setReadOnly (false);
1947  }
1948 
1950  {
1951  m_new_encoding = enc;
1952  }
1953 
1954  QsciScintilla::EolMode file_editor_tab::detect_eol_mode (void)
1955  {
1956  QByteArray text = m_edit_area->text ().toLatin1 ();
1957 
1958  QByteArray eol_lf = QByteArray (1,0x0a);
1959  QByteArray eol_cr = QByteArray (1,0x0d);
1960  QByteArray eol_crlf = eol_cr;
1961  eol_crlf.append (eol_lf);
1962 
1963  int count_crlf = text.count (eol_crlf);
1964  int count_lf = text.count (eol_lf) - count_crlf; // isolated lf
1965  int count_cr = text.count (eol_cr) - count_crlf; // isolated cr
1966 
1968  gui_settings *settings = rmgr.get_settings ();
1969  QsciScintilla::EolMode eol_mode
1970  = static_cast<QsciScintilla::EolMode> (settings->value (ed_default_eol_mode).toInt ());
1971 
1972  int count_max = 0;
1973 
1974  if (count_crlf > count_max)
1975  {
1976  eol_mode = QsciScintilla::EolWindows;
1977  count_max = count_crlf;
1978  }
1979  if (count_lf > count_max)
1980  {
1981  eol_mode = QsciScintilla::EolUnix;
1982  count_max = count_lf;
1983  }
1984  if (count_cr > count_max)
1985  {
1986  eol_mode = QsciScintilla::EolMac;
1987  }
1988 
1989  return eol_mode;
1990  }
1991 
1993  {
1994  switch (m_edit_area->eolMode ())
1995  {
1996  case QsciScintilla::EolWindows:
1997  m_eol_indicator->setText ("CRLF");
1998  break;
1999  case QsciScintilla::EolMac:
2000  m_eol_indicator->setText ("CR");
2001  break;
2002  case QsciScintilla::EolUnix:
2003  m_eol_indicator->setText ("LF");
2004  break;
2005  }
2006  }
2007 
2009  {
2010  if (m_file_name.isEmpty ())
2011  return;
2012 
2013  // Create and queue the command object.
2014 
2015  emit interpreter_event
2016  ([this] (interpreter& interp)
2017  {
2018  // INTERPRETER THREAD
2019 
2020  octave_value_list argout = Fdbstatus (interp, ovl (), 1);
2021 
2022  connect (this, SIGNAL (update_breakpoints_signal (const octave_value_list&)),
2023  this, SLOT (update_breakpoints_handler (const octave_value_list&)),
2024  Qt::QueuedConnection);
2025 
2026  emit update_breakpoints_signal (argout);
2027  });
2028  }
2029 
2031  {
2032  octave_map dbg = argout(0).map_value ();
2033  octave_idx_type n_dbg = dbg.numel ();
2034 
2035  Cell file = dbg.contents ("file");
2036  Cell line = dbg.contents ("line");
2037  Cell cond = dbg.contents ("cond");
2038 
2039  for (octave_idx_type i = 0; i < n_dbg; i++)
2040  {
2041  if (file (i).string_value () == m_file_name.toStdString ())
2042  do_breakpoint_marker (true, this, line (i).int_value (),
2043  QString::fromStdString (cond (i).string_value ()));
2044  }
2045  }
2046 
2047  void file_editor_tab::new_file (const QString& commands)
2048  {
2049  update_window_title (false); // window title (no modification)
2050 
2052  gui_settings *settings = rmgr.get_settings ();
2053 
2054  // set the eol mode from the settings or depending on the OS if the entry is
2055  // missing in the settings
2056  m_edit_area->setEolMode (static_cast<QsciScintilla::EolMode> (settings->value (ed_default_eol_mode).toInt ()));
2057 
2059 
2060  update_lexer ();
2061 
2062  m_edit_area->setText (commands);
2063  m_edit_area->setModified (!commands.isEmpty ());
2064  }
2065 
2066  void file_editor_tab::confirm_dbquit_and_save (const QString& file_to_save,
2067  const QString& base_name,
2068  bool remove_on_success,
2069  bool restore_breakpoints)
2070  {
2071  int ans = QMessageBox::question (nullptr, tr ("Debug or Save"),
2072  tr ("This file is currently being executed.\n"
2073  "Quit debugging and save?"),
2074  QMessageBox::Save | QMessageBox::Cancel);
2075 
2076  if (ans == QMessageBox::Save)
2077  {
2078  emit interpreter_event
2079  ([this, file_to_save, base_name, remove_on_success, restore_breakpoints] (interpreter& interp)
2080  {
2081  // INTERPRETER THREAD
2082 
2083  tree_evaluator& tw = interp.get_evaluator ();
2084 
2085  tw.dbquit (true);
2086 
2088 
2089  std::string std_base_name = base_name.toStdString ();
2090 
2091  symbol_table& symtab = interp.get_symbol_table ();
2092 
2093  symtab.clear_user_function (std_base_name);
2094 
2095  emit do_save_file_signal (file_to_save, remove_on_success,
2096  restore_breakpoints);
2097  });
2098  }
2099  }
2100 
2101  void file_editor_tab::save_file (const QString& saveFileName,
2102  bool remove_on_success,
2103  bool restore_breakpoints)
2104  {
2105  // If it is a new file with no name, signal that saveFileAs
2106  // should be performed.
2107  if (! valid_file_name (saveFileName))
2108  {
2109  save_file_as (remove_on_success);
2110  return;
2111  }
2112 
2113  m_encoding = m_new_encoding; // consider a possible new encoding
2114 
2115  // set the desired codec (if suitable for contents)
2116  QTextCodec *codec = check_valid_codec ();
2117  if (! codec)
2118  return; // No valid codec
2119 
2120  // Get a list of breakpoint line numbers, before exiting debug mode
2121  // and clearing function in interpreter_event action below.
2122 
2124 
2125  // get the absolute path (if existing)
2126  QFileInfo file_info = QFileInfo (saveFileName);
2127  QString file_to_save;
2128  if (file_info.exists ())
2129  {
2130  file_to_save = file_info.canonicalFilePath ();
2131  QString base_name = file_info.baseName ();
2132 
2133  emit interpreter_event
2134  ([this, file_to_save, base_name, remove_on_success, restore_breakpoints] (interpreter& interp)
2135  {
2136  // INTERPRETER THREAD
2137 
2138  // Force reloading of a file after it is saved.
2139  // This is needed to get the right line numbers for
2140  // breakpoints (bug #46632).
2141 
2142  tree_evaluator& tw = interp.get_evaluator ();
2143 
2144  symbol_table& symtab = interp.get_symbol_table ();
2145 
2146  std::string std_base_name = base_name.toStdString ();
2147 
2148  if (tw.in_debug_repl ())
2149  {
2150  octave_value sym;
2151  try
2152  {
2153  sym = symtab.find_user_function (std_base_name);
2154  }
2155  catch (const execution_exception& e)
2156  {
2157  interp.recover_from_exception ();
2158 
2159  // Ignore syntax error. It was in the old file on disk;
2160  // the user may have fixed it already.
2161  }
2162 
2163  // Return early if this file is not loaded in the symbol table
2164  if (! sym.is_defined () || ! sym.is_user_code ())
2165  {
2166  emit do_save_file_signal (file_to_save, remove_on_success,
2167  restore_breakpoints);
2168  return;
2169  }
2170 
2171  octave_user_code *fcn = sym.user_code_value ();
2172 
2173  std::string full_name = file_to_save.toStdString ();
2174 
2175  if (sys::canonicalize_file_name (full_name)
2176  != sys::canonicalize_file_name (fcn->fcn_file_name ()))
2177  {
2178  emit do_save_file_signal (file_to_save, remove_on_success,
2179  restore_breakpoints);
2180  return;
2181  }
2182 
2183  // If this file is loaded, check that we aren't currently
2184  // running it.
2185  // FIXME: is there a better way to get this info?
2186 
2187  octave_idx_type curr_frame = -1;
2188 
2189  octave_map stk = tw.backtrace (curr_frame, false);
2190 
2191  Cell names = stk.contents ("name");
2192 
2193  for (octave_idx_type i = names.numel () - 1; i >= 0; i--)
2194  {
2195  if (names(i).string_value () == std_base_name)
2196  {
2197  emit confirm_dbquit_and_save_signal
2198  (file_to_save, base_name, remove_on_success,
2199  restore_breakpoints);
2200  return;
2201  }
2202  }
2203  }
2204 
2205  symtab.clear_user_function (std_base_name);
2206 
2207  emit do_save_file_signal (file_to_save, remove_on_success,
2208  restore_breakpoints);
2209  });
2210  }
2211  else
2212  emit do_save_file_signal (saveFileName, remove_on_success,
2213  restore_breakpoints);
2214  }
2215 
2216  void file_editor_tab::do_save_file (const QString& file_to_save,
2217  bool remove_on_success,
2218  bool restore_breakpoints)
2219  {
2220  QFile file (file_to_save);
2221 
2222  // stop watching file
2223  QStringList trackedFiles = m_file_system_watcher.files ();
2224  if (trackedFiles.contains (file_to_save))
2225  m_file_system_watcher.removePath (file_to_save);
2226 
2227  // open the file for writing
2228  if (! file.open (QIODevice::WriteOnly))
2229  {
2230  // Unsuccessful, begin watching file again if it was being
2231  // watched previously.
2232  if (trackedFiles.contains (file_to_save))
2233  m_file_system_watcher.addPath (file_to_save);
2234 
2235  // Create a NonModal message about error.
2236  QMessageBox *msgBox
2237  = new QMessageBox (QMessageBox::Critical,
2238  tr ("Octave Editor"),
2239  tr ("Could not open file %1 for write:\n%2.").
2240  arg (file_to_save).arg (file.errorString ()),
2241  QMessageBox::Ok, nullptr);
2242  show_dialog (msgBox, false);
2243 
2244  return;
2245  }
2246 
2247  // save the contents into the file
2248 
2249  // write the file
2250  QTextStream out (&file);
2251 
2252  // set the desired codec (if suitable for contents)
2253  QTextCodec *codec = check_valid_codec ();
2254  if (! codec)
2255  return; // No valid codec
2256 
2257  out.setCodec (codec);
2258 
2259  QApplication::setOverrideCursor (Qt::WaitCursor);
2260  out << m_edit_area->text ();
2261  out.flush ();
2262  QApplication::restoreOverrideCursor ();
2263  file.flush ();
2264  file.close ();
2265 
2266  // file exists now
2267  QFileInfo file_info = QFileInfo (file);
2268  QString full_file_to_save = file_info.canonicalFilePath ();
2269 
2270  // save filename after closing file as set_file_name starts watching again
2271  set_file_name (full_file_to_save); // make absolute
2272 
2273  // set the window title to actual filename (not modified)
2274  update_window_title (false);
2275 
2276  // file is save -> not modified, update encoding in statusbar
2277  m_edit_area->setModified (false);
2278  m_enc_indicator->setText (m_encoding);
2279 
2280  emit tab_ready_to_close ();
2281 
2282  if (remove_on_success)
2283  {
2284  emit tab_remove_request ();
2285  return; // Don't touch member variables after removal
2286  }
2287 
2288  // Attempt to restore the breakpoints if that is desired.
2289  // This is only allowed if the tab is not closing since changing
2290  // breakpoints would reopen the tab in this case.
2291  if (restore_breakpoints)
2293  }
2294 
2295  void file_editor_tab::save_file_as (bool remove_on_success)
2296  {
2297  // Simply put up the file chooser dialog box with a slot connection
2298  // then return control to the system waiting for a file selection.
2299 
2300  // reset m_new_encoding
2302 
2303  // If the tab is removed in response to a QFileDialog signal, the tab
2304  // can't be a parent.
2305  QFileDialog *fileDialog;
2306  if (remove_on_success)
2307  {
2308  // If tab is closed, "this" cannot be parent in which case modality
2309  // has no effect. Disable editing instead.
2310  m_edit_area->setReadOnly (true);
2311  fileDialog = new QFileDialog ();
2312  }
2313  else
2314  fileDialog = new QFileDialog (this);
2315 
2316  // add the possible filters and the default suffix
2317  QStringList filters;
2318  filters << tr ("Octave Files (*.m)")
2319  << tr ("All Files (*)");
2320  fileDialog->setNameFilters (filters);
2321  fileDialog->setDefaultSuffix ("m");
2322 
2323  if (valid_file_name ())
2324  {
2325  fileDialog->selectFile (m_file_name);
2326  QFileInfo file_info (m_file_name);
2327  if (file_info.suffix () != "m")
2328  {
2329  // it is not an octave file
2330  fileDialog->selectNameFilter (filters.at (1)); // "All Files"
2331  fileDialog->setDefaultSuffix (""); // no default suffix
2332  }
2333  }
2334  else
2335  {
2336  fileDialog->selectFile ("");
2337  fileDialog->setDirectory (m_ced);
2338 
2339  // propose a name corresponding to the function name
2340  QString fname = get_function_name ();
2341  if (! fname.isEmpty ())
2342  fileDialog->selectFile (fname + ".m");
2343  }
2344 
2345  fileDialog->setAcceptMode (QFileDialog::AcceptSave);
2346  fileDialog->setViewMode (QFileDialog::Detail);
2347 
2348  // FIXME: Remove, if for all common KDE versions (bug #54607) is resolved.
2350  gui_settings *settings = rmgr.get_settings ();
2351  if (! settings->value (global_use_native_dialogs).toBool ())
2352  fileDialog->setOption(QFileDialog::DontUseNativeDialog);
2353 
2354  connect (fileDialog, SIGNAL (filterSelected (const QString&)),
2355  this, SLOT (handle_save_as_filter_selected (const QString&)));
2356 
2357  if (remove_on_success)
2358  {
2359  connect (fileDialog, SIGNAL (fileSelected (const QString&)),
2360  this, SLOT (handle_save_file_as_answer_close (const QString&)));
2361 
2362  connect (fileDialog, SIGNAL (rejected ()),
2363  this, SLOT (handle_save_file_as_answer_cancel ()));
2364  }
2365  else
2366  {
2367  connect (fileDialog, SIGNAL (fileSelected (const QString&)),
2368  this, SLOT (handle_save_file_as_answer (const QString&)));
2369  }
2370 
2371  show_dialog (fileDialog, ! valid_file_name ());
2372  }
2373 
2375  {
2376  QFileDialog *file_dialog = qobject_cast<QFileDialog *> (sender ());
2377 
2378  QRegExp rx ("\\*\\.([^ ^\\)]*)[ \\)]"); // regexp for suffix in filter
2379  int index = rx.indexIn (filter,0); // get first suffix in filter
2380 
2381  if (index > -1)
2382  file_dialog->setDefaultSuffix (rx.cap (1)); // found a suffix, set default
2383  else
2384  file_dialog->setDefaultSuffix (""); // not found, clear default
2385  }
2386 
2388  {
2389  QFileInfo file = QFileInfo (file_name);
2390  QString base_name = file.baseName ();
2391 
2392  if ((file.suffix () == "m")
2393  && (! valid_identifier (base_name.toStdString ())))
2394  {
2395  int ans = QMessageBox::question (nullptr, tr ("Octave Editor"),
2396  tr ("\"%1\"\n"
2397  "is not a valid identifier.\n\n"
2398  "If you keep this filename, you will not be able to\n"
2399  "call your script using its name as an Octave command.\n\n"
2400  "Do you want to choose another name?").arg (base_name),
2401  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
2402 
2403  if (ans == QMessageBox::Yes)
2404  return true;
2405  }
2406 
2407  return false;
2408  }
2409 
2411  {
2412  QTextCodec *codec = QTextCodec::codecForName (m_encoding.toLatin1 ());
2413 
2414  // "SYSTEM" is used as alias for the locale encoding.
2415  if ((! codec) && m_encoding.startsWith("SYSTEM"))
2416  codec = QTextCodec::codecForLocale ();
2417 
2418  if (! codec)
2419  {
2420  QMessageBox::critical (nullptr,
2421  tr ("Octave Editor"),
2422  tr ("The current encoding %1\n"
2423  "can not be applied.\n\n"
2424  "Please select another one!").arg (m_encoding));
2425 
2426  return nullptr;
2427  }
2428 
2429  QString editor_text = m_edit_area->text ();
2430  bool can_encode = codec->canEncode (editor_text);
2431 
2432  // We cannot rely on QTextCodec::canEncode because it uses the
2433  // ConverterState of convertFromUnicode which isn't updated by some
2434  // implementations.
2435  if (can_encode)
2436  {
2437  QVector<uint> u32_str = editor_text.toUcs4 ();
2438  const uint32_t *src = reinterpret_cast<const uint32_t *>
2439  (u32_str.data ());
2440 
2441  size_t length;
2442  char *res_str =
2443  octave_u32_conv_to_encoding_strict (m_encoding.toStdString ().c_str (),
2444  src, u32_str.size (), &length);
2445  if (! res_str)
2446  {
2447  if (errno == EILSEQ)
2448  can_encode = false;
2449  }
2450  else
2451  ::free (static_cast<void *> (res_str));
2452  }
2453 
2454  if (! can_encode)
2455  {
2456  QMessageBox::StandardButton pressed_button
2457  = QMessageBox::critical (nullptr,
2458  tr ("Octave Editor"),
2459  tr ("The current editor contents can not be encoded\n"
2460  "with the selected encoding %1.\n"
2461  "Using it would result in data loss!\n\n"
2462  "Please select another one!").arg (m_encoding),
2463  QMessageBox::Cancel | QMessageBox::Ignore,
2464  QMessageBox::Cancel);
2465 
2466  if (pressed_button == QMessageBox::Ignore)
2467  return codec;
2468  else
2469  return nullptr;
2470  }
2471 
2472  return codec;
2473  }
2474 
2475  void file_editor_tab::handle_save_file_as_answer (const QString& saveFileName)
2476  {
2477  if (m_save_as_desired_eol != m_edit_area->eolMode ())
2479 
2480  if (saveFileName == m_file_name)
2481  {
2482  save_file (saveFileName);
2483  }
2484  else
2485  {
2486  // Have editor check for conflict, do not delete tab after save.
2487  if (check_valid_identifier (saveFileName))
2488  save_file_as (false);
2489  else
2490  emit editor_check_conflict_save (saveFileName, false);
2491  }
2492  }
2493 
2494  void file_editor_tab::handle_save_file_as_answer_close (const QString& saveFileName)
2495  {
2496  if (m_save_as_desired_eol != m_edit_area->eolMode ())
2497  {
2498  m_edit_area->setReadOnly (false); // was set to read-only in save_file_as
2500  m_edit_area->setReadOnly (true); // restore read-only mode
2501  }
2502 
2503  // saveFileName == m_file_name can not happen, because we only can get here
2504  // when we close a tab and m_file_name is not a valid filename yet
2505 
2506  // Have editor check for conflict, delete tab after save.
2507  if (check_valid_identifier (saveFileName))
2508  save_file_as (true);
2509  else
2510  emit editor_check_conflict_save (saveFileName, true);
2511  }
2512 
2514  {
2515  // User canceled, allow editing again.
2516  m_edit_area->setReadOnly (false);
2517  }
2518 
2519  void file_editor_tab::file_has_changed (const QString&, bool do_close)
2520  {
2521  bool file_exists = QFile::exists (m_file_name);
2522 
2523  if (file_exists && ! do_close)
2524  {
2525  // Test if file is really modified or if just the timezone has
2526  // changed. In the latter, just return without doing anything.
2527  QDateTime modified = QFileInfo (m_file_name).lastModified ().toUTC ();
2528 
2529  if (modified <= m_last_modified)
2530  return;
2531 
2532  m_last_modified = modified;
2533  }
2534 
2535  // Prevent popping up multiple message boxes when the file has
2536  // been changed multiple times by temporarily removing from the
2537  // file watcher.
2538  QStringList trackedFiles = m_file_system_watcher.files ();
2539  if (! trackedFiles.isEmpty ())
2540  m_file_system_watcher.removePath (m_file_name);
2541 
2542  if (file_exists && ! do_close)
2543  {
2544 
2545  // The file is modified
2548 
2549  else
2550  {
2551  // give editor and this tab the focus,
2552  // possibly making the editor visible if it is hidden
2553  emit set_focus_editor_signal (this);
2554  m_edit_area->setFocus ();
2555 
2556  // Create a WindowModal message that blocks the edit area
2557  // by making m_edit_area parent.
2558  QMessageBox *msgBox
2559  = new QMessageBox (QMessageBox::Warning,
2560  tr ("Octave Editor"),
2561  tr ("It seems that \'%1\' has been modified by another application. Do you want to reload it?").
2562  arg (m_file_name),
2563  QMessageBox::Yes | QMessageBox::No, this);
2564 
2565  connect (msgBox, SIGNAL (finished (int)),
2566  this, SLOT (handle_file_reload_answer (int)));
2567 
2568  msgBox->setWindowModality (Qt::WindowModal);
2569  msgBox->setAttribute (Qt::WA_DeleteOnClose);
2570  msgBox->show ();
2571  }
2572  }
2573  else
2574  {
2575  // If desired and if file is not modified,
2576  // close the file without any user interaction
2577  if (do_close && ! m_edit_area->isModified ())
2578  {
2579  handle_file_resave_answer (QMessageBox::Cancel);
2580  return;
2581  }
2582 
2583  // give editor and this tab the focus,
2584  // possibly making the editor visible if it is hidden
2585  emit set_focus_editor_signal (this);
2586  m_edit_area->setFocus ();
2587 
2588  QString modified = "";
2589  if (m_edit_area->isModified ())
2590  modified = tr ("\n\nWarning: The contents in the editor is modified!");
2591 
2592  // Create a WindowModal message. The file editor tab can't be made
2593  // parent because it may be deleted depending upon the response.
2594  // Instead, change the m_edit_area to read only.
2595  QMessageBox *msgBox
2596  = new QMessageBox (QMessageBox::Warning, tr ("Octave Editor"),
2597  tr ("It seems that the file\n"
2598  "%1\n"
2599  "has been deleted or renamed. Do you want to save it now?%2").
2600  arg (m_file_name).arg (modified),
2601  QMessageBox::Save | QMessageBox::Close, nullptr);
2602 
2603  m_edit_area->setReadOnly (true);
2604 
2605  connect (msgBox, SIGNAL (finished (int)),
2606  this, SLOT (handle_file_resave_answer (int)));
2607 
2608  msgBox->setWindowModality (Qt::WindowModal);
2609  msgBox->setAttribute (Qt::WA_DeleteOnClose);
2610  msgBox->show ();
2611  }
2612  }
2613 
2615  {
2616  if (! settings)
2617  return;
2618 
2619  if (! init)
2621 
2622  // code folding
2623  if (settings->value (ed_code_folding).toBool ())
2624  {
2625  m_edit_area->setMarginType (3, QsciScintilla::SymbolMargin);
2626  m_edit_area->setFolding (QsciScintilla::BoxedTreeFoldStyle, 3);
2627  }
2628  else
2629  {
2630  m_edit_area->setFolding (QsciScintilla::NoFoldStyle, 3);
2631  }
2632 
2633  // status bar
2634  if (settings->value (ed_show_edit_status_bar).toBool ())
2635  m_status_bar->show ();
2636  else
2637  m_status_bar->hide ();
2638 
2639  //highlight current line color
2640  QColor setting_color = settings->value (ed_highlight_current_line_color).value<QColor> ();
2641  m_edit_area->setCaretLineBackgroundColor (setting_color);
2642  m_edit_area->setCaretLineVisible
2643  (settings->value (ed_highlight_current_line).toBool ());
2644 
2645  bool match_keywords = settings->value
2646  (ed_code_completion_keywords).toBool ();
2647  bool match_document = settings->value
2648  (ed_code_completion_document).toBool ();
2649 
2650  QsciScintilla::AutoCompletionSource source = QsciScintilla::AcsNone;
2651  if (match_keywords)
2652  if (match_document)
2653  source = QsciScintilla::AcsAll;
2654  else
2655  source = QsciScintilla::AcsAPIs;
2656  else if (match_document)
2657  source = QsciScintilla::AcsDocument;
2658  m_edit_area->setAutoCompletionSource (source);
2659 
2660  m_edit_area->setAutoCompletionReplaceWord
2661  (settings->value (ed_code_completion_replace).toBool ());
2662  m_edit_area->setAutoCompletionCaseSensitivity
2663  (settings->value (ed_code_completion_case).toBool ());
2664 
2665  if (settings->value (ed_code_completion).toBool ())
2666  m_edit_area->setAutoCompletionThreshold
2667  (settings->value (ed_code_completion_threshold).toInt ());
2668  else
2669  m_edit_area->setAutoCompletionThreshold (-1);
2670 
2671  if (settings->value (ed_show_white_space).toBool ())
2672  if (settings->value (ed_show_white_space_indent).toBool ())
2673  m_edit_area->setWhitespaceVisibility (QsciScintilla::WsVisibleAfterIndent);
2674  else
2675  m_edit_area->setWhitespaceVisibility (QsciScintilla::WsVisible);
2676  else
2677  m_edit_area->setWhitespaceVisibility (QsciScintilla::WsInvisible);
2678 
2679  m_edit_area->setEolVisibility (settings->value (ed_show_eol_chars).toBool ());
2680 
2681  if (settings->value (ed_show_line_numbers).toBool ())
2682  {
2683  m_edit_area->setMarginLineNumbers (2, true);
2684  auto_margin_width ();
2685  connect (m_edit_area, SIGNAL (linesChanged ()),
2686  this, SLOT (auto_margin_width ()));
2687  }
2688  else
2689  {
2690  m_edit_area->setMarginLineNumbers (2, false);
2691  disconnect (m_edit_area, SIGNAL (linesChanged ()), nullptr, nullptr);
2692  }
2693 
2694  m_smart_indent = settings->value (ed_auto_indent).toBool ();
2695  m_edit_area->setAutoIndent (m_smart_indent);
2696  m_edit_area->setTabIndents
2697  (settings->value (ed_tab_indents_line).toBool ());
2698  m_edit_area->setBackspaceUnindents
2699  (settings->value (ed_backspace_unindents_line).toBool ());
2700  m_edit_area->setIndentationGuides
2701  (settings->value (ed_show_indent_guides).toBool ());
2702  m_edit_area->setIndentationsUseTabs
2703  (settings->value (ed_indent_uses_tabs).toBool ());
2704  m_edit_area->setIndentationWidth
2705  (settings->value (ed_indent_width).toInt ());
2706 
2707  m_edit_area->setTabWidth
2708  (settings->value (ed_tab_width).toInt ());
2709 
2710  m_ind_char_width = 1;
2711  if (m_edit_area->indentationsUseTabs ())
2712  m_ind_char_width = m_edit_area->tabWidth ();
2713 
2714  m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETHSCROLLBAR,
2715  settings->value (ed_show_hscroll_bar).toBool ());
2716  m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETSCROLLWIDTH,-1);
2717  m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETSCROLLWIDTHTRACKING,true);
2718 
2719  m_long_title = settings->value (ed_long_window_title).toBool ();
2720  update_window_title (m_edit_area->isModified ());
2721 
2722  m_auto_endif = settings->value (ed_auto_endif).toInt ();
2723 
2724  // long line marker
2725  int line_length = settings->value (ed_long_line_column).toInt ();
2726  m_edit_area->setEdgeColumn (line_length);
2727 
2728  if (settings->value (ed_long_line_marker).toBool ())
2729  {
2730  if (settings->value (ed_long_line_marker_line).toBool ())
2731  m_edit_area->setEdgeMode (QsciScintilla::EdgeLine);
2732  else
2733  {
2735  .toBool ())
2736  m_edit_area->setEdgeMode (QsciScintilla::EdgeBackground);
2737  else
2738  m_edit_area->setEdgeMode (QsciScintilla::EdgeLine);
2739  }
2740  }
2741  else
2742  m_edit_area->setEdgeMode (QsciScintilla::EdgeNone);
2743 
2744  // line wrapping and breaking
2745  m_edit_area->setWrapVisualFlags (QsciScintilla::WrapFlagByBorder);
2746  m_edit_area->setWrapIndentMode (QsciScintilla::WrapIndentSame);
2747 
2748  if (settings->value (ed_wrap_lines).toBool ())
2749  m_edit_area->setWrapMode (QsciScintilla::WrapWord);
2750  else
2751  m_edit_area->setWrapMode (QsciScintilla::WrapNone);
2752 
2753  if (settings->value (ed_break_lines).toBool ())
2754  m_line_break = line_length;
2755  else
2756  m_line_break = 0;
2757 
2759  settings->value (ed_break_lines_comments).toBool ();
2760 
2761  // highlight all occurrences of a word selected by a double click
2763  settings->value (ed_highlight_all_occurrences).toBool ();
2764 
2765  // reload changed files
2767  settings->value (ed_always_reload_changed_files).toBool ();
2768 
2769  // Set cursor blinking depending on the settings.
2770  // QScintilla ignores the application global settings, so some special
2771  // handling is required
2772  bool cursor_blinking;
2773 
2774  if (settings->contains (global_cursor_blinking.key))
2775  cursor_blinking = settings->value (global_cursor_blinking).toBool ();
2776  else
2777  cursor_blinking = settings->value (cs_cursor_blinking).toBool ();
2778 
2779  if (cursor_blinking)
2780  m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETCARETPERIOD,500);
2781  else
2782  m_edit_area->SendScintilla (QsciScintillaBase::SCI_SETCARETPERIOD,0);
2783 
2784  }
2785 
2786 
2788  {
2789  m_edit_area->setMarginWidth (2, "1" + QString::number (m_edit_area->lines ()));
2790  }
2791 
2792  // the following close request was changed from a signal slot into a
2793  // normal function because we need the return value from close whether
2794  // the tab really was closed (for canceling exiting octave).
2795  // When emitting a signal, only the return value from the last slot
2796  // goes back to the sender
2798  {
2799  return close ();
2800  }
2801 
2803  {
2804  if (ID != this)
2805  return;
2806 
2808  }
2809 
2811  {
2812  if (decision == QMessageBox::Yes)
2813  {
2814  // reload: file is readded to the file watcher in set_file_name ()
2816  }
2817  else
2818  {
2819  // do not reload: readd to the file watcher
2821  }
2822  }
2823 
2825  {
2826  // check decision of user in dialog
2827  if (decision == QMessageBox::Save)
2828  {
2829  save_file (m_file_name); // readds file to watcher in set_file_name ()
2830  m_edit_area->setReadOnly (false); // delete read only flag
2831  }
2832  else
2833  {
2834  // Definitely close the file.
2835  // Set modified to false to prevent the dialog box when the close event
2836  // is posted. If the user cancels the close in this dialog the tab is
2837  // left open with a non-existing file.
2838  m_edit_area->setModified (false);
2839  close ();
2840  }
2841  }
2842 
2844  {
2845  if (ID != this || ID == nullptr)
2846  return;
2847 
2848  emit remove_all_positions (); // debugger_position, unsure_debugger_position
2849 
2850  if (line > 0)
2851  {
2852  marker *dp;
2853 
2854  if (m_edit_area->isModified ())
2855  {
2856  // The best that can be done if the editor contents have been
2857  // modified is to see if there is a match with the original
2858  // line number of any existing breakpoints. We can put a normal
2859  // debugger pointer at that breakpoint position. Otherwise, it
2860  // isn't certain whether the original line number and current line
2861  // number match.
2862  int editor_linenr = -1;
2863  marker *dummy;
2864  emit find_translated_line_number (line, editor_linenr, dummy);
2865  if (editor_linenr != -1)
2866  {
2867  // Match with an existing breakpoint.
2868  dp = new marker (m_edit_area, line,
2869  marker::debugger_position, editor_linenr);
2870  }
2871  else
2872  {
2873  int original_linenr = -1;
2874  editor_linenr = -1;
2875  emit find_linenr_just_before (line, original_linenr, editor_linenr);
2876  if (original_linenr >= 0)
2877  {
2878  // Make a guess by using an offset from the breakpoint.
2879  int linenr_guess = editor_linenr + line - original_linenr;
2880  dp = new marker (m_edit_area, line,
2882  linenr_guess);
2883  }
2884  else
2885  {
2886  // Can't make a very good guess, so just use the debugger
2887  // line number.
2888  dp = new marker (m_edit_area, line,
2890  }
2891  }
2892  }
2893  else
2894  {
2896 
2897  // In case of a not modified file we might have to remove
2898  // a breakpoint here if we have stepped into the file
2900  {
2904  }
2905  }
2906 
2907  connect (this, SIGNAL (remove_position_via_debugger_linenr (int)),
2908  dp, SLOT (handle_remove_via_original_linenr (int)));
2909  connect (this, SIGNAL (remove_all_positions (void)),
2910  dp, SLOT (handle_remove (void)));
2911 
2912  center_current_line (false);
2913  }
2914  }
2915 
2917  {
2918  if (ID != this || ID == nullptr)
2919  return;
2920 
2921  if (line > 0)
2923  }
2924 
2926  const QWidget *ID, int line,
2927  const QString& cond)
2928  {
2929  if (ID != this || ID == nullptr)
2930  return;
2931 
2932  if (line > 0)
2933  {
2934  if (insert)
2935  {
2936  int editor_linenr = -1;
2937  marker *bp = nullptr;
2938 
2939  // If comes back indicating a non-zero breakpoint marker,
2940  // reuse it if possible
2941  emit find_translated_line_number (line, editor_linenr, bp);
2942  if (bp != nullptr)
2943  {
2944  if ((cond == "") != (bp->get_cond () == ""))
2945  {
2946  // can only reuse conditional bp as conditional
2948  bp = nullptr;
2949  }
2950  else
2951  bp->set_cond (cond);
2952  }
2953 
2954  if (bp == nullptr)
2955  {
2956  bp = new marker (m_edit_area, line,
2957  cond == "" ? marker::breakpoint
2958  : marker::cond_break, cond);
2959 
2960  connect (this, SIGNAL (remove_breakpoint_via_debugger_linenr
2961  (int)),
2962  bp, SLOT (handle_remove_via_original_linenr (int)));
2963  connect (this, SIGNAL (request_remove_breakpoint_via_editor_linenr
2964  (int)),
2965  bp, SLOT (handle_request_remove_via_editor_linenr
2966  (int)));
2967  connect (this, SIGNAL (remove_all_breakpoints (void)),
2968  bp, SLOT (handle_remove (void)));
2969  connect (this, SIGNAL (find_translated_line_number (int, int&,
2970  marker*&)),
2971  bp, SLOT (handle_find_translation (int, int&,
2972  marker*&)));
2973  connect (this, SIGNAL (find_linenr_just_before (int, int&, int&)),
2974  bp, SLOT (handle_find_just_before (int, int&, int&)));
2975  connect (this, SIGNAL (report_marker_linenr (QIntList&,
2976  QStringList&)),
2977  bp, SLOT (handle_report_editor_linenr (QIntList&,
2978  QStringList&)));
2979  connect (bp, SIGNAL (request_remove (int)),
2980  this, SLOT (handle_request_remove_breakpoint (int)));
2981  }
2982  }
2983  else
2985  }
2986  }
2987 
2989  {
2990  long int visible_lines
2991  = m_edit_area->SendScintilla (QsciScintillaBase::SCI_LINESONSCREEN);
2992 
2993  if (visible_lines > 2)
2994  {
2995  int line, index;
2996  m_edit_area->getCursorPosition (&line, &index);
2997  // compensate for "folding":
2998  // step 1: expand the current line, if it was folded
2999  m_edit_area->SendScintilla (2232, line); // SCI_ENSUREVISIBLE
3000 
3001  // step 2: map file line num to "visible" one // SCI_VISIBLEFROMDOCLINE
3002  int vis_line = m_edit_area->SendScintilla (2220, line);
3003 
3004  int first_line = m_edit_area->firstVisibleLine ();
3005 
3006  if (always || vis_line == first_line
3007  || vis_line > first_line + visible_lines - 2)
3008  {
3009  first_line += (vis_line - first_line - (visible_lines - 1) / 2);
3010  m_edit_area->SendScintilla (2613, first_line); // SCI_SETFIRSTVISIBLELINE
3011  }
3012  }
3013  }
3014 
3016  {
3017  // the related signal is emitted before cursor-move-signal!
3018  m_lines_changed = true;
3019  }
3020 
3022  {
3023  // Cursor has moved, first check wether an autocompletion list
3024  // is active or if it was closed. Scintilla provides signals for
3025  // completed or cancelled lists, but not for list that where hidden
3026  // due to a new character not matching anymore with the list entries
3027  if (m_edit_area->SendScintilla (QsciScintillaBase::SCI_AUTOCACTIVE))
3028  m_autoc_active = true;
3029  else if (m_autoc_active)
3030  {
3031  m_autoc_active = false;
3032  emit autoc_closed (); // Tell editor about closed list
3033  }
3034 
3035  // Lines changed? Take care of indentation
3036  if (m_lines_changed) // cursor moved and lines have changed
3037  {
3038  m_lines_changed = false;
3039  if (m_is_octave_file && line == m_line+1 && col < m_col)
3040  {
3041  // Obviously, we have a newline here
3045  }
3046  }
3047 
3048  // Update line and column indicator in the status bar
3049  m_line = line;
3050  m_col = col;
3051  m_row_indicator->setNum (line+1);
3052  m_col_indicator->setNum (col+1);
3053  }
3054 
3055  // Slot that is entered each time a new character was typed.
3056  // It is used for handling line breaking if this is desired.
3057  // The related signal is emitted after the signal for a moved cursor
3058  // such that m_col and m_line can not be used for current position.
3060  {
3061  if (m_line_break)
3062  {
3063  // If line breaking is desired, get the current line and column.
3064  // For taking the tab width into consideration, use own function
3065  int line, col, pos;
3066  m_edit_area->get_current_position (&pos, &line, &col);
3067 
3068  // immediately return if line has not reached the max. line length
3069  if (col <= m_line_break)
3070  return;
3071 
3072  // If line breaking is only desired in comments,
3073  // return if not in a comment
3074  int style_comment = octave_qscintilla::ST_NONE;
3076  {
3077  // line breaking only in comments, check for comment style
3078  style_comment = m_edit_area->is_style_comment ();
3079  if (! style_comment)
3080  return; // no comment, return
3081  }
3082 
3083  // Here we go for breaking the current line by inserting a newline.
3084  // For determining the position of a specific column, we have to get
3085  // the column from the QScintilla function without taking tab lengths
3086  // into account, since the calculation from line/col to position
3087  // ignores this, too.
3088  m_edit_area->getCursorPosition (&line, &col);
3089  int c = 0;
3090  int col_space = col;
3091  int indentation = m_edit_area->indentation (line);
3092 
3093  // Search the first occurrence of space or tab backwards starting from
3094  // the current column (col_space).
3095  while (c != ' ' && c != '\t' && col_space > indentation)
3096  {
3097  pos = m_edit_area->positionFromLineIndex (line, col_space--);
3098  c = m_edit_area->SendScintilla (QsciScintillaBase::SCI_GETCHARAT, pos);
3099  }
3100 
3101  // If a space or tab was found, break at this char,
3102  // otherwise break at cursor position
3103  int col_newline = col - 1;
3104  if (c == ' ' || c == '\t')
3105  col_newline = col_space + 1;
3106 
3107  // Insert a newline char for breaking the line possibly followed
3108  // by a line comment string
3109  QString newline = QString ("\n");
3110  style_comment = m_edit_area->is_style_comment ();
3111  if (style_comment == octave_qscintilla::ST_LINE_COMMENT)
3112  newline = newline + m_edit_area->comment_string ().at (0);
3113  m_edit_area->insertAt (newline, line, col_newline);
3114 
3115  // Automatically indent the new line to the indentation of previous line
3116  // and set the cursor position to the end of the indentation.
3117  m_edit_area->setIndentation (line + 1, indentation);
3118  m_edit_area->SendScintilla (QsciScintillaBase::SCI_LINEEND);
3119  }
3120  }
3121 
3122  // Slot handling a double click into the text area
3123  void file_editor_tab::handle_double_click (int, int, int modifier)
3124  {
3125  if (! modifier)
3126  {
3127  // double clicks without modifier
3128  // clear any existing indicators of this type
3130 
3132  {
3133  // Clear any previous selection.
3135 
3136  // highlighting of all occurrences of the clicked word is enabled
3137 
3138  // get the resulting cursor position
3139  // (required if click was beyond a line ending)
3140  int line, col;
3141  m_edit_area->getCursorPosition (&line, &col);
3142 
3143  // get the word at the cursor (if any)
3144  QString word = m_edit_area->wordAtLineIndex (line, col);
3145  word = word.trimmed ();
3146 
3147  if (! word.isEmpty ())
3148  {
3149  // word is not empty, so find all occurrences of the word
3150 
3151  // remember first visible line and x-offset for restoring the view afterwards
3152  int first_line = m_edit_area->firstVisibleLine ();
3153  int x_offset = m_edit_area->SendScintilla(QsciScintillaBase::SCI_GETXOFFSET);
3154 
3155  // search for first occurrence of the detected word
3156  bool find_result_available
3157  = m_edit_area->findFirst (word,
3158  false, // no regexp
3159  true, // case sensitive
3160  true, // whole words only
3161  false, // do not wrap
3162  true, // forward
3163  0,0, // from the beginning
3164  false
3165 #if defined (HAVE_QSCI_VERSION_2_6_0)
3166  , true
3167 #endif
3168  );
3169 
3170  // loop over all occurrences and set the related indicator
3171  int oline, ocol;
3172  int wlen = word.length ();
3173 
3174  while (find_result_available)
3175  {
3176  // get cursor position after having found an occurrence
3177  m_edit_area->getCursorPosition (&oline, &ocol);
3178  // mark the selection
3179  m_edit_area->show_selection_markers (oline, ocol-wlen, oline, ocol);
3180 
3181  // find next occurrence
3182  find_result_available = m_edit_area->findNext ();
3183  }
3184 
3185  // restore the visible area of the file, the cursor position,
3186  // and the selection
3187  m_edit_area->setFirstVisibleLine (first_line);
3188  m_edit_area->SendScintilla(QsciScintillaBase::SCI_SETXOFFSET, x_offset);
3189  m_edit_area->setCursorPosition (line, col);
3190  m_edit_area->setSelection (line, col - wlen, line, col);
3192  }
3193  }
3194  }
3195  }
3196 
3198  {
3199  QRegExp rxfun1 ("^[\t ]*function[^=]+=([^\\(]+)\\([^\\)]*\\)[\t ]*$");
3200  QRegExp rxfun2 ("^[\t ]*function[\t ]+([^\\(]+)\\([^\\)]*\\)[\t ]*$");
3201  QRegExp rxfun3 ("^[\t ]*function[^=]+=[\t ]*([^\\s]+)[\t ]*$");
3202  QRegExp rxfun4 ("^[\t ]*function[\t ]+([^\\s]+)[\t ]*$");
3203  QRegExp rxfun5 ("^[\t ]*classdef[\t ]+([^\\s]+)[\t ]*$");
3204 
3205  QStringList lines = m_edit_area->text ().split ("\n");
3206 
3207  for (int i = 0; i < lines.count (); i++)
3208  {
3209  if (rxfun1.indexIn (lines.at (i)) != -1)
3210  return rxfun1.cap (1).remove (QRegExp ("[ \t]*"));
3211  else if (rxfun2.indexIn (lines.at (i)) != -1)
3212  return rxfun2.cap (1).remove (QRegExp ("[ \t]*"));
3213  else if (rxfun3.indexIn (lines.at (i)) != -1)
3214  return rxfun3.cap (1).remove (QRegExp ("[ \t]*"));
3215  else if (rxfun4.indexIn (lines.at (i)) != -1)
3216  return rxfun4.cap (1).remove (QRegExp ("[ \t]*"));
3217  else if (rxfun5.indexIn (lines.at (i)) != -1)
3218  return rxfun5.cap (1).remove (QRegExp ("[ \t]*"));
3219  }
3220 
3221  return QString ();
3222  }
3223 }
3224 
3225 #endif
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:377
Definition: Cell.h:43
Base class for Octave interfaces that use Qt.
resource_manager & get_resource_manager(void)
bool condition_valid(const std::string &cond)
Definition: bp-table.cc:222
std::map< int, int > intmap
Definition: bp-table.h:65
static bool interrupt(bool=true)
Definition: cmd-edit.cc:1616
void interpreter_try(octave::unwind_protect &frame)
Definition: error.cc:858
void set_encoding(const QString &new_encoding)
void confirm_dbquit_and_save_signal(const QString &file_to_save, const QString &base_name, bool remove_on_success, bool restore_breakpoints)
void set_file_name(const QString &fileName)
void goto_line(const QWidget *ID, int line=-1)
void report_marker_linenr(QIntList &lines, QStringList &conditions)
QsciScintilla::EolMode m_save_as_desired_eol
void toggle_breakpoint(const QWidget *ID)
void zoom_out(const QWidget *ID)
void delete_debugger_pointer(const QWidget *ID, int line=-1)
void run_file(const QWidget *ID, bool step_into=false)
void remove_breakpoint_via_debugger_linenr(int debugger_linenr)
void save_file_as(const QWidget *ID)
void tab_ready_to_close(void)
void set_current_directory(const QString &dir)
void handle_file_resave_answer(int decision)
file_editor_tab(base_qobject &oct_qobj, const QString &directory="")
A file_editor_tab object consists of a text area and three left margins.
void handle_margin_clicked(int line, int margin, Qt::KeyboardModifiers state)
void editor_state_changed(bool copy_available, bool is_octave_file)
void handle_save_as_filter_selected(const QString &filter)
void handle_double_click(int p, int l, int modifier)
octave_qscintilla * m_edit_area
void set_modified(bool modified=true)
void dbstop_if(const QString &prompt, int line, const QString &cond)
void handle_request_add_breakpoint(int line, const QString &cond)
void zoom_normal(const QWidget *ID)
void set_focus_editor_signal(QWidget *)
void editor_check_conflict_save(const QString &saveFileName, bool remove_on_success)
void interpreter_event(const fcn_callback &fcn)
bool check_valid_identifier(QString file_name)
QString load_file(const QString &fileName)
void next_bookmark(const QWidget *ID)
void previous_breakpoint(const QWidget *ID)
void convert_eol(const QWidget *ID, QsciScintilla::EolMode)
void save_file(const QWidget *ID)
void handle_save_file_as_answer(const QString &fileName)
void add_breakpoint_event(const bp_info &info)
void toggle_bookmark(const QWidget *ID)
void do_comment_selected_text(bool comment, bool input_str=false)
void handle_remove_next(int remove_line)
void handle_file_reload_answer(int decision)
void notice_settings(const gui_settings *settings, bool init=false)
void set_focus(const QWidget *ID)
QFileSystemWatcher m_file_system_watcher
void unindent_selected_text(const QWidget *ID)
void update_window_title(bool modified)
void handle_cursor_moved(int line, int col)
void do_breakpoint_marker(bool insert, const QWidget *ID, int line=-1, const QString &cond="")
void request_remove_breakpoint_via_editor_linenr(int editor_linenr)
void update_breakpoints_signal(const octave_value_list &args)
void handle_save_file_as_answer_close(const QString &fileName)
void insert_debugger_pointer(const QWidget *ID, int line=-1)
void move_match_brace(const QWidget *ID, bool select)
void next_breakpoint(const QWidget *ID)
void find_linenr_just_before(int linenr, int &original_linenr, int &editor_linenr)
void show_dialog(QDialog *dlg, bool modal)
void edit_mfile_request(const QString &, const QString &, const QString &, int)
void do_indent_selected_text(bool indent)
void handle_context_menu_edit(const QString &)
void previous_bookmark(const QWidget *ID)
void request_add_breakpoint(int line, const QString &cond)
void update_breakpoints_handler(const octave_value_list &argout)
void handle_save_file_as_answer_cancel(void)
void print_file(const QWidget *ID)
void do_save_file_signal(const QString &file_to_save, bool remove_on_success, bool restore_breakpoints)
void run_file_signal(const QFileInfo &info)
void indent_selected_text(const QWidget *ID)
bool valid_file_name(const QString &file=QString())
void file_name_changed(const QString &fileName, const QString &toolTip, bool modified)
void smart_indent_line_or_selected_text(const QWidget *ID)
void closeEvent(QCloseEvent *event)
base_qobject & m_octave_qobj
void do_save_file(const QString &file_to_save, bool remove_on_success, bool restore_breakpoints)
int check_file_modified(bool remove=false)
void context_edit(const QWidget *ID)
void uncomment_selected_text(const QWidget *ID)
void handle_current_enc_changed(const QString &enc)
void tab_remove_request(void)
void do_smart_indent_line_or_selected_text(void)
void handle_add_octave_apis(const QStringList &api_entries)
void context_help(const QWidget *ID, bool)
void remove_bookmark(const QWidget *ID)
void find_translated_line_number(int original_linenr, int &translated_linenr, marker *&)
void context_run(const QWidget *ID)
void handle_decode_warning_answer(QAbstractButton *btn)
void confirm_dbquit_and_save(const QString &file_to_save, const QString &base_name, bool remove_on_success, bool restore_breakpoints)
void scintilla_command(const QWidget *, unsigned int)
void comment_selected_text(const QWidget *ID, bool input_str)
QsciScintilla::EolMode detect_eol_mode(void)
void change_editor_state(const QWidget *ID)
void maybe_remove_next(int remove_line)
void edit_area_changed(octave_qscintilla *edit_area)
void remove_all_breakpoints(void)
void zoom_in(const QWidget *ID)
void remove_all_positions(void)
void handle_context_menu_break_condition(int linenr)
void handle_char_added(int character)
void file_has_changed(const QString &path, bool do_close=false)
void handle_request_remove_breakpoint(int line)
void mru_add_file(const QString &file_name, const QString &encoding)
void api_entries_added(void)
void request_open_file(const QString &, const QString &=QString())
void handle_dbstop_if(const QString &prompt, int line, const QString &cond)
void center_current_line(bool always=true)
void remove_position_via_debugger_linenr(int debugger_linenr)
QString file_name(void) const
QTextCodec * check_valid_codec(void)
void new_file(const QString &commands=QString())
void show_auto_completion(const QWidget *ID)
breakpoint_info m_breakpoint_info
void request_add_octave_apis(const QStringList &)
void handle_copy_available(bool enableCopy)
std::list< std::string > autoloaded_functions(void) const
load_path & get_load_path(void)
Definition: interpreter.h:243
void recover_from_exception(void)
symbol_table & get_symbol_table(void)
Definition: interpreter.h:258
error_system & get_error_system(void)
Definition: interpreter.h:213
tree_evaluator & get_evaluator(void)
lexer(interpreter &interp)
Definition: lex.h:767
bool contains_file_in_dir(const std::string &file_name, const std::string &dir_name)
Definition: load-path.cc:435
string_vector fcn_names(void) const
Definition: load-path.cc:886
@ unsure_debugger_position
Definition: marker.h:60
@ debugger_position
Definition: marker.h:59
const QString & get_cond(void) const
Definition: marker.h:73
void set_cond(const QString &cond)
Definition: marker.h:75
void get_current_position(int *pos, int *line, int *col)
void smart_indent_line_or_selected_text(int lineFrom, int lineTo)
void set_selection_marker_color(const QColor &c)
void smart_indent(bool do_smart_indent, int do_auto_close, int line, int ind_char_width)
void set_word_selection(const QString &word=QString())
QStringList comment_string(bool comment=true)
void show_selection_markers(int l1, int c1, int l2, int c2)
void combo_encoding(QComboBox *combo, const QString &current=QString())
gui_settings * get_settings(void) const
void clear_user_function(const std::string &name)
Definition: symtab.cc:469
std::list< std::string > built_in_function_names(void)
Definition: symtab.cc:597
bool in_debug_repl(void) const
Definition: pt-eval.cc:4076
bp_table & get_bp_table(void)
Definition: pt-eval.h:372
void dbquit(bool all=false)
Definition: pt-eval.cc:4088
octave_map backtrace(octave_idx_type &curr_user_frame, bool print_subfn=true) const
Definition: pt-eval.cc:1984
virtual octave_user_code * user_code_value(bool silent=false)
Definition: ov-base.cc:900
octave_idx_type numel(void) const
Definition: oct-map.h:389
const Cell & contents(const_iterator p) const
Definition: oct-map.h:331
Cell cell_value(void) const
Definition: ovl.h:105
octave_idx_type numel(void) const
Definition: str-vec.h:100
OCTAVE_EXPORT octave_value_list Fdbstatus(octave::interpreter &interp, const octave_value_list &args, int nargout)
Definition: debug.cc:382
QList< int > QIntList
Definition: dialog.h:40
MArray< T > filter(MArray< T > &b, MArray< T > &a, MArray< T > &x, MArray< T > &si, int dim=0)
Definition: filter.cc:46
const gui_pref cs_cursor_blinking("terminal/cursorBlinking", QVariant(true))
const gui_pref ed_always_reload_changed_files("editor/always_reload_changed_files", QVariant(false))
const gui_pref ed_indent_uses_tabs("editor/indent_uses_tabs", QVariant(false))
const gui_pref ed_code_completion("editor/codeCompletion", QVariant(true))
const gui_pref ed_show_hscroll_bar("editor/show_hscroll_bar", QVariant(true))
const gui_pref ed_code_completion_octave_functions("editor/codeCompletion_octave_functions", QVariant(true))
const gui_pref ed_auto_endif("editor/auto_endif", QVariant(1))
const gui_pref ed_highlight_current_line_color("editor/highlight_current_line_color", QVariant(QColor(240, 240, 240)))
const gui_pref ed_long_window_title("editor/longWindowTitle", QVariant(false))
const gui_pref ed_show_white_space("editor/show_white_space", QVariant(false))
const gui_pref ed_default_enc("editor/default_encoding", QVariant(QString("SYSTEM (")+QString(octave_locale_charset_wrapper()).toUpper()+QString(")")))
const gui_pref ed_line_numbers_size("editor/line_numbers_size", QVariant(0))
const gui_pref ed_wrap_lines("editor/wrap_lines", QVariant(false))
const gui_pref ed_highlight_current_line("editor/highlightCurrentLine", QVariant(true))
const gui_pref ed_code_completion_threshold("editor/codeCompletion_threshold", QVariant(2))
const gui_pref ed_break_lines("editor/break_lines", QVariant(false))
const gui_pref ed_break_lines_comments("editor/break_lines_comments", QVariant(false))
const QString ed_last_comment_str("editor/oct_last_comment_str")
const gui_pref ed_long_line_marker("editor/long_line_marker", QVariant(true))
const gui_pref ed_tab_indents_line("editor/tab_indents_line", QVariant(false))
const gui_pref ed_code_completion_octave_builtins("editor/codeCompletion_octave_builtins", QVariant(true))
const gui_pref ed_show_edit_status_bar("editor/show_edit_status_bar", QVariant(true))
const gui_pref ed_code_completion_document("editor/codeCompletion_document", QVariant(true))
const gui_pref ed_backspace_unindents_line("editor/backspace_unindents_line", QVariant(false))
const gui_pref ed_code_completion_case("editor/codeCompletion_case", QVariant(true))
const gui_pref ed_long_line_marker_background("editor/long_line_marker_background", QVariant(false))
const gui_pref ed_show_white_space_indent("editor/show_white_space_indent", QVariant(false))
const gui_pref ed_show_line_numbers("editor/showLineNumbers", QVariant(true))
const gui_pref ed_highlight_all_occurrences("editor/highlight_all_occurrences", QVariant(true))
const gui_pref ed_show_eol_chars("editor/show_eol_chars", QVariant(false))
const gui_pref ed_show_indent_guides("editor/show_indent_guides", QVariant(false))
const gui_pref ed_code_folding("editor/code_folding", QVariant(true))
const gui_pref ed_indent_width("editor/indent_width", QVariant(2))
const gui_pref ed_tab_width("editor/tab_width", QVariant(2))
const gui_pref ed_long_line_column("editor/long_line_column", QVariant(80))
const gui_pref ed_default_eol_mode("editor/default_eol_mode", QVariant(os_eol_mode))
const gui_pref ed_code_completion_replace("editor/codeCompletion_replace", QVariant(false))
const gui_pref ed_code_completion_keywords("editor/codeCompletion_keywords", QVariant(true))
const gui_pref ed_auto_indent("editor/auto_indent", QVariant(true))
const gui_pref ed_long_line_marker_line("editor/long_line_marker_line", QVariant(true))
const gui_pref global_use_native_dialogs("use_native_file_dialogs", QVariant(true))
const gui_pref global_cursor_blinking("cursor_blinking", QVariant(true))
OCTAVE_EXPORT octave_value_list Fiskeyword(const octave_value_list &args, int)
Definition: lex.cc:4971
const char * octave_locale_charset_wrapper(void)
#define OCTAVE_VERSION
Definition: main.in.cc:52
for(octave_idx_type i=0;i< n;i++) ac+
Definition: mx-inlines.cc:756
QString fromStdString(const std::string &s)
std::string toStdString(const QString &s)
std::string concat(const std::string &dir, const std::string &file)
Definition: file-ops.cc:354
std::string dir_sep_chars(void)
Definition: file-ops.cc:252
std::string canonicalize_file_name(const std::string &name)
Definition: file-ops.cc:693
static uint32_t state[624]
Definition: randmtzig.cc:190
bool valid_identifier(const char *s)
Definition: utils.cc:77
std::function< void(octave::interpreter &)> meth_callback
Definition: event-manager.h:47
static double f(double k, double l_nu, double c_pm)
Definition: randpoisson.cc:118
std::function< void(void)> fcn_callback
Definition: event-manager.h:46
#define lexer
Definition: oct-parse.cc:146
void free(void *)
octave_value::octave_value(const Array< char > &chm, char type) return retval
Definition: ov.cc:811
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:211
static OCTAVE_NORETURN void eval_error(const char *msg, const dim_vector &x, const dim_vector &y)
Definition: pt-tm-const.cc:56
const QString key
bp_info(const QString &fname, int l=0, const QString &cond="")
char * octave_u32_conv_to_encoding_strict(const char *tocode, const uint32_t *src, size_t srclen, size_t *lengthp)
F77_RET_T len
Definition: xerbla.cc:61