GNU Octave 7.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
octave-qscintilla.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2013-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#if defined (HAVE_CONFIG_H)
27# include "config.h"
28#endif
29
30#if defined (HAVE_QSCINTILLA)
31
32#include <Qsci/qscilexer.h>
33
34#include <QDir>
35#include <QKeySequence>
36#include <QMessageBox>
37#include <QMimeData>
38#include <QShortcut>
39#include <QToolTip>
40#include <QVBoxLayout>
41#if defined (HAVE_QSCI_QSCILEXEROCTAVE_H)
42# define HAVE_LEXER_OCTAVE 1
43# include <Qsci/qscilexeroctave.h>
44#elif defined (HAVE_QSCI_QSCILEXERMATLAB_H)
45# define HAVE_LEXER_MATLAB 1
46# include <Qsci/qscilexermatlab.h>
47#endif
48#include <Qsci/qscicommandset.h>
49#include <Qsci/qscilexerbash.h>
50#include <Qsci/qscilexerbatch.h>
51#include <Qsci/qscilexercpp.h>
52#include <Qsci/qscilexerdiff.h>
53#include <Qsci/qscilexerperl.h>
54
55#include "file-editor-tab.h"
56#include "gui-preferences-ed.h"
57// FIXME: hardwired marker numbers?
58#include "marker.h"
59#include "octave-qobject.h"
60#include "octave-qscintilla.h"
61#include "shortcut-manager.h"
62#include "workspace-model.h"
63
64#include "builtin-defun-decls.h"
65#include "cmd-edit.h"
66#include "interpreter-private.h"
67#include "interpreter.h"
68
69// Return true if CANDIDATE is a "closing" that matches OPENING,
70// such as "end" or "endif" for "if", or "catch" for "try".
71// Used for testing the last word of an "if" etc. line,
72// or the first word of the following line.
73
74namespace octave
75{
76 static bool
77 is_end (const QString& candidate, const QString& opening)
78 {
79 bool retval = false;
80
81 if (opening == "do") // The only one that can't be ended by "end"
82 {
83 if (candidate == "until")
84 retval = true;
85 }
86 else
87 {
88 if (candidate == "end")
89 retval = true;
90 else
91 {
92 if (opening == "try")
93 {
94 if (candidate == "catch" || candidate == "end_try_catch")
95 retval = true;
96 }
97 else if (opening == "unwind_protect")
98 {
99 if (candidate == "unwind_protect_cleanup"
100 || candidate == "end_unwind_protect")
101 retval = true;
102 }
103 else if (candidate == "end" + opening)
104 retval = true;
105 else if (opening == "if" && candidate == "else")
106 retval = true;
107 }
108 }
109
110 return retval;
111 }
112
114 : QsciScintilla (p), m_octave_qobj (oct_qobj), m_debug_mode (false),
115 m_word_at_cursor (), m_selection (), m_selection_replacement (),
116 m_selection_line (-1), m_selection_col (-1), m_indicator_id (1)
117 {
118 connect (this, SIGNAL (textChanged (void)),
119 this, SLOT (text_changed (void)));
120
121 connect (this, SIGNAL (cursorPositionChanged (int, int)),
122 this, SLOT (cursor_position_changed (int, int)));
123
126 Qt::QueuedConnection);
127
128 // clear scintilla edit shortcuts that are handled by the editor
129 QsciCommandSet *cmd_set = standardCommands ();
130
131 // Disable buffered drawing on all systems
132 SendScintilla (SCI_SETBUFFEREDDRAW, false);
133
134#if defined (HAVE_QSCI_VERSION_2_6_0)
135 // find () was added in QScintilla 2.6
136 cmd_set->find (QsciCommand::SelectionCopy)->setKey (0);
137 cmd_set->find (QsciCommand::SelectionCut)->setKey (0);
138 cmd_set->find (QsciCommand::Paste)->setKey (0);
139 cmd_set->find (QsciCommand::SelectAll)->setKey (0);
140 cmd_set->find (QsciCommand::SelectionDuplicate)->setKey (0);
141 cmd_set->find (QsciCommand::LineTranspose)->setKey (0);
142 cmd_set->find (QsciCommand::Undo)->setKey (0);
143 cmd_set->find (QsciCommand::Redo)->setKey (0);
144 cmd_set->find (QsciCommand::SelectionUpperCase)->setKey (0);
145 cmd_set->find (QsciCommand::SelectionLowerCase)->setKey (0);
146 cmd_set->find (QsciCommand::ZoomIn)->setKey (0);
147 cmd_set->find (QsciCommand::ZoomOut)->setKey (0);
148 cmd_set->find (QsciCommand::DeleteWordLeft)->setKey (0);
149 cmd_set->find (QsciCommand::DeleteWordRight)->setKey (0);
150 cmd_set->find (QsciCommand::DeleteLineLeft)->setKey (0);
151 cmd_set->find (QsciCommand::DeleteLineRight)->setKey (0);
152 cmd_set->find (QsciCommand::LineDelete)->setKey (0);
153 cmd_set->find (QsciCommand::LineCut)->setKey (0);
154 cmd_set->find (QsciCommand::LineCopy)->setKey (0);
155#else
156 // find commands via its default key (tricky way without find ())
157 QList< QsciCommand * > cmd_list = cmd_set->commands ();
158 for (int i = 0; i < cmd_list.length (); i++)
159 {
160 int cmd_key = cmd_list.at (i)->key ();
161 switch (cmd_key)
162 {
163 case Qt::Key_C | Qt::CTRL : // SelectionCopy
164 case Qt::Key_X | Qt::CTRL : // SelectionCut
165 case Qt::Key_V | Qt::CTRL : // Paste
166 case Qt::Key_A | Qt::CTRL : // SelectAll
167 case Qt::Key_D | Qt::CTRL : // SelectionDuplicate
168 case Qt::Key_T | Qt::CTRL : // LineTranspose
169 case Qt::Key_Z | Qt::CTRL : // Undo
170 case Qt::Key_Y | Qt::CTRL : // Redo
171 case Qt::Key_Z | Qt::CTRL | Qt::SHIFT : // Redo
172 case Qt::Key_U | Qt::CTRL : // SelectionLowerCase
173 case Qt::Key_U | Qt::CTRL | Qt::SHIFT : // SelectionUpperCase
174 case Qt::Key_Plus | Qt::CTRL : // ZoomIn
175 case Qt::Key_Minus | Qt::CTRL : // ZoomOut
176 case Qt::Key_Backspace | Qt::CTRL | Qt::SHIFT : // DeleteLineLeft
177 case Qt::Key_Delete | Qt::CTRL | Qt::SHIFT : // DeleteLineRight
178 case Qt::Key_K | Qt::META : // DeleteLineRight
179 case Qt::Key_Backspace | Qt::CTRL : // DeleteWordLeft
180 case Qt::Key_Delete | Qt::CTRL : // DeleteWordRight
181 case Qt::Key_L | Qt::CTRL | Qt::SHIFT : // LineDelete
182 case Qt::Key_L | Qt::CTRL : // LineCut
183 case Qt::Key_T | Qt::CTRL | Qt::SHIFT : // LineCopy
184 cmd_list.at (i)->setKey (0);
185 }
186 }
187#endif
188
189#if defined (Q_OS_MAC)
190 // Octave interprets Cmd key as Meta whereas Qscintilla interprets it
191 // as Ctrl. We thus invert Meta/Ctrl in Qscintilla's shortcuts list.
192 QList< QsciCommand * > cmd_list_mac = cmd_set->commands ();
193 for (int i = 0; i < cmd_list_mac.length (); i++)
194 {
195 // Primary key
196 int key = cmd_list_mac.at (i)->key ();
197
198 if (static_cast<int> (key | Qt::META) == key
199 && static_cast<int> (key | Qt::CTRL) != key)
200 key = (key ^ Qt::META) | Qt::CTRL;
201 else if (static_cast<int> (key | Qt::CTRL) == key)
202 key = (key ^ Qt::CTRL) | Qt::META;
203
204 cmd_list_mac.at (i)->setKey (key);
205
206 // Alternate key
207 key = cmd_list_mac.at (i)->alternateKey ();
208
209 if (static_cast<int> (key | Qt::META) == key
210 && static_cast<int> (key | Qt::CTRL) != key)
211 key = (key ^ Qt::META) | Qt::CTRL;
212 else if (static_cast<int> (key | Qt::CTRL) == key)
213 key = (key ^ Qt::CTRL) | Qt::META;
214
215 cmd_list_mac.at (i)->setAlternateKey (key);
216 }
217#endif
218
219 // selection markers
220
221 m_indicator_id = indicatorDefine (QsciScintilla::StraightBoxIndicator);
222 if (m_indicator_id == -1)
223 m_indicator_id = 1;
224
225 setIndicatorDrawUnder (true, m_indicator_id);
226
227 markerDefine (QsciScintilla::Minus, marker::selection);
228
229 // init state of undo/redo action for this tab
230 emit status_update (isUndoAvailable (), isRedoAvailable ());
231 }
232
234 {
235 QsciScintilla::setCursorPosition (line, col);
236 emit update_rowcol_indicator_signal (line, col);
237 }
238
240 {
241 QColor ic = c;
242 ic.setAlphaF (0.45);
243 setIndicatorForegroundColor (ic, m_indicator_id);
244 setIndicatorOutlineColor (ic, m_indicator_id);
245
246 setMarkerForegroundColor (c, marker::selection);
247 setMarkerBackgroundColor (c, marker::selection);
248 }
249
250 // context menu requested
251 void octave_qscintilla::contextMenuEvent (QContextMenuEvent *e)
252 {
253#if defined (HAVE_QSCI_VERSION_2_6_0)
254 QPoint global_pos, local_pos; // the menu's position
255 QMenu *context_menu = createStandardContextMenu (); // standard menu
256
257 bool in_left_margin = false;
258
259 // determine position depending on mouse or keyboard event
260 if (e->reason () == QContextMenuEvent::Mouse)
261 {
262 // context menu by mouse
263 global_pos = e->globalPos (); // global mouse position
264 local_pos = e->pos (); // local mouse position
265 if (e->x () < marginWidth (1) + marginWidth (2))
266 in_left_margin = true;
267 }
268 else
269 {
270 // context menu by keyboard or other: get point of text cursor
271 get_global_textcursor_pos (&global_pos, &local_pos);
272 QRect editor_rect = geometry (); // editor rect mapped to global
273 editor_rect.moveTopLeft
274 (parentWidget ()->mapToGlobal (editor_rect.topLeft ()));
275 if (! editor_rect.contains (global_pos)) // is cursor outside editor?
276 global_pos = editor_rect.topLeft (); // yes, take top left corner
277 }
278
279# if defined (HAVE_QSCI_VERSION_2_6_0)
280 if (! in_left_margin)
281# endif
282 {
283 // fill context menu with editor's standard actions
284 emit create_context_menu_signal (context_menu);
285
286 // additional custom entries of the context menu
287 context_menu->addSeparator (); // separator before custom entries
288
289 // help menu: get the position of the mouse or the text cursor
290 // (only for octave files)
291 QString lexer_name = lexer ()->lexer ();
292 if (lexer_name == "octave" || lexer_name == "matlab")
293 {
294 m_word_at_cursor = wordAtPoint (local_pos);
295 if (! m_word_at_cursor.isEmpty ())
296 {
297 context_menu->addAction (tr ("Help on") + ' ' + m_word_at_cursor,
299 context_menu->addAction (tr ("Documentation on")
300 + ' ' + m_word_at_cursor,
302 context_menu->addAction (tr ("Edit") + ' ' + m_word_at_cursor,
304 }
305 }
306 }
307# if defined (HAVE_QSCI_VERSION_2_6_0)
308 else
309 {
310 // remove all standard actions from scintilla
311 QList<QAction *> all_actions = context_menu->actions ();
312
313 for (auto *a : all_actions)
314 context_menu->removeAction (a);
315
316 QAction *act
317 = context_menu->addAction (tr ("dbstop if ..."), this,
319 act->setData (local_pos);
320 }
321# endif
322
323 // finally show the menu
324 context_menu->exec (global_pos);
325
326#else
327
328 octave_unused_parameter (e);
329
330#endif
331 }
332
333 // common function with flag for documentation
335 {
336 if (documentation)
338 else
340 }
341
342 // call edit the function related to the current word
344 {
345 if (get_actual_word ())
346 contextmenu_edit (true);
347 }
348
349 // call edit the function related to the current word
351 {
352 if (hasSelectedText ())
353 {
354 contextmenu_run (true);
355
357 ([] (interpreter&)
359 }
360 }
361
363 QPoint *local_pos)
364 {
365 long position = SendScintilla (SCI_GETCURRENTPOS);
366 long point_x = SendScintilla (SCI_POINTXFROMPOSITION, 0, position);
367 long point_y = SendScintilla (SCI_POINTYFROMPOSITION, 0, position);
368 *local_pos = QPoint (point_x, point_y); // local cursor position
369 *global_pos = mapToGlobal (*local_pos); // global position of cursor
370 }
371
372 // determine the actual word and whether we are in an octave or matlab script
374 {
375 QPoint global_pos, local_pos;
376 get_global_textcursor_pos (&global_pos, &local_pos);
377 m_word_at_cursor = wordAtPoint (local_pos);
378 QString lexer_name = lexer ()->lexer ();
379 return ((lexer_name == "octave" || lexer_name == "matlab")
380 && ! m_word_at_cursor.isEmpty ());
381 }
382
383 // helper function for clearing all indicators of a specific style
385 {
386 int end_pos = text ().length ();
387 int end_line, end_col;
388 lineIndexFromPosition (end_pos, &end_line, &end_col);
389 clearIndicatorRange (0, 0, end_line, end_col, m_indicator_id);
390
391 markerDeleteAll (marker::selection);
392 }
393
395 {
396 switch (eolMode ())
397 {
398 case QsciScintilla::EolWindows:
399 return ("\r\n");
400 case QsciScintilla::EolMac:
401 return ("\r");
402 case QsciScintilla::EolUnix:
403 return ("\n");
404 }
405
406 // Last resort, if the above goes wrong (should never happen)
407 return ("\r\n");
408 }
409
410 // Function returning the true cursor position where the tab length
411 // is taken into account.
412 void octave_qscintilla::get_current_position (int *pos, int *line, int *col)
413 {
414 *pos = SendScintilla (QsciScintillaBase::SCI_GETCURRENTPOS);
415 *line = SendScintilla (QsciScintillaBase::SCI_LINEFROMPOSITION, *pos);
416 *col = SendScintilla (QsciScintillaBase::SCI_GETCOLUMN, *pos);
417 }
418
419 // Function returning the comment string of the current lexer
420 QStringList octave_qscintilla::comment_string (bool comment)
421 {
422 int lexer = SendScintilla (SCI_GETLEXER);
423
424 switch (lexer)
425 {
426#if defined (HAVE_LEXER_OCTAVE) || defined (HAVE_LEXER_MATLAB)
427#if defined (HAVE_LEXER_OCTAVE)
428 case SCLEX_OCTAVE:
429#else
430 case SCLEX_MATLAB:
431#endif
432 {
435 int comment_string;
436
437 if (comment)
438 {
439 // The commenting string is requested
440 if (settings->contains (ed_comment_str.key))
441 // new version (radio buttons)
442 comment_string = settings->value (ed_comment_str).toInt ();
443 else
444 // old version (combo box)
446 ed_comment_str.def).toInt ();
447
448 return (QStringList (ed_comment_strings.at (comment_string)));
449 }
450 else
451 {
452 QStringList c_str;
453
454 // The possible uncommenting string(s) are requested
455 comment_string = settings->value (ed_uncomment_str).toInt ();
456
457 for (int i = 0; i < ed_comment_strings_count; i++)
458 {
459 if (1 << i & comment_string)
460 c_str.append (ed_comment_strings.at (i));
461 }
462
463 return c_str;
464 }
465
466 }
467#endif
468
469 case SCLEX_PERL:
470 case SCLEX_BASH:
471 case SCLEX_DIFF:
472 return QStringList ("#");
473
474 case SCLEX_CPP:
475 return QStringList ("//");
476
477 case SCLEX_BATCH:
478 return QStringList ("REM ");
479 }
480
481 return QStringList ("%"); // should never happen
482 }
483
484
485 // provide the style at a specific position
487 {
488 int position;
489 if (pos < 0)
490 // The positition has to be reduced by 2 for getting the real style (?)
491 position = SendScintilla (QsciScintillaBase::SCI_GETCURRENTPOS) - 2;
492 else
493 position = pos;
494
495 return SendScintilla (QsciScintillaBase::SCI_GETSTYLEAT, position);
496 }
497
498 // Is a specific cursor position in a line or block comment?
500 {
501 int lexer = SendScintilla (QsciScintillaBase::SCI_GETLEXER);
502 int style = get_style (pos);
503
504 switch (lexer)
505 {
506 case SCLEX_CPP:
507 return (ST_LINE_COMMENT * (style == QsciLexerCPP::CommentLine
508 || style == QsciLexerCPP::CommentLineDoc)
509 + ST_BLOCK_COMMENT * (style == QsciLexerCPP::Comment
510 || style == QsciLexerCPP::CommentDoc
511 || style == QsciLexerCPP::CommentDocKeyword
512 || style == QsciLexerCPP::CommentDocKeywordError));
513
514#if defined (HAVE_LEXER_MATLAB)
515 case SCLEX_MATLAB:
516 return (ST_LINE_COMMENT * (style == QsciLexerMatlab::Comment));
517#endif
518#if defined (HAVE_LEXER_OCTAVE)
519 case SCLEX_OCTAVE:
520 return (ST_LINE_COMMENT * (style == QsciLexerOctave::Comment));
521#endif
522
523 case SCLEX_PERL:
524 return (ST_LINE_COMMENT * (style == QsciLexerPerl::Comment));
525
526 case SCLEX_BATCH:
527 return (ST_LINE_COMMENT * (style == QsciLexerBatch::Comment));
528
529 case SCLEX_DIFF:
530 return (ST_LINE_COMMENT * (style == QsciLexerDiff::Comment));
531
532 case SCLEX_BASH:
533 return (ST_LINE_COMMENT * (style == QsciLexerBash::Comment));
534
535 }
536
537 return ST_NONE;
538 }
539
540 // Do smart indentation after if, for, ...
541 void octave_qscintilla::smart_indent (bool do_smart_indent, int do_auto_close,
542 int line, int ind_char_width)
543 {
544 QString prevline = text (line);
545
546 QRegExp bkey = QRegExp ("^[\t ]*(if|for|while|switch"
547 "|do|function|properties|events|classdef"
548 "|unwind_protect|try"
549 "|parfor|methods)"
550 "[\r]?[\n\t #%]");
551 // last word except for comments, assuming no ' or " in comment.
552 // rx_end = QRegExp ("(\\w+)[ \t;\r\n]*([%#][^\"']*)?$");
553
554 // last word except for comments,
555 // allowing % and # in single or double quoted strings
556 // FIXME: This will get confused by transpose.
557 QRegExp ekey = QRegExp ("(?:(?:['\"][^'\"]*['\"])?[^%#]*)*"
558 "(\\w+)[ \t;\r\n]*(?:[%#].*)?$");
559
560 int bpos = bkey.indexIn (prevline, 0);
561 int epos;
562
563 if (bpos > -1)
564 {
565 // Found keyword after that indentation should be added
566
567 // Check for existing end statement in the same line
568 epos = ekey.indexIn (prevline, bpos);
569 QString first_word = bkey.cap(1);
570 bool inline_end = (epos > -1) && is_end (ekey.cap(1), first_word);
571
572 if (do_smart_indent && ! inline_end)
573 {
574 // Do smart indent in the current line (line+1)
575 indent (line+1);
576 setCursorPosition (line+1, indentation (line+1) / ind_char_width);
577 }
578
579 if (do_auto_close
580 && ! inline_end
581 && ! first_word.contains (QRegExp ("(?:case|otherwise|unwind_protect_cleanup)")))
582 {
583 // Do auto close
584 auto_close (do_auto_close, line, prevline, first_word);
585 }
586
587 return;
588 }
589
590 QRegExp mkey = QRegExp ("^[\t ]*(?:else|elseif|catch|unwind_protect_cleanup)"
591 "[\r]?[\t #%\n]");
592 if (prevline.contains (mkey))
593 {
594 int prev_ind = indentation (line-1);
595 int act_ind = indentation (line);
596
597 if (prev_ind == act_ind)
598 unindent (line);
599 else if (prev_ind > act_ind)
600 {
601 setIndentation (line+1, prev_ind);
602 setCursorPosition (line+1, prev_ind);
603 }
604 return;
605 }
606
607 QRegExp case_key = QRegExp ("^[\t ]*(?:case|otherwise)[\r]?[\t #%\n]");
608 if (prevline.contains (case_key) && do_smart_indent)
609 {
610 QString last_line = text (line-1);
611 int prev_ind = indentation (line-1);
612 int act_ind = indentation (line);
613
614 if (last_line.contains (QRegExp ("^[\t ]*switch")))
615 {
616 indent (line+1);
617 act_ind = indentation (line+1);
618 }
619 else
620 {
621 if (prev_ind == act_ind)
622 unindent (line);
623 else if (prev_ind > act_ind)
624 act_ind = prev_ind;
625 }
626
627 setIndentation (line+1, act_ind);
628 setCursorPosition (line+1, act_ind);
629 }
630
631 ekey = QRegExp ("^[\t ]*(?:end|endif|endfor|endwhile|until|endfunction"
632 "|endswitch|end_try_catch|end_unwind_protect)[\r]?[\t #%\n(;]");
633 if (prevline.contains (ekey))
634 {
635 if (indentation (line-1) <= indentation (line))
636 {
637 unindent (line+1);
638 unindent (line);
639 if (prevline.contains ("endswitch"))
640 {
641 // endswitch has to me unndented twice
642 unindent (line+1);
643 unindent (line);
644 }
645 setCursorPosition (line+1,
646 indentation (line));
647 }
648 return;
649 }
650 }
651
652 // Do smart indentation of current selection or line.
654 int lineTo)
655 {
656 QRegExp blank_line_regexp = QRegExp ("^[\t ]*$");
657
658 // end[xxxxx] [# comment] at end of a line
659 QRegExp end_word_regexp
660 = QRegExp ("(?:(?:['\"][^'\"]*['\"])?[^%#]*)*"
661 "(?:end\\w*)[\r\n\t ;]*(?:[%#].*)?$");
662
663 QRegExp begin_block_regexp
664 = QRegExp ("^[\t ]*(?:if|elseif|else"
665 "|for|while|do|parfor"
666 "|switch|case|otherwise"
667 "|function"
668 "|classdef|properties|events|enumeration|methods"
669 "|unwind_protect|unwind_protect_cleanup|try|catch)"
670 "[\r\n\t #%]");
671
672 QRegExp mid_block_regexp
673 = QRegExp ("^[\t ]*(?:elseif|else"
674 "|unwind_protect_cleanup|catch)"
675 "[\r\n\t #%]");
676
677 QRegExp end_block_regexp
678 = QRegExp ("^[\t ]*(?:end"
679 "|end(for|function|if|parfor|switch|while"
680 "|classdef|enumeration|events|methods|properties)"
681 "|end_(try_catch|unwind_protect)"
682 "|until)"
683 "[\r\n\t #%]");
684
685 QRegExp case_block_regexp
686 = QRegExp ("^[\t ]*(?:case|otherwise)"
687 "[\r\n\t #%]");
688
689 int indent_column = -1;
690 int indent_increment = indentationWidth ();
691 bool in_switch = false;
692
693 for (int line = lineFrom-1; line >= 0; line--)
694 {
695 QString line_text = text (line);
696
697 if (blank_line_regexp.indexIn (line_text) < 0)
698 {
699 // Found first non-blank line above beginning of region or
700 // current line. Base indentation from this line, increasing
701 // indentation by indentationWidth if it looks like the
702 // beginning of a code block.
703
704 indent_column = indentation (line);
705
706 if (begin_block_regexp.indexIn (line_text) > -1)
707 {
708 indent_column += indent_increment;
709 if (line_text.contains ("switch"))
710 in_switch = true;
711 }
712
713 break;
714 }
715 }
716
717 if (indent_column < 0)
718 indent_column = indentation (lineFrom);
719
720 QString prev_line;
721 for (int line = lineFrom; line <= lineTo; line++)
722 {
723 QString line_text = text (line);
724
725 if (end_block_regexp.indexIn (line_text) > -1)
726 {
727 indent_column -= indent_increment;
728 if (line_text.contains ("endswitch"))
729 {
730 // need a double de-indent for endswitch
731 if (in_switch)
732 indent_column -= indent_increment;
733 in_switch = false;
734 }
735 }
736
737 if (mid_block_regexp.indexIn (line_text) > -1)
738 indent_column -= indent_increment;
739
740 if (case_block_regexp.indexIn (line_text) > -1)
741 {
742 if (case_block_regexp.indexIn (prev_line) < 0
743 && !prev_line.contains("switch"))
744 indent_column -= indent_increment;
745 in_switch = true;
746 }
747
748 setIndentation (line, indent_column);
749
750
751 int bpos = begin_block_regexp.indexIn (line_text);
752 if (bpos > -1)
753 {
754 // Check for existing end statement in the same line
755 int epos = end_word_regexp.indexIn (line_text, bpos);
756 if (epos == -1)
757 indent_column += indent_increment;
758 if (line_text.contains ("switch"))
759 in_switch = true;
760 }
761
762 if (blank_line_regexp.indexIn (line_text) < 0)
763 prev_line = line_text;
764 }
765 }
766
767 void octave_qscintilla::set_word_selection (const QString& word)
768 {
769 m_selection = word;
770
771 if (word.isEmpty ())
772 {
773 m_selection_line = -1;
774 m_selection_col = -1;
775
777
779
780 QToolTip::hideText ();
781 }
782 else
783 {
784 int pos;
786 }
787 }
788
789 void octave_qscintilla::show_selection_markers (int l1, int c1, int l2, int c2)
790 {
791 fillIndicatorRange (l1, c1, l2, c2, m_indicator_id);
792
793 if (l1 == l2)
794 markerAdd (l1, marker::selection);
795 }
796
798 {
799 contextmenu_help_doc (false);
800 }
801
803 {
805 }
806
808 {
809 if (get_actual_word ())
811 }
812
814 {
816 }
817
819 {
820 QMessageBox::critical (this, tr ("Octave Editor"),
821 tr ("Creating temporary files failed.\n"
822 "Make sure you have write access to temp. directory\n"
823 "%1\n\n"
824 "\"Run Selection\" requires temporary files.").arg (QDir::tempPath ()));
825 }
826
828 {
830
831 // Take selected code and extend it by commands for echoing each
832 // evaluated line and for adding the line to the history (use script)
833 QString code = QString ();
834 QString hist = QString ();
835
836 // Split contents into single lines and complete commands
837 QStringList lines = selectedText ().split (QRegExp ("[\r\n]"),
838#if defined (HAVE_QT_SPLITBEHAVIOR_ENUM)
839 Qt::SkipEmptyParts);
840#else
841 QString::SkipEmptyParts);
842#endif
843 for (int i = 0; i < lines.count (); i++)
844 {
845 QString line = lines.at (i);
846 if (line.trimmed ().isEmpty ())
847 continue;
848 QString line_escaped = line;
849 line_escaped.replace (QString ("'"), QString ("''"));
850 QString line_history = line;
851
852 // Prevent output of breakpoint in temp. file for keyboard
853 QString next_bp_quiet;
854 QString next_bp_quiet_reset;
855 if (line.contains ("keyboard"))
856 {
857 // Define commands for not showing bp location and for resetting
858 // this in case "keyboard" was within a comment
859 next_bp_quiet = "__db_next_breakpoint_quiet__;\n";
860 next_bp_quiet_reset = "\n__db_next_breakpoint_quiet__(false);";
861 }
862
863 // Add codeline
864 code += next_bp_quiet + line + next_bp_quiet_reset + "\n";
865 hist += line_history + "\n";
866 }
867
868 octave_stdout << hist.toStdString ();
869
870 // Create tmp file with the code to be executed by the interpreter
871 QPointer<QTemporaryFile> tmp_file
872 = rmgr.create_tmp_file ("m", code);
873
874 bool tmp = (tmp_file && tmp_file->open ());
875 if (! tmp)
876 {
877 // tmp files not working: use old way to run selection
879 return;
880 }
881
882 tmp_file->close ();
883
884 // Create tmp file required for adding command to history
885 QPointer<QTemporaryFile> tmp_hist
886 = rmgr.create_tmp_file ("", hist); // empty tmp file for history
887
888 tmp = (tmp_hist && tmp_hist->open ());
889 if (! tmp)
890 {
891 // tmp files not working: use old way to run selection
893 return;
894 }
895
896 tmp_hist->close ();
897
898 // Add commands to the history
900 ([=] (interpreter& interp)
901 {
902 // INTERPRETER THREAD
903
904 std::string opt = "-r";
905 std::string path = tmp_hist->fileName ().toStdString ();
906
907 Fhistory (interp, ovl (opt, path));
908 });
909
910 // Disable opening a file at a breakpoint in case keyboard () is used
912 bool show_dbg_file = settings->value (ed_show_dbg_file).toBool ();
913 settings->setValue (ed_show_dbg_file.key, false);
914
915 // Let the interpreter execute the tmp file
917 ([=] (interpreter& interp)
918 {
919 // INTERPRETER THREAD
920
921 std::string file = tmp_file->fileName ().toStdString ();
922
923 std::string pending_input = command_editor::get_current_line ();
924
925 int err_line = -1; // For storing the line of a poss. error
926
927 // Get current state of auto command repeat in debug mode
928 octave_value_list ovl_dbg = Fisdebugmode (interp);
929 bool dbg = ovl_dbg(0).bool_value ();
930 octave_value_list ovl_auto_repeat = ovl (true);
931 if (dbg)
932 ovl_auto_repeat = Fauto_repeat_debug_command (interp, ovl (false), 1);
933 bool auto_repeat = ovl_auto_repeat(0).bool_value ();
934
935 try
936 {
937 // Do the job
938 interp.source_file (file);
939 }
940 catch (const execution_exception& ee)
941 {
942 // Catch errors otherwise the rest of the interpreter
943 // will not be executed (cleaning up).
944
945 // New error message and error stack
946 QString new_msg = QString::fromStdString (ee.message ());
947 std::list<frame_info> stack = ee.stack_info ();
948
949 // Remove line and column from first line of error message only
950 // if it is related to the tmp itself, i.e. only if the
951 // the error stack size is 0 or 1
952 if (stack.size () < 2)
953 {
954 QRegExp rx ("source: error sourcing file [^\n]*$");
955 if (new_msg.contains (rx))
956 {
957 // Selected code has syntax errors
958 new_msg.replace (rx, "error sourcing selected code");
959 err_line = 0; // Nothing into history?
960 }
961 else
962 {
963 // Normal error, detect line and remove file
964 // name from message
965 QStringList rx_list;
966 rx_list << "near line (\\d+),[^\n]*\n";
967 rx_list << "near line (\\d+),[^\n]*$";
968
969 QStringList replace_list;
970 replace_list << "\n";
971 replace_list << "";
972
973 for (int i = 0; i < rx_list.length (); i++)
974 {
975 int pos = 0;
976 rx = QRegExp (rx_list.at (i));
977 pos = rx.indexIn (new_msg, pos);
978 if (pos != -1)
979 {
980 err_line = rx.cap (1).toInt ();
981 new_msg = new_msg.replace (rx, replace_list.at (i));
982 }
983 }
984 }
985 }
986
987 // Drop first stack level, i.e. temporary function file
988 if (stack.size () > 0)
989 stack.pop_back ();
990
991 // Clean up before throwing the modified error.
992 emit ctx_menu_run_finished_signal (show_dbg_file, err_line,
993 tmp_file, tmp_hist,
994 dbg, auto_repeat);
995
996 // New exception with updated message and stack
997 execution_exception nee (ee.err_type (), ee.identifier (),
998 new_msg.toStdString (), stack);
999
1000 // Throw
1001 throw (nee);
1002 }
1003
1004 // Clean up
1005
1006 emit ctx_menu_run_finished_signal (show_dbg_file, err_line,
1007 tmp_file, tmp_hist,
1008 dbg, auto_repeat);
1009
1012 command_editor::set_initial_input (pending_input);
1017
1018 });
1019 }
1020
1021 void octave_qscintilla::ctx_menu_run_finished (bool show_dbg_file, int,
1022 QTemporaryFile* tmp_file, QTemporaryFile* tmp_hist,
1023 bool dbg, bool auto_repeat)
1024 {
1026
1027 // TODO: Use line nr. (int argument) of possible error for removing
1028 // lines from history that were never executed. For this,
1029 // possible lines from commands at a debug prompt must be
1030 // taken into consideration.
1033 settings->setValue (ed_show_dbg_file.key, show_dbg_file);
1034 rmgr.remove_tmp_file (tmp_file);
1035 rmgr.remove_tmp_file (tmp_hist);
1036
1038 ([this, dbg, auto_repeat] (interpreter& interp)
1039 {
1040 // INTERPRETER THREAD
1041 if (dbg)
1042 Fauto_repeat_debug_command (interp, ovl (auto_repeat));
1043 });
1044 }
1045
1046
1047 // wrappers for dbstop related context menu items
1048
1049 // FIXME: Why can't the data be sent as the argument to the function???
1051 {
1052#if defined (HAVE_QSCI_VERSION_2_6_0)
1053 QAction *action = qobject_cast<QAction *>(sender ());
1054 QPoint local_pos = action->data ().value<QPoint> ();
1055
1056 // pick point just right of margins, so lineAt doesn't give -1
1057 int margins = marginWidth (1) + marginWidth (2) + marginWidth (3);
1058 local_pos = QPoint (margins + 1, local_pos.y ());
1059
1060 emit context_menu_break_condition_signal (lineAt (local_pos));
1061#endif
1062 }
1063
1064 void octave_qscintilla::contextmenu_break_once (const QPoint& local_pos)
1065 {
1066#if defined (HAVE_QSCI_VERSION_2_6_0)
1067 emit context_menu_break_once (lineAt (local_pos));
1068#else
1069 octave_unused_parameter (local_pos);
1070#endif
1071 }
1072
1074 {
1075 emit status_update (isUndoAvailable (), isRedoAvailable ());
1076 }
1077
1079 {
1080 // Clear the selection if we move away from it. We have to check the
1081 // position, because we allow entering text at the point of the
1082 // selection to trigger a search and replace that does not clear the
1083 // selection until it is complete.
1084
1085 if (! m_selection.isEmpty ()
1086 && (line != m_selection_line || col != m_selection_col))
1088 }
1089
1090 // when edit area gets focus update information on undo/redo actions
1091 void octave_qscintilla::focusInEvent (QFocusEvent *focusEvent)
1092 {
1093 emit status_update (isUndoAvailable (), isRedoAvailable ());
1094
1095 QsciScintilla::focusInEvent (focusEvent);
1096 }
1097
1099 {
1100 int pos;
1102
1103 // Offer to replace other instances.
1104
1105 QKeySequence keyseq = Qt::SHIFT + Qt::Key_Return;
1106
1107 QString msg = (tr ("Press '%1' to replace all occurrences of '%2' with '%3'.")
1108 . arg (keyseq.toString ())
1109 . arg (m_selection)
1110 . arg (m_selection_replacement));
1111
1112 QPoint global_pos;
1113 QPoint local_pos;
1114
1115 get_global_textcursor_pos (&global_pos, &local_pos);
1116
1117 QFontMetrics ttfm (QToolTip::font ());
1118
1119 // Try to avoid overlapping with the text completion dialog
1120 // and the text that is currently being edited.
1121
1122 global_pos += QPoint (2*ttfm.maxWidth (), -3*ttfm.height ());
1123
1124 QToolTip::showText (global_pos, msg);
1125 }
1126
1127 void octave_qscintilla::replace_all (const QString& o_str, const QString& n_str,
1128 bool re, bool cs, bool wo)
1129 {
1130 // get the resulting cursor position
1131 int pos, line, col, nline, ncol;
1132 get_current_position (&pos, &line, &col);
1133
1134 // remember first visible line for restoring the view afterwards
1135 int first_line = firstVisibleLine ();
1136
1137 // search for first occurrence of the detected word
1138 bool find_result_available = findFirst (o_str, re, cs, wo,
1139 false, true, 0, 0);
1140 // replace and find more occurrences in a loop
1141 beginUndoAction ();
1142 while (find_result_available)
1143 {
1144 // findNext doesn't work properly if the length of the replacement
1145 // text is different from the original
1146 replace (n_str);
1147 get_current_position (&pos, &nline, &ncol);
1148
1149 find_result_available = findFirst (o_str, re, cs, wo,
1150 false, true, nline, ncol);
1151 }
1152 endUndoAction ();
1153
1154 // restore the visible area
1155 setFirstVisibleLine (first_line);
1156
1157 // fix cursor column if outside of new line length
1158 int eol_len = eol_string ().length ();
1159 if (line == lines () - 1)
1160 eol_len = 0;
1161 const int col_max = text (line).length () - eol_len;
1162 if (col_max < col)
1163 col = col_max;
1164
1165 setCursorPosition (line, col);
1166 }
1167
1169 {
1170 if (m_debug_mode && e->type() == QEvent::ToolTip)
1171 {
1172 QHelpEvent *help_e = static_cast<QHelpEvent *>(e);
1173 QString variable = wordAtPoint (help_e->pos());
1174 QStringList symbol_names
1176 int symbol_idx = symbol_names.indexOf (variable);
1177 if (symbol_idx > -1)
1178 {
1179 QStringList symbol_values
1181 QToolTip::showText (help_e->globalPos(), variable
1182 + " = " + symbol_values.at (symbol_idx));
1183 }
1184 else
1185 {
1186 QToolTip::hideText();
1187 e->ignore();
1188 }
1189
1190 return true;
1191 }
1192
1193 return QsciScintilla::event(e);
1194 }
1195
1196 void octave_qscintilla::keyPressEvent (QKeyEvent *key_event)
1197 {
1198 if (m_selection.isEmpty ())
1199 QsciScintilla::keyPressEvent (key_event);
1200 else
1201 {
1202 int key = key_event->key ();
1203 Qt::KeyboardModifiers modifiers = key_event->modifiers ();
1204
1205 if (key == Qt::Key_Return && modifiers == Qt::ShiftModifier)
1206 {
1208 false, true, true);
1209
1210 // Clear the selection.
1212 }
1213 else
1214 {
1215 // The idea here is to allow backspace to remove the last
1216 // character of the replacement text to allow minimal editing
1217 // and to also end the selection replacement action if text is
1218 // not valid as a word constituent (control characters,
1219 // etc.). Is there a better way than having special cases for
1220 // DEL and ESC here?
1221
1222 QString text = key_event->text ();
1223
1224 bool cancel_replacement = false;
1225
1226 if (key == Qt::Key_Backspace)
1227 {
1228 if (m_selection_replacement.isEmpty ())
1229 cancel_replacement = true;
1230 else
1231 m_selection_replacement.chop (1);
1232 }
1233 else if (key == Qt::Key_Delete || key == Qt::Key_Escape)
1234 cancel_replacement = true;
1235 else if (! text.isEmpty ())
1237 else if (modifiers != Qt::ShiftModifier)
1238 cancel_replacement = true;
1239
1240 // Perform default action.
1241
1242 QsciScintilla::keyPressEvent (key_event);
1243
1244 if (cancel_replacement)
1246
1247 if (! m_selection_replacement.isEmpty ())
1249 }
1250 }
1251 }
1252
1253 void octave_qscintilla::auto_close (int auto_endif, int linenr,
1254 const QString& line, QString& first_word)
1255 {
1256 // Insert an "end" for an "if" etc., if needed.
1257 // (Use of "while" allows "return" to skip the rest.
1258 // It may be clearer to use "if" and "goto",
1259 // but that violates the coding standards.)
1260
1261 bool autofill_simple_end = (auto_endif == 2);
1262
1263 std::size_t start = line.toStdString ().find_first_not_of (" \t");
1264
1265 // Check if following line has the same or less indentation
1266 // Check if the following line does not start with
1267 // end* (until) (catch)
1268 if (linenr < lines () - 1)
1269 {
1270 int offset = 2; // linenr is the old line, thus, linnr+1 is the
1271 // new one and can not be taken into account
1272 std::size_t next_start;
1273 QString next_line;
1274
1275 do // find next non-blank line
1276 {
1277 next_line = text (linenr + offset++);
1278 next_start = next_line.toStdString ().find_first_not_of (" \t\n");
1279 }
1280 while (linenr + offset < lines ()
1281 && next_start == std::string::npos);
1282
1283 if (next_start == std::string::npos)
1284 next_start = 0;
1285 if (start == 0 && next_start == 0)
1286 return; // bug #56160, don't add at 0
1287 if (next_start > start) // more indented => don't add "end"
1288 return;
1289 if (next_start == start) // same => check if already is "end"
1290 {
1291 QRegExp rx_start = QRegExp (R"((\w+))");
1292 int tmp = rx_start.indexIn (next_line, start);
1293 if (tmp != -1 && is_end (rx_start.cap(1), first_word))
1294 return;
1295 }
1296 }
1297
1298 // If all of the above, insert a new line, with matching indent
1299 // and either 'end' or 'end...', depending on a flag.
1300
1301 // If we insert directly after the last line, the "end" is autoindented,
1302 // so add a dummy line.
1303 if (linenr + 2 == lines ())
1304 insertAt (QString ("\n"), linenr + 2, 0);
1305
1306 // For try/catch/end, fill "end" first, so "catch" is top of undo stack
1307 if (first_word == "try")
1308 insertAt (QString (start, ' ')
1309 + (autofill_simple_end ? "end\n" : "end_try_catch\n"),
1310 linenr + 2, 0);
1311 else if (first_word == "unwind_protect")
1312 insertAt (QString (start, ' ')
1313 + (autofill_simple_end ? "end\n" : "end_unwind_protect\n"),
1314 linenr + 2, 0);
1315
1316 QString next_line;
1317 if (first_word == "do")
1318 next_line = "until\n";
1319 else if (first_word == "try")
1320 next_line = "catch\n";
1321 else if (first_word == "unwind_protect")
1322 next_line = "unwind_protect_cleanup\n";
1323 else if (autofill_simple_end)
1324 next_line = "end\n";
1325 else
1326 {
1327 if (first_word == "unwind_protect")
1328 first_word = '_' + first_word;
1329 next_line = "end" + first_word + "\n";
1330 }
1331
1332 //insertAt (QString (start, ' ') + next_line, linenr + 2, 0);
1333 insertAt (next_line, linenr + 2, 0);
1334 setIndentation (linenr + 2, indentation (linenr));
1335 }
1336
1337 void octave_qscintilla::dragEnterEvent (QDragEnterEvent *e)
1338 {
1339 // if is not dragging a url, pass to qscintilla to handle,
1340 // otherwise ignore it so that it will be handled by
1341 // the parent
1342 if (!e->mimeData ()->hasUrls ())
1343 {
1344 QsciScintilla::dragEnterEvent (e);
1345 }
1346 else
1347 {
1348 e->ignore();
1349 }
1350 }
1351
1353 {
1354 m_debug_mode = true;
1355 }
1356
1358 {
1359 m_debug_mode = false;
1360 }
1361
1362}
1363
1364#endif
void source_file(const std::string &file_name, const std::string &context="", bool verbose=false, bool require_file=true)
Base class for Octave interfaces that use Qt.
workspace_model * get_workspace_model(void)
resource_manager & get_resource_manager(void)
void show_documentation_window(const QString &file)
static void replace_line(const std::string &text, bool clear_undo=true)
Definition: cmd-edit.cc:1454
static bool erase_empty_line(bool flag)
Definition: cmd-edit.cc:1311
static void set_initial_input(const std::string &text)
Definition: cmd-edit.cc:1106
static std::string get_current_line(void)
Definition: cmd-edit.cc:1440
static void accept_line(void)
Definition: cmd-edit.cc:1482
static void interrupt_event_loop(bool flag=true)
Definition: cmd-edit.cc:1638
static void redisplay(void)
Definition: cmd-edit.cc:1231
The documentation main class derived from QSplitter.
void update_rowcol_indicator_signal(int line, int col)
void focus_console_after_command_signal(void)
void keyPressEvent(QKeyEvent *e)
void dragEnterEvent(QDragEnterEvent *e)
void get_current_position(int *pos, int *line, int *col)
void contextmenu_break_once(const QPoint &)
void ctx_menu_run_finished_signal(bool, int, QTemporaryFile *, QTemporaryFile *, bool, bool)
void smart_indent_line_or_selected_text(int lineFrom, int lineTo)
octave_qscintilla(QWidget *p, base_qobject &oct_qobj)
void status_update(bool, bool)
void get_global_textcursor_pos(QPoint *global_pos, QPoint *local_pos)
void replace_all(const QString &o_str, const QString &n_str, bool re, bool cs, bool wo)
void set_selection_marker_color(const QColor &c)
void auto_close(int auto_endif, int l, const QString &line, QString &first_word)
void interpreter_event(const fcn_callback &fcn)
void smart_indent(bool do_smart_indent, int do_auto_close, int line, int ind_char_width)
virtual void contextMenuEvent(QContextMenuEvent *e)
void set_word_selection(const QString &word=QString())
QStringList comment_string(bool comment=true)
void cursor_position_changed(int, int)
void focusInEvent(QFocusEvent *focusEvent)
virtual void setCursorPosition(int line, int col)
void context_menu_break_condition_signal(int)
void context_menu_edit_signal(const QString &)
void show_selection_markers(int l1, int c1, int l2, int c2)
void create_context_menu_signal(QMenu *)
void ctx_menu_run_finished(bool, int, QTemporaryFile *, QTemporaryFile *, bool, bool)
void execute_command_in_terminal_signal(const QString &)
gui_settings * get_settings(void) const
QPointer< QTemporaryFile > create_tmp_file(const QString &extension=QString(), const QString &contents=QString())
void remove_tmp_file(QPointer< QTemporaryFile > tmp_file)
QStringList get_symbol_names(void) const
QStringList get_symbol_values(void) const
OCTAVE_EXPORT octave_value_list Fisdebugmode(octave::interpreter &interp, const octave_value_list &args, int)
Definition: debug.cc:1177
QString path
const gui_pref ed_comment_str_old("editor/octave_comment_string", QVariant(0))
const gui_pref ed_uncomment_str("editor/oct_uncomment_str", QVariant(1+2+4+8))
const QStringList ed_comment_strings(QStringList()<< "##"<< "#"<< "%"<< "%%"<< "%!")
const gui_pref ed_show_dbg_file("editor/show_dbg_file", QVariant(true))
const gui_pref ed_comment_str("editor/oct_comment_str", QVariant(0))
const int ed_comment_strings_count
const Qt::KeyboardModifier CTRL
QString fromStdString(const std::string &s)
std::string toStdString(const QString &s)
static bool is_end(const QString &candidate, const QString &opening)
OCTAVE_EXPORT octave_value_list Fhistory(octave::interpreter &interp, const octave_value_list &args, int nargout)
Definition: oct-hist.cc:668
#define lexer
Definition: oct-parse.cc:146
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:211
#define octave_stdout
Definition: pager.h:314
const QString key
const QVariant def