scide: DocManager - report warnings via the status bar
[supercollider.git] / editors / sc-ide / widgets / code_editor / editor.cpp
blob7bed2735fd2cdca2f79ec8ab049535b39ba4f5dc
1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "autocompleter.hpp"
22 #include "editor.hpp"
23 #include "highlighter.hpp"
24 #include "line_indicator.hpp"
25 #include "../../core/doc_manager.hpp"
26 #include "../../core/main.hpp"
27 #include "../../core/settings/manager.hpp"
29 #include <QApplication>
30 #include <QDebug>
31 #include <QKeyEvent>
32 #include <QPainter>
33 #include <QPaintEvent>
34 #include <QScrollBar>
35 #include <QTextBlock>
36 #include <QTextDocumentFragment>
37 #include <QUrl>
39 namespace ScIDE {
41 GenericCodeEditor::GenericCodeEditor( Document *doc, QWidget *parent ):
42 QPlainTextEdit( parent ),
43 mLineIndicator( new LineIndicator(this) ),
44 mDoc(doc)
46 Q_ASSERT(mDoc != 0);
48 setFrameShape( QFrame::NoFrame );
50 mLineIndicator->move( contentsRect().topLeft() );
52 connect( mDoc, SIGNAL(defaultFontChanged()), this, SLOT(onDocumentFontChanged()) );
54 connect( this, SIGNAL(blockCountChanged(int)),
55 mLineIndicator, SLOT(setLineCount(int)) );
57 connect( mLineIndicator, SIGNAL( widthChanged() ),
58 this, SLOT( updateLayout() ) );
60 connect( this, SIGNAL(updateRequest(QRect,int)),
61 this, SLOT(updateLineIndicator(QRect,int)) );
63 connect( this, SIGNAL(selectionChanged()),
64 mLineIndicator, SLOT(update()) );
66 QTextDocument *tdoc = doc->textDocument();
67 QPlainTextEdit::setDocument(tdoc);
68 onDocumentFontChanged();
69 mLineIndicator->setLineCount(blockCount());
73 bool GenericCodeEditor::showWhitespace()
75 QTextOption options( textDocument()->defaultTextOption() );
76 return options.flags().testFlag( QTextOption::ShowTabsAndSpaces );
79 void GenericCodeEditor::setShowWhitespace(bool show)
81 QTextDocument *doc = textDocument();
82 QTextOption opt( doc->defaultTextOption() );
83 if( show )
84 opt.setFlags( opt.flags() | QTextOption::ShowTabsAndSpaces );
85 else
86 opt.setFlags( opt.flags() & ~QTextOption::ShowTabsAndSpaces );
87 doc->setDefaultTextOption(opt);
90 static bool findInBlock(QTextDocument *doc, const QTextBlock &block, const QRegExp &expr, int offset,
91 QTextDocument::FindFlags options, QTextCursor &cursor)
93 QString text = block.text();
94 if(options & QTextDocument::FindBackward)
95 text.truncate(offset);
96 text.replace(QChar::Nbsp, QLatin1Char(' '));
98 int idx = -1;
99 while (offset >=0 && offset <= text.length()) {
100 idx = (options & QTextDocument::FindBackward) ?
101 expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
102 if (idx == -1)
103 return false;
105 if (options & QTextDocument::FindWholeWords) {
106 const int start = idx;
107 const int end = start + expr.matchedLength();
108 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
109 || (end != text.length() && text.at(end).isLetterOrNumber())) {
110 //if this is not a whole word, continue the search in the string
111 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
112 idx = -1;
113 continue;
116 //we have a hit, return the cursor for that.
117 break;
120 if (idx == -1)
121 return false;
123 cursor = QTextCursor(doc);
124 cursor.setPosition(block.position() + idx);
125 cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor);
126 return true;
129 bool GenericCodeEditor::find( const QRegExp &expr, QTextDocument::FindFlags options )
131 // Although QTextDocument provides a find() method, we implement
132 // our own, because the former one is not adequate.
134 if(expr.isEmpty()) return true;
136 bool backwards = options & QTextDocument::FindBackward;
138 QTextCursor c( textCursor() );
139 int pos;
140 if (c.hasSelection())
142 bool matching = expr.exactMatch(c.selectedText());
144 if( backwards == matching )
145 pos = c.selectionStart();
146 else
147 pos = c.selectionEnd();
149 else
150 pos = c.position();
152 QTextDocument *doc = QPlainTextEdit::document();
153 QTextBlock startBlock = doc->findBlock(pos);
154 int startBlockOffset = pos - startBlock.position();
156 QTextCursor cursor;
158 if (!backwards) {
159 int blockOffset = startBlockOffset;
160 QTextBlock block = startBlock;
161 while (block.isValid()) {
162 if (findInBlock(doc, block, expr, blockOffset, options, cursor))
163 break;
164 blockOffset = 0;
165 block = block.next();
167 if(cursor.isNull())
169 blockOffset = 0;
170 block = doc->begin();
171 while(true) {
172 if (findInBlock(doc, block, expr, blockOffset, options, cursor)
173 || block == startBlock)
174 break;
175 block = block.next();
178 } else {
179 int blockOffset = startBlockOffset;
180 QTextBlock block = startBlock;
181 while (block.isValid()) {
182 if (findInBlock(doc, block, expr, blockOffset, options, cursor))
183 break;
184 block = block.previous();
185 blockOffset = block.length() - 1;
187 if(cursor.isNull())
189 block = doc->end();
190 while(true) {
191 blockOffset = block.length() - 1;
192 if (findInBlock(doc, block, expr, blockOffset, options, cursor)
193 || block == startBlock)
194 break;
195 block = block.previous();
200 if(!cursor.isNull()) {
201 setTextCursor(cursor);
202 return true;
204 else
205 return false;
208 int GenericCodeEditor::findAll( const QRegExp &expr, QTextDocument::FindFlags options )
210 mSearchSelections.clear();
212 if(expr.isEmpty()) {
213 this->updateExtraSelections();
214 return 0;
217 QTextEdit::ExtraSelection selection;
218 selection.format.setBackground(Qt::darkYellow);
220 QTextDocument *doc = QPlainTextEdit::document();
221 QTextBlock block = doc->begin();
222 QTextCursor cursor;
224 while (block.isValid()) {
225 int blockPos = block.position();
226 int offset = 0;
227 while(findInBlock(doc, block, expr, offset, options, cursor))
229 offset = cursor.selectionEnd() - blockPos;
230 selection.cursor = cursor;
231 mSearchSelections.append(selection);
233 block = block.next();
236 this->updateExtraSelections();
238 return mSearchSelections.count();
241 //#define CSTR(QSTR) QSTR.toStdString().c_str()
243 static QString resolvedReplacement( const QString &replacement, const QRegExp &expr )
245 //qDebug("START");
246 static const QRegExp rexpr("(\\\\\\\\)|(\\\\[0-9]+)");
247 QString str( replacement );
248 int i=0;
249 while(i < str.size() && ((i = rexpr.indexIn(str, i)) != -1))
251 int len = rexpr.matchedLength();
252 if(rexpr.pos(1) != -1)
254 //qDebug("%i (%s): escape", i, CSTR(rexpr.cap(1)));
255 str.replace(i, len, "\\");
256 i += 1;
258 else if(rexpr.pos(2) != -1)
260 QString num_str = rexpr.cap(2);
261 num_str.remove(0, 1);
262 int num = num_str.toInt();
263 //qDebug("%i (%s): backref = %i", i, CSTR(rexpr.cap(2)), num);
264 if(num <= expr.captureCount())
266 QString cap = expr.cap(num);
267 //qDebug("resolving ref to: %s", CSTR(cap));
268 str.replace(i, len, cap);
269 i += cap.size();
271 else
273 //qDebug("ref out of range", i, num);
274 str.remove(i, len);
277 else
279 //qDebug("%i (%s): unknown match", i, CSTR(rexpr.cap(0)));
280 str.remove(i, len);
282 //qDebug(">> [%s] %i", CSTR(str), i);
284 //qDebug("END");
285 return str;
288 bool GenericCodeEditor::replace( const QRegExp &expr, const QString &replacement, QTextDocument::FindFlags options )
290 if(expr.isEmpty()) return true;
292 QTextCursor cursor = textCursor();
293 if (cursor.hasSelection() && expr.exactMatch(cursor.selectedText()))
295 QString rstr = replacement;
296 if(expr.patternSyntax() != QRegExp::FixedString)
297 rstr = resolvedReplacement(rstr, expr);
298 cursor.insertText(rstr);
301 return find(expr, options);
304 int GenericCodeEditor::replaceAll( const QRegExp &expr, const QString &replacement, QTextDocument::FindFlags options )
306 mSearchSelections.clear();
307 updateExtraSelections();
309 if(expr.isEmpty()) return 0;
311 int replacements = 0;
312 bool caps = expr.patternSyntax() != QRegExp::FixedString;
314 QTextDocument *doc = QPlainTextEdit::document();
315 QTextBlock block = doc->begin();
316 QTextCursor cursor;
318 QTextCursor(doc).beginEditBlock();
320 while (block.isValid())
322 int blockPos = block.position();
323 int offset = 0;
324 while(findInBlock(doc, block, expr, offset, options, cursor))
326 QString rstr = replacement;
327 if(caps)
328 rstr = resolvedReplacement(rstr, expr);
329 cursor.insertText(rstr);
330 ++replacements;
331 offset = cursor.selectionEnd() - blockPos;
333 block = block.next();
336 QTextCursor(doc).endEditBlock();
338 return replacements;
341 void GenericCodeEditor::showPosition( int pos, int selectionLength )
343 if (pos < 0) return;
345 QTextDocument *doc = QPlainTextEdit::document();
346 if (!doc) return;
348 int lineNumber = doc->findBlock(pos).firstLineNumber();
349 verticalScrollBar()->setValue(lineNumber);
351 QTextCursor cursor(doc);
352 cursor.setPosition(pos);
353 if (selectionLength)
354 cursor.setPosition(pos + selectionLength, QTextCursor::KeepAnchor);
356 setTextCursor(cursor);
359 QString GenericCodeEditor::symbolUnderCursor()
361 QTextCursor cursor = textCursor();
362 if (!cursor.hasSelection())
363 cursor.select(QTextCursor::WordUnderCursor);
364 return cursor.selectedText();
367 void GenericCodeEditor::keyPressEvent(QKeyEvent * e)
369 hideMouseCursor();
371 QTextCursor cursor( textCursor() );
373 switch (e->key()) {
374 case Qt::Key_Enter:
375 case Qt::Key_Return:
376 // override to avoid entering a "soft" new line when certain modifier is held
377 cursor.insertBlock();
378 break;
380 default:
381 QPlainTextEdit::keyPressEvent(e);
384 switch (e->key()) {
385 case Qt::Key_Enter:
386 case Qt::Key_Return:
387 case Qt::Key_Backspace:
388 cursor.setVerticalMovementX(-1);
389 setTextCursor( cursor );
390 ensureCursorVisible();
391 break;
392 default:;
396 void GenericCodeEditor::wheelEvent( QWheelEvent * e )
398 if (e->modifiers() == Qt::ControlModifier) {
399 if (e->delta() > 0)
400 zoomIn();
401 else
402 zoomOut();
403 return;
406 QPlainTextEdit::wheelEvent(e);
409 void GenericCodeEditor::dragEnterEvent( QDragEnterEvent * event )
411 const QMimeData * data = event->mimeData();
412 if (data->hasUrls()) {
413 // Propagate event to parent.
414 // URL drops are ultimately handled by MainWindow.
415 event->ignore();
416 return;
419 QPlainTextEdit::dragEnterEvent(event);
422 void GenericCodeEditor::clearSearchHighlighting()
424 mSearchSelections.clear();
425 this->updateExtraSelections();
428 void GenericCodeEditor::zoomIn(int steps)
430 zoomFont(steps);
433 void GenericCodeEditor::zoomOut(int steps)
435 zoomFont(-steps);
438 void GenericCodeEditor::resetFontSize()
440 mDoc->resetDefaultFont();
443 void GenericCodeEditor::zoomFont(int steps)
445 QFont currentFont = mDoc->defaultFont();
446 const int newSize = currentFont.pointSize() + steps;
447 if (newSize <= 0)
448 return;
449 currentFont.setPointSize(newSize);
450 mDoc->setDefaultFont(currentFont);
453 void GenericCodeEditor::onDocumentFontChanged()
455 QFont font = mDoc->defaultFont();
456 setFont(font);
459 void GenericCodeEditor::updateLayout()
461 setViewportMargins( mLineIndicator->width(), 0, 0, 0 );
464 void GenericCodeEditor::updateLineIndicator( QRect r, int dy )
466 if (dy)
467 mLineIndicator->scroll(0, dy);
468 else
469 mLineIndicator->update(0, r.y(), mLineIndicator->width(), r.height() );
472 void GenericCodeEditor::updateExtraSelections()
474 QList<QTextEdit::ExtraSelection> selections;
475 selections.append(mSearchSelections);
476 setExtraSelections(selections);
479 void GenericCodeEditor::resizeEvent( QResizeEvent *e )
481 QPlainTextEdit::resizeEvent( e );
483 QRect cr = contentsRect();
484 mLineIndicator->resize( mLineIndicator->width(), cr.height() );
487 void GenericCodeEditor::paintLineIndicator( QPaintEvent *e )
489 QPalette plt( mLineIndicator->palette() );
490 QRect r( e->rect() );
491 QPainter p( mLineIndicator );
493 p.fillRect( r, plt.color( QPalette::Button ) );
494 p.setPen( plt.color(QPalette::ButtonText) );
495 p.drawLine( r.topRight(), r.bottomRight() );
497 QTextDocument *doc = QPlainTextEdit::document();
498 QTextCursor cursor(textCursor());
499 int selStartBlock, selEndBlock;
500 if (cursor.hasSelection()) {
501 selStartBlock = doc->findBlock(cursor.selectionStart()).blockNumber();
502 selEndBlock = doc->findBlock(cursor.selectionEnd()).blockNumber();
504 else
505 selStartBlock = selEndBlock = -1;
507 QTextBlock block = firstVisibleBlock();
508 int blockNumber = block.blockNumber();
509 int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
510 int bottom = top + (int) blockBoundingRect(block).height();
512 while (block.isValid() && top <= e->rect().bottom()) {
513 if (block.isVisible() && bottom >= e->rect().top()) {
514 p.save();
516 QRect numRect( 0, top, mLineIndicator->width() - 1, bottom - top );
518 int num = blockNumber;
519 if (num >= selStartBlock && num <= selEndBlock) {
520 num -= selStartBlock;
521 p.setPen(Qt::NoPen);
522 p.setBrush(plt.color(QPalette::Highlight));
523 p.drawRect(numRect);
524 p.setPen(plt.color(QPalette::HighlightedText));
527 QString number = QString::number(num + 1);
528 p.drawText(0, top, mLineIndicator->width() - 4, bottom - top,
529 Qt::AlignRight, number);
531 p.restore();
534 block = block.next();
535 top = bottom;
536 bottom = top + (int) blockBoundingRect(block).height();
537 ++blockNumber;
541 void GenericCodeEditor::copyUpDown(bool up)
543 // directly taken from qtcreator
544 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
545 // GNU Lesser General Public License
546 QTextCursor cursor = textCursor();
547 QTextCursor move = cursor;
548 move.beginEditBlock();
550 bool hasSelection = cursor.hasSelection();
552 if (hasSelection) {
553 move.setPosition(cursor.selectionStart());
554 move.movePosition(QTextCursor::StartOfBlock);
555 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
556 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
557 QTextCursor::KeepAnchor);
558 } else {
559 move.movePosition(QTextCursor::StartOfBlock);
560 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
563 QString text = move.selectedText();
565 if (up) {
566 move.setPosition(cursor.selectionStart());
567 move.movePosition(QTextCursor::StartOfBlock);
568 move.insertBlock();
569 move.movePosition(QTextCursor::Left);
570 } else {
571 move.movePosition(QTextCursor::EndOfBlock);
572 if (move.atBlockStart()) {
573 move.movePosition(QTextCursor::NextBlock);
574 move.insertBlock();
575 move.movePosition(QTextCursor::Left);
576 } else {
577 move.insertBlock();
581 int start = move.position();
582 move.clearSelection();
583 move.insertText(text);
584 int end = move.position();
586 move.setPosition(start);
587 move.setPosition(end, QTextCursor::KeepAnchor);
589 move.endEditBlock();
591 setTextCursor(move);
595 void GenericCodeEditor::toggleOverwriteMode()
597 setOverwriteMode(!overwriteMode());
601 void GenericCodeEditor::copyLineDown()
603 copyUpDown(false);
606 void GenericCodeEditor::copyLineUp()
608 copyUpDown(true);
611 void GenericCodeEditor::moveLineUpDown(bool up)
613 // directly taken from qtcreator
614 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
615 // GNU Lesser General Public License
616 QTextCursor cursor = textCursor();
617 QTextCursor move = cursor;
619 move.setVisualNavigation(false); // this opens folded items instead of destroying them
621 move.beginEditBlock();
623 bool hasSelection = cursor.hasSelection();
625 if (cursor.hasSelection()) {
626 move.setPosition(cursor.selectionStart());
627 move.movePosition(QTextCursor::StartOfBlock);
628 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
629 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
630 QTextCursor::KeepAnchor);
631 } else {
632 move.movePosition(QTextCursor::StartOfBlock);
633 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
635 QString text = move.selectedText();
637 move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
638 move.removeSelectedText();
640 if (up) {
641 move.movePosition(QTextCursor::PreviousBlock);
642 move.insertBlock();
643 move.movePosition(QTextCursor::Left);
644 } else {
645 move.movePosition(QTextCursor::EndOfBlock);
646 if (move.atBlockStart()) { // empty block
647 move.movePosition(QTextCursor::NextBlock);
648 move.insertBlock();
649 move.movePosition(QTextCursor::Left);
650 } else {
651 move.insertBlock();
655 int start = move.position();
656 move.clearSelection();
657 move.insertText(text);
658 int end = move.position();
660 if (hasSelection) {
661 move.setPosition(start);
662 move.setPosition(end, QTextCursor::KeepAnchor);
665 move.endEditBlock();
667 setTextCursor(move);
670 void GenericCodeEditor::moveLineUp()
672 moveLineUpDown(true);
675 void GenericCodeEditor::moveLineDown()
677 moveLineUpDown(false);
680 void GenericCodeEditor::gotoPreviousEmptyLine()
682 gotoEmptyLineUpDown(true);
685 void GenericCodeEditor::gotoNextEmptyLine()
687 gotoEmptyLineUpDown(false);
690 void GenericCodeEditor::gotoEmptyLineUpDown(bool up)
692 static const QRegExp whiteSpaceLine("^\\s*$");
694 const QTextCursor::MoveOperation direction = up ? QTextCursor::PreviousBlock
695 : QTextCursor::NextBlock;
697 QTextCursor cursor = textCursor();
698 cursor.beginEditBlock();
700 bool cursorMoved = false;
702 // find first non-whitespace line
703 while ( cursor.movePosition(direction) ) {
704 if ( !whiteSpaceLine.exactMatch(cursor.block().text()) )
705 break;
708 // find first whitespace line
709 while ( cursor.movePosition(direction) ) {
710 if ( whiteSpaceLine.exactMatch(cursor.block().text()) ) {
711 setTextCursor(cursor);
712 cursorMoved = true;
713 break;
717 if (!cursorMoved) {
718 const QTextCursor::MoveOperation startOrEnd = up ? QTextCursor::Start
719 : QTextCursor::End;
721 cursor.movePosition(startOrEnd);
722 setTextCursor(cursor);
725 cursor.endEditBlock();
728 void GenericCodeEditor::hideMouseCursor()
730 QCursor * overrideCursor = QApplication::overrideCursor();
731 if (!overrideCursor || overrideCursor->shape() != Qt::BlankCursor)
732 QApplication::setOverrideCursor( Qt::BlankCursor );
737 CodeEditor::CodeEditor( Document *doc, QWidget *parent ) :
738 GenericCodeEditor( doc, parent ),
739 mSpaceIndent(true),
740 mBlinkDuration(600),
741 mMouseBracketMatch(false),
742 mOverlay( new QGraphicsScene(this) ),
743 mAutoCompleter( new AutoCompleter(this) )
745 Q_ASSERT(mDoc != 0);
747 setFrameShape( QFrame::NoFrame );
749 mLineIndicator->move( contentsRect().topLeft() );
751 connect( this, SIGNAL(cursorPositionChanged()),
752 this, SLOT(matchBrackets()) );
754 connect( mOverlay, SIGNAL(changed(const QList<QRectF>&)),
755 this, SLOT(onOverlayChanged(const QList<QRectF>&)) );
757 connect( Main::instance(), SIGNAL(applySettingsRequest(Settings::Manager*)),
758 this, SLOT(applySettings(Settings::Manager*)) );
760 QTextDocument *tdoc = doc->textDocument();
761 mAutoCompleter->documentChanged(tdoc);
762 mLineIndicator->setLineCount(blockCount());
764 applySettings(Main::settings());
768 QTextCursor CodeEditor::currentRegion()
770 QTextCursor cursor = textCursor();
771 QTextBlock block = cursor.block();
772 int positionInBlock = cursor.positionInBlock();
774 if (TokenIterator(block, positionInBlock - 1).type() == Token::ClosingBracket)
775 cursor.movePosition( QTextCursor::PreviousCharacter );
776 else if (TokenIterator(block, positionInBlock).type() == Token::OpeningBracket)
777 cursor.movePosition( QTextCursor::NextCharacter );
779 return regionAtCursor( cursor );
783 QTextCursor CodeEditor::regionAtCursor(const QTextCursor & cursor)
785 QTextBlock block(cursor.block());
786 int positionInBlock = cursor.positionInBlock();
788 TokenIterator start;
789 TokenIterator end;
790 int topLevel = 0;
791 int level = 0;
793 // search unmatched opening bracket
794 TokenIterator it = TokenIterator::leftOf( block, positionInBlock );
795 while(it.isValid())
797 char chr = it->character;
798 if(chr == '(') {
799 ++level;
800 if(level > topLevel) {
801 topLevel = level;
802 start = it;
805 else if(chr == ')') {
806 --level;
808 --it;
811 if(topLevel < 1)
812 // no unmatched opening bracket
813 return QTextCursor();
815 // match the found opening bracket
816 it = TokenIterator::rightOf( block, positionInBlock );
817 while(it.isValid())
819 char chr = it->character;
820 if(chr == '(')
821 ++topLevel;
822 else if(chr == ')')
824 --topLevel;
825 if(topLevel == 0)
827 end = it;
828 break;
831 ++it;
834 if(start.isValid() && end.isValid())
836 // only care about brackets at beginning of a line
837 if(start->positionInBlock != 0)
838 return QTextCursor();
840 // check whether the bracket makes part of an event
841 it = start.next();
842 if (it.isValid()) {
843 if (it->type == Token::SymbolArg)
844 return QTextCursor();
845 else {
846 ++it;
847 if (it.isValid() && it->character == ':')
848 return QTextCursor();
852 // ok, this is is a real top-level region
853 QTextCursor c(QPlainTextEdit::document());
854 c.setPosition(start.position() + 1);
855 c.setPosition(end.position(), QTextCursor::KeepAnchor);
856 return c;
859 return QTextCursor();
862 QTextCursor CodeEditor::blockAtCursor(const QTextCursor & cursor)
864 TokenIterator it (cursor.block(), cursor.positionInBlock());
866 if (it.isValid()) {
867 switch (it->type) {
868 case Token::OpeningBracket:
869 case Token::ClosingBracket:
871 BracketMatch match;
872 matchBracket(it, match);
874 if (match.first.isValid()) {
875 QTextCursor selection(textDocument());
876 selection.setPosition(match.first.position());
877 selection.setPosition(match.second.position() + 1, QTextCursor::KeepAnchor);
878 return selection;
880 break;
883 default:
884 break;
888 return QTextCursor();
892 void CodeEditor::applySettings( Settings::Manager *settings )
894 settings->beginGroup("IDE/editor");
896 mSpaceIndent = settings->value("spaceIndent").toBool();
898 mBlinkDuration = settings->value("blinkDuration").toInt();
900 QPalette palette;
902 settings->beginGroup("colors");
904 if (settings->contains("text")) {
905 QTextCharFormat format = settings->value("text").value<QTextCharFormat>();
906 QBrush bg = format.background();
907 QBrush fg = format.foreground();
908 if (bg.style() != Qt::NoBrush)
909 palette.setBrush(QPalette::Base, bg);
910 if (fg.style() != Qt::NoBrush)
911 palette.setBrush(QPalette::Text, fg);
914 if (settings->contains("lineNumbers")) {
915 QPalette lineNumPlt;
916 QTextCharFormat format = settings->value("lineNumbers").value<QTextCharFormat>();
917 QBrush bg = format.background();
918 QBrush fg = format.foreground();
919 if (bg.style() != Qt::NoBrush)
920 palette.setBrush(QPalette::Button, bg);
921 if (fg.style() != Qt::NoBrush)
922 palette.setBrush(QPalette::ButtonText, fg);
923 mLineIndicator->setPalette(lineNumPlt);
926 mBracketHighlight = settings->value("matchingBrackets").value<QTextCharFormat>();
928 settings->endGroup(); // colors
930 setPalette(palette);
932 settings->endGroup();
935 bool CodeEditor::event( QEvent *e )
937 switch (e->type())
939 case QEvent::KeyPress:
941 QKeyEvent *ke = static_cast<QKeyEvent*>(e);
942 int key = ke->key();
943 switch (key)
945 case Qt::Key_Tab:
946 indent();
947 e->accept();
948 return true;
949 default:;
951 break;
953 default:;
955 return QPlainTextEdit::event(e);
958 void CodeEditor::keyPressEvent( QKeyEvent *e )
960 switch (e->key()) {
961 case Qt::Key_Home:
963 Qt::KeyboardModifiers mods(e->modifiers());
964 if (mods && mods != Qt::ShiftModifier) {
965 GenericCodeEditor::keyPressEvent(e);
966 return;
969 hideMouseCursor();
971 QTextCursor::MoveMode mode =
972 mods & Qt::ShiftModifier ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
974 QTextCursor c(textCursor());
975 QTextBlock b(c.block());
977 int pos = indentedStartOfLine(b);
978 pos += b.position();
980 if (c.position() == pos)
981 c.movePosition(QTextCursor::StartOfLine, mode);
982 else
983 c.setPosition(pos, mode);
985 setTextCursor(c);
987 return;
990 case Qt::Key_Backtab:
992 hideMouseCursor();
993 QTextCursor cursor = textCursor();
994 cursor.insertText("\t");
995 ensureCursorVisible();
996 return;
999 default:;
1002 switch (e->key()) {
1003 case Qt::Key_Enter:
1004 case Qt::Key_Return:
1005 case Qt::Key_BraceRight:
1006 case Qt::Key_BracketRight:
1007 case Qt::Key_ParenRight: {
1008 // Wrap superclass' implementation into an edit block,
1009 // so it can be joined with indentation later:
1011 QTextCursor cursor = textCursor();
1013 cursor.beginEditBlock();
1014 GenericCodeEditor::keyPressEvent(e);
1015 cursor.endEditBlock();
1017 cursor.joinPreviousEditBlock();
1018 indent();
1019 cursor.endEditBlock();
1021 cursor.setVerticalMovementX(-1);
1022 setTextCursor(cursor);
1024 break;
1026 default:
1027 GenericCodeEditor::keyPressEvent(e);
1030 mAutoCompleter->keyPress(e);
1033 void CodeEditor::mouseReleaseEvent ( QMouseEvent *e )
1035 // Prevent deselection of bracket match:
1036 if(!mMouseBracketMatch)
1037 GenericCodeEditor::mouseReleaseEvent(e);
1039 mMouseBracketMatch = false;
1042 void CodeEditor::mouseDoubleClickEvent( QMouseEvent * e )
1044 QTextCursor cursor = cursorForPosition(e->pos());
1045 QTextCursor selection = blockAtCursor(cursor);
1047 if (!selection.isNull()) {
1048 setTextCursor(selection);
1049 return;
1052 GenericCodeEditor::mouseDoubleClickEvent(e);
1055 void CodeEditor::mouseMoveEvent( QMouseEvent *e )
1057 // Prevent initiating a text drag:
1058 if(!mMouseBracketMatch)
1059 GenericCodeEditor::mouseMoveEvent(e);
1063 void CodeEditor::onOverlayChanged ( const QList<QRectF> & region )
1065 foreach(QRectF r, region)
1067 viewport()->update(r.toRect());
1071 void CodeEditor::paintEvent( QPaintEvent *e )
1073 GenericCodeEditor::paintEvent(e);
1075 QPainter p(viewport());
1076 mOverlay->render(&p, e->rect(), e->rect());
1079 void CodeEditor::matchBrackets()
1081 mBracketSelections.clear();
1083 QTextCursor cursor(textCursor());
1084 QTextBlock block( cursor.block() );
1085 int posInBlock = cursor.positionInBlock();
1086 TokenIterator it(block);
1087 while (it.isValid() && it.block() == block)
1089 const Token & token = *it;
1090 if (token.positionInBlock > posInBlock) {
1091 it = TokenIterator();
1092 break;
1093 } else if (
1094 (token.positionInBlock == posInBlock && token.type == Token::OpeningBracket) ||
1095 (token.positionInBlock == posInBlock - 1 && token.type == Token::ClosingBracket)
1097 break;
1098 ++it;
1101 BracketMatch match;
1102 if( it.isValid() && it.block() == block)
1103 matchBracket( it, match );
1105 if( match.first.isValid() && match.second.isValid() )
1107 const Token & tok1 = *match.first;
1108 const Token & tok2 = *match.second;
1110 if (
1111 (tok1.character == '(' && tok2.character == ')')
1112 || (tok1.character == '[' && tok2.character == ']')
1113 || (tok1.character == '{' && tok2.character == '}')
1115 QTextEdit::ExtraSelection selection;
1116 selection.format = mBracketHighlight;
1118 cursor.setPosition(match.first.position());
1119 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1120 selection.cursor = cursor;
1121 mBracketSelections.append(selection);
1123 cursor.setPosition(match.second.position());
1124 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1125 selection.cursor = cursor;
1126 mBracketSelections.append(selection);
1128 else {
1129 QTextEdit::ExtraSelection selection;
1130 selection.format.setBackground(Qt::red);
1131 cursor.setPosition(match.first.position());
1132 cursor.setPosition(match.second.position()+1, QTextCursor::KeepAnchor);
1133 selection.cursor = cursor;
1134 mBracketSelections.append(selection);
1138 updateExtraSelections();
1141 void CodeEditor::matchBracket( const TokenIterator & bracket , BracketMatch & match )
1143 TokenIterator it(bracket);
1145 if(it->type == Token::OpeningBracket)
1147 match.first = it;
1148 int level = 1;
1149 while((++it).isValid())
1151 Token::Type type = it->type;
1152 if(type == Token::ClosingBracket)
1153 --level;
1154 else if(type == Token::OpeningBracket)
1155 ++level;
1156 if(level == 0) {
1157 match.second = it;
1158 return;
1162 else if(it->type == Token::ClosingBracket)
1164 match.second = it;
1165 int level = 1;
1166 while((--it).isValid())
1168 Token::Type type = it->type;
1169 if(type == Token::OpeningBracket)
1170 --level;
1171 else if(type == Token::ClosingBracket)
1172 ++level;
1173 if(level == 0) {
1174 match.first = it;
1175 return;
1181 int CodeEditor::indentedStartOfLine( const QTextBlock &b )
1183 QString t(b.text());
1184 int n = t.size();
1185 int i = 0;
1186 while (i < n) {
1187 QChar c(t[i]);
1188 if (c != ' ' && c != '\t')
1189 break;
1190 ++i;
1193 return i;
1196 void CodeEditor::updateExtraSelections()
1198 QList<QTextEdit::ExtraSelection> selections;
1199 selections.append(mBracketSelections);
1200 selections.append(mSearchSelections);
1201 setExtraSelections(selections);
1204 void CodeEditor::indentCurrentRegion()
1206 indent(currentRegion());
1209 void CodeEditor::indent()
1211 indent(textCursor());
1214 void CodeEditor::indent( const QTextCursor & selection )
1216 if (selection.isNull())
1217 return;
1219 QTextCursor cursor(selection);
1221 cursor.beginEditBlock();
1223 QTextDocument *doc = QPlainTextEdit::document();
1224 int startBlockNum = doc->findBlock(cursor.selectionStart()).blockNumber();
1225 int endBlockNum = cursor.hasSelection() ?
1226 doc->findBlock(cursor.selectionEnd()).blockNumber() : startBlockNum;
1228 QStack<int> stack;
1229 int level = 0;
1230 int blockNum = 0;
1231 QTextBlock block = QPlainTextEdit::document()->begin();
1232 while (block.isValid())
1234 if (level > 0) {
1235 stack.push(level);
1236 level = 0;
1239 int initialStackSize = stack.size();
1241 TextBlockData *data = static_cast<TextBlockData*>(block.userData());
1242 if (data)
1244 int count = data->tokens.size();
1245 for (int idx = 0; idx < count; ++idx)
1247 const Token & token = data->tokens[idx];
1248 switch (token.type)
1250 case Token::OpeningBracket:
1251 if (token.character != '(' || stack.size() || token.positionInBlock)
1252 level += 1;
1253 break;
1255 case Token::ClosingBracket:
1256 if (level)
1257 level -= 1;
1258 else if(!stack.isEmpty()) {
1259 stack.top() -= 1;
1260 if (stack.top() <= 0)
1261 stack.pop();
1263 break;
1265 default:
1271 if(blockNum >= startBlockNum) {
1272 int indentLevel;
1273 if (data && data->tokens.size() && data->tokens[0].type == Token::ClosingBracket)
1274 indentLevel = stack.size();
1275 else
1276 indentLevel = initialStackSize;
1277 block = indent(block, indentLevel);
1280 if(blockNum == endBlockNum)
1281 break;
1283 block = block.next();
1284 ++blockNum;
1287 cursor.endEditBlock();
1290 QString CodeEditor::makeIndentationString(int level)
1292 if (level <= 0)
1293 return QString();
1295 if ( mSpaceIndent ) {
1296 const int spaces = mDoc->indentWidth() * level;
1297 QString indentationString (spaces, QChar(' '));
1298 return indentationString;
1299 } else {
1300 const int tabs = level;
1301 QString indentationString (tabs, QChar('\t'));
1302 return indentationString;
1306 QTextBlock CodeEditor::indent( const QTextBlock & block, int level )
1308 QTextCursor cursor(block);
1309 cursor.movePosition(QTextCursor::StartOfBlock);
1310 cursor.setPosition(cursor.position() + indentedStartOfLine(block), QTextCursor::KeepAnchor);
1312 cursor.insertText(makeIndentationString(level));
1314 // modification has invalidated the block, so return a new one
1315 return cursor.block();
1318 int CodeEditor::indentationLevel(const QTextCursor & cursor)
1320 QTextDocument *doc = QPlainTextEdit::document();
1321 int startBlockNum = doc->findBlock(cursor.selectionStart()).blockNumber();
1323 QStack<int> stack;
1324 int level = 0;
1325 int blockNum = 0;
1326 QTextBlock block = QPlainTextEdit::document()->begin();
1327 while (block.isValid()) {
1328 if (level > 0) {
1329 stack.push(level);
1330 level = 0;
1333 TextBlockData *data = static_cast<TextBlockData*>(block.userData());
1334 if (data) {
1335 int count = data->tokens.size();
1336 for (int idx = 0; idx < count; ++idx) {
1337 const Token & token = data->tokens[idx];
1338 switch (token.type) {
1339 case Token::OpeningBracket:
1340 if (token.character != '(' || stack.size() || token.positionInBlock)
1341 level += 1;
1342 break;
1344 case Token::ClosingBracket:
1345 if (level)
1346 level -= 1;
1347 else if(!stack.isEmpty()) {
1348 stack.top() -= 1;
1349 if (stack.top() <= 0)
1350 stack.pop();
1352 break;
1354 default:
1360 if (blockNum == startBlockNum)
1361 return stack.size();
1363 block = block.next();
1364 ++blockNum;
1367 return -1;
1370 void CodeEditor::triggerAutoCompletion()
1372 mAutoCompleter->triggerCompletion();
1375 void CodeEditor::triggerMethodCallAid()
1377 mAutoCompleter->triggerMethodCallAid();
1380 static bool isSingleLineComment(QTextBlock const & block)
1382 static QRegExp commentRegex("^\\s*//.*");
1383 return commentRegex.exactMatch(block.text());
1386 static bool isSelectionComment(QString const & text)
1388 QString trimmed = text.trimmed();
1389 if ( trimmed.startsWith(QString("/*")) && trimmed.endsWith(QString("*/")) )
1390 return true;
1391 else
1392 return false;
1395 void CodeEditor::toggleComment()
1397 QTextCursor cursor = textCursor();
1399 if (cursor.hasSelection())
1400 toggleCommentSelection();
1401 else
1402 toggleCommentSingleLine();
1405 void CodeEditor::toggleCommentSingleLine()
1407 QTextCursor cursor = textCursor();
1408 cursor.beginEditBlock();
1410 toggleCommentSingleLine( cursor );
1412 cursor.endEditBlock();
1415 void CodeEditor::addSingleLineComment(QTextCursor cursor, int indentation)
1417 QTextBlock currentBlock(cursor.block());
1418 int blockIndentationLevel = indentationLevel(cursor);
1420 cursor.movePosition(QTextCursor::StartOfBlock);
1421 cursor.setPosition(cursor.position() + indentedStartOfLine(currentBlock), QTextCursor::KeepAnchor);
1423 QString commentString = makeIndentationString(indentation) + QString("// ")
1424 + makeIndentationString(blockIndentationLevel - indentation);
1426 cursor.insertText(commentString);
1428 cursor.movePosition(QTextCursor::StartOfBlock);
1431 void CodeEditor::removeSingleLineComment(QTextCursor cursor)
1433 QTextBlock currentBlock(cursor.block());
1434 cursor.movePosition(QTextCursor::StartOfBlock);
1435 cursor.setPosition(cursor.position() + indentedStartOfLine(currentBlock) + 2, QTextCursor::KeepAnchor);
1437 if (!cursor.selectedText().endsWith(QString("//")))
1438 cursor.setPosition(cursor.anchor() + indentedStartOfLine(currentBlock), QTextCursor::KeepAnchor);
1440 cursor.insertText("");
1443 void CodeEditor::toggleCommentSingleLine(QTextCursor cursor)
1445 QTextBlock currentBlock(cursor.block());
1447 cursor.beginEditBlock();
1449 if (!isSingleLineComment(currentBlock)) {
1450 int blockIndentation = indentationLevel(cursor);
1451 addSingleLineComment(cursor, blockIndentation);
1452 } else
1453 removeSingleLineComment(cursor);
1455 cursor.endEditBlock();
1456 indent(cursor);
1459 static bool isBlockOnlySelection(QTextCursor cursor)
1461 Q_ASSERT(cursor.hasSelection());
1463 QTextCursor begin(cursor);
1464 begin.setPosition(begin.anchor());
1466 if (begin.atBlockStart() && (cursor.atBlockStart() || cursor.atBlockEnd()))
1467 return true;
1468 else
1469 return false;
1472 void CodeEditor::toggleCommentSelection()
1474 QTextCursor cursor = textCursor();
1475 cursor.beginEditBlock();
1477 if (isBlockOnlySelection(cursor)) {
1478 QTextCursor selectionCursor(cursor);
1479 selectionCursor.setPosition(cursor.selectionStart());
1481 QTextBlock currentBlock = selectionCursor.block();
1482 bool isComment = isSingleLineComment(currentBlock);
1483 int firstBlockIndentation = isComment ? 0
1484 : indentationLevel(selectionCursor);
1486 do {
1487 QTextCursor blockCursor(currentBlock);
1488 if (!isComment)
1489 addSingleLineComment(blockCursor, firstBlockIndentation);
1490 else
1491 removeSingleLineComment(blockCursor);
1492 currentBlock = currentBlock.next();
1493 } while (currentBlock.isValid() && currentBlock.position() < cursor.selectionEnd());
1494 } else {
1495 QString selectionText = cursor.selectedText();
1496 QTextCursor selectionCursor(cursor);
1497 if (isSelectionComment(selectionText)) {
1498 selectionText = selectionText.trimmed().remove(0, 2);
1499 selectionText.chop(2);
1500 selectionCursor.insertText(selectionText);
1501 } else {
1502 selectionText = QString("/* ") + selectionText + QString(" */");
1503 selectionCursor.insertText(selectionText);
1505 int position = selectionCursor.position();
1506 cursor.setPosition(position - selectionText.size());
1507 cursor.setPosition(position, QTextCursor::KeepAnchor);
1508 setTextCursor(cursor);
1512 cursor.endEditBlock();
1515 // taking nested brackets into account
1516 static TokenIterator previousOpeningBracket(TokenIterator it)
1518 int level = 0;
1519 while (it.isValid()) {
1520 switch (it->type) {
1521 case Token::OpeningBracket:
1522 if (level == 0)
1523 return it;
1524 --level;
1525 break;
1527 case Token::ClosingBracket:
1528 ++level;
1530 default:
1531 break;
1533 --it;
1535 return it;
1538 // taking nested brackets into account
1539 static TokenIterator nextClosingBracket(TokenIterator it)
1541 int level = 0;
1542 while (it.isValid()) {
1543 switch (it->type) {
1544 case Token::ClosingBracket:
1545 if (level == 0)
1546 return it;
1547 --level;
1548 break;
1550 case Token::OpeningBracket:
1551 ++level;
1553 default:
1554 break;
1556 ++it;
1558 return it;
1562 void CodeEditor::gotoNextBlock()
1564 QTextCursor cursor = textCursor();
1566 TokenIterator tokenIt = TokenIterator::rightOf( cursor.block(), cursor.positionInBlock() );
1567 if (tokenIt.type() == Token::OpeningBracket
1568 && tokenIt.block() == cursor.block()
1569 && tokenIt->positionInBlock == cursor.positionInBlock())
1570 ++tokenIt;
1572 tokenIt = nextClosingBracket( tokenIt );
1574 if (tokenIt.isValid())
1575 setTextCursor( cursorAt(tokenIt, 1) );
1576 else {
1577 cursor.movePosition( QTextCursor::End );
1578 setTextCursor( cursor );
1582 void CodeEditor::gotoPreviousBlock()
1584 QTextCursor cursor = textCursor();
1586 TokenIterator tokenIt = TokenIterator::leftOf(cursor.block(), cursor.positionInBlock());
1587 if (tokenIt.type() == Token::ClosingBracket
1588 && tokenIt.block() == cursor.block()
1589 && tokenIt->positionInBlock == cursor.positionInBlock() - 1)
1590 --tokenIt;
1593 tokenIt = previousOpeningBracket( tokenIt );
1595 if (tokenIt.isValid())
1596 setTextCursor( cursorAt(tokenIt) );
1597 else {
1598 cursor.movePosition( QTextCursor::Start );
1599 setTextCursor( cursor );
1603 void CodeEditor::selectCurrentRegion()
1605 QTextCursor selectedRegionCursor = currentRegion();
1606 if (!selectedRegionCursor.isNull() && selectedRegionCursor.hasSelection())
1607 setTextCursor(selectedRegionCursor);
1610 void CodeEditor::gotoNextRegion()
1612 QTextCursor cursor = textCursor();
1614 QTextCursor regionCursor = regionAtCursor(cursor);
1615 if (!regionCursor.isNull()) {
1616 cursor = regionCursor;
1617 // regionCursor does not include region's closing bracket, so skip it
1618 cursor.movePosition(QTextCursor::NextCharacter);
1621 // Skip potential opening bracket immediately right of cursor:
1622 cursor.movePosition(QTextCursor::NextCharacter);
1624 TokenIterator it = TokenIterator::rightOf(cursor.block(), cursor.positionInBlock());
1626 while (it.isValid()) {
1627 if ( (it->type == Token::OpeningBracket) && (it->character == '(') &&
1628 (it->positionInBlock == 0) ) {
1629 setTextCursor( cursorAt(it, 1) );
1630 return;
1632 ++it;
1635 cursor.movePosition(QTextCursor::End);
1636 setTextCursor(cursor);
1639 void CodeEditor::gotoPreviousRegion()
1641 QTextCursor cursor = textCursor();
1643 QTextCursor regionCursor = regionAtCursor(cursor);
1645 if (!regionCursor.isNull()) {
1646 // regionCursor does not include region's opening bracket, so skip it:
1647 cursor.setPosition( regionCursor.selectionStart() - 1 );
1650 // Skip potential closing bracket immediately left of cursor:
1651 cursor.movePosition( QTextCursor::PreviousCharacter );
1653 TokenIterator it = TokenIterator::leftOf(cursor.block(), cursor.positionInBlock());
1655 while (it.isValid()) {
1656 if ( (it->type == Token::ClosingBracket) && (it->character == ')') &&
1657 (it->positionInBlock == 0) ) {
1658 setTextCursor( cursorAt(it) );
1659 return;
1661 --it;
1664 cursor.movePosition(QTextCursor::Start);
1665 setTextCursor(cursor);
1668 bool CodeEditor::openDocumentation()
1670 return Main::openDocumentation(symbolUnderCursor());
1673 void CodeEditor::openDefinition()
1675 Main::openDefinition(symbolUnderCursor(), this);
1678 void CodeEditor::findReferences()
1680 Main::findReferences(symbolUnderCursor(), this);
1683 QTextCursor CodeEditor::cursorAt(const TokenIterator it, int offset)
1685 Q_ASSERT(it.isValid());
1687 QTextCursor textCursor(textDocument());
1688 textCursor.setPosition(it.position() + offset);
1690 return textCursor;
1693 } // namespace ScIDE