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