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