scide: CodeEditor - cosmetics - reorder function implementations
[supercollider.git] / editors / sc-ide / widgets / code_editor / editor.cpp
blob3ade91a9920ecdb1fcacba39d4b31037d8ffc918
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 BracketMatch 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 void CodeEditor::matchBracket( const TokenIterator & bracket , BracketMatch & match )
1019 TokenIterator it(bracket);
1021 if(it->type == Token::OpeningBracket)
1023 match.first = it;
1024 int level = 1;
1025 while((++it).isValid())
1027 Token::Type type = it->type;
1028 if(type == Token::ClosingBracket)
1029 --level;
1030 else if(type == Token::OpeningBracket)
1031 ++level;
1032 if(level == 0) {
1033 match.second = it;
1034 return;
1038 else if(it->type == Token::ClosingBracket)
1040 match.second = it;
1041 int level = 1;
1042 while((--it).isValid())
1044 Token::Type type = it->type;
1045 if(type == Token::OpeningBracket)
1046 --level;
1047 else if(type == Token::ClosingBracket)
1048 ++level;
1049 if(level == 0) {
1050 match.first = it;
1051 return;
1057 int CodeEditor::indentedStartOfLine( const QTextBlock &b )
1059 QString t(b.text());
1060 int n = t.size();
1061 int i = 0;
1062 while (i < n) {
1063 QChar c(t[i]);
1064 if (c != ' ' && c != '\t')
1065 break;
1066 ++i;
1069 return i;
1072 void CodeEditor::updateExtraSelections()
1074 QList<QTextEdit::ExtraSelection> selections;
1075 selections.append(mBracketSelections);
1076 selections.append(mSearchSelections);
1077 setExtraSelections(selections);
1080 void CodeEditor::indentCurrentRegion()
1082 indent(currentRegion());
1085 void CodeEditor::indent()
1087 indent(textCursor());
1090 void CodeEditor::indent( const QTextCursor & selection )
1092 if (selection.isNull())
1093 return;
1095 QTextCursor cursor(selection);
1097 cursor.beginEditBlock();
1099 QTextDocument *doc = QPlainTextEdit::document();
1100 int startBlockNum = doc->findBlock(cursor.selectionStart()).blockNumber();
1101 int endBlockNum = cursor.hasSelection() ?
1102 doc->findBlock(cursor.selectionEnd()).blockNumber() : startBlockNum;
1104 QStack<int> stack;
1105 int level = 0;
1106 int blockNum = 0;
1107 QTextBlock block = QPlainTextEdit::document()->begin();
1108 while (block.isValid())
1110 if (level > 0) {
1111 stack.push(level);
1112 level = 0;
1115 int initialStackSize = stack.size();
1117 TextBlockData *data = static_cast<TextBlockData*>(block.userData());
1118 if (data)
1120 int count = data->tokens.size();
1121 for (int idx = 0; idx < count; ++idx)
1123 const Token & token = data->tokens[idx];
1124 switch (token.type)
1126 case Token::OpeningBracket:
1127 if (token.character != '(' || stack.size() || token.positionInBlock)
1128 level += 1;
1129 break;
1131 case Token::ClosingBracket:
1132 if (level)
1133 level -= 1;
1134 else if(!stack.isEmpty()) {
1135 stack.top() -= 1;
1136 if (stack.top() <= 0)
1137 stack.pop();
1139 break;
1141 default:
1147 if(blockNum >= startBlockNum) {
1148 int indentLevel;
1149 if (data && data->tokens.size() && data->tokens[0].type == Token::ClosingBracket)
1150 indentLevel = stack.size();
1151 else
1152 indentLevel = initialStackSize;
1153 block = indent(block, indentLevel);
1156 if(blockNum == endBlockNum)
1157 break;
1159 block = block.next();
1160 ++blockNum;
1163 cursor.endEditBlock();
1166 QString CodeEditor::makeIndentationString(int level)
1168 if (level <= 0)
1169 return QString();
1171 if ( mSpaceIndent ) {
1172 const int spaces = mDoc->indentWidth() * level;
1173 QString indentationString (spaces, QChar(' '));
1174 return indentationString;
1175 } else {
1176 const int tabs = level;
1177 QString indentationString (tabs, QChar('\t'));
1178 return indentationString;
1182 QTextBlock CodeEditor::indent( const QTextBlock & block, int level )
1184 QTextCursor cursor(block);
1185 cursor.movePosition(QTextCursor::StartOfBlock);
1186 cursor.setPosition(cursor.position() + indentedStartOfLine(block), QTextCursor::KeepAnchor);
1188 cursor.insertText(makeIndentationString(level));
1190 // modification has invalidated the block, so return a new one
1191 return cursor.block();
1194 int CodeEditor::indentationLevel(const QTextCursor & cursor)
1196 QTextDocument *doc = QPlainTextEdit::document();
1197 int startBlockNum = doc->findBlock(cursor.selectionStart()).blockNumber();
1199 QStack<int> stack;
1200 int level = 0;
1201 int blockNum = 0;
1202 QTextBlock block = QPlainTextEdit::document()->begin();
1203 while (block.isValid()) {
1204 if (level > 0) {
1205 stack.push(level);
1206 level = 0;
1209 TextBlockData *data = static_cast<TextBlockData*>(block.userData());
1210 if (data) {
1211 int count = data->tokens.size();
1212 for (int idx = 0; idx < count; ++idx) {
1213 const Token & token = data->tokens[idx];
1214 switch (token.type) {
1215 case Token::OpeningBracket:
1216 if (token.character != '(' || stack.size() || token.positionInBlock)
1217 level += 1;
1218 break;
1220 case Token::ClosingBracket:
1221 if (level)
1222 level -= 1;
1223 else if(!stack.isEmpty()) {
1224 stack.top() -= 1;
1225 if (stack.top() <= 0)
1226 stack.pop();
1228 break;
1230 default:
1236 if (blockNum == startBlockNum)
1237 return stack.size();
1239 block = block.next();
1240 ++blockNum;
1243 return -1;
1246 void CodeEditor::triggerAutoCompletion()
1248 mAutoCompleter->triggerCompletion();
1251 void CodeEditor::triggerMethodCallAid()
1253 mAutoCompleter->triggerMethodCallAid();
1256 static bool isSingleLineComment(QTextBlock const & block)
1258 static QRegExp commentRegex("^\\s*//.*");
1259 return commentRegex.exactMatch(block.text());
1262 static bool isSelectionComment(QString const & text)
1264 QString trimmed = text.trimmed();
1265 if ( trimmed.startsWith(QString("/*")) && trimmed.endsWith(QString("*/")) )
1266 return true;
1267 else
1268 return false;
1271 void CodeEditor::toggleComment()
1273 QTextCursor cursor = textCursor();
1275 if (cursor.hasSelection())
1276 toggleCommentSelection();
1277 else
1278 toggleCommentSingleLine();
1281 void CodeEditor::toggleCommentSingleLine()
1283 QTextCursor cursor = textCursor();
1284 cursor.beginEditBlock();
1286 toggleCommentSingleLine( cursor );
1288 cursor.endEditBlock();
1291 void CodeEditor::addSingleLineComment(QTextCursor cursor, int indentation)
1293 QTextBlock currentBlock(cursor.block());
1294 int blockIndentationLevel = indentationLevel(cursor);
1296 cursor.movePosition(QTextCursor::StartOfBlock);
1297 cursor.setPosition(cursor.position() + indentedStartOfLine(currentBlock), QTextCursor::KeepAnchor);
1299 QString commentString = makeIndentationString(indentation) + QString("// ")
1300 + makeIndentationString(blockIndentationLevel - indentation);
1302 cursor.insertText(commentString);
1304 cursor.movePosition(QTextCursor::StartOfBlock);
1307 void CodeEditor::removeSingleLineComment(QTextCursor cursor)
1309 QTextBlock currentBlock(cursor.block());
1310 cursor.movePosition(QTextCursor::StartOfBlock);
1311 cursor.setPosition(cursor.position() + indentedStartOfLine(currentBlock) + 2, QTextCursor::KeepAnchor);
1313 if (!cursor.selectedText().endsWith(QString("//")))
1314 cursor.setPosition(cursor.anchor() + indentedStartOfLine(currentBlock), QTextCursor::KeepAnchor);
1316 cursor.insertText("");
1319 void CodeEditor::toggleCommentSingleLine(QTextCursor cursor)
1321 QTextBlock currentBlock(cursor.block());
1323 cursor.beginEditBlock();
1325 if (!isSingleLineComment(currentBlock)) {
1326 int blockIndentation = indentationLevel(cursor);
1327 addSingleLineComment(cursor, blockIndentation);
1328 } else
1329 removeSingleLineComment(cursor);
1331 cursor.endEditBlock();
1332 indent(cursor);
1335 static bool isBlockOnlySelection(QTextCursor cursor)
1337 Q_ASSERT(cursor.hasSelection());
1339 QTextCursor begin(cursor);
1340 begin.setPosition(begin.anchor());
1342 if (begin.atBlockStart() && (cursor.atBlockStart() || cursor.atBlockEnd()))
1343 return true;
1344 else
1345 return false;
1348 void CodeEditor::toggleCommentSelection()
1350 QTextCursor cursor = textCursor();
1351 cursor.beginEditBlock();
1353 if (isBlockOnlySelection(cursor)) {
1354 QTextCursor selectionCursor(cursor);
1355 selectionCursor.setPosition(cursor.selectionStart());
1357 QTextBlock currentBlock = selectionCursor.block();
1358 bool isComment = isSingleLineComment(currentBlock);
1359 int firstBlockIndentation = isComment ? 0
1360 : indentationLevel(selectionCursor);
1362 do {
1363 QTextCursor blockCursor(currentBlock);
1364 if (!isComment)
1365 addSingleLineComment(blockCursor, firstBlockIndentation);
1366 else
1367 removeSingleLineComment(blockCursor);
1368 currentBlock = currentBlock.next();
1369 } while (currentBlock.isValid() && currentBlock.position() < cursor.selectionEnd());
1370 } else {
1371 QString selectionText = cursor.selectedText();
1372 QTextCursor selectionCursor(cursor);
1373 if (isSelectionComment(selectionText)) {
1374 selectionText = selectionText.trimmed().remove(0, 2);
1375 selectionText.chop(2);
1376 selectionCursor.insertText(selectionText);
1377 } else {
1378 selectionText = QString("/* ") + selectionText + QString(" */");
1379 selectionCursor.insertText(selectionText);
1381 int position = selectionCursor.position();
1382 cursor.setPosition(position - selectionText.size());
1383 cursor.setPosition(position, QTextCursor::KeepAnchor);
1384 setTextCursor(cursor);
1388 cursor.endEditBlock();
1391 // taking nested brackets into account
1392 static TokenIterator previousOpeningBracket(TokenIterator it)
1394 int level = 0;
1395 while (it.isValid()) {
1396 switch (it->type) {
1397 case Token::OpeningBracket:
1398 if (level == 0)
1399 return it;
1400 --level;
1401 break;
1403 case Token::ClosingBracket:
1404 ++level;
1406 default:
1407 break;
1409 --it;
1411 return it;
1414 // taking nested brackets into account
1415 static TokenIterator nextClosingBracket(TokenIterator it)
1417 int level = 0;
1418 while (it.isValid()) {
1419 switch (it->type) {
1420 case Token::ClosingBracket:
1421 if (level == 0)
1422 return it;
1423 --level;
1424 break;
1426 case Token::OpeningBracket:
1427 ++level;
1429 default:
1430 break;
1432 ++it;
1434 return it;
1437 QTextCursor CodeEditor::blockAtCursor(const QTextCursor & cursor)
1439 TokenIterator it (cursor.block(), cursor.positionInBlock());
1441 if (it.isValid()) {
1442 switch (it->type) {
1443 case Token::OpeningBracket:
1444 case Token::ClosingBracket:
1446 BracketMatch match;
1447 matchBracket(it, match);
1449 if (match.first.isValid()) {
1450 QTextCursor selection(textDocument());
1451 selection.setPosition(match.first.position());
1452 selection.setPosition(match.second.position() + 1, QTextCursor::KeepAnchor);
1453 return selection;
1455 break;
1458 default:
1459 break;
1463 return QTextCursor();
1466 void CodeEditor::gotoNextBlock()
1468 QTextCursor cursor = textCursor();
1470 TokenIterator tokenIt = TokenIterator::rightOf( cursor.block(), cursor.positionInBlock() );
1471 if (tokenIt.type() == Token::OpeningBracket
1472 && tokenIt.block() == cursor.block()
1473 && tokenIt->positionInBlock == cursor.positionInBlock())
1474 ++tokenIt;
1476 tokenIt = nextClosingBracket( tokenIt );
1478 if (tokenIt.isValid())
1479 setTextCursor( cursorAt(tokenIt, 1) );
1480 else {
1481 cursor.movePosition( QTextCursor::End );
1482 setTextCursor( cursor );
1486 void CodeEditor::gotoPreviousBlock()
1488 QTextCursor cursor = textCursor();
1490 TokenIterator tokenIt = TokenIterator::leftOf(cursor.block(), cursor.positionInBlock());
1491 if (tokenIt.type() == Token::ClosingBracket
1492 && tokenIt.block() == cursor.block()
1493 && tokenIt->positionInBlock == cursor.positionInBlock() - 1)
1494 --tokenIt;
1497 tokenIt = previousOpeningBracket( tokenIt );
1499 if (tokenIt.isValid())
1500 setTextCursor( cursorAt(tokenIt) );
1501 else {
1502 cursor.movePosition( QTextCursor::Start );
1503 setTextCursor( cursor );
1507 QTextCursor CodeEditor::regionAtCursor(const QTextCursor & cursor)
1509 QTextBlock block(cursor.block());
1510 int positionInBlock = cursor.positionInBlock();
1512 TokenIterator start;
1513 TokenIterator end;
1514 int topLevel = 0;
1515 int level = 0;
1517 // search suitable opening bracket
1518 TokenIterator it = TokenIterator::leftOf( block, positionInBlock );
1519 while(it.isValid())
1521 char chr = it->character;
1522 if(chr == '(') {
1523 ++level;
1524 if(level > topLevel) {
1525 topLevel = level;
1526 if (bracketDefinesRegion(it))
1527 start = it;
1530 else if(chr == ')') {
1531 --level;
1533 --it;
1536 if (!start.isValid())
1537 return QTextCursor();
1539 // match the found opening bracket
1540 it = TokenIterator::rightOf( block, positionInBlock );
1541 while(it.isValid())
1543 char chr = it->character;
1544 if(chr == '(')
1545 ++topLevel;
1546 else if(chr == ')')
1548 --topLevel;
1549 if(topLevel == 0)
1551 if (bracketDefinesRegion(it))
1552 end = it;
1553 break;
1556 ++it;
1559 if(start.isValid() && end.isValid())
1562 FIXME: the following should be checked for every candidate opening bracket,
1563 and continue searching if check fails.
1565 #if 0
1567 // check whether the bracket makes part of an event
1568 it = start.next();
1569 if (it.isValid()) {
1570 if (it->type == Token::SymbolArg)
1571 return QTextCursor();
1572 else {
1573 ++it;
1574 if (it.isValid() && it->character == ':')
1575 return QTextCursor();
1578 #endif
1579 // ok, this is is a real top-level region
1580 QTextCursor c(QPlainTextEdit::document());
1581 c.setPosition(start.position() + 1);
1582 c.setPosition(end.position(), QTextCursor::KeepAnchor);
1583 return c;
1586 return QTextCursor();
1589 QTextCursor CodeEditor::currentRegion()
1591 QTextCursor cursor = textCursor();
1592 QTextBlock block = cursor.block();
1593 int positionInBlock = cursor.positionInBlock();
1595 if (TokenIterator(block, positionInBlock - 1).type() == Token::ClosingBracket)
1596 cursor.movePosition( QTextCursor::PreviousCharacter );
1597 else if (TokenIterator(block, positionInBlock).type() == Token::OpeningBracket)
1598 cursor.movePosition( QTextCursor::NextCharacter );
1600 return regionAtCursor( 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