scide: CodeEditor - streamline matchBracket()
[supercollider.git] / editors / sc-ide / widgets / code_editor / editor.cpp
blob9b5086f8cded9db9d26250d347fd26977669b509
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>
38 #include <QGraphicsView>
40 namespace ScIDE {
42 GenericCodeEditor::GenericCodeEditor( Document *doc, QWidget *parent ):
43 QPlainTextEdit( parent ),
44 mDoc(doc)
46 Q_ASSERT(mDoc != 0);
48 setFrameShape( QFrame::NoFrame );
50 mLineIndicator = new LineIndicator(this);
51 mLineIndicator->move( contentsRect().topLeft() );
53 mOverlay = new QGraphicsScene(this);
55 QGraphicsView *overlayView = new QGraphicsView(mOverlay, this);
56 overlayView->setFrameShape( QFrame::NoFrame );
57 overlayView->setBackgroundBrush( Qt::NoBrush );
58 overlayView->setStyleSheet("background: transparent");
59 overlayView->setFocusPolicy( Qt::NoFocus );
60 overlayView->setAttribute(Qt::WA_TransparentForMouseEvents, true);
61 overlayView->setSceneRect(QRectF(0,0,1,1));
62 overlayView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
64 mOverlayWidget = overlayView;
66 connect( mDoc, SIGNAL(defaultFontChanged()), this, SLOT(onDocumentFontChanged()) );
68 connect( this, SIGNAL(blockCountChanged(int)),
69 mLineIndicator, SLOT(setLineCount(int)) );
71 connect( mLineIndicator, SIGNAL( widthChanged() ),
72 this, SLOT( updateLayout() ) );
74 connect( this, SIGNAL(updateRequest(QRect,int)),
75 this, SLOT(updateLineIndicator(QRect,int)) );
77 connect( this, SIGNAL(selectionChanged()),
78 mLineIndicator, SLOT(update()) );
80 QTextDocument *tdoc = doc->textDocument();
81 QPlainTextEdit::setDocument(tdoc);
82 onDocumentFontChanged();
83 mLineIndicator->setLineCount(blockCount());
86 bool GenericCodeEditor::showWhitespace()
88 QTextOption options( textDocument()->defaultTextOption() );
89 return options.flags().testFlag( QTextOption::ShowTabsAndSpaces );
92 void GenericCodeEditor::setShowWhitespace(bool show)
94 QTextDocument *doc = textDocument();
95 QTextOption opt( doc->defaultTextOption() );
96 if( show )
97 opt.setFlags( opt.flags() | QTextOption::ShowTabsAndSpaces );
98 else
99 opt.setFlags( opt.flags() & ~QTextOption::ShowTabsAndSpaces );
100 doc->setDefaultTextOption(opt);
103 static bool findInBlock(QTextDocument *doc, const QTextBlock &block, const QRegExp &expr, int offset,
104 QTextDocument::FindFlags options, QTextCursor &cursor)
106 QString text = block.text();
107 if(options & QTextDocument::FindBackward)
108 text.truncate(offset);
109 text.replace(QChar::Nbsp, QLatin1Char(' '));
111 int idx = -1;
112 while (offset >=0 && offset <= text.length()) {
113 idx = (options & QTextDocument::FindBackward) ?
114 expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
115 if (idx == -1)
116 return false;
118 if (options & QTextDocument::FindWholeWords) {
119 const int start = idx;
120 const int end = start + expr.matchedLength();
121 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
122 || (end != text.length() && text.at(end).isLetterOrNumber())) {
123 //if this is not a whole word, continue the search in the string
124 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
125 idx = -1;
126 continue;
129 //we have a hit, return the cursor for that.
130 break;
133 if (idx == -1)
134 return false;
136 cursor = QTextCursor(doc);
137 cursor.setPosition(block.position() + idx);
138 cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor);
139 return true;
142 bool GenericCodeEditor::find( const QRegExp &expr, QTextDocument::FindFlags options )
144 // Although QTextDocument provides a find() method, we implement
145 // our own, because the former one is not adequate.
147 if(expr.isEmpty()) return true;
149 bool backwards = options & QTextDocument::FindBackward;
151 QTextCursor c( textCursor() );
152 int pos;
153 if (c.hasSelection())
155 bool matching = expr.exactMatch(c.selectedText());
157 if( backwards == matching )
158 pos = c.selectionStart();
159 else
160 pos = c.selectionEnd();
162 else
163 pos = c.position();
165 QTextDocument *doc = QPlainTextEdit::document();
166 QTextBlock startBlock = doc->findBlock(pos);
167 int startBlockOffset = pos - startBlock.position();
169 QTextCursor cursor;
171 if (!backwards) {
172 int blockOffset = startBlockOffset;
173 QTextBlock block = startBlock;
174 while (block.isValid()) {
175 if (findInBlock(doc, block, expr, blockOffset, options, cursor))
176 break;
177 blockOffset = 0;
178 block = block.next();
180 if(cursor.isNull())
182 blockOffset = 0;
183 block = doc->begin();
184 while(true) {
185 if (findInBlock(doc, block, expr, blockOffset, options, cursor)
186 || block == startBlock)
187 break;
188 block = block.next();
191 } else {
192 int blockOffset = startBlockOffset;
193 QTextBlock block = startBlock;
194 while (block.isValid()) {
195 if (findInBlock(doc, block, expr, blockOffset, options, cursor))
196 break;
197 block = block.previous();
198 blockOffset = block.length() - 1;
200 if(cursor.isNull())
202 block = doc->end();
203 while(true) {
204 blockOffset = block.length() - 1;
205 if (findInBlock(doc, block, expr, blockOffset, options, cursor)
206 || block == startBlock)
207 break;
208 block = block.previous();
213 if(!cursor.isNull()) {
214 setTextCursor(cursor);
215 return true;
217 else
218 return false;
221 int GenericCodeEditor::findAll( const QRegExp &expr, QTextDocument::FindFlags options )
223 mSearchSelections.clear();
225 if(expr.isEmpty()) {
226 this->updateExtraSelections();
227 return 0;
230 QTextEdit::ExtraSelection selection;
231 selection.format.setBackground(Qt::darkYellow);
233 QTextDocument *doc = QPlainTextEdit::document();
234 QTextBlock block = doc->begin();
235 QTextCursor cursor;
237 while (block.isValid()) {
238 int blockPos = block.position();
239 int offset = 0;
240 while(findInBlock(doc, block, expr, offset, options, cursor))
242 offset = cursor.selectionEnd() - blockPos;
243 selection.cursor = cursor;
244 mSearchSelections.append(selection);
246 block = block.next();
249 this->updateExtraSelections();
251 return mSearchSelections.count();
254 //#define CSTR(QSTR) QSTR.toStdString().c_str()
256 static QString resolvedReplacement( const QString &replacement, const QRegExp &expr )
258 //qDebug("START");
259 static const QRegExp rexpr("(\\\\\\\\)|(\\\\[0-9]+)");
260 QString str( replacement );
261 int i=0;
262 while(i < str.size() && ((i = rexpr.indexIn(str, i)) != -1))
264 int len = rexpr.matchedLength();
265 if(rexpr.pos(1) != -1)
267 //qDebug("%i (%s): escape", i, CSTR(rexpr.cap(1)));
268 str.replace(i, len, "\\");
269 i += 1;
271 else if(rexpr.pos(2) != -1)
273 QString num_str = rexpr.cap(2);
274 num_str.remove(0, 1);
275 int num = num_str.toInt();
276 //qDebug("%i (%s): backref = %i", i, CSTR(rexpr.cap(2)), num);
277 if(num <= expr.captureCount())
279 QString cap = expr.cap(num);
280 //qDebug("resolving ref to: %s", CSTR(cap));
281 str.replace(i, len, cap);
282 i += cap.size();
284 else
286 //qDebug("ref out of range", i, num);
287 str.remove(i, len);
290 else
292 //qDebug("%i (%s): unknown match", i, CSTR(rexpr.cap(0)));
293 str.remove(i, len);
295 //qDebug(">> [%s] %i", CSTR(str), i);
297 //qDebug("END");
298 return str;
301 bool GenericCodeEditor::replace( const QRegExp &expr, const QString &replacement, QTextDocument::FindFlags options )
303 if(expr.isEmpty()) return true;
305 QTextCursor cursor = textCursor();
306 if (cursor.hasSelection() && expr.exactMatch(cursor.selectedText()))
308 QString rstr = replacement;
309 if(expr.patternSyntax() != QRegExp::FixedString)
310 rstr = resolvedReplacement(rstr, expr);
311 cursor.insertText(rstr);
314 return find(expr, options);
317 int GenericCodeEditor::replaceAll( const QRegExp &expr, const QString &replacement, QTextDocument::FindFlags options )
319 mSearchSelections.clear();
320 updateExtraSelections();
322 if(expr.isEmpty()) return 0;
324 int replacements = 0;
325 bool caps = expr.patternSyntax() != QRegExp::FixedString;
327 QTextDocument *doc = QPlainTextEdit::document();
328 QTextBlock block = doc->begin();
329 QTextCursor cursor;
331 QTextCursor(doc).beginEditBlock();
333 while (block.isValid())
335 int blockPos = block.position();
336 int offset = 0;
337 while(findInBlock(doc, block, expr, offset, options, cursor))
339 QString rstr = replacement;
340 if(caps)
341 rstr = resolvedReplacement(rstr, expr);
342 cursor.insertText(rstr);
343 ++replacements;
344 offset = cursor.selectionEnd() - blockPos;
346 block = block.next();
349 QTextCursor(doc).endEditBlock();
351 return replacements;
354 void GenericCodeEditor::showPosition( int pos, int selectionLength )
356 if (pos < 0) return;
358 QTextDocument *doc = QPlainTextEdit::document();
359 if (!doc) return;
361 int lineNumber = doc->findBlock(pos).firstLineNumber();
362 verticalScrollBar()->setValue(lineNumber);
364 QTextCursor cursor(doc);
365 cursor.setPosition(pos);
366 if (selectionLength)
367 cursor.setPosition(pos + selectionLength, QTextCursor::KeepAnchor);
369 setTextCursor(cursor);
372 QString GenericCodeEditor::symbolUnderCursor()
374 QTextCursor cursor = textCursor();
375 if (!cursor.hasSelection())
376 cursor.select(QTextCursor::WordUnderCursor);
377 return cursor.selectedText();
380 void GenericCodeEditor::keyPressEvent(QKeyEvent * e)
382 hideMouseCursor();
384 QTextCursor cursor( textCursor() );
386 switch (e->key()) {
387 case Qt::Key_Enter:
388 case Qt::Key_Return:
389 // override to avoid entering a "soft" new line when certain modifier is held
390 cursor.insertBlock();
391 break;
393 default:
394 QPlainTextEdit::keyPressEvent(e);
397 switch (e->key()) {
398 case Qt::Key_Enter:
399 case Qt::Key_Return:
400 case Qt::Key_Backspace:
401 cursor.setVerticalMovementX(-1);
402 setTextCursor( cursor );
403 ensureCursorVisible();
404 break;
405 default:;
409 void GenericCodeEditor::wheelEvent( QWheelEvent * e )
411 if (e->modifiers() == Qt::ControlModifier) {
412 if (e->delta() > 0)
413 zoomIn();
414 else
415 zoomOut();
416 return;
419 QPlainTextEdit::wheelEvent(e);
422 void GenericCodeEditor::dragEnterEvent( QDragEnterEvent * event )
424 const QMimeData * data = event->mimeData();
425 if (data->hasUrls()) {
426 // Propagate event to parent.
427 // URL drops are ultimately handled by MainWindow.
428 event->ignore();
429 return;
432 QPlainTextEdit::dragEnterEvent(event);
435 void GenericCodeEditor::clearSearchHighlighting()
437 mSearchSelections.clear();
438 this->updateExtraSelections();
441 void GenericCodeEditor::zoomIn(int steps)
443 zoomFont(steps);
446 void GenericCodeEditor::zoomOut(int steps)
448 zoomFont(-steps);
451 void GenericCodeEditor::resetFontSize()
453 mDoc->resetDefaultFont();
456 void GenericCodeEditor::zoomFont(int steps)
458 QFont currentFont = mDoc->defaultFont();
459 const int newSize = currentFont.pointSize() + steps;
460 if (newSize <= 0)
461 return;
462 currentFont.setPointSize(newSize);
463 mDoc->setDefaultFont(currentFont);
466 void GenericCodeEditor::onDocumentFontChanged()
468 QFont font = mDoc->defaultFont();
469 setFont(font);
472 void GenericCodeEditor::updateLayout()
474 setViewportMargins( mLineIndicator->width(), 0, 0, 0 );
475 mOverlayWidget->setGeometry( viewport()->geometry() );
478 void GenericCodeEditor::updateLineIndicator( QRect r, int dy )
480 if (dy)
481 mLineIndicator->scroll(0, dy);
482 else
483 mLineIndicator->update(0, r.y(), mLineIndicator->width(), r.height() );
486 void GenericCodeEditor::updateExtraSelections()
488 QList<QTextEdit::ExtraSelection> selections;
489 selections.append(mSearchSelections);
490 setExtraSelections(selections);
493 void GenericCodeEditor::resizeEvent( QResizeEvent *e )
495 QPlainTextEdit::resizeEvent( e );
497 QRect cr = contentsRect();
498 mLineIndicator->resize( mLineIndicator->width(), cr.height() );
500 mOverlayWidget->setGeometry( viewport()->geometry() );
503 void GenericCodeEditor::paintLineIndicator( QPaintEvent *e )
505 QPalette plt( mLineIndicator->palette() );
506 QRect r( e->rect() );
507 QPainter p( mLineIndicator );
509 p.fillRect( r, plt.color( QPalette::Button ) );
510 p.setPen( plt.color(QPalette::ButtonText) );
511 p.drawLine( r.topRight(), r.bottomRight() );
513 QTextDocument *doc = QPlainTextEdit::document();
514 QTextCursor cursor(textCursor());
515 int selStartBlock, selEndBlock;
516 if (cursor.hasSelection()) {
517 selStartBlock = doc->findBlock(cursor.selectionStart()).blockNumber();
518 selEndBlock = doc->findBlock(cursor.selectionEnd()).blockNumber();
520 else
521 selStartBlock = selEndBlock = -1;
523 QTextBlock block = firstVisibleBlock();
524 int blockNumber = block.blockNumber();
525 int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
526 int bottom = top + (int) blockBoundingRect(block).height();
528 while (block.isValid() && top <= e->rect().bottom()) {
529 if (block.isVisible() && bottom >= e->rect().top()) {
530 p.save();
532 QRect numRect( 0, top, mLineIndicator->width() - 1, bottom - top );
534 int num = blockNumber;
535 if (num >= selStartBlock && num <= selEndBlock) {
536 num -= selStartBlock;
537 p.setPen(Qt::NoPen);
538 p.setBrush(plt.color(QPalette::Highlight));
539 p.drawRect(numRect);
540 p.setPen(plt.color(QPalette::HighlightedText));
543 QString number = QString::number(num + 1);
544 p.drawText(0, top, mLineIndicator->width() - 4, bottom - top,
545 Qt::AlignRight, number);
547 p.restore();
550 block = block.next();
551 top = bottom;
552 bottom = top + (int) blockBoundingRect(block).height();
553 ++blockNumber;
557 void GenericCodeEditor::copyUpDown(bool up)
559 // directly taken from qtcreator
560 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
561 // GNU Lesser General Public License
562 QTextCursor cursor = textCursor();
563 QTextCursor move = cursor;
564 move.beginEditBlock();
566 bool hasSelection = cursor.hasSelection();
568 if (hasSelection) {
569 move.setPosition(cursor.selectionStart());
570 move.movePosition(QTextCursor::StartOfBlock);
571 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
572 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
573 QTextCursor::KeepAnchor);
574 } else {
575 move.movePosition(QTextCursor::StartOfBlock);
576 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
579 QString text = move.selectedText();
581 if (up) {
582 move.setPosition(cursor.selectionStart());
583 move.movePosition(QTextCursor::StartOfBlock);
584 move.insertBlock();
585 move.movePosition(QTextCursor::Left);
586 } else {
587 move.movePosition(QTextCursor::EndOfBlock);
588 if (move.atBlockStart()) {
589 move.movePosition(QTextCursor::NextBlock);
590 move.insertBlock();
591 move.movePosition(QTextCursor::Left);
592 } else {
593 move.insertBlock();
597 int start = move.position();
598 move.clearSelection();
599 move.insertText(text);
600 int end = move.position();
602 move.setPosition(start);
603 move.setPosition(end, QTextCursor::KeepAnchor);
605 move.endEditBlock();
607 setTextCursor(move);
611 void GenericCodeEditor::toggleOverwriteMode()
613 setOverwriteMode(!overwriteMode());
617 void GenericCodeEditor::copyLineDown()
619 copyUpDown(false);
622 void GenericCodeEditor::copyLineUp()
624 copyUpDown(true);
627 void GenericCodeEditor::moveLineUpDown(bool up)
629 // directly taken from qtcreator
630 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
631 // GNU Lesser General Public License
632 QTextCursor cursor = textCursor();
633 QTextCursor move = cursor;
635 move.setVisualNavigation(false); // this opens folded items instead of destroying them
637 move.beginEditBlock();
639 bool hasSelection = cursor.hasSelection();
641 if (cursor.hasSelection()) {
642 move.setPosition(cursor.selectionStart());
643 move.movePosition(QTextCursor::StartOfBlock);
644 move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
645 move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
646 QTextCursor::KeepAnchor);
647 } else {
648 move.movePosition(QTextCursor::StartOfBlock);
649 move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
651 QString text = move.selectedText();
653 move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
654 move.removeSelectedText();
656 if (up) {
657 move.movePosition(QTextCursor::PreviousBlock);
658 move.insertBlock();
659 move.movePosition(QTextCursor::Left);
660 } else {
661 move.movePosition(QTextCursor::EndOfBlock);
662 if (move.atBlockStart()) { // empty block
663 move.movePosition(QTextCursor::NextBlock);
664 move.insertBlock();
665 move.movePosition(QTextCursor::Left);
666 } else {
667 move.insertBlock();
671 int start = move.position();
672 move.clearSelection();
673 move.insertText(text);
674 int end = move.position();
676 if (hasSelection) {
677 move.setPosition(start);
678 move.setPosition(end, QTextCursor::KeepAnchor);
681 move.endEditBlock();
683 setTextCursor(move);
686 void GenericCodeEditor::moveLineUp()
688 moveLineUpDown(true);
691 void GenericCodeEditor::moveLineDown()
693 moveLineUpDown(false);
696 void GenericCodeEditor::gotoPreviousEmptyLine()
698 gotoEmptyLineUpDown(true);
701 void GenericCodeEditor::gotoNextEmptyLine()
703 gotoEmptyLineUpDown(false);
706 void GenericCodeEditor::gotoEmptyLineUpDown(bool up)
708 static const QRegExp whiteSpaceLine("^\\s*$");
710 const QTextCursor::MoveOperation direction = up ? QTextCursor::PreviousBlock
711 : QTextCursor::NextBlock;
713 QTextCursor cursor = textCursor();
714 cursor.beginEditBlock();
716 bool cursorMoved = false;
718 // find first non-whitespace line
719 while ( cursor.movePosition(direction) ) {
720 if ( !whiteSpaceLine.exactMatch(cursor.block().text()) )
721 break;
724 // find first whitespace line
725 while ( cursor.movePosition(direction) ) {
726 if ( whiteSpaceLine.exactMatch(cursor.block().text()) ) {
727 setTextCursor(cursor);
728 cursorMoved = true;
729 break;
733 if (!cursorMoved) {
734 const QTextCursor::MoveOperation startOrEnd = up ? QTextCursor::Start
735 : QTextCursor::End;
737 cursor.movePosition(startOrEnd);
738 setTextCursor(cursor);
741 cursor.endEditBlock();
744 void GenericCodeEditor::hideMouseCursor()
746 QCursor * overrideCursor = QApplication::overrideCursor();
747 if (!overrideCursor || overrideCursor->shape() != Qt::BlankCursor)
748 QApplication::setOverrideCursor( Qt::BlankCursor );
751 inline static bool bracketDefinesRegion( const TokenIterator & it )
753 Q_ASSERT(it.isValid());
754 bool result = it->positionInBlock == 0;
755 result = result && static_cast<TextBlockData*>(it.block().userData())->tokens.size() == 1;
756 return result;
759 CodeEditor::CodeEditor( Document *doc, QWidget *parent ) :
760 GenericCodeEditor( doc, parent ),
761 mSpaceIndent(true),
762 mBlinkDuration(600),
763 mMouseBracketMatch(false),
764 mAutoCompleter( new AutoCompleter(this) )
766 Q_ASSERT(mDoc != 0);
768 setFrameShape( QFrame::NoFrame );
770 mLineIndicator->move( contentsRect().topLeft() );
772 connect( this, SIGNAL(cursorPositionChanged()),
773 this, SLOT(matchBrackets()) );
775 connect( Main::instance(), SIGNAL(applySettingsRequest(Settings::Manager*)),
776 this, SLOT(applySettings(Settings::Manager*)) );
778 QTextDocument *tdoc = doc->textDocument();
779 mAutoCompleter->documentChanged(tdoc);
780 mLineIndicator->setLineCount(blockCount());
782 applySettings(Main::settings());
785 void CodeEditor::applySettings( Settings::Manager *settings )
787 settings->beginGroup("IDE/editor");
789 mSpaceIndent = settings->value("spaceIndent").toBool();
791 mBlinkDuration = settings->value("blinkDuration").toInt();
793 QPalette palette;
795 settings->beginGroup("colors");
797 if (settings->contains("text")) {
798 QTextCharFormat format = settings->value("text").value<QTextCharFormat>();
799 QBrush bg = format.background();
800 QBrush fg = format.foreground();
801 if (bg.style() != Qt::NoBrush)
802 palette.setBrush(QPalette::Base, bg);
803 if (fg.style() != Qt::NoBrush)
804 palette.setBrush(QPalette::Text, fg);
807 if (settings->contains("lineNumbers")) {
808 QPalette lineNumPlt;
809 QTextCharFormat format = settings->value("lineNumbers").value<QTextCharFormat>();
810 QBrush bg = format.background();
811 QBrush fg = format.foreground();
812 if (bg.style() != Qt::NoBrush)
813 palette.setBrush(QPalette::Button, bg);
814 if (fg.style() != Qt::NoBrush)
815 palette.setBrush(QPalette::ButtonText, fg);
816 mLineIndicator->setPalette(lineNumPlt);
819 mBracketHighlight = settings->value("matchingBrackets").value<QTextCharFormat>();
821 settings->endGroup(); // colors
823 setPalette(palette);
825 settings->endGroup();
828 bool CodeEditor::event( QEvent *e )
830 switch (e->type())
832 case QEvent::KeyPress:
834 QKeyEvent *ke = static_cast<QKeyEvent*>(e);
835 int key = ke->key();
836 switch (key)
838 case Qt::Key_Tab:
839 indent();
840 e->accept();
841 return true;
842 default:;
844 break;
846 default:;
848 return QPlainTextEdit::event(e);
851 void CodeEditor::keyPressEvent( QKeyEvent *e )
853 switch (e->key()) {
854 case Qt::Key_Home:
856 Qt::KeyboardModifiers mods(e->modifiers());
857 if (mods && mods != Qt::ShiftModifier) {
858 GenericCodeEditor::keyPressEvent(e);
859 return;
862 hideMouseCursor();
864 QTextCursor::MoveMode mode =
865 mods & Qt::ShiftModifier ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
867 QTextCursor c(textCursor());
868 QTextBlock b(c.block());
870 int pos = indentedStartOfLine(b);
871 pos += b.position();
873 if (c.position() == pos)
874 c.movePosition(QTextCursor::StartOfLine, mode);
875 else
876 c.setPosition(pos, mode);
878 setTextCursor(c);
880 return;
883 case Qt::Key_Backtab:
885 hideMouseCursor();
886 QTextCursor cursor = textCursor();
887 cursor.insertText("\t");
888 ensureCursorVisible();
889 return;
892 default:;
895 switch (e->key()) {
896 case Qt::Key_Enter:
897 case Qt::Key_Return:
898 case Qt::Key_BraceRight:
899 case Qt::Key_BracketRight:
900 case Qt::Key_ParenRight: {
901 // Wrap superclass' implementation into an edit block,
902 // so it can be joined with indentation later:
904 QTextCursor cursor = textCursor();
906 cursor.beginEditBlock();
907 GenericCodeEditor::keyPressEvent(e);
908 cursor.endEditBlock();
910 cursor.joinPreviousEditBlock();
911 indent();
912 cursor.endEditBlock();
914 cursor.setVerticalMovementX(-1);
915 setTextCursor(cursor);
917 break;
919 default:
920 GenericCodeEditor::keyPressEvent(e);
923 mAutoCompleter->keyPress(e);
926 void CodeEditor::mouseReleaseEvent ( QMouseEvent *e )
928 // Prevent deselection of bracket match:
929 if(!mMouseBracketMatch)
930 GenericCodeEditor::mouseReleaseEvent(e);
932 mMouseBracketMatch = false;
935 void CodeEditor::mouseDoubleClickEvent( QMouseEvent * e )
937 QTextCursor cursor = cursorForPosition(e->pos());
938 QTextCursor selection = blockAtCursor(cursor);
940 if (!selection.isNull()) {
941 setTextCursor(selection);
942 return;
945 GenericCodeEditor::mouseDoubleClickEvent(e);
948 void CodeEditor::mouseMoveEvent( QMouseEvent *e )
950 // Prevent initiating a text drag:
951 if(!mMouseBracketMatch)
952 GenericCodeEditor::mouseMoveEvent(e);
955 void CodeEditor::matchBrackets()
957 mBracketSelections.clear();
959 QTextCursor cursor(textCursor());
960 QTextBlock block( cursor.block() );
961 int posInBlock = cursor.positionInBlock();
962 TokenIterator it(block);
963 while (it.isValid() && it.block() == block)
965 const Token & token = *it;
966 if (token.positionInBlock > posInBlock) {
967 it = TokenIterator();
968 break;
969 } else if (
970 (token.positionInBlock == posInBlock && token.type == Token::OpeningBracket) ||
971 (token.positionInBlock == posInBlock - 1 && token.type == Token::ClosingBracket)
973 break;
974 ++it;
977 BracketPair match;
978 if( it.isValid() && it.block() == block)
979 matchBracket( it, match );
981 if( match.first.isValid() && match.second.isValid() )
983 const Token & tok1 = *match.first;
984 const Token & tok2 = *match.second;
986 if (
987 (tok1.character == '(' && tok2.character == ')')
988 || (tok1.character == '[' && tok2.character == ']')
989 || (tok1.character == '{' && tok2.character == '}')
991 QTextEdit::ExtraSelection selection;
992 selection.format = mBracketHighlight;
994 cursor.setPosition(match.first.position());
995 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
996 selection.cursor = cursor;
997 mBracketSelections.append(selection);
999 cursor.setPosition(match.second.position());
1000 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1001 selection.cursor = cursor;
1002 mBracketSelections.append(selection);
1004 else {
1005 QTextEdit::ExtraSelection selection;
1006 selection.format.setBackground(Qt::red);
1007 cursor.setPosition(match.first.position());
1008 cursor.setPosition(match.second.position()+1, QTextCursor::KeepAnchor);
1009 selection.cursor = cursor;
1010 mBracketSelections.append(selection);
1014 updateExtraSelections();
1017 int CodeEditor::indentedStartOfLine( const QTextBlock &b )
1019 QString t(b.text());
1020 int n = t.size();
1021 int i = 0;
1022 while (i < n) {
1023 QChar c(t[i]);
1024 if (c != ' ' && c != '\t')
1025 break;
1026 ++i;
1029 return i;
1032 void CodeEditor::updateExtraSelections()
1034 QList<QTextEdit::ExtraSelection> selections;
1035 selections.append(mBracketSelections);
1036 selections.append(mSearchSelections);
1037 setExtraSelections(selections);
1040 void CodeEditor::indentCurrentRegion()
1042 indent(currentRegion());
1045 void CodeEditor::indent()
1047 indent(textCursor());
1050 void CodeEditor::indent( const QTextCursor & selection )
1052 if (selection.isNull())
1053 return;
1055 QTextCursor cursor(selection);
1057 cursor.beginEditBlock();
1059 QTextDocument *doc = QPlainTextEdit::document();
1060 int startBlockNum = doc->findBlock(cursor.selectionStart()).blockNumber();
1061 int endBlockNum = cursor.hasSelection() ?
1062 doc->findBlock(cursor.selectionEnd()).blockNumber() : startBlockNum;
1064 QStack<int> stack;
1065 int level = 0;
1066 int blockNum = 0;
1067 QTextBlock block = QPlainTextEdit::document()->begin();
1068 while (block.isValid())
1070 if (level > 0) {
1071 stack.push(level);
1072 level = 0;
1075 int initialStackSize = stack.size();
1077 TextBlockData *data = static_cast<TextBlockData*>(block.userData());
1078 if (data)
1080 int count = data->tokens.size();
1081 for (int idx = 0; idx < count; ++idx)
1083 const Token & token = data->tokens[idx];
1084 switch (token.type)
1086 case Token::OpeningBracket:
1087 if (token.character != '(' || stack.size() || token.positionInBlock)
1088 level += 1;
1089 break;
1091 case Token::ClosingBracket:
1092 if (level)
1093 level -= 1;
1094 else if(!stack.isEmpty()) {
1095 stack.top() -= 1;
1096 if (stack.top() <= 0)
1097 stack.pop();
1099 break;
1101 default:
1107 if(blockNum >= startBlockNum) {
1108 int indentLevel;
1109 if (data && data->tokens.size() && data->tokens[0].type == Token::ClosingBracket)
1110 indentLevel = stack.size();
1111 else
1112 indentLevel = initialStackSize;
1113 block = indent(block, indentLevel);
1116 if(blockNum == endBlockNum)
1117 break;
1119 block = block.next();
1120 ++blockNum;
1123 cursor.endEditBlock();
1126 QString CodeEditor::makeIndentationString(int level)
1128 if (level <= 0)
1129 return QString();
1131 if ( mSpaceIndent ) {
1132 const int spaces = mDoc->indentWidth() * level;
1133 QString indentationString (spaces, QChar(' '));
1134 return indentationString;
1135 } else {
1136 const int tabs = level;
1137 QString indentationString (tabs, QChar('\t'));
1138 return indentationString;
1142 QTextBlock CodeEditor::indent( const QTextBlock & block, int level )
1144 QTextCursor cursor(block);
1145 cursor.movePosition(QTextCursor::StartOfBlock);
1146 cursor.setPosition(cursor.position() + indentedStartOfLine(block), QTextCursor::KeepAnchor);
1148 cursor.insertText(makeIndentationString(level));
1150 // modification has invalidated the block, so return a new one
1151 return cursor.block();
1154 int CodeEditor::indentationLevel(const QTextCursor & cursor)
1156 QTextDocument *doc = QPlainTextEdit::document();
1157 int startBlockNum = doc->findBlock(cursor.selectionStart()).blockNumber();
1159 QStack<int> stack;
1160 int level = 0;
1161 int blockNum = 0;
1162 QTextBlock block = QPlainTextEdit::document()->begin();
1163 while (block.isValid()) {
1164 if (level > 0) {
1165 stack.push(level);
1166 level = 0;
1169 TextBlockData *data = static_cast<TextBlockData*>(block.userData());
1170 if (data) {
1171 int count = data->tokens.size();
1172 for (int idx = 0; idx < count; ++idx) {
1173 const Token & token = data->tokens[idx];
1174 switch (token.type) {
1175 case Token::OpeningBracket:
1176 if (token.character != '(' || stack.size() || token.positionInBlock)
1177 level += 1;
1178 break;
1180 case Token::ClosingBracket:
1181 if (level)
1182 level -= 1;
1183 else if(!stack.isEmpty()) {
1184 stack.top() -= 1;
1185 if (stack.top() <= 0)
1186 stack.pop();
1188 break;
1190 default:
1196 if (blockNum == startBlockNum)
1197 return stack.size();
1199 block = block.next();
1200 ++blockNum;
1203 return -1;
1206 void CodeEditor::triggerAutoCompletion()
1208 mAutoCompleter->triggerCompletion();
1211 void CodeEditor::triggerMethodCallAid()
1213 mAutoCompleter->triggerMethodCallAid();
1216 static bool isSingleLineComment(QTextBlock const & block)
1218 static QRegExp commentRegex("^\\s*//.*");
1219 return commentRegex.exactMatch(block.text());
1222 static bool isSelectionComment(QString const & text)
1224 QString trimmed = text.trimmed();
1225 if ( trimmed.startsWith(QString("/*")) && trimmed.endsWith(QString("*/")) )
1226 return true;
1227 else
1228 return false;
1231 void CodeEditor::toggleComment()
1233 QTextCursor cursor = textCursor();
1235 if (cursor.hasSelection())
1236 toggleCommentSelection();
1237 else
1238 toggleCommentSingleLine();
1241 void CodeEditor::toggleCommentSingleLine()
1243 QTextCursor cursor = textCursor();
1244 cursor.beginEditBlock();
1246 toggleCommentSingleLine( cursor );
1248 cursor.endEditBlock();
1251 void CodeEditor::addSingleLineComment(QTextCursor cursor, int indentation)
1253 QTextBlock currentBlock(cursor.block());
1254 int blockIndentationLevel = indentationLevel(cursor);
1256 cursor.movePosition(QTextCursor::StartOfBlock);
1257 cursor.setPosition(cursor.position() + indentedStartOfLine(currentBlock), QTextCursor::KeepAnchor);
1259 QString commentString = makeIndentationString(indentation) + QString("// ")
1260 + makeIndentationString(blockIndentationLevel - indentation);
1262 cursor.insertText(commentString);
1264 cursor.movePosition(QTextCursor::StartOfBlock);
1267 void CodeEditor::removeSingleLineComment(QTextCursor cursor)
1269 QTextBlock currentBlock(cursor.block());
1270 cursor.movePosition(QTextCursor::StartOfBlock);
1271 cursor.setPosition(cursor.position() + indentedStartOfLine(currentBlock) + 2, QTextCursor::KeepAnchor);
1273 if (!cursor.selectedText().endsWith(QString("//")))
1274 cursor.setPosition(cursor.anchor() + indentedStartOfLine(currentBlock), QTextCursor::KeepAnchor);
1276 cursor.insertText("");
1279 void CodeEditor::toggleCommentSingleLine(QTextCursor cursor)
1281 QTextBlock currentBlock(cursor.block());
1283 cursor.beginEditBlock();
1285 if (!isSingleLineComment(currentBlock)) {
1286 int blockIndentation = indentationLevel(cursor);
1287 addSingleLineComment(cursor, blockIndentation);
1288 } else
1289 removeSingleLineComment(cursor);
1291 cursor.endEditBlock();
1292 indent(cursor);
1295 static bool isBlockOnlySelection(QTextCursor cursor)
1297 Q_ASSERT(cursor.hasSelection());
1299 QTextCursor begin(cursor);
1300 begin.setPosition(begin.anchor());
1302 if (begin.atBlockStart() && (cursor.atBlockStart() || cursor.atBlockEnd()))
1303 return true;
1304 else
1305 return false;
1308 void CodeEditor::toggleCommentSelection()
1310 QTextCursor cursor = textCursor();
1311 cursor.beginEditBlock();
1313 if (isBlockOnlySelection(cursor)) {
1314 QTextCursor selectionCursor(cursor);
1315 selectionCursor.setPosition(cursor.selectionStart());
1317 QTextBlock currentBlock = selectionCursor.block();
1318 bool isComment = isSingleLineComment(currentBlock);
1319 int firstBlockIndentation = isComment ? 0
1320 : indentationLevel(selectionCursor);
1322 do {
1323 QTextCursor blockCursor(currentBlock);
1324 if (!isComment)
1325 addSingleLineComment(blockCursor, firstBlockIndentation);
1326 else
1327 removeSingleLineComment(blockCursor);
1328 currentBlock = currentBlock.next();
1329 } while (currentBlock.isValid() && currentBlock.position() < cursor.selectionEnd());
1330 } else {
1331 QString selectionText = cursor.selectedText();
1332 QTextCursor selectionCursor(cursor);
1333 if (isSelectionComment(selectionText)) {
1334 selectionText = selectionText.trimmed().remove(0, 2);
1335 selectionText.chop(2);
1336 selectionCursor.insertText(selectionText);
1337 } else {
1338 selectionText = QString("/* ") + selectionText + QString(" */");
1339 selectionCursor.insertText(selectionText);
1341 int position = selectionCursor.position();
1342 cursor.setPosition(position - selectionText.size());
1343 cursor.setPosition(position, QTextCursor::KeepAnchor);
1344 setTextCursor(cursor);
1348 cursor.endEditBlock();
1351 // taking nested brackets into account
1352 static TokenIterator previousOpeningBracket(TokenIterator it)
1354 int level = 0;
1355 while (it.isValid()) {
1356 switch (it->type) {
1357 case Token::OpeningBracket:
1358 if (level == 0)
1359 return it;
1360 --level;
1361 break;
1363 case Token::ClosingBracket:
1364 ++level;
1366 default:
1367 break;
1369 --it;
1371 return it;
1374 // taking nested brackets into account
1375 static TokenIterator nextClosingBracket(TokenIterator it)
1377 int level = 0;
1378 while (it.isValid()) {
1379 switch (it->type) {
1380 case Token::ClosingBracket:
1381 if (level == 0)
1382 return it;
1383 --level;
1384 break;
1386 case Token::OpeningBracket:
1387 ++level;
1389 default:
1390 break;
1392 ++it;
1394 return it;
1397 void CodeEditor::matchBracket( const TokenIterator & bracket, BracketPair & match )
1399 TokenIterator it(bracket);
1401 switch(it->type) {
1402 case Token::OpeningBracket:
1403 match.first = it;
1404 match.second = nextClosingBracket(++it);
1405 break;
1406 case Token::ClosingBracket:
1407 match.second = it;
1408 match.first = previousOpeningBracket(--it);
1409 break;
1410 default:
1411 match.first = TokenIterator();
1412 match.second = TokenIterator();
1416 QTextCursor CodeEditor::blockAtCursor(const QTextCursor & cursor)
1418 TokenIterator it (cursor.block(), cursor.positionInBlock());
1420 if (it.isValid()) {
1421 switch (it->type) {
1422 case Token::OpeningBracket:
1423 case Token::ClosingBracket:
1425 BracketPair match;
1426 matchBracket(it, match);
1428 if (match.first.isValid() && match.second.isValid()) {
1429 QTextCursor selection(textDocument());
1430 selection.setPosition(match.first.position());
1431 selection.setPosition(match.second.position() + 1, QTextCursor::KeepAnchor);
1432 return selection;
1434 break;
1437 default:
1438 break;
1442 return QTextCursor();
1445 void CodeEditor::gotoNextBlock()
1447 QTextCursor cursor = textCursor();
1449 TokenIterator tokenIt = TokenIterator::rightOf( cursor.block(), cursor.positionInBlock() );
1450 if (tokenIt.type() == Token::OpeningBracket
1451 && tokenIt.block() == cursor.block()
1452 && tokenIt->positionInBlock == cursor.positionInBlock())
1453 ++tokenIt;
1455 tokenIt = nextClosingBracket( tokenIt );
1457 if (tokenIt.isValid())
1458 setTextCursor( cursorAt(tokenIt, 1) );
1459 else {
1460 cursor.movePosition( QTextCursor::End );
1461 setTextCursor( cursor );
1465 void CodeEditor::gotoPreviousBlock()
1467 QTextCursor cursor = textCursor();
1469 TokenIterator tokenIt = TokenIterator::leftOf(cursor.block(), cursor.positionInBlock());
1470 if (tokenIt.type() == Token::ClosingBracket
1471 && tokenIt.block() == cursor.block()
1472 && tokenIt->positionInBlock == cursor.positionInBlock() - 1)
1473 --tokenIt;
1476 tokenIt = previousOpeningBracket( tokenIt );
1478 if (tokenIt.isValid())
1479 setTextCursor( cursorAt(tokenIt) );
1480 else {
1481 cursor.movePosition( QTextCursor::Start );
1482 setTextCursor( cursor );
1486 QTextCursor CodeEditor::regionAtCursor(const QTextCursor & cursor)
1488 QTextBlock block(cursor.block());
1489 int positionInBlock = cursor.positionInBlock();
1491 TokenIterator start;
1492 TokenIterator end;
1493 int topLevel = 0;
1494 int level = 0;
1496 // search suitable opening bracket
1497 TokenIterator it = TokenIterator::leftOf( block, positionInBlock );
1498 while(it.isValid())
1500 char chr = it->character;
1501 if(chr == '(') {
1502 ++level;
1503 if(level > topLevel) {
1504 topLevel = level;
1505 if (bracketDefinesRegion(it))
1506 start = it;
1509 else if(chr == ')') {
1510 --level;
1512 --it;
1515 if (!start.isValid())
1516 return QTextCursor();
1518 // match the found opening bracket
1519 it = TokenIterator::rightOf( block, positionInBlock );
1520 while(it.isValid())
1522 char chr = it->character;
1523 if(chr == '(')
1524 ++topLevel;
1525 else if(chr == ')')
1527 --topLevel;
1528 if(topLevel == 0)
1530 if (bracketDefinesRegion(it))
1531 end = it;
1532 break;
1535 ++it;
1538 if(start.isValid() && end.isValid())
1541 FIXME: the following should be checked for every candidate opening bracket,
1542 and continue searching if check fails.
1544 #if 0
1546 // check whether the bracket makes part of an event
1547 it = start.next();
1548 if (it.isValid()) {
1549 if (it->type == Token::SymbolArg)
1550 return QTextCursor();
1551 else {
1552 ++it;
1553 if (it.isValid() && it->character == ':')
1554 return QTextCursor();
1557 #endif
1558 // ok, this is is a real top-level region
1559 QTextCursor c(QPlainTextEdit::document());
1560 c.setPosition(start.position() + 1);
1561 c.setPosition(end.position(), QTextCursor::KeepAnchor);
1562 return c;
1565 return QTextCursor();
1568 QTextCursor CodeEditor::currentRegion()
1570 QTextCursor cursor = textCursor();
1571 QTextBlock block = cursor.block();
1572 int positionInBlock = cursor.positionInBlock();
1574 if (TokenIterator(block, positionInBlock - 1).type() == Token::ClosingBracket)
1575 cursor.movePosition( QTextCursor::PreviousCharacter );
1576 else if (TokenIterator(block, positionInBlock).type() == Token::OpeningBracket)
1577 cursor.movePosition( QTextCursor::NextCharacter );
1579 return regionAtCursor( cursor );
1582 void CodeEditor::selectCurrentRegion()
1584 QTextCursor selectedRegionCursor = currentRegion();
1585 if (!selectedRegionCursor.isNull() && selectedRegionCursor.hasSelection())
1586 setTextCursor(selectedRegionCursor);
1589 void CodeEditor::gotoNextRegion()
1591 QTextCursor cursor = textCursor();
1593 QTextCursor regionCursor = regionAtCursor(cursor);
1594 if (!regionCursor.isNull()) {
1595 cursor = regionCursor;
1596 // regionCursor does not include region's closing bracket, so skip it
1597 cursor.movePosition(QTextCursor::NextCharacter);
1600 // Skip potential opening bracket immediately right of cursor:
1601 cursor.movePosition(QTextCursor::NextCharacter);
1603 TokenIterator it = TokenIterator::rightOf(cursor.block(), cursor.positionInBlock());
1605 while (it.isValid()) {
1606 if ( (it->type == Token::OpeningBracket) && (it->character == '(') &&
1607 (it->positionInBlock == 0) ) {
1608 setTextCursor( cursorAt(it, 1) );
1609 return;
1611 ++it;
1614 cursor.movePosition(QTextCursor::End);
1615 setTextCursor(cursor);
1618 void CodeEditor::gotoPreviousRegion()
1620 QTextCursor cursor = textCursor();
1622 QTextCursor regionCursor = regionAtCursor(cursor);
1624 if (!regionCursor.isNull()) {
1625 // regionCursor does not include region's opening bracket, so skip it:
1626 cursor.setPosition( regionCursor.selectionStart() - 1 );
1629 // Skip potential closing bracket immediately left of cursor:
1630 cursor.movePosition( QTextCursor::PreviousCharacter );
1632 TokenIterator it = TokenIterator::leftOf(cursor.block(), cursor.positionInBlock());
1634 while (it.isValid()) {
1635 if ( (it->type == Token::ClosingBracket) && (it->character == ')') &&
1636 (it->positionInBlock == 0) ) {
1637 setTextCursor( cursorAt(it) );
1638 return;
1640 --it;
1643 cursor.movePosition(QTextCursor::Start);
1644 setTextCursor(cursor);
1647 bool CodeEditor::openDocumentation()
1649 return Main::openDocumentation(symbolUnderCursor());
1652 void CodeEditor::openDefinition()
1654 Main::openDefinition(symbolUnderCursor(), this);
1657 void CodeEditor::findReferences()
1659 Main::findReferences(symbolUnderCursor(), this);
1662 QTextCursor CodeEditor::cursorAt(const TokenIterator it, int offset)
1664 Q_ASSERT(it.isValid());
1666 QTextCursor textCursor(textDocument());
1667 textCursor.setPosition(it.position() + offset);
1669 return textCursor;
1672 } // namespace ScIDE