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"
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>
33 #include <QPaintEvent>
36 #include <QTextDocumentFragment>
41 GenericCodeEditor::GenericCodeEditor( Document
*doc
, QWidget
*parent
):
42 QPlainTextEdit( parent
),
43 mLineIndicator( new LineIndicator(this) ),
48 setFrameShape( QFrame::NoFrame
);
50 mLineIndicator
->move( contentsRect().topLeft() );
52 connect( mDoc
, SIGNAL(defaultFontChanged()), this, SLOT(onDocumentFontChanged()) );
54 connect( this, SIGNAL(blockCountChanged(int)),
55 mLineIndicator
, SLOT(setLineCount(int)) );
57 connect( mLineIndicator
, SIGNAL( widthChanged() ),
58 this, SLOT( updateLayout() ) );
60 connect( this, SIGNAL(updateRequest(QRect
,int)),
61 this, SLOT(updateLineIndicator(QRect
,int)) );
63 connect( this, SIGNAL(selectionChanged()),
64 mLineIndicator
, SLOT(update()) );
66 QTextDocument
*tdoc
= doc
->textDocument();
67 QPlainTextEdit::setDocument(tdoc
);
68 onDocumentFontChanged();
69 mLineIndicator
->setLineCount(blockCount());
73 bool GenericCodeEditor::showWhitespace()
75 QTextOption
options( textDocument()->defaultTextOption() );
76 return options
.flags().testFlag( QTextOption::ShowTabsAndSpaces
);
79 void GenericCodeEditor::setShowWhitespace(bool show
)
81 QTextDocument
*doc
= textDocument();
82 QTextOption
opt( doc
->defaultTextOption() );
84 opt
.setFlags( opt
.flags() | QTextOption::ShowTabsAndSpaces
);
86 opt
.setFlags( opt
.flags() & ~QTextOption::ShowTabsAndSpaces
);
87 doc
->setDefaultTextOption(opt
);
90 static bool findInBlock(QTextDocument
*doc
, const QTextBlock
&block
, const QRegExp
&expr
, int offset
,
91 QTextDocument::FindFlags options
, QTextCursor
&cursor
)
93 QString text
= block
.text();
94 if(options
& QTextDocument::FindBackward
)
95 text
.truncate(offset
);
96 text
.replace(QChar::Nbsp
, QLatin1Char(' '));
99 while (offset
>=0 && offset
<= text
.length()) {
100 idx
= (options
& QTextDocument::FindBackward
) ?
101 expr
.lastIndexIn(text
, offset
) : expr
.indexIn(text
, offset
);
105 if (options
& QTextDocument::FindWholeWords
) {
106 const int start
= idx
;
107 const int end
= start
+ expr
.matchedLength();
108 if ((start
!= 0 && text
.at(start
- 1).isLetterOrNumber())
109 || (end
!= text
.length() && text
.at(end
).isLetterOrNumber())) {
110 //if this is not a whole word, continue the search in the string
111 offset
= (options
& QTextDocument::FindBackward
) ? idx
-1 : end
+1;
116 //we have a hit, return the cursor for that.
123 cursor
= QTextCursor(doc
);
124 cursor
.setPosition(block
.position() + idx
);
125 cursor
.setPosition(cursor
.position() + expr
.matchedLength(), QTextCursor::KeepAnchor
);
129 bool GenericCodeEditor::find( const QRegExp
&expr
, QTextDocument::FindFlags options
)
131 // Although QTextDocument provides a find() method, we implement
132 // our own, because the former one is not adequate.
134 if(expr
.isEmpty()) return true;
136 bool backwards
= options
& QTextDocument::FindBackward
;
138 QTextCursor
c( textCursor() );
140 if (c
.hasSelection())
142 bool matching
= expr
.exactMatch(c
.selectedText());
144 if( backwards
== matching
)
145 pos
= c
.selectionStart();
147 pos
= c
.selectionEnd();
152 QTextDocument
*doc
= QPlainTextEdit::document();
153 QTextBlock startBlock
= doc
->findBlock(pos
);
154 int startBlockOffset
= pos
- startBlock
.position();
159 int blockOffset
= startBlockOffset
;
160 QTextBlock block
= startBlock
;
161 while (block
.isValid()) {
162 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
))
165 block
= block
.next();
170 block
= doc
->begin();
172 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
)
173 || block
== startBlock
)
175 block
= block
.next();
179 int blockOffset
= startBlockOffset
;
180 QTextBlock block
= startBlock
;
181 while (block
.isValid()) {
182 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
))
184 block
= block
.previous();
185 blockOffset
= block
.length() - 1;
191 blockOffset
= block
.length() - 1;
192 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
)
193 || block
== startBlock
)
195 block
= block
.previous();
200 if(!cursor
.isNull()) {
201 setTextCursor(cursor
);
208 int GenericCodeEditor::findAll( const QRegExp
&expr
, QTextDocument::FindFlags options
)
210 mSearchSelections
.clear();
213 this->updateExtraSelections();
217 QTextEdit::ExtraSelection selection
;
218 selection
.format
.setBackground(Qt::darkYellow
);
220 QTextDocument
*doc
= QPlainTextEdit::document();
221 QTextBlock block
= doc
->begin();
224 while (block
.isValid()) {
225 int blockPos
= block
.position();
227 while(findInBlock(doc
, block
, expr
, offset
, options
, cursor
))
229 offset
= cursor
.selectionEnd() - blockPos
;
230 selection
.cursor
= cursor
;
231 mSearchSelections
.append(selection
);
233 block
= block
.next();
236 this->updateExtraSelections();
238 return mSearchSelections
.count();
241 //#define CSTR(QSTR) QSTR.toStdString().c_str()
243 static QString
resolvedReplacement( const QString
&replacement
, const QRegExp
&expr
)
246 static const QRegExp
rexpr("(\\\\\\\\)|(\\\\[0-9]+)");
247 QString
str( replacement
);
249 while(i
< str
.size() && ((i
= rexpr
.indexIn(str
, i
)) != -1))
251 int len
= rexpr
.matchedLength();
252 if(rexpr
.pos(1) != -1)
254 //qDebug("%i (%s): escape", i, CSTR(rexpr.cap(1)));
255 str
.replace(i
, len
, "\\");
258 else if(rexpr
.pos(2) != -1)
260 QString num_str
= rexpr
.cap(2);
261 num_str
.remove(0, 1);
262 int num
= num_str
.toInt();
263 //qDebug("%i (%s): backref = %i", i, CSTR(rexpr.cap(2)), num);
264 if(num
<= expr
.captureCount())
266 QString cap
= expr
.cap(num
);
267 //qDebug("resolving ref to: %s", CSTR(cap));
268 str
.replace(i
, len
, cap
);
273 //qDebug("ref out of range", i, num);
279 //qDebug("%i (%s): unknown match", i, CSTR(rexpr.cap(0)));
282 //qDebug(">> [%s] %i", CSTR(str), i);
288 bool GenericCodeEditor::replace( const QRegExp
&expr
, const QString
&replacement
, QTextDocument::FindFlags options
)
290 if(expr
.isEmpty()) return true;
292 QTextCursor cursor
= textCursor();
293 if (cursor
.hasSelection() && expr
.exactMatch(cursor
.selectedText()))
295 QString rstr
= replacement
;
296 if(expr
.patternSyntax() != QRegExp::FixedString
)
297 rstr
= resolvedReplacement(rstr
, expr
);
298 cursor
.insertText(rstr
);
301 return find(expr
, options
);
304 int GenericCodeEditor::replaceAll( const QRegExp
&expr
, const QString
&replacement
, QTextDocument::FindFlags options
)
306 mSearchSelections
.clear();
307 updateExtraSelections();
309 if(expr
.isEmpty()) return 0;
311 int replacements
= 0;
312 bool caps
= expr
.patternSyntax() != QRegExp::FixedString
;
314 QTextDocument
*doc
= QPlainTextEdit::document();
315 QTextBlock block
= doc
->begin();
318 QTextCursor(doc
).beginEditBlock();
320 while (block
.isValid())
322 int blockPos
= block
.position();
324 while(findInBlock(doc
, block
, expr
, offset
, options
, cursor
))
326 QString rstr
= replacement
;
328 rstr
= resolvedReplacement(rstr
, expr
);
329 cursor
.insertText(rstr
);
331 offset
= cursor
.selectionEnd() - blockPos
;
333 block
= block
.next();
336 QTextCursor(doc
).endEditBlock();
341 void GenericCodeEditor::showPosition( int pos
, int selectionLength
)
345 QTextDocument
*doc
= QPlainTextEdit::document();
348 int lineNumber
= doc
->findBlock(pos
).firstLineNumber();
349 verticalScrollBar()->setValue(lineNumber
);
351 QTextCursor
cursor(doc
);
352 cursor
.setPosition(pos
);
354 cursor
.setPosition(pos
+ selectionLength
, QTextCursor::KeepAnchor
);
356 setTextCursor(cursor
);
359 QString
GenericCodeEditor::symbolUnderCursor()
361 QTextCursor cursor
= textCursor();
362 if (!cursor
.hasSelection())
363 cursor
.select(QTextCursor::WordUnderCursor
);
364 return cursor
.selectedText();
367 void GenericCodeEditor::keyPressEvent(QKeyEvent
* e
)
371 QTextCursor
cursor( textCursor() );
376 // override to avoid entering a "soft" new line when certain modifier is held
377 cursor
.insertBlock();
381 QPlainTextEdit::keyPressEvent(e
);
387 case Qt::Key_Backspace
:
388 cursor
.setVerticalMovementX(-1);
389 setTextCursor( cursor
);
390 ensureCursorVisible();
396 void GenericCodeEditor::wheelEvent( QWheelEvent
* e
)
398 if (e
->modifiers() == Qt::ControlModifier
) {
406 QPlainTextEdit::wheelEvent(e
);
409 void GenericCodeEditor::dragEnterEvent( QDragEnterEvent
* event
)
411 const QMimeData
* data
= event
->mimeData();
412 if (data
->hasUrls()) {
413 // Propagate event to parent.
414 // URL drops are ultimately handled by MainWindow.
419 QPlainTextEdit::dragEnterEvent(event
);
422 void GenericCodeEditor::clearSearchHighlighting()
424 mSearchSelections
.clear();
425 this->updateExtraSelections();
428 void GenericCodeEditor::zoomIn(int steps
)
433 void GenericCodeEditor::zoomOut(int steps
)
438 void GenericCodeEditor::resetFontSize()
440 mDoc
->resetDefaultFont();
443 void GenericCodeEditor::zoomFont(int steps
)
445 QFont currentFont
= mDoc
->defaultFont();
446 const int newSize
= currentFont
.pointSize() + steps
;
449 currentFont
.setPointSize(newSize
);
450 mDoc
->setDefaultFont(currentFont
);
453 void GenericCodeEditor::onDocumentFontChanged()
455 QFont font
= mDoc
->defaultFont();
459 void GenericCodeEditor::updateLayout()
461 setViewportMargins( mLineIndicator
->width(), 0, 0, 0 );
464 void GenericCodeEditor::updateLineIndicator( QRect r
, int dy
)
467 mLineIndicator
->scroll(0, dy
);
469 mLineIndicator
->update(0, r
.y(), mLineIndicator
->width(), r
.height() );
472 void GenericCodeEditor::updateExtraSelections()
474 QList
<QTextEdit::ExtraSelection
> selections
;
475 selections
.append(mSearchSelections
);
476 setExtraSelections(selections
);
479 void GenericCodeEditor::resizeEvent( QResizeEvent
*e
)
481 QPlainTextEdit::resizeEvent( e
);
483 QRect cr
= contentsRect();
484 mLineIndicator
->resize( mLineIndicator
->width(), cr
.height() );
487 void GenericCodeEditor::paintLineIndicator( QPaintEvent
*e
)
489 QPalette
plt( mLineIndicator
->palette() );
490 QRect
r( e
->rect() );
491 QPainter
p( mLineIndicator
);
493 p
.fillRect( r
, plt
.color( QPalette::Button
) );
494 p
.setPen( plt
.color(QPalette::ButtonText
) );
495 p
.drawLine( r
.topRight(), r
.bottomRight() );
497 QTextDocument
*doc
= QPlainTextEdit::document();
498 QTextCursor
cursor(textCursor());
499 int selStartBlock
, selEndBlock
;
500 if (cursor
.hasSelection()) {
501 selStartBlock
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
502 selEndBlock
= doc
->findBlock(cursor
.selectionEnd()).blockNumber();
505 selStartBlock
= selEndBlock
= -1;
507 QTextBlock block
= firstVisibleBlock();
508 int blockNumber
= block
.blockNumber();
509 int top
= (int) blockBoundingGeometry(block
).translated(contentOffset()).top();
510 int bottom
= top
+ (int) blockBoundingRect(block
).height();
512 while (block
.isValid() && top
<= e
->rect().bottom()) {
513 if (block
.isVisible() && bottom
>= e
->rect().top()) {
516 QRect
numRect( 0, top
, mLineIndicator
->width() - 1, bottom
- top
);
518 int num
= blockNumber
;
519 if (num
>= selStartBlock
&& num
<= selEndBlock
) {
520 num
-= selStartBlock
;
522 p
.setBrush(plt
.color(QPalette::Highlight
));
524 p
.setPen(plt
.color(QPalette::HighlightedText
));
527 QString number
= QString::number(num
+ 1);
528 p
.drawText(0, top
, mLineIndicator
->width() - 4, bottom
- top
,
529 Qt::AlignRight
, number
);
534 block
= block
.next();
536 bottom
= top
+ (int) blockBoundingRect(block
).height();
541 void GenericCodeEditor::copyUpDown(bool up
)
543 // directly taken from qtcreator
544 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
545 // GNU Lesser General Public License
546 QTextCursor cursor
= textCursor();
547 QTextCursor move
= cursor
;
548 move
.beginEditBlock();
550 bool hasSelection
= cursor
.hasSelection();
553 move
.setPosition(cursor
.selectionStart());
554 move
.movePosition(QTextCursor::StartOfBlock
);
555 move
.setPosition(cursor
.selectionEnd(), QTextCursor::KeepAnchor
);
556 move
.movePosition(move
.atBlockStart() ? QTextCursor::Left
: QTextCursor::EndOfBlock
,
557 QTextCursor::KeepAnchor
);
559 move
.movePosition(QTextCursor::StartOfBlock
);
560 move
.movePosition(QTextCursor::EndOfBlock
, QTextCursor::KeepAnchor
);
563 QString text
= move
.selectedText();
566 move
.setPosition(cursor
.selectionStart());
567 move
.movePosition(QTextCursor::StartOfBlock
);
569 move
.movePosition(QTextCursor::Left
);
571 move
.movePosition(QTextCursor::EndOfBlock
);
572 if (move
.atBlockStart()) {
573 move
.movePosition(QTextCursor::NextBlock
);
575 move
.movePosition(QTextCursor::Left
);
581 int start
= move
.position();
582 move
.clearSelection();
583 move
.insertText(text
);
584 int end
= move
.position();
586 move
.setPosition(start
);
587 move
.setPosition(end
, QTextCursor::KeepAnchor
);
595 void GenericCodeEditor::toggleOverwriteMode()
597 setOverwriteMode(!overwriteMode());
601 void GenericCodeEditor::copyLineDown()
606 void GenericCodeEditor::copyLineUp()
611 void GenericCodeEditor::moveLineUpDown(bool up
)
613 // directly taken from qtcreator
614 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
615 // GNU Lesser General Public License
616 QTextCursor cursor
= textCursor();
617 QTextCursor move
= cursor
;
619 move
.setVisualNavigation(false); // this opens folded items instead of destroying them
621 move
.beginEditBlock();
623 bool hasSelection
= cursor
.hasSelection();
625 if (cursor
.hasSelection()) {
626 move
.setPosition(cursor
.selectionStart());
627 move
.movePosition(QTextCursor::StartOfBlock
);
628 move
.setPosition(cursor
.selectionEnd(), QTextCursor::KeepAnchor
);
629 move
.movePosition(move
.atBlockStart() ? QTextCursor::Left
: QTextCursor::EndOfBlock
,
630 QTextCursor::KeepAnchor
);
632 move
.movePosition(QTextCursor::StartOfBlock
);
633 move
.movePosition(QTextCursor::EndOfBlock
, QTextCursor::KeepAnchor
);
635 QString text
= move
.selectedText();
637 move
.movePosition(QTextCursor::Right
, QTextCursor::KeepAnchor
);
638 move
.removeSelectedText();
641 move
.movePosition(QTextCursor::PreviousBlock
);
643 move
.movePosition(QTextCursor::Left
);
645 move
.movePosition(QTextCursor::EndOfBlock
);
646 if (move
.atBlockStart()) { // empty block
647 move
.movePosition(QTextCursor::NextBlock
);
649 move
.movePosition(QTextCursor::Left
);
655 int start
= move
.position();
656 move
.clearSelection();
657 move
.insertText(text
);
658 int end
= move
.position();
661 move
.setPosition(start
);
662 move
.setPosition(end
, QTextCursor::KeepAnchor
);
670 void GenericCodeEditor::moveLineUp()
672 moveLineUpDown(true);
675 void GenericCodeEditor::moveLineDown()
677 moveLineUpDown(false);
680 void GenericCodeEditor::gotoPreviousEmptyLine()
682 gotoEmptyLineUpDown(true);
685 void GenericCodeEditor::gotoNextEmptyLine()
687 gotoEmptyLineUpDown(false);
690 void GenericCodeEditor::gotoEmptyLineUpDown(bool up
)
692 static const QRegExp
whiteSpaceLine("^\\s*$");
694 const QTextCursor::MoveOperation direction
= up
? QTextCursor::PreviousBlock
695 : QTextCursor::NextBlock
;
697 QTextCursor cursor
= textCursor();
698 cursor
.beginEditBlock();
700 bool cursorMoved
= false;
702 // find first non-whitespace line
703 while ( cursor
.movePosition(direction
) ) {
704 if ( !whiteSpaceLine
.exactMatch(cursor
.block().text()) )
708 // find first whitespace line
709 while ( cursor
.movePosition(direction
) ) {
710 if ( whiteSpaceLine
.exactMatch(cursor
.block().text()) ) {
711 setTextCursor(cursor
);
718 const QTextCursor::MoveOperation startOrEnd
= up
? QTextCursor::Start
721 cursor
.movePosition(startOrEnd
);
722 setTextCursor(cursor
);
725 cursor
.endEditBlock();
728 void GenericCodeEditor::hideMouseCursor()
730 QCursor
* overrideCursor
= QApplication::overrideCursor();
731 if (!overrideCursor
|| overrideCursor
->shape() != Qt::BlankCursor
)
732 QApplication::setOverrideCursor( Qt::BlankCursor
);
737 CodeEditor::CodeEditor( Document
*doc
, QWidget
*parent
) :
738 GenericCodeEditor( doc
, parent
),
741 mMouseBracketMatch(false),
742 mOverlay( new QGraphicsScene(this) ),
743 mAutoCompleter( new AutoCompleter(this) )
747 setFrameShape( QFrame::NoFrame
);
749 mLineIndicator
->move( contentsRect().topLeft() );
751 connect( this, SIGNAL(cursorPositionChanged()),
752 this, SLOT(matchBrackets()) );
754 connect( mOverlay
, SIGNAL(changed(const QList
<QRectF
>&)),
755 this, SLOT(onOverlayChanged(const QList
<QRectF
>&)) );
757 connect( Main::instance(), SIGNAL(applySettingsRequest(Settings::Manager
*)),
758 this, SLOT(applySettings(Settings::Manager
*)) );
760 QTextDocument
*tdoc
= doc
->textDocument();
761 mAutoCompleter
->documentChanged(tdoc
);
762 mLineIndicator
->setLineCount(blockCount());
764 applySettings(Main::settings());
768 QTextCursor
CodeEditor::currentRegion()
770 QTextCursor cursor
= textCursor();
771 QTextBlock block
= cursor
.block();
772 int positionInBlock
= cursor
.positionInBlock();
774 if (TokenIterator(block
, positionInBlock
- 1).type() == Token::ClosingBracket
)
775 cursor
.movePosition( QTextCursor::PreviousCharacter
);
776 else if (TokenIterator(block
, positionInBlock
).type() == Token::OpeningBracket
)
777 cursor
.movePosition( QTextCursor::NextCharacter
);
779 return regionAtCursor( cursor
);
783 QTextCursor
CodeEditor::regionAtCursor(const QTextCursor
& cursor
)
785 QTextBlock
block(cursor
.block());
786 int positionInBlock
= cursor
.positionInBlock();
793 // search unmatched opening bracket
794 TokenIterator it
= TokenIterator::leftOf( block
, positionInBlock
);
797 char chr
= it
->character
;
800 if(level
> topLevel
) {
805 else if(chr
== ')') {
812 // no unmatched opening bracket
813 return QTextCursor();
815 // match the found opening bracket
816 it
= TokenIterator::rightOf( block
, positionInBlock
);
819 char chr
= it
->character
;
834 if(start
.isValid() && end
.isValid())
836 // only care about brackets at beginning of a line
837 if(start
->positionInBlock
!= 0)
838 return QTextCursor();
840 // check whether the bracket makes part of an event
843 if (it
->type
== Token::SymbolArg
)
844 return QTextCursor();
847 if (it
.isValid() && it
->character
== ':')
848 return QTextCursor();
852 // ok, this is is a real top-level region
853 QTextCursor
c(QPlainTextEdit::document());
854 c
.setPosition(start
.position() + 1);
855 c
.setPosition(end
.position(), QTextCursor::KeepAnchor
);
859 return QTextCursor();
862 QTextCursor
CodeEditor::blockAtCursor(const QTextCursor
& cursor
)
864 TokenIterator
it (cursor
.block(), cursor
.positionInBlock());
868 case Token::OpeningBracket
:
869 case Token::ClosingBracket
:
872 matchBracket(it
, match
);
874 if (match
.first
.isValid()) {
875 QTextCursor
selection(textDocument());
876 selection
.setPosition(match
.first
.position());
877 selection
.setPosition(match
.second
.position() + 1, QTextCursor::KeepAnchor
);
888 return QTextCursor();
892 void CodeEditor::applySettings( Settings::Manager
*settings
)
894 settings
->beginGroup("IDE/editor");
896 mSpaceIndent
= settings
->value("spaceIndent").toBool();
898 mBlinkDuration
= settings
->value("blinkDuration").toInt();
902 settings
->beginGroup("colors");
904 if (settings
->contains("text")) {
905 QTextCharFormat format
= settings
->value("text").value
<QTextCharFormat
>();
906 QBrush bg
= format
.background();
907 QBrush fg
= format
.foreground();
908 if (bg
.style() != Qt::NoBrush
)
909 palette
.setBrush(QPalette::Base
, bg
);
910 if (fg
.style() != Qt::NoBrush
)
911 palette
.setBrush(QPalette::Text
, fg
);
914 if (settings
->contains("lineNumbers")) {
916 QTextCharFormat format
= settings
->value("lineNumbers").value
<QTextCharFormat
>();
917 QBrush bg
= format
.background();
918 QBrush fg
= format
.foreground();
919 if (bg
.style() != Qt::NoBrush
)
920 palette
.setBrush(QPalette::Button
, bg
);
921 if (fg
.style() != Qt::NoBrush
)
922 palette
.setBrush(QPalette::ButtonText
, fg
);
923 mLineIndicator
->setPalette(lineNumPlt
);
926 mBracketHighlight
= settings
->value("matchingBrackets").value
<QTextCharFormat
>();
928 settings
->endGroup(); // colors
932 settings
->endGroup();
935 bool CodeEditor::event( QEvent
*e
)
939 case QEvent::KeyPress
:
941 QKeyEvent
*ke
= static_cast<QKeyEvent
*>(e
);
955 return QPlainTextEdit::event(e
);
958 void CodeEditor::keyPressEvent( QKeyEvent
*e
)
963 Qt::KeyboardModifiers
mods(e
->modifiers());
964 if (mods
&& mods
!= Qt::ShiftModifier
) {
965 GenericCodeEditor::keyPressEvent(e
);
971 QTextCursor::MoveMode mode
=
972 mods
& Qt::ShiftModifier
? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor
;
974 QTextCursor
c(textCursor());
975 QTextBlock
b(c
.block());
977 int pos
= indentedStartOfLine(b
);
980 if (c
.position() == pos
)
981 c
.movePosition(QTextCursor::StartOfLine
, mode
);
983 c
.setPosition(pos
, mode
);
990 case Qt::Key_Backtab
:
993 QTextCursor cursor
= textCursor();
994 cursor
.insertText("\t");
995 ensureCursorVisible();
1004 case Qt::Key_Return
:
1005 case Qt::Key_BraceRight
:
1006 case Qt::Key_BracketRight
:
1007 case Qt::Key_ParenRight
: {
1008 // Wrap superclass' implementation into an edit block,
1009 // so it can be joined with indentation later:
1011 QTextCursor cursor
= textCursor();
1013 cursor
.beginEditBlock();
1014 GenericCodeEditor::keyPressEvent(e
);
1015 cursor
.endEditBlock();
1017 cursor
.joinPreviousEditBlock();
1019 cursor
.endEditBlock();
1021 cursor
.setVerticalMovementX(-1);
1022 setTextCursor(cursor
);
1027 GenericCodeEditor::keyPressEvent(e
);
1030 mAutoCompleter
->keyPress(e
);
1033 void CodeEditor::mouseReleaseEvent ( QMouseEvent
*e
)
1035 // Prevent deselection of bracket match:
1036 if(!mMouseBracketMatch
)
1037 GenericCodeEditor::mouseReleaseEvent(e
);
1039 mMouseBracketMatch
= false;
1042 void CodeEditor::mouseDoubleClickEvent( QMouseEvent
* e
)
1044 QTextCursor cursor
= cursorForPosition(e
->pos());
1045 QTextCursor selection
= blockAtCursor(cursor
);
1047 if (!selection
.isNull()) {
1048 setTextCursor(selection
);
1052 GenericCodeEditor::mouseDoubleClickEvent(e
);
1055 void CodeEditor::mouseMoveEvent( QMouseEvent
*e
)
1057 // Prevent initiating a text drag:
1058 if(!mMouseBracketMatch
)
1059 GenericCodeEditor::mouseMoveEvent(e
);
1063 void CodeEditor::onOverlayChanged ( const QList
<QRectF
> & region
)
1065 foreach(QRectF r
, region
)
1067 viewport()->update(r
.toRect());
1071 void CodeEditor::paintEvent( QPaintEvent
*e
)
1073 GenericCodeEditor::paintEvent(e
);
1075 QPainter
p(viewport());
1076 mOverlay
->render(&p
, e
->rect(), e
->rect());
1079 void CodeEditor::matchBrackets()
1081 mBracketSelections
.clear();
1083 QTextCursor
cursor(textCursor());
1084 QTextBlock
block( cursor
.block() );
1085 int posInBlock
= cursor
.positionInBlock();
1086 TokenIterator
it(block
);
1087 while (it
.isValid() && it
.block() == block
)
1089 const Token
& token
= *it
;
1090 if (token
.positionInBlock
> posInBlock
) {
1091 it
= TokenIterator();
1094 (token
.positionInBlock
== posInBlock
&& token
.type
== Token::OpeningBracket
) ||
1095 (token
.positionInBlock
== posInBlock
- 1 && token
.type
== Token::ClosingBracket
)
1102 if( it
.isValid() && it
.block() == block
)
1103 matchBracket( it
, match
);
1105 if( match
.first
.isValid() && match
.second
.isValid() )
1107 const Token
& tok1
= *match
.first
;
1108 const Token
& tok2
= *match
.second
;
1111 (tok1
.character
== '(' && tok2
.character
== ')')
1112 || (tok1
.character
== '[' && tok2
.character
== ']')
1113 || (tok1
.character
== '{' && tok2
.character
== '}')
1115 QTextEdit::ExtraSelection selection
;
1116 selection
.format
= mBracketHighlight
;
1118 cursor
.setPosition(match
.first
.position());
1119 cursor
.movePosition(QTextCursor::NextCharacter
, QTextCursor::KeepAnchor
);
1120 selection
.cursor
= cursor
;
1121 mBracketSelections
.append(selection
);
1123 cursor
.setPosition(match
.second
.position());
1124 cursor
.movePosition(QTextCursor::NextCharacter
, QTextCursor::KeepAnchor
);
1125 selection
.cursor
= cursor
;
1126 mBracketSelections
.append(selection
);
1129 QTextEdit::ExtraSelection selection
;
1130 selection
.format
.setBackground(Qt::red
);
1131 cursor
.setPosition(match
.first
.position());
1132 cursor
.setPosition(match
.second
.position()+1, QTextCursor::KeepAnchor
);
1133 selection
.cursor
= cursor
;
1134 mBracketSelections
.append(selection
);
1138 updateExtraSelections();
1141 void CodeEditor::matchBracket( const TokenIterator
& bracket
, BracketMatch
& match
)
1143 TokenIterator
it(bracket
);
1145 if(it
->type
== Token::OpeningBracket
)
1149 while((++it
).isValid())
1151 Token::Type type
= it
->type
;
1152 if(type
== Token::ClosingBracket
)
1154 else if(type
== Token::OpeningBracket
)
1162 else if(it
->type
== Token::ClosingBracket
)
1166 while((--it
).isValid())
1168 Token::Type type
= it
->type
;
1169 if(type
== Token::OpeningBracket
)
1171 else if(type
== Token::ClosingBracket
)
1181 int CodeEditor::indentedStartOfLine( const QTextBlock
&b
)
1183 QString
t(b
.text());
1188 if (c
!= ' ' && c
!= '\t')
1196 void CodeEditor::updateExtraSelections()
1198 QList
<QTextEdit::ExtraSelection
> selections
;
1199 selections
.append(mBracketSelections
);
1200 selections
.append(mSearchSelections
);
1201 setExtraSelections(selections
);
1204 void CodeEditor::indentCurrentRegion()
1206 indent(currentRegion());
1209 void CodeEditor::indent()
1211 indent(textCursor());
1214 void CodeEditor::indent( const QTextCursor
& selection
)
1216 if (selection
.isNull())
1219 QTextCursor
cursor(selection
);
1221 cursor
.beginEditBlock();
1223 QTextDocument
*doc
= QPlainTextEdit::document();
1224 int startBlockNum
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
1225 int endBlockNum
= cursor
.hasSelection() ?
1226 doc
->findBlock(cursor
.selectionEnd()).blockNumber() : startBlockNum
;
1231 QTextBlock block
= QPlainTextEdit::document()->begin();
1232 while (block
.isValid())
1239 int initialStackSize
= stack
.size();
1241 TextBlockData
*data
= static_cast<TextBlockData
*>(block
.userData());
1244 int count
= data
->tokens
.size();
1245 for (int idx
= 0; idx
< count
; ++idx
)
1247 const Token
& token
= data
->tokens
[idx
];
1250 case Token::OpeningBracket
:
1251 if (token
.character
!= '(' || stack
.size() || token
.positionInBlock
)
1255 case Token::ClosingBracket
:
1258 else if(!stack
.isEmpty()) {
1260 if (stack
.top() <= 0)
1271 if(blockNum
>= startBlockNum
) {
1273 if (data
&& data
->tokens
.size() && data
->tokens
[0].type
== Token::ClosingBracket
)
1274 indentLevel
= stack
.size();
1276 indentLevel
= initialStackSize
;
1277 block
= indent(block
, indentLevel
);
1280 if(blockNum
== endBlockNum
)
1283 block
= block
.next();
1287 cursor
.endEditBlock();
1290 QString
CodeEditor::makeIndentationString(int level
)
1295 if ( mSpaceIndent
) {
1296 const int spaces
= mDoc
->indentWidth() * level
;
1297 QString
indentationString (spaces
, QChar(' '));
1298 return indentationString
;
1300 const int tabs
= level
;
1301 QString
indentationString (tabs
, QChar('\t'));
1302 return indentationString
;
1306 QTextBlock
CodeEditor::indent( const QTextBlock
& block
, int level
)
1308 QTextCursor
cursor(block
);
1309 cursor
.movePosition(QTextCursor::StartOfBlock
);
1310 cursor
.setPosition(cursor
.position() + indentedStartOfLine(block
), QTextCursor::KeepAnchor
);
1312 cursor
.insertText(makeIndentationString(level
));
1314 // modification has invalidated the block, so return a new one
1315 return cursor
.block();
1318 int CodeEditor::indentationLevel(const QTextCursor
& cursor
)
1320 QTextDocument
*doc
= QPlainTextEdit::document();
1321 int startBlockNum
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
1326 QTextBlock block
= QPlainTextEdit::document()->begin();
1327 while (block
.isValid()) {
1333 TextBlockData
*data
= static_cast<TextBlockData
*>(block
.userData());
1335 int count
= data
->tokens
.size();
1336 for (int idx
= 0; idx
< count
; ++idx
) {
1337 const Token
& token
= data
->tokens
[idx
];
1338 switch (token
.type
) {
1339 case Token::OpeningBracket
:
1340 if (token
.character
!= '(' || stack
.size() || token
.positionInBlock
)
1344 case Token::ClosingBracket
:
1347 else if(!stack
.isEmpty()) {
1349 if (stack
.top() <= 0)
1360 if (blockNum
== startBlockNum
)
1361 return stack
.size();
1363 block
= block
.next();
1370 void CodeEditor::triggerAutoCompletion()
1372 mAutoCompleter
->triggerCompletion();
1375 void CodeEditor::triggerMethodCallAid()
1377 mAutoCompleter
->triggerMethodCallAid();
1380 static bool isSingleLineComment(QTextBlock
const & block
)
1382 static QRegExp
commentRegex("^\\s*//.*");
1383 return commentRegex
.exactMatch(block
.text());
1386 static bool isSelectionComment(QString
const & text
)
1388 QString trimmed
= text
.trimmed();
1389 if ( trimmed
.startsWith(QString("/*")) && trimmed
.endsWith(QString("*/")) )
1395 void CodeEditor::toggleComment()
1397 QTextCursor cursor
= textCursor();
1399 if (cursor
.hasSelection())
1400 toggleCommentSelection();
1402 toggleCommentSingleLine();
1405 void CodeEditor::toggleCommentSingleLine()
1407 QTextCursor cursor
= textCursor();
1408 cursor
.beginEditBlock();
1410 toggleCommentSingleLine( cursor
);
1412 cursor
.endEditBlock();
1415 void CodeEditor::addSingleLineComment(QTextCursor cursor
, int indentation
)
1417 QTextBlock
currentBlock(cursor
.block());
1418 int blockIndentationLevel
= indentationLevel(cursor
);
1420 cursor
.movePosition(QTextCursor::StartOfBlock
);
1421 cursor
.setPosition(cursor
.position() + indentedStartOfLine(currentBlock
), QTextCursor::KeepAnchor
);
1423 QString commentString
= makeIndentationString(indentation
) + QString("// ")
1424 + makeIndentationString(blockIndentationLevel
- indentation
);
1426 cursor
.insertText(commentString
);
1428 cursor
.movePosition(QTextCursor::StartOfBlock
);
1431 void CodeEditor::removeSingleLineComment(QTextCursor cursor
)
1433 QTextBlock
currentBlock(cursor
.block());
1434 cursor
.movePosition(QTextCursor::StartOfBlock
);
1435 cursor
.setPosition(cursor
.position() + indentedStartOfLine(currentBlock
) + 2, QTextCursor::KeepAnchor
);
1437 if (!cursor
.selectedText().endsWith(QString("//")))
1438 cursor
.setPosition(cursor
.anchor() + indentedStartOfLine(currentBlock
), QTextCursor::KeepAnchor
);
1440 cursor
.insertText("");
1443 void CodeEditor::toggleCommentSingleLine(QTextCursor cursor
)
1445 QTextBlock
currentBlock(cursor
.block());
1447 cursor
.beginEditBlock();
1449 if (!isSingleLineComment(currentBlock
)) {
1450 int blockIndentation
= indentationLevel(cursor
);
1451 addSingleLineComment(cursor
, blockIndentation
);
1453 removeSingleLineComment(cursor
);
1455 cursor
.endEditBlock();
1459 static bool isBlockOnlySelection(QTextCursor cursor
)
1461 Q_ASSERT(cursor
.hasSelection());
1463 QTextCursor
begin(cursor
);
1464 begin
.setPosition(begin
.anchor());
1466 if (begin
.atBlockStart() && (cursor
.atBlockStart() || cursor
.atBlockEnd()))
1472 void CodeEditor::toggleCommentSelection()
1474 QTextCursor cursor
= textCursor();
1475 cursor
.beginEditBlock();
1477 if (isBlockOnlySelection(cursor
)) {
1478 QTextCursor
selectionCursor(cursor
);
1479 selectionCursor
.setPosition(cursor
.selectionStart());
1481 QTextBlock currentBlock
= selectionCursor
.block();
1482 bool isComment
= isSingleLineComment(currentBlock
);
1483 int firstBlockIndentation
= isComment
? 0
1484 : indentationLevel(selectionCursor
);
1487 QTextCursor
blockCursor(currentBlock
);
1489 addSingleLineComment(blockCursor
, firstBlockIndentation
);
1491 removeSingleLineComment(blockCursor
);
1492 currentBlock
= currentBlock
.next();
1493 } while (currentBlock
.isValid() && currentBlock
.position() < cursor
.selectionEnd());
1495 QString selectionText
= cursor
.selectedText();
1496 QTextCursor
selectionCursor(cursor
);
1497 if (isSelectionComment(selectionText
)) {
1498 selectionText
= selectionText
.trimmed().remove(0, 2);
1499 selectionText
.chop(2);
1500 selectionCursor
.insertText(selectionText
);
1502 selectionText
= QString("/* ") + selectionText
+ QString(" */");
1503 selectionCursor
.insertText(selectionText
);
1505 int position
= selectionCursor
.position();
1506 cursor
.setPosition(position
- selectionText
.size());
1507 cursor
.setPosition(position
, QTextCursor::KeepAnchor
);
1508 setTextCursor(cursor
);
1512 cursor
.endEditBlock();
1515 // taking nested brackets into account
1516 static TokenIterator
previousOpeningBracket(TokenIterator it
)
1519 while (it
.isValid()) {
1521 case Token::OpeningBracket
:
1527 case Token::ClosingBracket
:
1538 // taking nested brackets into account
1539 static TokenIterator
nextClosingBracket(TokenIterator it
)
1542 while (it
.isValid()) {
1544 case Token::ClosingBracket
:
1550 case Token::OpeningBracket
:
1562 void CodeEditor::gotoNextBlock()
1564 QTextCursor cursor
= textCursor();
1566 TokenIterator tokenIt
= TokenIterator::rightOf( cursor
.block(), cursor
.positionInBlock() );
1567 if (tokenIt
.type() == Token::OpeningBracket
1568 && tokenIt
.block() == cursor
.block()
1569 && tokenIt
->positionInBlock
== cursor
.positionInBlock())
1572 tokenIt
= nextClosingBracket( tokenIt
);
1574 if (tokenIt
.isValid())
1575 setTextCursor( cursorAt(tokenIt
, 1) );
1577 cursor
.movePosition( QTextCursor::End
);
1578 setTextCursor( cursor
);
1582 void CodeEditor::gotoPreviousBlock()
1584 QTextCursor cursor
= textCursor();
1586 TokenIterator tokenIt
= TokenIterator::leftOf(cursor
.block(), cursor
.positionInBlock());
1587 if (tokenIt
.type() == Token::ClosingBracket
1588 && tokenIt
.block() == cursor
.block()
1589 && tokenIt
->positionInBlock
== cursor
.positionInBlock() - 1)
1593 tokenIt
= previousOpeningBracket( tokenIt
);
1595 if (tokenIt
.isValid())
1596 setTextCursor( cursorAt(tokenIt
) );
1598 cursor
.movePosition( QTextCursor::Start
);
1599 setTextCursor( cursor
);
1603 void CodeEditor::selectCurrentRegion()
1605 QTextCursor selectedRegionCursor
= currentRegion();
1606 if (!selectedRegionCursor
.isNull() && selectedRegionCursor
.hasSelection())
1607 setTextCursor(selectedRegionCursor
);
1610 void CodeEditor::gotoNextRegion()
1612 QTextCursor cursor
= textCursor();
1614 QTextCursor regionCursor
= regionAtCursor(cursor
);
1615 if (!regionCursor
.isNull()) {
1616 cursor
= regionCursor
;
1617 // regionCursor does not include region's closing bracket, so skip it
1618 cursor
.movePosition(QTextCursor::NextCharacter
);
1621 // Skip potential opening bracket immediately right of cursor:
1622 cursor
.movePosition(QTextCursor::NextCharacter
);
1624 TokenIterator it
= TokenIterator::rightOf(cursor
.block(), cursor
.positionInBlock());
1626 while (it
.isValid()) {
1627 if ( (it
->type
== Token::OpeningBracket
) && (it
->character
== '(') &&
1628 (it
->positionInBlock
== 0) ) {
1629 setTextCursor( cursorAt(it
, 1) );
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
) );
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
);
1693 } // namespace ScIDE