Mercurial > octave
view libgui/src/m-editor/octave-qscintilla.cc @ 27918:b442ec6dda5c
use centralized file for copyright info for individual contributors
* COPYRIGHT.md: New file.
* In most other files, use "Copyright (C) YYYY-YYYY The Octave Project
Developers" instead of tracking individual names in separate source
files. The motivation is to reduce the effort required to update the
notices each year.
Until now, the Octave source files contained copyright notices that
list individual contributors. I adopted these file-scope copyright
notices because that is what everyone was doing 30 years ago in the
days before distributed version control systems. But now, with many
contributors and modern version control systems, having these
file-scope copyright notices causes trouble when we update copyright
years or refactor code.
Over time, the file-scope copyright notices may become outdated as new
contributions are made or code is moved from one file to
another. Sometimes people contribute significant patches but do not
add a line claiming copyright. Other times, people add a copyright
notice for their contribution but then a later refactoring moves part
or all of their contribution to another file and the notice is not
moved with the code. As a practical matter, moving such notices is
difficult -- determining what parts are due to a particular
contributor requires a time-consuming search through the project
history. Even managing the yearly update of copyright years is
problematic. We have some contributors who are no longer
living. Should we update the copyright dates for their contributions
when we release new versions? Probably not, but we do still want to
claim copyright for the project as a whole.
To minimize the difficulty of maintaining the copyright notices, I
would like to change Octave's sources to use what is described here:
https://softwarefreedom.org/resources/2012/ManagingCopyrightInformation.html
in the section "Maintaining centralized copyright notices":
The centralized notice approach consolidates all copyright
notices in a single location, usually a top-level file.
This file should contain all of the copyright notices
provided project contributors, unless the contribution was
clearly insignificant. It may also credit -- without a copyright
notice -- anyone who helped with the project but did not
contribute code or other copyrighted material.
This approach captures less information about contributions
within individual files, recognizing that the DVCS is better
equipped to record those details. As we mentioned before, it
does have one disadvantage as compared to the file-scope
approach: if a single file is separated from the distribution,
the recipient won't see the contributors' copyright notices.
But this can be easily remedied by including a single
copyright notice in each file's header, pointing to the
top-level file:
Copyright YYYY-YYYY The Octave Project Developers
See the COPYRIGHT file at the top-level directory
of this distribution or at https://octave.org/COPYRIGHT.html.
followed by the usual GPL copyright statement.
For more background, see the discussion here:
https://lists.gnu.org/archive/html/octave-maintainers/2020-01/msg00009.html
Most files in the following directories have been skipped intentinally
in this changeset:
doc
libgui/qterminal
liboctave/external
m4
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Mon, 06 Jan 2020 15:38:17 -0500 |
parents | a044e50c8dcb |
children | 1891570abac8 |
line wrap: on
line source
/* Copyright (C) 2013-2019 The Octave Project Developers See the file COPYRIGHT.md in the top-level directory of this distribution or <https://octave.org/COPYRIGHT.html/>. This file is part of Octave. Octave is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Octave is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Octave; see the file COPYING. If not, see <https://www.gnu.org/licenses/>. */ // Author: Torsten Lilge <ttl-octave@mailbox.org> #if defined (HAVE_CONFIG_H) # include "config.h" #endif #if defined (HAVE_QSCINTILLA) #include <Qsci/qscilexer.h> #include <QDir> #include <QKeySequence> #include <QMessageBox> #include <QMimeData> #include <QShortcut> #include <QToolTip> #include <QVBoxLayout> #if defined (HAVE_QSCI_QSCILEXEROCTAVE_H) # define HAVE_LEXER_OCTAVE 1 # include <Qsci/qscilexeroctave.h> #elif defined (HAVE_QSCI_QSCILEXERMATLAB_H) # define HAVE_LEXER_MATLAB 1 # include <Qsci/qscilexermatlab.h> #endif #include <Qsci/qscicommandset.h> #include <Qsci/qscilexerbash.h> #include <Qsci/qscilexerbatch.h> #include <Qsci/qscilexercpp.h> #include <Qsci/qscilexerdiff.h> #include <Qsci/qscilexerperl.h> #include "file-editor-tab.h" #include "gui-preferences-ed.h" // FIXME: hardwired marker numbers? #include "marker.h" #include "octave-qobject.h" #include "octave-qscintilla.h" #include "shortcut-manager.h" #include "builtin-defun-decls.h" #include "cmd-edit.h" #include "interpreter-private.h" #include "interpreter.h" // Return true if CANDIDATE is a "closing" that matches OPENING, // such as "end" or "endif" for "if", or "catch" for "try". // Used for testing the last word of an "if" etc. line, // or the first word of the following line. namespace octave { static bool is_end (const QString& candidate, const QString& opening) { bool retval = false; if (opening == "do") // The only one that can't be ended by "end" { if (candidate == "until") retval = true; } else { if (candidate == "end") retval = true; else { if (opening == "try") { if (candidate == "catch" || candidate == "end_try_catch") retval = true; } else if (opening == "unwind_protect") { if (candidate == "unwind_protect_cleanup" || candidate == "end_unwind_protect") retval = true; } else if (candidate == "end" + opening) retval = true; else if (opening == "if" && candidate == "else") retval = true; } } return retval; } octave_qscintilla::octave_qscintilla (QWidget *p, base_qobject& oct_qobj) : QsciScintilla (p), m_octave_qobj (oct_qobj), m_word_at_cursor (), m_selection (), m_selection_replacement (), m_selection_line (-1), m_selection_col (-1), m_indicator_id (1) { connect (this, SIGNAL (textChanged (void)), this, SLOT (text_changed (void))); connect (this, SIGNAL (cursorPositionChanged (int, int)), this, SLOT (cursor_position_changed (int, int))); connect (this, SIGNAL (ctx_menu_run_finished_signal (bool, QTemporaryFile*, QTemporaryFile*, QTemporaryFile*)), this, SLOT (ctx_menu_run_finished (bool, QTemporaryFile*, QTemporaryFile*, QTemporaryFile*)), Qt::QueuedConnection); // clear scintilla edit shortcuts that are handled by the editor QsciCommandSet *cmd_set = standardCommands (); // Disable buffered drawing on all systems SendScintilla (SCI_SETBUFFEREDDRAW, false); #if defined (HAVE_QSCI_VERSION_2_6_0) // find () was added in QScintilla 2.6 cmd_set->find (QsciCommand::SelectionCopy)->setKey (0); cmd_set->find (QsciCommand::SelectionCut)->setKey (0); cmd_set->find (QsciCommand::Paste)->setKey (0); cmd_set->find (QsciCommand::SelectAll)->setKey (0); cmd_set->find (QsciCommand::SelectionDuplicate)->setKey (0); cmd_set->find (QsciCommand::LineTranspose)->setKey (0); cmd_set->find (QsciCommand::Undo)->setKey (0); cmd_set->find (QsciCommand::Redo)->setKey (0); cmd_set->find (QsciCommand::SelectionUpperCase)->setKey (0); cmd_set->find (QsciCommand::SelectionLowerCase)->setKey (0); cmd_set->find (QsciCommand::ZoomIn)->setKey (0); cmd_set->find (QsciCommand::ZoomOut)->setKey (0); cmd_set->find (QsciCommand::DeleteWordLeft)->setKey (0); cmd_set->find (QsciCommand::DeleteWordRight)->setKey (0); cmd_set->find (QsciCommand::DeleteLineLeft)->setKey (0); cmd_set->find (QsciCommand::DeleteLineRight)->setKey (0); cmd_set->find (QsciCommand::LineDelete)->setKey (0); cmd_set->find (QsciCommand::LineCut)->setKey (0); cmd_set->find (QsciCommand::LineCopy)->setKey (0); #else // find commands via its default key (tricky way without find ()) QList< QsciCommand * > cmd_list = cmd_set->commands (); for (int i = 0; i < cmd_list.length (); i++) { int cmd_key = cmd_list.at (i)->key (); switch (cmd_key) { case Qt::Key_C | Qt::CTRL : // SelectionCopy case Qt::Key_X | Qt::CTRL : // SelectionCut case Qt::Key_V | Qt::CTRL : // Paste case Qt::Key_A | Qt::CTRL : // SelectAll case Qt::Key_D | Qt::CTRL : // SelectionDuplicate case Qt::Key_T | Qt::CTRL : // LineTranspose case Qt::Key_Z | Qt::CTRL : // Undo case Qt::Key_Y | Qt::CTRL : // Redo case Qt::Key_Z | Qt::CTRL | Qt::SHIFT : // Redo case Qt::Key_U | Qt::CTRL : // SelectionLowerCase case Qt::Key_U | Qt::CTRL | Qt::SHIFT : // SelectionUpperCase case Qt::Key_Plus | Qt::CTRL : // ZoomIn case Qt::Key_Minus | Qt::CTRL : // ZoomOut case Qt::Key_Backspace | Qt::CTRL | Qt::SHIFT : // DeleteLineLeft case Qt::Key_Delete | Qt::CTRL | Qt::SHIFT : // DeleteLineRight case Qt::Key_K | Qt::META : // DeleteLineRight case Qt::Key_Backspace | Qt::CTRL : // DeleteWordLeft case Qt::Key_Delete | Qt::CTRL : // DeleteWordRight case Qt::Key_L | Qt::CTRL | Qt::SHIFT : // LineDelete case Qt::Key_L | Qt::CTRL : // LineCut case Qt::Key_T | Qt::CTRL | Qt::SHIFT : // LineCopy cmd_list.at (i)->setKey (0); } } #endif #if defined (Q_OS_MAC) // Octave interprets Cmd key as Meta whereas Qscintilla interprets it // as Ctrl. We thus invert Meta/Ctrl in Qscintilla's shortcuts list. QList< QsciCommand * > cmd_list_mac = cmd_set->commands (); for (int i = 0; i < cmd_list_mac.length (); i++) { // Primary key int key = cmd_list_mac.at (i)->key (); if (static_cast<int> (key | Qt::META) == key && static_cast<int> (key | Qt::CTRL) != key) key = (key ^ Qt::META) | Qt::CTRL; else if (static_cast<int> (key | Qt::CTRL) == key) key = (key ^ Qt::CTRL) | Qt::META; cmd_list_mac.at (i)->setKey (key); // Alternate key key = cmd_list_mac.at (i)->alternateKey (); if (static_cast<int> (key | Qt::META) == key && static_cast<int> (key | Qt::CTRL) != key) key = (key ^ Qt::META) | Qt::CTRL; else if (static_cast<int> (key | Qt::CTRL) == key) key = (key ^ Qt::CTRL) | Qt::META; cmd_list_mac.at (i)->setAlternateKey (key); } #endif // selection markers m_indicator_id = indicatorDefine (QsciScintilla::StraightBoxIndicator); if (m_indicator_id == -1) m_indicator_id = 1; setIndicatorDrawUnder (true, m_indicator_id); markerDefine (QsciScintilla::Minus, marker::selection); // init state of undo/redo action for this tab emit status_update (isUndoAvailable (), isRedoAvailable ()); } void octave_qscintilla::set_selection_marker_color (const QColor& c) { QColor ic = c; ic.setAlphaF (0.25); setIndicatorForegroundColor (ic, m_indicator_id); setIndicatorOutlineColor (ic, m_indicator_id); setMarkerForegroundColor (c, marker::selection); setMarkerBackgroundColor (c, marker::selection); } // context menu requested void octave_qscintilla::contextMenuEvent (QContextMenuEvent *e) { #if defined (HAVE_QSCI_VERSION_2_6_0) QPoint global_pos, local_pos; // the menu's position QMenu *context_menu = createStandardContextMenu (); // standard menu bool in_left_margin = false; // determine position depending on mouse or keyboard event if (e->reason () == QContextMenuEvent::Mouse) { // context menu by mouse global_pos = e->globalPos (); // global mouse position local_pos = e->pos (); // local mouse position if (e->x () < marginWidth (1) + marginWidth (2)) in_left_margin = true; } else { // context menu by keyboard or other: get point of text cursor get_global_textcursor_pos (&global_pos, &local_pos); QRect editor_rect = geometry (); // editor rect mapped to global editor_rect.moveTopLeft (parentWidget ()->mapToGlobal (editor_rect.topLeft ())); if (! editor_rect.contains (global_pos)) // is cursor outside editor? global_pos = editor_rect.topLeft (); // yes, take top left corner } #if defined (HAVE_QSCI_VERSION_2_6_0) if (! in_left_margin) #endif { // fill context menu with editor's standard actions emit create_context_menu_signal (context_menu); // additional custom entries of the context menu context_menu->addSeparator (); // separator before custom entries // help menu: get the position of the mouse or the text cursor // (only for octave files) QString lexer_name = lexer ()->lexer (); if (lexer_name == "octave" || lexer_name == "matlab") { m_word_at_cursor = wordAtPoint (local_pos); if (! m_word_at_cursor.isEmpty ()) { context_menu->addAction (tr ("Help on") + ' ' + m_word_at_cursor, this, SLOT (contextmenu_help (bool))); context_menu->addAction (tr ("Documentation on") + ' ' + m_word_at_cursor, this, SLOT (contextmenu_doc (bool))); context_menu->addAction (tr ("Edit") + ' ' + m_word_at_cursor, this, SLOT (contextmenu_edit (bool))); } } } #if defined (HAVE_QSCI_VERSION_2_6_0) else { // remove all standard actions from scintilla QList<QAction *> all_actions = context_menu->actions (); for (auto *a : all_actions) context_menu->removeAction (a); QAction *act = context_menu->addAction (tr ("dbstop if ..."), this, SLOT (contextmenu_break_condition (bool))); act->setData (local_pos); } #endif // finaly show the menu context_menu->exec (global_pos); #endif } // common function with flag for documentation void octave_qscintilla::contextmenu_help_doc (bool documentation) { if (documentation) emit show_doc_signal (m_word_at_cursor); else emit execute_command_in_terminal_signal ("help " + m_word_at_cursor); } // call edit the function related to the current word void octave_qscintilla::context_edit (void) { if (get_actual_word ()) contextmenu_edit (true); } // call edit the function related to the current word void octave_qscintilla::context_run (void) { if (hasSelectedText ()) { contextmenu_run (true); emit interpreter_event ([] (interpreter&) { command_editor::erase_empty_line (false); }); } } void octave_qscintilla::get_global_textcursor_pos (QPoint *global_pos, QPoint *local_pos) { long position = SendScintilla (SCI_GETCURRENTPOS); long point_x = SendScintilla (SCI_POINTXFROMPOSITION,0,position); long point_y = SendScintilla (SCI_POINTYFROMPOSITION,0,position); *local_pos = QPoint (point_x,point_y); // local cursor position *global_pos = mapToGlobal (*local_pos); // global position of cursor } // determine the actual word and whether we are in an octave or matlab script bool octave_qscintilla::get_actual_word (void) { QPoint global_pos, local_pos; get_global_textcursor_pos (&global_pos, &local_pos); m_word_at_cursor = wordAtPoint (local_pos); QString lexer_name = lexer ()->lexer (); return ((lexer_name == "octave" || lexer_name == "matlab") && ! m_word_at_cursor.isEmpty ()); } // helper function for clearing all indicators of a specific style void octave_qscintilla::clear_selection_markers (void) { int end_pos = text ().length (); int end_line, end_col; lineIndexFromPosition (end_pos, &end_line, &end_col); clearIndicatorRange (0, 0, end_line, end_col, m_indicator_id); markerDeleteAll (marker::selection); } // Function returning the true cursor position where the tab length // is taken into account. void octave_qscintilla::get_current_position (int *pos, int *line, int *col) { *pos = SendScintilla (QsciScintillaBase::SCI_GETCURRENTPOS); *line = SendScintilla (QsciScintillaBase::SCI_LINEFROMPOSITION, *pos); *col = SendScintilla (QsciScintillaBase::SCI_GETCOLUMN, *pos); } // Function returning the comment string of the current lexer QStringList octave_qscintilla::comment_string (bool comment) { int lexer = SendScintilla (SCI_GETLEXER); switch (lexer) { #if defined (HAVE_LEXER_OCTAVE) || defined (HAVE_LEXER_MATLAB) #if defined (HAVE_LEXER_OCTAVE) case SCLEX_OCTAVE: #else case SCLEX_MATLAB: #endif { resource_manager& rmgr = m_octave_qobj.get_resource_manager (); gui_settings *settings = rmgr.get_settings (); int comment_string; if (comment) { // The commenting string is requested if (settings->contains (ed_comment_str.key)) // new version (radio buttons) comment_string = settings->value (ed_comment_str).toInt (); else // old version (combo box) comment_string = settings->value (ed_comment_str_old.key, ed_comment_str.def).toInt (); return (QStringList (ed_comment_strings.at (comment_string))); } else { QStringList c_str; // The possible uncommenting string(s) are requested comment_string = settings->value (ed_uncomment_str).toInt (); for (int i = 0; i < ed_comment_strings_count; i++) { if (1 << i & comment_string) c_str.append (ed_comment_strings.at (i)); } return c_str; } } #endif case SCLEX_PERL: case SCLEX_BASH: case SCLEX_DIFF: return QStringList ("#"); case SCLEX_CPP: return QStringList ("//"); case SCLEX_BATCH: return QStringList ("REM "); } return QStringList ("%"); // should never happen } // provide the style at a specific position int octave_qscintilla::get_style (int pos) { int position; if (pos < 0) // The positition has to be reduced by 2 for getting the real style (?) position = SendScintilla (QsciScintillaBase::SCI_GETCURRENTPOS) - 2; else position = pos; return SendScintilla (QsciScintillaBase::SCI_GETSTYLEAT, position); } // Is a specific cursor position in a line or block comment? int octave_qscintilla::is_style_comment (int pos) { int lexer = SendScintilla (QsciScintillaBase::SCI_GETLEXER); int style = get_style (pos); switch (lexer) { case SCLEX_CPP: return (ST_LINE_COMMENT * (style == QsciLexerCPP::CommentLine || style == QsciLexerCPP::CommentLineDoc) + ST_BLOCK_COMMENT * (style == QsciLexerCPP::Comment || style == QsciLexerCPP::CommentDoc || style == QsciLexerCPP::CommentDocKeyword || style == QsciLexerCPP::CommentDocKeywordError)); #if defined (HAVE_LEXER_MATLAB) case SCLEX_MATLAB: return (ST_LINE_COMMENT * (style == QsciLexerMatlab::Comment)); #endif #if defined (HAVE_LEXER_OCTAVE) case SCLEX_OCTAVE: return (ST_LINE_COMMENT * (style == QsciLexerOctave::Comment)); #endif case SCLEX_PERL: return (ST_LINE_COMMENT * (style == QsciLexerPerl::Comment)); case SCLEX_BATCH: return (ST_LINE_COMMENT * (style == QsciLexerBatch::Comment)); case SCLEX_DIFF: return (ST_LINE_COMMENT * (style == QsciLexerDiff::Comment)); case SCLEX_BASH: return (ST_LINE_COMMENT * (style == QsciLexerBash::Comment)); } return ST_NONE; } // Do smart indendation after if, for, ... void octave_qscintilla::smart_indent (bool do_smart_indent, int do_auto_close, int line, int ind_char_width) { QString prevline = text (line); QRegExp bkey = QRegExp ("^[\t ]*(if|for|while|switch" "|do|function|properties|events|classdef" "|unwind_protect|try" "|parfor|methods)" "[\r]?[\n\t #%]"); // last word except for comments, assuming no ' or " in comment. // rx_end = QRegExp ("(\\w+)[ \t;\r\n]*([%#][^\"']*)?$"); // last word except for comments, // allowing % and # in single or double quoted strings // FIXME: This will get confused by transpose. QRegExp ekey = QRegExp ("(?:(?:['\"][^'\"]*['\"])?[^%#]*)*" "(\\w+)[ \t;\r\n]*(?:[%#].*)?$"); int bpos = bkey.indexIn (prevline, 0); int epos; if (bpos > -1) { // Found keyword after that indentation should be added // Check for existing end statement in the same line epos = ekey.indexIn (prevline, bpos); QString first_word = bkey.cap(1); bool inline_end = (epos > -1) && is_end (ekey.cap(1), first_word); if (do_smart_indent && ! inline_end) { // Do smart indent in the current line (line+1) indent (line+1); setCursorPosition (line+1, indentation (line+1) / ind_char_width); } if (do_auto_close && ! inline_end && ! first_word.contains (QRegExp ("(?:case|otherwise|unwind_protect_cleanup)"))) { // Do auto close auto_close (do_auto_close, line, prevline, first_word); } return; } QRegExp mkey = QRegExp ("^[\t ]*(?:else|elseif|catch|unwind_protect_cleanup)" "[\r]?[\t #%\n]"); if (prevline.contains (mkey)) { int prev_ind = indentation (line-1); int act_ind = indentation (line); if (prev_ind == act_ind) unindent (line); else if (prev_ind > act_ind) { setIndentation (line+1, prev_ind); setCursorPosition (line+1, prev_ind); } return; } QRegExp case_key = QRegExp ("^[\t ]*(?:case|otherwise)[\r]?[\t #%\n]"); if (prevline.contains (case_key) && do_smart_indent) { QString last_line = text (line-1); int act_ind = indentation (line); if (last_line.contains ("switch")) { indent (line+1); act_ind = indentation (line+1); } else unindent (line); setIndentation (line+1, act_ind); setCursorPosition (line+1, act_ind); } ekey = QRegExp ("^[\t ]*(?:end|endif|endfor|endwhile|until|endfunction" "|end_try_catch|end_unwind_protect)[\r]?[\t #%\n(;]"); if (prevline.contains (ekey)) { if (indentation (line-1) <= indentation (line)) { unindent (line+1); unindent (line); setCursorPosition (line+1, indentation (line)); } return; } } // Do smart indentation of current selection or line. void octave_qscintilla::smart_indent_line_or_selected_text (int lineFrom, int lineTo) { QRegExp blank_line_regexp = QRegExp ("^[\t ]*$"); // end[xxxxx] [# comment] at end of a line QRegExp end_word_regexp = QRegExp ("(?:(?:['\"][^'\"]*['\"])?[^%#]*)*" "(?:end\\w*)[\r\n\t ;]*(?:[%#].*)?$"); QRegExp begin_block_regexp = QRegExp ("^[\t ]*(?:if|elseif|else" "|for|while|do|parfor" "|switch|case|otherwise" "|function" "|classdef|properties|events|enumeration|methods" "|unwind_protect|unwind_protect_cleanup|try|catch)" "[\r\n\t #%]"); QRegExp mid_block_regexp = QRegExp ("^[\t ]*(?:elseif|else" "|otherwise" "|unwind_protect_cleanup|catch)" "[\r\n\t #%]"); QRegExp end_block_regexp = QRegExp ("^[\t ]*(?:end" "|end(for|function|if|parfor|switch|while" "|classdef|enumeration|events|methods|properties)" "|end_(try_catch|unwind_protect)" "|until)" "[\r\n\t #%]"); QRegExp case_block_regexp = QRegExp ("^[\t ]*(?:case|otherwise)" "[\r\n\t #%]"); int indent_column = -1; int indent_increment = indentationWidth (); bool in_switch = false; for (int line = lineFrom-1; line >= 0; line--) { QString line_text = text (line); if (blank_line_regexp.indexIn (line_text) < 0) { // Found first non-blank line above beginning of region or // current line. Base indentation from this line, increasing // indentation by indentationWidth if it looks like the // beginning of a code block. indent_column = indentation (line); if (begin_block_regexp.indexIn (line_text) > -1) { indent_column += indent_increment; if (line_text.contains ("switch")) in_switch = true; } break; } } if (indent_column < 0) indent_column = indentation (lineFrom); QString prev_line; for (int line = lineFrom; line <= lineTo; line++) { QString line_text = text (line); if (end_block_regexp.indexIn (line_text) > -1) { indent_column -= indent_increment; if (line_text.contains ("endswitch")) { // need a double de-indent for endswitch if (in_switch) indent_column -= indent_increment; in_switch = false; } } if (mid_block_regexp.indexIn (line_text) > -1) indent_column -= indent_increment; if (case_block_regexp.indexIn (line_text) > -1) { if (case_block_regexp.indexIn (prev_line) < 0 && !prev_line.contains("switch")) indent_column -= indent_increment; in_switch = true; } setIndentation (line, indent_column); int bpos = begin_block_regexp.indexIn (line_text); if (bpos > -1) { // Check for existing end statement in the same line int epos = end_word_regexp.indexIn (line_text, bpos); if (epos == -1) indent_column += indent_increment; if (line_text.contains ("switch")) in_switch = true; } if (blank_line_regexp.indexIn (line_text) < 0) prev_line = line_text; } } void octave_qscintilla::set_word_selection (const QString& word) { m_selection = word; if (word.isEmpty ()) { m_selection_line = -1; m_selection_col = -1; m_selection_replacement = ""; clear_selection_markers (); QToolTip::hideText (); } else { int pos; get_current_position (&pos, &m_selection_line, &m_selection_col); } } void octave_qscintilla::show_selection_markers (int l1, int c1, int l2, int c2) { fillIndicatorRange (l1, c1, l2, c2, m_indicator_id); if (l1 == l2) markerAdd (l1, marker::selection); } void octave_qscintilla::contextmenu_help (bool) { contextmenu_help_doc (false); } void octave_qscintilla::contextmenu_doc (bool) { contextmenu_help_doc (true); } void octave_qscintilla::context_help_doc (bool documentation) { if (get_actual_word ()) contextmenu_help_doc (documentation); } void octave_qscintilla::contextmenu_edit (bool) { emit context_menu_edit_signal (m_word_at_cursor); } void octave_qscintilla::contextmenu_run_temp_error (void) { QMessageBox::critical (this, tr ("Octave Editor"), tr ("Creating temporary files failed.\n" "Make sure you have write access to temp. directory\n" "%1\n\n" "\"Run Selection\" requires temporary files.").arg (QDir::tempPath ())); } void octave_qscintilla::contextmenu_run (bool) { resource_manager& rmgr = m_octave_qobj.get_resource_manager (); // Create tmp file required for adding command to history QPointer<QTemporaryFile> tmp_hist = rmgr.create_tmp_file (); // empty tmp file for history // Create tmp file required for the script echoing and adding cmd to hist QPointer<QTemporaryFile> tmp_script = rmgr.create_tmp_file ("m"); // tmp script file bool tmp = (tmp_hist && tmp_hist->open () && tmp_script && tmp_script->open()); if (! tmp) { // tmp files not working: use old way to run selection contextmenu_run_temp_error (); return; } tmp_hist->close (); QString tmp_hist_name = QFileInfo (tmp_hist->fileName ()).baseName (); QString tmp_script_name = QFileInfo (tmp_script->fileName ()).baseName (); // Create tmp file with script for echoing a command and adding // the the history QString echo_hist = QString ( "function %2 (i, command, line)\n" " persistent cnt;\n" " mlock ();\n" " if (i == 0)\n" " cnt = -1;\n" " end\n" " if cnt < i\n" " cnt = i;\n" " prompt = PS1;\n" " if (i == 0)\n" " prompt = '';\n" " end\n" " disp ([prompt, command]);\n" " if (history_save ())\n" " fid = fopen ('%1','w');\n" " if (fid != -1)\n" " fprintf (fid, [line,'\\n']);\n" " fclose (fid);\n" " end;\n" " history -r '%1'\n" " end\n" " end\n" " end\n").arg (tmp_hist->fileName ()).arg (tmp_script_name); tmp_script->write (echo_hist.toUtf8 ()); tmp_script->close (); // Take selected code and extend it by commands for echoing each // evaluated line and for adding the line to the history (use script) QString tmp_dir = QFileInfo (tmp_script->fileName ()).absolutePath (); QString code = QString (); // Split contents into single lines and complete commands QStringList lines = selectedText ().split (QRegExp ("[\r\n]"), QString::SkipEmptyParts); for (int i = 0; i < lines.count (); i++) { QString line = lines.at (i); QString line_history = line; line_history.replace (QString ("\\"), QString ("\\\\")); line_history.replace (QString ("\""), QString ("\\\"")); line_history.replace (QString ("%"), QString ("%%")); // Prevent output of breakpoint in temp. file for keyboard QString next_bp_quiet; QString next_bp_quiet_reset; if (line.contains ("keyboard")) { // Define commands for not showing bp location and for resetting // thisin case "keyboard" was within a comment next_bp_quiet = "__db_next_breakpoint_quiet__;\n"; next_bp_quiet_reset = "__db_next_breakpoint_quiet__(false);\n"; } // Add codeline togetcher with call to echo/hitory function to tmp // %1 : function name for displaying and adding to history // %2 : line number // %3 : command line (eval and display) // %4 : command line for history (via fprintf) code += QString ("%1 (%2, '%3', '%4');\n" + next_bp_quiet + "%3\n" + next_bp_quiet_reset + "\n") .arg (tmp_script_name) .arg (i) .arg (line) .arg (line_history); } code += QString ("munlock (\"%1\"); clear %1;\n").arg (tmp_script_name); // Create tmp file with the code to be executed by the interpreter QPointer<QTemporaryFile> tmp_file = rmgr.create_tmp_file ("m", code); tmp = (tmp_file && tmp_file->open ()); if (! tmp) { // tmp files not working: use old way to run selection contextmenu_run_temp_error (); return; } tmp_file->close (); // Disable opening a file at a breakpoint in case keyboard () is used gui_settings* settings = rmgr.get_settings (); bool show_dbg_file = settings->value (ed_show_dbg_file).toBool (); settings->setValue (ed_show_dbg_file.key, false); emit focus_console_after_command_signal (); // Let the interpreter execute the tmp file emit interpreter_event ([this, tmp_file, tmp_hist, tmp_script, show_dbg_file] (interpreter& interp) { // INTERPRETER THREAD std::string file = tmp_file->fileName ().toStdString (); std::string pending_input = command_editor::get_current_line (); // Add tmp dir to the path for echo/hist script octave_value_list path = ovl (QFileInfo (tmp_script->fileName ()).absolutePath ().toStdString ()); // Add tmp dir to the path Faddpath (interp, path); try { // Do the job interp.source_file (file); } catch (const execution_exception& e) { // Catch errors otherwise the rest of the interpreter // will not be executed (cleaning up). Clean up before. Frmpath (interp, path); emit ctx_menu_run_finished_signal (show_dbg_file, tmp_file, tmp_hist, tmp_script); // New error message and error stack QString new_msg = QString::fromStdString (e.message ()); std::list<frame_info> stack = e.stack_info (); // Remove line and column from first line of error message only // if it is related to the tmp itself, i.e. only if the // the error stack size is 0 or 1 if (stack.size () < 2) { new_msg = new_msg.replace ( QRegExp ("near line [^\n]*\n"), QString ("\n")); new_msg = new_msg.replace ( QRegExp ("near line [^\n]*$"), QString ("")); } // Drop first stack level, i.e. temporary function file if (stack.size () > 0) stack.pop_back (); // New exception with updated message and stack octave::execution_exception ee (e.err_type (),e.identifier (), new_msg.toStdString (), stack); // Throw throw (ee); } // Clean up Frmpath (interp, path); emit ctx_menu_run_finished_signal (show_dbg_file, tmp_file, tmp_hist, tmp_script); command_editor::erase_empty_line (true); command_editor::replace_line (""); command_editor::set_initial_input (pending_input); command_editor::redisplay (); command_editor::interrupt_event_loop (); command_editor::accept_line (); command_editor::erase_empty_line (true); }); } void octave_qscintilla::ctx_menu_run_finished (bool show_dbg_file, QTemporaryFile* tmp_file, QTemporaryFile* tmp_hist, QTemporaryFile* tmp_script) { resource_manager& rmgr = m_octave_qobj.get_resource_manager (); gui_settings *settings = rmgr.get_settings (); settings->setValue (ed_show_dbg_file.key, show_dbg_file); rmgr.remove_tmp_file (tmp_file); rmgr.remove_tmp_file (tmp_hist); rmgr.remove_tmp_file (tmp_script); } // wrappers for dbstop related context menu items // FIXME: Why can't the data be sent as the argument to the function??? void octave_qscintilla::contextmenu_break_condition (bool) { #if defined (HAVE_QSCI_VERSION_2_6_0) QAction *action = qobject_cast<QAction *>(sender ()); QPoint local_pos = action->data ().value<QPoint> (); // pick point just right of margins, so lineAt doesn't give -1 int margins = marginWidth (1) + marginWidth (2) + marginWidth (3); local_pos = QPoint (margins + 1, local_pos.y ()); emit context_menu_break_condition_signal (lineAt (local_pos)); #endif } void octave_qscintilla::contextmenu_break_once (const QPoint& local_pos) { #if defined (HAVE_QSCI_VERSION_2_6_0) emit context_menu_break_once (lineAt (local_pos)); #endif } void octave_qscintilla::text_changed (void) { emit status_update (isUndoAvailable (), isRedoAvailable ()); } void octave_qscintilla::cursor_position_changed (int line, int col) { // Clear the selection if we move away from it. We have to check the // position, because we allow entering text at the point of the // selection to trigger a search and replace that does not clear the // selection until it is complete. if (! m_selection.isEmpty () && (line != m_selection_line || col != m_selection_col)) set_word_selection (); } // when edit area gets focus update information on undo/redo actions void octave_qscintilla::focusInEvent (QFocusEvent *focusEvent) { emit status_update (isUndoAvailable (), isRedoAvailable ()); QsciScintilla::focusInEvent (focusEvent); } void octave_qscintilla::show_replace_action_tooltip (void) { int pos; get_current_position (&pos, &m_selection_line, &m_selection_col); // Offer to replace other instances. QKeySequence keyseq = Qt::SHIFT + Qt::Key_Return; QString msg = (tr ("Press '%1' to replace all occurrences of '%2' with '%3'.") . arg (keyseq.toString ()) . arg (m_selection) . arg (m_selection_replacement)); QPoint global_pos; QPoint local_pos; get_global_textcursor_pos (&global_pos, &local_pos); QFontMetrics ttfm (QToolTip::font ()); // Try to avoid overlapping with the text completion dialog // and the text that is currently being edited. global_pos += QPoint (2*ttfm.maxWidth (), -3*ttfm.height ()); QToolTip::showText (global_pos, msg); } void octave_qscintilla::keyPressEvent (QKeyEvent *key_event) { if (m_selection.isEmpty ()) QsciScintilla::keyPressEvent (key_event); else { int key = key_event->key (); Qt::KeyboardModifiers modifiers = key_event->modifiers (); if (key == Qt::Key_Return && modifiers == Qt::ShiftModifier) { // get the resulting cursor position // (required if click was beyond a line ending) int pos, line, col; get_current_position (&pos, &line, &col); // remember first visible line for restoring the view afterwards int first_line = firstVisibleLine (); // search for first occurrence of the detected word bool find_result_available = findFirst (m_selection, false, // no regexp true, // case sensitive true, // whole words only false, // do not wrap true, // forward 0, 0, // from the beginning false #if defined (HAVE_QSCI_VERSION_2_6_0) , true #endif ); while (find_result_available) { replace (m_selection_replacement); // FIXME: is this the right thing to do? findNext doesn't // work properly if the length of the replacement text is // different from the original. int new_line, new_col; get_current_position (&pos, &new_line, &new_col); find_result_available = findFirst (m_selection, false, // no regexp true, // case sensitive true, // whole words only false, // do not wrap true, // forward new_line, new_col, // from new pos false #if defined (HAVE_QSCI_VERSION_2_6_0) , true #endif ); } // restore the visible area of the file, the cursor position, // and the selection setFirstVisibleLine (first_line); setCursorPosition (line, col); // Clear the selection. set_word_selection (); } else { // The idea here is to allow backspace to remove the last // character of the replacement text to allow minimal editing // and to also end the selection replacement action if text is // not valid as a word constituent (control characters, // etc.). Is there a better way than having special cases for // DEL and ESC here? QString text = key_event->text (); bool cancel_replacement = false; if (key == Qt::Key_Backspace) { if (m_selection_replacement.isEmpty ()) cancel_replacement = true; else m_selection_replacement.chop (1); } else if (key == Qt::Key_Delete || key == Qt::Key_Escape) cancel_replacement = true; else if (! text.isEmpty ()) m_selection_replacement += text; else if (modifiers != Qt::ShiftModifier) cancel_replacement = true; // Perform default action. QsciScintilla::keyPressEvent (key_event); if (cancel_replacement) set_word_selection (); if (! m_selection_replacement.isEmpty ()) show_replace_action_tooltip (); } } } void octave_qscintilla::auto_close (int auto_endif, int linenr, const QString& line, QString& first_word) { // Insert an "end" for an "if" etc., if needed. // (Use of "while" allows "return" to skip the rest. // It may be clearer to use "if" and "goto", // but that violates the coding standards.) bool autofill_simple_end = (auto_endif == 2); size_t start = line.toStdString ().find_first_not_of (" \t"); // Check if following line has the same or less indentation // Check if the following line does not start with // end* (until) (catch) if (linenr < lines () - 1) { int offset = 2; // linenr is the old line, thus, linnr+1 is the // new one and can not be taken into account size_t next_start; QString next_line; do // find next non-blank line { next_line = text (linenr + offset++); next_start = next_line.toStdString ().find_first_not_of (" \t\n"); } while (linenr + offset < lines () && next_start == std::string::npos); if (next_start == std::string::npos) next_start = 0; if (start == 0 && next_start == 0) return; // bug #56160, don't add at 0 if (next_start > start) // more indented => don't add "end" return; if (next_start == start) // same => check if already is "end" { QRegExp rx_start = QRegExp (R"((\w+))"); int tmp = rx_start.indexIn (next_line, start); if (tmp != -1 && is_end (rx_start.cap(1), first_word)) return; } } // If all of the above, insert a new line, with matching indent // and either 'end' or 'end...', depending on a flag. // If we insert directly after the last line, the "end" is autoindented, // so add a dummy line. if (linenr + 2 == lines ()) insertAt (QString ("\n"), linenr + 2, 0); // For try/catch/end, fill "end" first, so "catch" is top of undo stack if (first_word == "try") insertAt (QString (start, ' ') + (autofill_simple_end ? "end\n" : "end_try_catch\n"), linenr + 2, 0); else if (first_word == "unwind_protect") insertAt (QString (start, ' ') + (autofill_simple_end ? "end\n" : "end_unwind_protect\n"), linenr + 2, 0); QString next_line; if (first_word == "do") next_line = "until\n"; else if (first_word == "try") next_line = "catch\n"; else if (first_word == "unwind_protect") next_line = "unwind_protect_cleanup\n"; else if (autofill_simple_end) next_line = "end\n"; else { if (first_word == "unwind_protect") first_word = '_' + first_word; next_line = "end" + first_word + "\n"; } //insertAt (QString (start, ' ') + next_line, linenr + 2, 0); insertAt (next_line, linenr + 2, 0); setIndentation (linenr + 2, indentation (linenr)); } void octave_qscintilla::dragEnterEvent (QDragEnterEvent *e) { // if is not dragging a url, pass to qscintilla to handle, // otherwise ignore it so that it will be handled by // the parent if (!e->mimeData ()->hasUrls ()) { QsciScintilla::dragEnterEvent (e); } else { e->ignore(); } } } #endif