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