sc ide: simplify CodeEditor::gotoNextRegion() and ::gotoPreviousRegion()
[supercollider.git] / editors / sc-ide / widgets / code_editor / editor.cpp
blob94c6b879a9cf44a97311bd8f9c1c2eb45bbd8cff
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 "../sc_symbol_references.hpp"
26 #include "../../core/doc_manager.hpp"
27 #include "../../core/main.hpp"
28 #include "../../core/settings/manager.hpp"
30 #include <QApplication>
31 #include <QDebug>
32 #include <QKeyEvent>
33 #include <QPainter>
34 #include <QPaintEvent>
35 #include <QScrollBar>
36 #include <QTextBlock>
37 #include <QTextDocumentFragment>
38 #include <QUrl>
40 namespace ScIDE {
42 GenericCodeEditor::GenericCodeEditor( Document *doc, QWidget *parent ):
43 QPlainTextEdit( parent ),
44 mLineIndicator( new LineIndicator(this) ),
45 mDoc(doc)
47 Q_ASSERT(mDoc != 0);
49 setFrameShape( QFrame::NoFrame );
51 mLineIndicator->move( contentsRect().topLeft() );
53 connect( mDoc, SIGNAL(defaultFontChanged()), this, SLOT(onDocumentFontChanged()) );
55 connect( this, SIGNAL(blockCountChanged(int)),
56 mLineIndicator, SLOT(setLineCount(int)) );
58 connect( mLineIndicator, SIGNAL( widthChanged() ),
59 this, SLOT( updateLayout() ) );
61 connect( this, SIGNAL(updateRequest(QRect,int)),
62 this, SLOT(updateLineIndicator(QRect,int)) );
64 connect( this, SIGNAL(selectionChanged()),
65 mLineIndicator, SLOT(update()) );
67 QTextDocument *tdoc = doc->textDocument();
68 QPlainTextEdit::setDocument(tdoc);
69 onDocumentFontChanged();
70 mLineIndicator->setLineCount(blockCount());
74 bool GenericCodeEditor::showWhitespace()
76 QTextOption options( textDocument()->defaultTextOption() );
77 return options.flags().testFlag( QTextOption::ShowTabsAndSpaces );
80 void GenericCodeEditor::setShowWhitespace(bool show)
82 QTextDocument *doc = textDocument();
83 QTextOption opt( doc->defaultTextOption() );
84 if( show )
85 opt.setFlags( opt.flags() | QTextOption::ShowTabsAndSpaces );
86 else
87 opt.setFlags( opt.flags() & ~QTextOption::ShowTabsAndSpaces );
88 doc->setDefaultTextOption(opt);
91 static bool findInBlock(QTextDocument *doc, const QTextBlock &block, const QRegExp &expr, int offset,
92 QTextDocument::FindFlags options, QTextCursor &cursor)
94 QString text = block.text();
95 if(options & QTextDocument::FindBackward)
96 text.truncate(offset);
97 text.replace(QChar::Nbsp, QLatin1Char(' '));
99 int idx = -1;
100 while (offset >=0 && offset <= text.length()) {
101 idx = (options & QTextDocument::FindBackward) ?
102 expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
103 if (idx == -1)
104 return false;
106 if (options & QTextDocument::FindWholeWords) {
107 const int start = idx;
108 const int end = start + expr.matchedLength();
109 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
110 || (end != text.length() && text.at(end).isLetterOrNumber())) {
111 //if this is not a whole word, continue the search in the string
112 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
113 idx = -1;
114 continue;
117 //we have a hit, return the cursor for that.
118 break;
121 if (idx == -1)
122 return false;
124 cursor = QTextCursor(doc);
125 cursor.setPosition(block.position() + idx);
126 cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor);
127 return true;
130 bool GenericCodeEditor::find( const QRegExp &expr, QTextDocument::FindFlags options )
132 // Although QTextDocument provides a find() method, we implement
133 // our own, because the former one is not adequate.
135 if(expr.isEmpty()) return true;
137 bool backwards = options & QTextDocument::FindBackward;
139 QTextCursor c( textCursor() );
140 int pos;
141 if (c.hasSelection())
143 bool matching = expr.exactMatch(c.selectedText());
145 if( backwards == matching )
146 pos = c.selectionStart();
147 else
148 pos = c.selectionEnd();
150 else
151 pos = c.position();
153 QTextDocument *doc = QPlainTextEdit::document();
154 QTextBlock startBlock = doc->findBlock(pos);
155 int startBlockOffset = pos - startBlock.position();
157 QTextCursor cursor;
159 if (!backwards) {
160 int blockOffset = startBlockOffset;
161 QTextBlock block = startBlock;
162 while (block.isValid()) {
163 if (findInBlock(doc, block, expr, blockOffset, options, cursor))
164 break;
165 blockOffset = 0;
166 block = block.next();
168 if(cursor.isNull())
170 blockOffset = 0;
171 block = doc->begin();
172 while(true) {
173 if (findInBlock(doc, block, expr, blockOffset, options, cursor)
174 || block == startBlock)
175 break;
176 block = block.next();
179 } else {
180 int blockOffset = startBlockOffset;
181 QTextBlock block = startBlock;
182 while (block.isValid()) {
183 if (findInBlock(doc, block, expr, blockOffset, options, cursor))
184 break;
185 block = block.previous();
186 blockOffset = block.length() - 1;
188 if(cursor.isNull())
190 block = doc->end();
191 while(true) {
192 blockOffset = block.length() - 1;
193 if (findInBlock(doc, block, expr, blockOffset, options, cursor)
194 || block == startBlock)
195 break;
196 block = block.previous();
201 if(!cursor.isNull()) {
202 setTextCursor(cursor);
203 return true;
205 else
206 return false;
209 int GenericCodeEditor::findAll( const QRegExp &expr, QTextDocument::FindFlags options )
211 mSearchSelections.clear();
213 if(expr.isEmpty()) {
214 this->updateExtraSelections();
215 return 0;
218 QTextEdit::ExtraSelection selection;
219 selection.format.setBackground(Qt::darkYellow);
221 QTextDocument *doc = QPlainTextEdit::document();
222 QTextBlock block = doc->begin();
223 QTextCursor cursor;
225 while (block.isValid()) {
226 int blockPos = block.position();
227 int offset = 0;
228 while(findInBlock(doc, block, expr, offset, options, cursor))
230 offset = cursor.selectionEnd() - blockPos;
231 selection.cursor = cursor;
232 mSearchSelections.append(selection);
234 block = block.next();
237 this->updateExtraSelections();
239 return mSearchSelections.count();
242 //#define CSTR(QSTR) QSTR.toStdString().c_str()
244 static QString resolvedReplacement( const QString &replacement, const QRegExp &expr )
246 //qDebug("START");
247 static const QRegExp rexpr("(\\\\\\\\)|(\\\\[0-9]+)");
248 QString str( replacement );
249 int i=0;
250 while(i < str.size() && ((i = rexpr.indexIn(str, i)) != -1))
252 int len = rexpr.matchedLength();
253 if(rexpr.pos(1) != -1)
255 //qDebug("%i (%s): escape", i, CSTR(rexpr.cap(1)));
256 str.replace(i, len, "\\");
257 i += 1;
259 else if(rexpr.pos(2) != -1)
261 QString num_str = rexpr.cap(2);
262 num_str.remove(0, 1);
263 int num = num_str.toInt();
264 //qDebug("%i (%s): backref = %i", i, CSTR(rexpr.cap(2)), num);
265 if(num <= expr.captureCount())
267 QString cap = expr.cap(num);
268 //qDebug("resolving ref to: %s", CSTR(cap));
269 str.replace(i, len, cap);
270 i += cap.size();
272 else
274 //qDebug("ref out of range", i, num);
275 str.remove(i, len);
278 else
280 //qDebug("%i (%s): unknown match", i, CSTR(rexpr.cap(0)));
281 str.remove(i, len);
283 //qDebug(">> [%s] %i", CSTR(str), i);
285 //qDebug("END");
286 return str;
289 bool GenericCodeEditor::replace( const QRegExp &expr, const QString &replacement, QTextDocument::FindFlags options )
291 if(expr.isEmpty()) return true;
293 QTextCursor cursor = textCursor();
294 if (cursor.hasSelection() && expr.exactMatch(cursor.selectedText()))
296 QString rstr = replacement;
297 if(expr.patternSyntax() != QRegExp::FixedString)
298 rstr = resolvedReplacement(rstr, expr);
299 cursor.insertText(rstr);
302 return find(expr, options);
305 int GenericCodeEditor::replaceAll( const QRegExp &expr, const QString &replacement, QTextDocument::FindFlags options )
307 mSearchSelections.clear();
308 updateExtraSelections();
310 if(expr.isEmpty()) return 0;
312 int replacements = 0;
313 bool caps = expr.patternSyntax() != QRegExp::FixedString;
315 QTextDocument *doc = QPlainTextEdit::document();
316 QTextBlock block = doc->begin();
317 QTextCursor cursor;
319 QTextCursor(doc).beginEditBlock();
321 while (block.isValid())
323 int blockPos = block.position();
324 int offset = 0;
325 while(findInBlock(doc, block, expr, offset, options, cursor))
327 QString rstr = replacement;
328 if(caps)
329 rstr = resolvedReplacement(rstr, expr);
330 cursor.insertText(rstr);
331 ++replacements;
332 offset = cursor.selectionEnd() - blockPos;
334 block = block.next();
337 QTextCursor(doc).endEditBlock();
339 return replacements;
342 void GenericCodeEditor::showPosition( int pos )
344 if (pos < 0) return;
346 QTextDocument *doc = QPlainTextEdit::document();
347 if (!doc) return;
349 int lineNumber = doc->findBlock(pos).firstLineNumber();
350 verticalScrollBar()->setValue(lineNumber);
352 QTextCursor cursor(doc);
353 cursor.setPosition(pos);
354 setTextCursor(cursor);
357 QString GenericCodeEditor::symbolUnderCursor()
359 QTextCursor cursor = textCursor();
360 if (!cursor.hasSelection())
361 cursor.select(QTextCursor::WordUnderCursor);
362 return cursor.selectedText();
365 void GenericCodeEditor::keyPressEvent(QKeyEvent * e)
367 hideMouseCursor();
369 switch (e->key()) {
370 case Qt::Key_Enter:
371 case Qt::Key_Return:
372 // override to avoid entering a "soft" new line when certain modifier is held
373 textCursor().insertBlock();
374 ensureCursorVisible();
375 break;
377 default:
378 QPlainTextEdit::keyPressEvent(e);
382 void GenericCodeEditor::wheelEvent( QWheelEvent * e )
384 if (e->modifiers() == Qt::ControlModifier) {
385 if (e->delta() > 0)
386 zoomIn();
387 else
388 zoomOut();
389 return;
392 QPlainTextEdit::wheelEvent(e);
396 void GenericCodeEditor::dragEnterEvent( QDragEnterEvent * event )
398 const QMimeData * data = event->mimeData();
399 if (data->hasUrls()) {
400 foreach (QUrl url, event->mimeData()->urls()) {
401 if (url.scheme() == QString("file")) { // LATER: use isLocalFile
402 // LATER: check mime type ?
403 event->acceptProposedAction();
404 return;
407 return;
410 static QString textPlain("text/plain");
411 if (data->formats().contains(textPlain))
412 event->acceptProposedAction();
415 void GenericCodeEditor::dropEvent( QDropEvent * event )
417 const QMimeData * data = event->mimeData();
418 if (data->hasUrls()) {
419 foreach (QUrl url, data->urls()) {
420 if (url.scheme() == QString("file")) // LATER: use isLocalFile
421 Main::documentManager()->open(url.toLocalFile());
423 return;
426 QTextCursor cursor = cursorForPosition(event->pos());
427 cursor.insertText(data->text());
431 void GenericCodeEditor::clearSearchHighlighting()
433 mSearchSelections.clear();
434 this->updateExtraSelections();
437 void GenericCodeEditor::zoomIn(int steps)
439 zoomFont(steps);
442 void GenericCodeEditor::zoomOut(int steps)
444 zoomFont(-steps);
447 void GenericCodeEditor::resetFontSize()
449 mDoc->resetDefaultFont();
452 void GenericCodeEditor::zoomFont(int steps)
454 QFont currentFont = mDoc->defaultFont();
455 const int newSize = currentFont.pointSize() + steps;
456 if (newSize <= 0)
457 return;
458 currentFont.setPointSize(newSize);
459 mDoc->setDefaultFont(currentFont);
462 void GenericCodeEditor::onDocumentFontChanged()
464 QFont font = mDoc->defaultFont();
465 setFont(font);
468 void GenericCodeEditor::updateLayout()
470 setViewportMargins( mLineIndicator->width(), 0, 0, 0 );
473 void GenericCodeEditor::updateLineIndicator( QRect r, int dy )
475 if (dy)
476 mLineIndicator->scroll(0, dy);
477 else
478 mLineIndicator->update(0, r.y(), mLineIndicator->width(), r.height() );
481 void GenericCodeEditor::updateExtraSelections()
483 QList<QTextEdit::ExtraSelection> selections;
484 selections.append(mSearchSelections);
485 setExtraSelections(selections);
488 void GenericCodeEditor::resizeEvent( QResizeEvent *e )
490 QPlainTextEdit::resizeEvent( e );
492 QRect cr = contentsRect();
493 mLineIndicator->resize( mLineIndicator->width(), cr.height() );
496 void GenericCodeEditor::paintLineIndicator( QPaintEvent *e )
498 QPalette plt( mLineIndicator->palette() );
499 QRect r( e->rect() );
500 QPainter p( mLineIndicator );
502 p.fillRect( r, plt.color( QPalette::Button ) );
503 p.setPen( plt.color(QPalette::ButtonText) );
504 p.drawLine( r.topRight(), r.bottomRight() );
506 QTextDocument *doc = QPlainTextEdit::document();
507 QTextCursor cursor(textCursor());
508 int selStartBlock, selEndBlock;
509 if (cursor.hasSelection()) {
510 selStartBlock = doc->findBlock(cursor.selectionStart()).blockNumber();
511 selEndBlock = doc->findBlock(cursor.selectionEnd()).blockNumber();
513 else
514 selStartBlock = selEndBlock = -1;
516 QTextBlock block = firstVisibleBlock();
517 int blockNumber = block.blockNumber();
518 int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
519 int bottom = top + (int) blockBoundingRect(block).height();
521 while (block.isValid() && top <= e->rect().bottom()) {
522 if (block.isVisible() && bottom >= e->rect().top()) {
523 p.save();
525 QRect numRect( 0, top, mLineIndicator->width() - 1, bottom - top );
527 int num = blockNumber;
528 if (num >= selStartBlock && num <= selEndBlock) {
529 num -= selStartBlock;
530 p.setPen(Qt::NoPen);
531 p.setBrush(plt.color(QPalette::Highlight));
532 p.drawRect(numRect);
533 p.setPen(plt.color(QPalette::HighlightedText));
536 QString number = QString::number(num + 1);
537 p.drawText(0, top, mLineIndicator->width() - 4, bottom - top,
538 Qt::AlignRight, number);
540 p.restore();
543 block = block.next();
544 top = bottom;
545 bottom = top + (int) blockBoundingRect(block).height();
546 ++blockNumber;
550 void GenericCodeEditor::copyUpDown(bool up)
552 // directly taken from qtcreator
553 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
554 // GNU Lesser General Public License
555 QTextCursor cursor = textCursor();
556 QTextCursor move = cursor;
557 move.beginEditBlock();
559 bool hasSelection = cursor.hasSelection();
561 if (hasSelection) {
562 move.setPosition(cursor.selectionStart());
563 move.movePosition(QTextCursor::StartOfBlock);
564 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
565 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
566 QTextCursor::KeepAnchor);
567 } else {
568 move.movePosition(QTextCursor::StartOfBlock);
569 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
572 QString text = move.selectedText();
574 if (up) {
575 move.setPosition(cursor.selectionStart());
576 move.movePosition(QTextCursor::StartOfBlock);
577 move.insertBlock();
578 move.movePosition(QTextCursor::Left);
579 } else {
580 move.movePosition(QTextCursor::EndOfBlock);
581 if (move.atBlockStart()) {
582 move.movePosition(QTextCursor::NextBlock);
583 move.insertBlock();
584 move.movePosition(QTextCursor::Left);
585 } else {
586 move.insertBlock();
590 int start = move.position();
591 move.clearSelection();
592 move.insertText(text);
593 int end = move.position();
595 move.setPosition(start);
596 move.setPosition(end, QTextCursor::KeepAnchor);
598 move.endEditBlock();
599 this->indentCurrentRegion();
601 setTextCursor(move);
605 void GenericCodeEditor::toggleOverwriteMode()
607 setOverwriteMode(!overwriteMode());
611 void GenericCodeEditor::copyLineDown()
613 copyUpDown(false);
616 void GenericCodeEditor::copyLineUp()
618 copyUpDown(true);
621 void GenericCodeEditor::moveLineUpDown(bool up)
623 // directly taken from qtcreator
624 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
625 // GNU Lesser General Public License
626 QTextCursor cursor = textCursor();
627 QTextCursor move = cursor;
629 move.setVisualNavigation(false); // this opens folded items instead of destroying them
631 move.beginEditBlock();
633 bool hasSelection = cursor.hasSelection();
635 if (cursor.hasSelection()) {
636 move.setPosition(cursor.selectionStart());
637 move.movePosition(QTextCursor::StartOfBlock);
638 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
639 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
640 QTextCursor::KeepAnchor);
641 } else {
642 move.movePosition(QTextCursor::StartOfBlock);
643 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
645 QString text = move.selectedText();
647 move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
648 move.removeSelectedText();
650 if (up) {
651 move.movePosition(QTextCursor::PreviousBlock);
652 move.insertBlock();
653 move.movePosition(QTextCursor::Left);
654 } else {
655 move.movePosition(QTextCursor::EndOfBlock);
656 if (move.atBlockStart()) { // empty block
657 move.movePosition(QTextCursor::NextBlock);
658 move.insertBlock();
659 move.movePosition(QTextCursor::Left);
660 } else {
661 move.insertBlock();
665 int start = move.position();
666 move.clearSelection();
667 move.insertText(text);
668 int end = move.position();
670 if (hasSelection) {
671 move.setPosition(start);
672 move.setPosition(end, QTextCursor::KeepAnchor);
675 move.endEditBlock();
676 this->indentCurrentRegion();
678 setTextCursor(move);
681 void GenericCodeEditor::moveLineUp()
683 moveLineUpDown(true);
686 void GenericCodeEditor::moveLineDown()
688 moveLineUpDown(false);
691 void GenericCodeEditor::gotoPreviousEmptyLine()
693 gotoEmptyLineUpDown(true);
696 void GenericCodeEditor::gotoNextEmptyLine()
698 gotoEmptyLineUpDown(false);
701 void GenericCodeEditor::gotoEmptyLineUpDown(bool up)
703 static const QRegExp whiteSpaceLine("^\\s*$");
705 const QTextCursor::MoveOperation direction = up ? QTextCursor::PreviousBlock
706 : QTextCursor::NextBlock;
708 QTextCursor cursor = textCursor();
709 cursor.beginEditBlock();
711 bool cursorMoved = false;
713 // find first non-whitespace line
714 while ( cursor.movePosition(direction) ) {
715 if ( !whiteSpaceLine.exactMatch(cursor.block().text()) )
716 break;
719 // find first whitespace line
720 while ( cursor.movePosition(direction) ) {
721 if ( whiteSpaceLine.exactMatch(cursor.block().text()) ) {
722 setTextCursor(cursor);
723 cursorMoved = true;
724 break;
728 if (!cursorMoved) {
729 const QTextCursor::MoveOperation startOrEnd = up ? QTextCursor::Start
730 : QTextCursor::End;
732 cursor.movePosition(startOrEnd);
733 setTextCursor(cursor);
736 cursor.endEditBlock();
739 void GenericCodeEditor::hideMouseCursor()
741 QCursor * overrideCursor = QApplication::overrideCursor();
742 if (!overrideCursor || overrideCursor->shape() != Qt::BlankCursor)
743 QApplication::setOverrideCursor( Qt::BlankCursor );
748 CodeEditor::CodeEditor( Document *doc, QWidget *parent ) :
749 GenericCodeEditor( doc, parent ),
750 mSpaceIndent(true),
751 mBlinkDuration(600),
752 mMouseBracketMatch(false),
753 mOverlay( new QGraphicsScene(this) ),
754 mAutoCompleter( new AutoCompleter(this) )
756 Q_ASSERT(mDoc != 0);
758 setFrameShape( QFrame::NoFrame );
760 mLineIndicator->move( contentsRect().topLeft() );
762 connect( this, SIGNAL(cursorPositionChanged()),
763 this, SLOT(matchBrackets()) );
765 connect( mOverlay, SIGNAL(changed(const QList<QRectF>&)),
766 this, SLOT(onOverlayChanged(const QList<QRectF>&)) );
768 connect( Main::instance(), SIGNAL(applySettingsRequest(Settings::Manager*)),
769 this, SLOT(applySettings(Settings::Manager*)) );
771 QTextDocument *tdoc = doc->textDocument();
772 mAutoCompleter->documentChanged(tdoc);
773 mLineIndicator->setLineCount(blockCount());
775 applySettings(Main::settings());
779 QTextCursor CodeEditor::currentRegion()
781 return regionAtCursor(textCursor());
785 QTextCursor CodeEditor::regionAtCursor(QTextCursor c)
787 QTextBlock block(c.block());
788 int positionInBlock = c.positionInBlock();
790 TokenIterator start;
791 TokenIterator end;
792 int topLevel = 0;
793 int level = 0;
795 // search unmatched opening bracket
796 TokenIterator it = TokenIterator::leftOf( block, positionInBlock );
797 while(it.isValid())
799 char chr = it->character;
800 if(chr == '(') {
801 ++level;
802 if(level > topLevel) {
803 topLevel = level;
804 start = it;
807 else if(chr == ')') {
808 --level;
810 --it;
813 if(topLevel < 1)
814 // no unmatched opening bracket
815 return QTextCursor();
817 // match the found opening bracket
818 it = TokenIterator::rightOf( block, positionInBlock );
819 while(it.isValid())
821 char chr = it->character;
822 if(chr == '(')
823 ++topLevel;
824 else if(chr == ')')
826 --topLevel;
827 if(topLevel == 0)
829 end = it;
830 break;
833 ++it;
836 if(start.isValid() && end.isValid())
838 // only care about brackets at beginning of a line
839 if(start->positionInBlock != 0)
840 return QTextCursor();
842 // check whether the bracket makes part of an event
843 it = start.next();
844 if (it.isValid()) {
845 if (it->type == Token::SymbolArg)
846 return QTextCursor();
847 else {
848 ++it;
849 if (it.isValid() && it->character == ':')
850 return QTextCursor();
854 // ok, this is is a real top-level region
855 QTextCursor c(QPlainTextEdit::document());
856 c.setPosition(start.position() + 1);
857 c.setPosition(end.position(), QTextCursor::KeepAnchor);
858 return c;
861 return QTextCursor();
864 QTextCursor CodeEditor::blockAtCursor(QTextCursor cursor)
866 TokenIterator it (cursor.block(), cursor.positionInBlock());
868 if (it.isValid()) {
869 switch (it->type) {
870 case Token::OpeningBracket:
871 case Token::ClosingBracket:
873 BracketMatch match;
874 matchBracket(it, match);
876 if (match.first.isValid()) {
877 QTextCursor selection(textDocument());
878 selection.setPosition(match.first.position());
879 selection.setPosition(match.second.position() + 1, QTextCursor::KeepAnchor);
880 return selection;
882 break;
885 default:
886 break;
890 return QTextCursor();
894 void CodeEditor::applySettings( Settings::Manager *settings )
896 settings->beginGroup("IDE/editor");
898 mSpaceIndent = settings->value("spaceIndent").toBool();
900 mBlinkDuration = settings->value("blinkDuration").toInt();
902 QPalette palette;
904 settings->beginGroup("colors");
906 if (settings->contains("text")) {
907 QTextCharFormat format = settings->value("text").value<QTextCharFormat>();
908 QBrush bg = format.background();
909 QBrush fg = format.foreground();
910 if (bg.style() != Qt::NoBrush)
911 palette.setBrush(QPalette::Base, bg);
912 if (fg.style() != Qt::NoBrush)
913 palette.setBrush(QPalette::Text, fg);
916 if (settings->contains("lineNumbers")) {
917 QPalette lineNumPlt;
918 QTextCharFormat format = settings->value("lineNumbers").value<QTextCharFormat>();
919 QBrush bg = format.background();
920 QBrush fg = format.foreground();
921 if (bg.style() != Qt::NoBrush)
922 palette.setBrush(QPalette::Button, bg);
923 if (fg.style() != Qt::NoBrush)
924 palette.setBrush(QPalette::ButtonText, fg);
925 mLineIndicator->setPalette(lineNumPlt);
928 mBracketHighlight = settings->value("matchingBrackets").value<QTextCharFormat>();
930 settings->endGroup(); // colors
932 setPalette(palette);
934 settings->endGroup();
937 bool CodeEditor::event( QEvent *e )
939 switch (e->type())
941 case QEvent::KeyPress:
943 QKeyEvent *ke = static_cast<QKeyEvent*>(e);
944 int key = ke->key();
945 switch (key)
947 case Qt::Key_Tab:
948 indent();
949 e->accept();
950 return true;
951 default:;
953 break;
955 default:;
957 return QPlainTextEdit::event(e);
960 void CodeEditor::keyPressEvent( QKeyEvent *e )
962 switch (e->key()) {
963 case Qt::Key_Home:
965 hideMouseCursor();
966 Qt::KeyboardModifiers mods(e->modifiers());
967 if (mods && mods != Qt::ShiftModifier) {
968 QPlainTextEdit::keyPressEvent(e);
969 break;
972 QTextCursor::MoveMode mode =
973 mods & Qt::ShiftModifier ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
975 QTextCursor c(textCursor());
976 QTextBlock b(c.block());
978 int pos = indentedStartOfLine(b);
979 pos += b.position();
981 if (c.position() == pos)
982 c.movePosition(QTextCursor::StartOfLine, mode);
983 else
984 c.setPosition(pos, mode);
986 setTextCursor(c);
988 return;
991 case Qt::Key_Backtab:
993 hideMouseCursor();
994 QTextCursor cursor = textCursor();
995 cursor.insertText("\t");
996 ensureCursorVisible();
997 return;
1000 default:
1001 GenericCodeEditor::keyPressEvent(e);
1004 switch (e->key()) {
1005 case Qt::Key_Enter:
1006 case Qt::Key_Return:
1007 case Qt::Key_BraceRight:
1008 case Qt::Key_BracketRight:
1009 case Qt::Key_ParenRight:
1010 indent();
1011 break;
1013 default:;
1016 mAutoCompleter->keyPress(e);
1019 void CodeEditor::mouseReleaseEvent ( QMouseEvent *e )
1021 // Prevent deselection of bracket match:
1022 if(!mMouseBracketMatch)
1023 GenericCodeEditor::mouseReleaseEvent(e);
1025 mMouseBracketMatch = false;
1028 void CodeEditor::mouseDoubleClickEvent( QMouseEvent * e )
1030 QTextCursor cursor = cursorForPosition(e->pos());
1031 QTextCursor selection = blockAtCursor(cursor);
1033 if (!selection.isNull()) {
1034 setTextCursor(selection);
1035 return;
1038 GenericCodeEditor::mouseDoubleClickEvent(e);
1041 void CodeEditor::mouseMoveEvent( QMouseEvent *e )
1043 // Prevent initiating a text drag:
1044 if(!mMouseBracketMatch)
1045 GenericCodeEditor::mouseMoveEvent(e);
1049 void CodeEditor::onOverlayChanged ( const QList<QRectF> & region )
1051 foreach(QRectF r, region)
1053 viewport()->update(r.toRect());
1057 void CodeEditor::paintEvent( QPaintEvent *e )
1059 GenericCodeEditor::paintEvent(e);
1061 QPainter p(viewport());
1062 mOverlay->render(&p, e->rect(), e->rect());
1065 void CodeEditor::matchBrackets()
1067 mBracketSelections.clear();
1069 QTextCursor cursor(textCursor());
1070 QTextBlock block( cursor.block() );
1071 int posInBlock = cursor.positionInBlock();
1072 TokenIterator it(block);
1073 while (it.isValid() && it.block() == block)
1075 const Token & token = *it;
1076 if (token.positionInBlock > posInBlock) {
1077 it = TokenIterator();
1078 break;
1079 } else if (
1080 (token.positionInBlock == posInBlock && token.type == Token::OpeningBracket) ||
1081 (token.positionInBlock == posInBlock - 1 && token.type == Token::ClosingBracket)
1083 break;
1084 ++it;
1087 BracketMatch match;
1088 if( it.isValid() && it.block() == block)
1089 matchBracket( it, match );
1091 if( match.first.isValid() && match.second.isValid() )
1093 const Token & tok1 = *match.first;
1094 const Token & tok2 = *match.second;
1096 if (
1097 (tok1.character == '(' && tok2.character == ')')
1098 || (tok1.character == '[' && tok2.character == ']')
1099 || (tok1.character == '{' && tok2.character == '}')
1101 QTextEdit::ExtraSelection selection;
1102 selection.format = mBracketHighlight;
1104 cursor.setPosition(match.first.position());
1105 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1106 selection.cursor = cursor;
1107 mBracketSelections.append(selection);
1109 cursor.setPosition(match.second.position());
1110 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1111 selection.cursor = cursor;
1112 mBracketSelections.append(selection);
1114 else {
1115 QTextEdit::ExtraSelection selection;
1116 selection.format.setBackground(Qt::red);
1117 cursor.setPosition(match.first.position());
1118 cursor.setPosition(match.second.position()+1, QTextCursor::KeepAnchor);
1119 selection.cursor = cursor;
1120 mBracketSelections.append(selection);
1124 updateExtraSelections();
1127 void CodeEditor::matchBracket( const TokenIterator & bracket , BracketMatch & match )
1129 TokenIterator it(bracket);
1131 if(it->type == Token::OpeningBracket)
1133 match.first = it;
1134 int level = 1;
1135 while((++it).isValid())
1137 Token::Type type = it->type;
1138 if(type == Token::ClosingBracket)
1139 --level;
1140 else if(type == Token::OpeningBracket)
1141 ++level;
1142 if(level == 0) {
1143 match.second = it;
1144 return;
1148 else if(it->type == Token::ClosingBracket)
1150 match.second = it;
1151 int level = 1;
1152 while((--it).isValid())
1154 Token::Type type = it->type;
1155 if(type == Token::OpeningBracket)
1156 --level;
1157 else if(type == Token::ClosingBracket)
1158 ++level;
1159 if(level == 0) {
1160 match.first = it;
1161 return;
1167 int CodeEditor::indentedStartOfLine( const QTextBlock &b )
1169 QString t(b.text());
1170 int n = t.size();
1171 int i = 0;
1172 while (i < n) {
1173 QChar c(t[i]);
1174 if (c != ' ' && c != '\t')
1175 break;
1176 ++i;
1179 return i;
1182 void CodeEditor::updateExtraSelections()
1184 QList<QTextEdit::ExtraSelection> selections;
1185 selections.append(mBracketSelections);
1186 selections.append(mSearchSelections);
1187 setExtraSelections(selections);
1190 void CodeEditor::indentCurrentRegion()
1192 indent(currentRegion());
1195 void CodeEditor::indent()
1197 indent(textCursor());
1200 void CodeEditor::indent( const QTextCursor & selection )
1202 QTextCursor cursor(selection);
1204 cursor.beginEditBlock();
1206 QTextDocument *doc = QPlainTextEdit::document();
1207 int startBlockNum = doc->findBlock(cursor.selectionStart()).blockNumber();
1208 int endBlockNum = cursor.hasSelection() ?
1209 doc->findBlock(cursor.selectionEnd()).blockNumber() : startBlockNum;
1211 QStack<int> stack;
1212 int level = 0;
1213 int blockNum = 0;
1214 QTextBlock block = QPlainTextEdit::document()->begin();
1215 while (block.isValid())
1217 if (level > 0) {
1218 stack.push(level);
1219 level = 0;
1222 int initialStackSize = stack.size();
1224 TextBlockData *data = static_cast<TextBlockData*>(block.userData());
1225 if (data)
1227 int count = data->tokens.size();
1228 for (int idx = 0; idx < count; ++idx)
1230 const Token & token = data->tokens[idx];
1231 switch (token.type)
1233 case Token::OpeningBracket:
1234 if (token.character != '(' || stack.size() || token.positionInBlock)
1235 level += 1;
1236 break;
1238 case Token::ClosingBracket:
1239 if (level)
1240 level -= 1;
1241 else if(!stack.isEmpty()) {
1242 stack.top() -= 1;
1243 if (stack.top() <= 0)
1244 stack.pop();
1246 break;
1248 default:
1254 if(blockNum >= startBlockNum) {
1255 int indentLevel;
1256 if (data && data->tokens.size() && data->tokens[0].type == Token::ClosingBracket)
1257 indentLevel = stack.size();
1258 else
1259 indentLevel = initialStackSize;
1260 block = indent(block, indentLevel);
1263 if(blockNum == endBlockNum)
1264 break;
1266 block = block.next();
1267 ++blockNum;
1270 cursor.endEditBlock();
1273 QString CodeEditor::makeIndentationString(int level)
1275 if (level <= 0)
1276 return QString();
1278 if ( mSpaceIndent ) {
1279 const int spaces = mDoc->indentWidth() * level;
1280 QString indentationString (spaces, QChar(' '));
1281 return indentationString;
1282 } else {
1283 const int tabs = level;
1284 QString indentationString (tabs, QChar('\t'));
1285 return indentationString;
1289 QTextBlock CodeEditor::indent( const QTextBlock & block, int level )
1291 QTextCursor cursor(block);
1292 cursor.movePosition(QTextCursor::StartOfBlock);
1293 cursor.setPosition(cursor.position() + indentedStartOfLine(block), QTextCursor::KeepAnchor);
1295 cursor.insertText(makeIndentationString(level));
1297 // modification has invalidated the block, so return a new one
1298 return cursor.block();
1301 int CodeEditor::indentationLevel(const QTextCursor & cursor)
1303 QTextDocument *doc = QPlainTextEdit::document();
1304 int startBlockNum = doc->findBlock(cursor.selectionStart()).blockNumber();
1306 QStack<int> stack;
1307 int level = 0;
1308 int blockNum = 0;
1309 QTextBlock block = QPlainTextEdit::document()->begin();
1310 while (block.isValid()) {
1311 if (level > 0) {
1312 stack.push(level);
1313 level = 0;
1316 TextBlockData *data = static_cast<TextBlockData*>(block.userData());
1317 if (data) {
1318 int count = data->tokens.size();
1319 for (int idx = 0; idx < count; ++idx) {
1320 const Token & token = data->tokens[idx];
1321 switch (token.type) {
1322 case Token::OpeningBracket:
1323 if (token.character != '(' || stack.size() || token.positionInBlock)
1324 level += 1;
1325 break;
1327 case Token::ClosingBracket:
1328 if (level)
1329 level -= 1;
1330 else if(!stack.isEmpty()) {
1331 stack.top() -= 1;
1332 if (stack.top() <= 0)
1333 stack.pop();
1335 break;
1337 default:
1343 if (blockNum == startBlockNum)
1344 return stack.size();
1346 block = block.next();
1347 ++blockNum;
1350 return -1;
1353 void CodeEditor::triggerAutoCompletion()
1355 mAutoCompleter->triggerCompletion();
1358 void CodeEditor::triggerMethodCallAid()
1360 mAutoCompleter->triggerMethodCallAid();
1363 static bool isSingleLineComment(QTextBlock const & block)
1365 static QRegExp commentRegex("^\\s*//.*");
1366 return commentRegex.exactMatch(block.text());
1369 static bool isSelectionComment(QString const & text)
1371 QString trimmed = text.trimmed();
1372 if ( trimmed.startsWith(QString("/*")) && trimmed.endsWith(QString("*/")) )
1373 return true;
1374 else
1375 return false;
1378 void CodeEditor::toggleComment()
1380 QTextCursor cursor = textCursor();
1382 if (cursor.hasSelection())
1383 toggleCommentSelection();
1384 else
1385 toggleCommentSingleLine();
1388 void CodeEditor::toggleCommentSingleLine()
1390 QTextCursor cursor = textCursor();
1391 cursor.beginEditBlock();
1393 toggleCommentSingleLine( cursor );
1395 cursor.endEditBlock();
1398 void CodeEditor::addSingleLineComment(QTextCursor cursor, int indentation)
1400 QTextBlock currentBlock(cursor.block());
1401 int blockIndentationLevel = indentationLevel(cursor);
1403 cursor.movePosition(QTextCursor::StartOfBlock);
1404 cursor.setPosition(cursor.position() + indentedStartOfLine(currentBlock), QTextCursor::KeepAnchor);
1406 QString commentString = makeIndentationString(indentation) + QString("// ")
1407 + makeIndentationString(blockIndentationLevel - indentation);
1409 cursor.insertText(commentString);
1411 cursor.movePosition(QTextCursor::StartOfBlock);
1414 void CodeEditor::removeSingleLineComment(QTextCursor cursor)
1416 QTextBlock currentBlock(cursor.block());
1417 cursor.movePosition(QTextCursor::StartOfBlock);
1418 cursor.setPosition(cursor.position() + indentedStartOfLine(currentBlock) + 2, QTextCursor::KeepAnchor);
1420 if (!cursor.selectedText().endsWith(QString("//")))
1421 cursor.setPosition(cursor.anchor() + indentedStartOfLine(currentBlock), QTextCursor::KeepAnchor);
1423 cursor.insertText("");
1426 void CodeEditor::toggleCommentSingleLine(QTextCursor cursor)
1428 QTextBlock currentBlock(cursor.block());
1430 cursor.beginEditBlock();
1432 if (!isSingleLineComment(currentBlock)) {
1433 int blockIndentation = indentationLevel(cursor);
1434 addSingleLineComment(cursor, blockIndentation);
1435 } else
1436 removeSingleLineComment(cursor);
1438 cursor.endEditBlock();
1439 indent(cursor);
1442 static bool isBlockOnlySelection(QTextCursor)
1444 return true;
1447 void CodeEditor::toggleCommentSelection()
1449 QTextCursor cursor = textCursor();
1450 cursor.beginEditBlock();
1452 if (isBlockOnlySelection(cursor)) {
1453 QTextCursor selectionCursor(cursor);
1454 selectionCursor.setPosition(cursor.selectionStart());
1456 QTextBlock currentBlock = selectionCursor.block();
1457 bool isComment = isSingleLineComment(currentBlock);
1458 int firstBlockIndentation = isComment ? 0
1459 : indentationLevel(selectionCursor);
1461 do {
1462 QTextCursor blockCursor(currentBlock);
1463 if (!isComment)
1464 addSingleLineComment(blockCursor, firstBlockIndentation);
1465 else
1466 removeSingleLineComment(blockCursor);
1467 currentBlock = currentBlock.next();
1468 } while (currentBlock.isValid() && currentBlock.position() < cursor.selectionEnd());
1469 } else {
1470 QString selectionText = cursor.selectedText();
1471 QTextCursor selectionCursor(cursor);
1473 if (isSelectionComment(selectionText)) {
1474 selectionText = selectionText.trimmed().remove(2);
1475 selectionText.chop(2);
1476 selectionCursor.insertText(selectionText);
1477 } else {
1478 selectionText = QString("/* ") + selectionText + QString(" */");
1479 selectionCursor.insertText(selectionText);
1483 cursor.endEditBlock();
1484 cursor.beginEditBlock();
1485 indent(currentRegion());
1486 cursor.endEditBlock();
1489 // taking nested brackets into account
1490 static TokenIterator previousOpeningBracket(TokenIterator it)
1492 int level = 0;
1493 while (it.isValid()) {
1494 switch (it->type) {
1495 case Token::OpeningBracket:
1496 if (level == 0)
1497 return it;
1498 --level;
1499 break;
1501 case Token::ClosingBracket:
1502 ++level;
1504 default:
1505 break;
1507 --it;
1509 return it;
1512 // taking nested brackets into account
1513 static TokenIterator nextClosingBracket(TokenIterator it)
1515 int level = 0;
1516 while (it.isValid()) {
1517 switch (it->type) {
1518 case Token::ClosingBracket:
1519 if (level == 0)
1520 return it;
1521 --level;
1522 break;
1524 case Token::OpeningBracket:
1525 ++level;
1527 default:
1528 break;
1530 ++it;
1532 return it;
1536 void CodeEditor::gotoNextBlock()
1538 QTextCursor cursor = textCursor();
1540 TokenIterator startIt (cursor.block(), cursor.positionInBlock());
1541 TokenIterator previousBracket;
1543 if (startIt.type() != Token::OpeningBracket) {
1544 startIt = TokenIterator::leftOf(cursor.block(), cursor.positionInBlock());
1545 previousBracket = previousOpeningBracket(startIt);
1546 } else
1547 previousBracket = startIt;
1549 if (previousBracket.isValid()) {
1550 TokenIterator nextBracket = nextClosingBracket(previousBracket.next());
1552 if (nextBracket.isValid()) {
1553 setTextCursor(cursorAt(nextBracket, 1));
1554 return;
1558 gotoNextRegion();
1561 void CodeEditor::gotoPreviousBlock()
1563 QTextCursor cursor = textCursor();
1565 TokenIterator startIt;
1566 if (cursor.positionInBlock())
1567 startIt = TokenIterator(cursor.block(), cursor.positionInBlock() - 1);
1568 else {
1569 QTextBlock previousBlock = cursor.block().previous();
1570 startIt = TokenIterator(previousBlock, previousBlock.length() - 1);
1573 TokenIterator nextBracket;
1575 if (startIt.type() != Token::ClosingBracket) {
1576 startIt = TokenIterator::rightOf(cursor.block(), cursor.positionInBlock());
1577 nextBracket = nextClosingBracket(startIt);
1578 } else
1579 nextBracket = startIt;
1581 if (nextBracket.isValid()) {
1582 TokenIterator previousBracket = previousOpeningBracket(nextBracket.previous());
1584 if (previousBracket.isValid()) {
1585 setTextCursor(cursorAt(previousBracket));
1586 return;
1590 gotoPreviousRegion();
1593 void CodeEditor::selectCurrentRegion()
1595 QTextCursor selectedRegionCursor = currentRegion();
1596 if (!selectedRegionCursor.isNull() && selectedRegionCursor.hasSelection())
1597 setTextCursor(selectedRegionCursor);
1600 void CodeEditor::gotoNextRegion()
1602 QTextCursor cursor = textCursor();
1604 QTextCursor regionCursor = regionAtCursor(cursor);
1605 if (!regionCursor.isNull()) {
1606 cursor = regionCursor;
1607 // regionCursor does not include region's closing bracket, so skip it
1608 cursor.movePosition(QTextCursor::NextCharacter);
1611 // Skip potential opening bracket immediately right of cursor:
1612 cursor.movePosition(QTextCursor::NextCharacter);
1614 TokenIterator it = TokenIterator::rightOf(cursor.block(), cursor.positionInBlock());
1616 while (it.isValid()) {
1617 if ( (it->type == Token::OpeningBracket) && (it->character == '(') &&
1618 (it->positionInBlock == 0) ) {
1619 setTextCursor( cursorAt(it, 1) );
1620 return;
1622 ++it;
1625 cursor.movePosition(QTextCursor::End);
1626 setTextCursor(cursor);
1629 void CodeEditor::gotoPreviousRegion()
1631 QTextCursor cursor = textCursor();
1633 QTextCursor regionCursor = regionAtCursor(cursor);
1635 if (!regionCursor.isNull()) {
1636 // regionCursor does not include region's opening bracket, so skip it:
1637 cursor.setPosition( regionCursor.selectionStart() - 1 );
1640 // Skip potential closing bracket immediately left of cursor:
1641 cursor.movePosition( QTextCursor::PreviousCharacter );
1643 TokenIterator it = TokenIterator::leftOf(cursor.block(), cursor.positionInBlock());
1645 while (it.isValid()) {
1646 if ( (it->type == Token::ClosingBracket) && (it->character == ')') &&
1647 (it->positionInBlock == 0) ) {
1648 setTextCursor( cursorAt(it) );
1649 return;
1651 --it;
1654 cursor.movePosition(QTextCursor::Start);
1655 setTextCursor(cursor);
1658 bool CodeEditor::openDocumentation()
1660 return Main::openDocumentation(symbolUnderCursor());
1663 void CodeEditor::openDefinition()
1665 Main::openDefinition(symbolUnderCursor(), this);
1668 void CodeEditor::findReferences()
1670 Main::findReferences(symbolUnderCursor(), this);
1673 QTextCursor CodeEditor::cursorAt(const TokenIterator it, int offset)
1675 Q_ASSERT(it.isValid());
1677 QTextCursor textCursor(textDocument());
1678 textCursor.setPosition(it.position() + offset);
1680 return textCursor;
1683 } // namespace ScIDE