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 CodeEditor::CodeEditor( Document
*doc
, QWidget
*parent
) :
43 QPlainTextEdit( parent
),
44 mLineIndicator( new LineIndicator(this) ),
48 mMouseBracketMatch(false),
49 mOverlay( new QGraphicsScene(this) ),
50 mAutoCompleter( new AutoCompleter(this) )
54 setFrameShape( QFrame::NoFrame
);
56 mLineIndicator
->move( contentsRect().topLeft() );
58 connect( mDoc
, SIGNAL(defaultFontChanged()), this, SLOT(onDocumentFontChanged()) );
60 connect( this, SIGNAL(blockCountChanged(int)),
61 mLineIndicator
, SLOT(setLineCount(int)) );
63 connect( mLineIndicator
, SIGNAL( widthChanged() ),
64 this, SLOT( updateLayout() ) );
66 connect( this, SIGNAL(updateRequest(QRect
,int)),
67 this, SLOT(updateLineIndicator(QRect
,int)) );
69 connect( this, SIGNAL(selectionChanged()),
70 mLineIndicator
, SLOT(update()) );
72 connect( this, SIGNAL(cursorPositionChanged()),
73 this, SLOT(matchBrackets()) );
75 connect( mOverlay
, SIGNAL(changed(const QList
<QRectF
>&)),
76 this, SLOT(onOverlayChanged(const QList
<QRectF
>&)) );
78 connect( Main::instance(), SIGNAL(applySettingsRequest(Settings::Manager
*)),
79 this, SLOT(applySettings(Settings::Manager
*)) );
81 QTextDocument
*tdoc
= doc
->textDocument();
82 QPlainTextEdit::setDocument(tdoc
);
83 mAutoCompleter
->documentChanged(tdoc
);
84 onDocumentFontChanged();
85 mLineIndicator
->setLineCount(blockCount());
87 applySettings(Main::settings());
90 bool CodeEditor::showWhitespace()
92 QTextOption
options( textDocument()->defaultTextOption() );
93 return options
.flags().testFlag( QTextOption::ShowTabsAndSpaces
);
96 void CodeEditor::setShowWhitespace(bool show
)
98 QTextDocument
*doc
= textDocument();
99 QTextOption
opt( doc
->defaultTextOption() );
101 opt
.setFlags( opt
.flags() | QTextOption::ShowTabsAndSpaces
);
103 opt
.setFlags( opt
.flags() & ~QTextOption::ShowTabsAndSpaces
);
104 doc
->setDefaultTextOption(opt
);
107 static bool findInBlock(QTextDocument
*doc
, const QTextBlock
&block
, const QRegExp
&expr
, int offset
,
108 QTextDocument::FindFlags options
, QTextCursor
&cursor
)
110 QString text
= block
.text();
111 if(options
& QTextDocument::FindBackward
)
112 text
.truncate(offset
);
113 text
.replace(QChar::Nbsp
, QLatin1Char(' '));
116 while (offset
>=0 && offset
<= text
.length()) {
117 idx
= (options
& QTextDocument::FindBackward
) ?
118 expr
.lastIndexIn(text
, offset
) : expr
.indexIn(text
, offset
);
122 if (options
& QTextDocument::FindWholeWords
) {
123 const int start
= idx
;
124 const int end
= start
+ expr
.matchedLength();
125 if ((start
!= 0 && text
.at(start
- 1).isLetterOrNumber())
126 || (end
!= text
.length() && text
.at(end
).isLetterOrNumber())) {
127 //if this is not a whole word, continue the search in the string
128 offset
= (options
& QTextDocument::FindBackward
) ? idx
-1 : end
+1;
133 //we have a hit, return the cursor for that.
140 cursor
= QTextCursor(doc
);
141 cursor
.setPosition(block
.position() + idx
);
142 cursor
.setPosition(cursor
.position() + expr
.matchedLength(), QTextCursor::KeepAnchor
);
146 bool CodeEditor::find( const QRegExp
&expr
, QTextDocument::FindFlags options
)
148 // Although QTextDocument provides a find() method, we implement
149 // our own, because the former one is not adequate.
151 if(expr
.isEmpty()) return true;
153 bool backwards
= options
& QTextDocument::FindBackward
;
155 QTextCursor
c( textCursor() );
157 if (c
.hasSelection())
159 bool matching
= expr
.exactMatch(c
.selectedText());
161 if( backwards
== matching
)
162 pos
= c
.selectionStart();
164 pos
= c
.selectionEnd();
169 QTextDocument
*doc
= QPlainTextEdit::document();
170 QTextBlock startBlock
= doc
->findBlock(pos
);
171 int startBlockOffset
= pos
- startBlock
.position();
176 int blockOffset
= startBlockOffset
;
177 QTextBlock block
= startBlock
;
178 while (block
.isValid()) {
179 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
))
182 block
= block
.next();
187 block
= doc
->begin();
189 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
)
190 || block
== startBlock
)
192 block
= block
.next();
196 int blockOffset
= startBlockOffset
;
197 QTextBlock block
= startBlock
;
198 while (block
.isValid()) {
199 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
))
201 block
= block
.previous();
202 blockOffset
= block
.length() - 1;
208 blockOffset
= block
.length() - 1;
209 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
)
210 || block
== startBlock
)
212 block
= block
.previous();
217 if(!cursor
.isNull()) {
218 setTextCursor(cursor
);
225 int CodeEditor::findAll( const QRegExp
&expr
, QTextDocument::FindFlags options
)
227 mSearchSelections
.clear();
230 updateExtraSelections();
234 QTextEdit::ExtraSelection selection
;
235 selection
.format
.setBackground(Qt::darkYellow
);
237 QTextDocument
*doc
= QPlainTextEdit::document();
238 QTextBlock block
= doc
->begin();
241 while (block
.isValid()) {
242 int blockPos
= block
.position();
244 while(findInBlock(doc
, block
, expr
, offset
, options
, cursor
))
246 offset
= cursor
.selectionEnd() - blockPos
;
247 selection
.cursor
= cursor
;
248 mSearchSelections
.append(selection
);
250 block
= block
.next();
253 updateExtraSelections();
255 return mSearchSelections
.count();
258 //#define CSTR(QSTR) QSTR.toStdString().c_str()
260 static QString
resolvedReplacement( const QString
&replacement
, const QRegExp
&expr
)
263 static const QRegExp
rexpr("(\\\\\\\\)|(\\\\[0-9]+)");
264 QString
str( replacement
);
266 while(i
< str
.size() && ((i
= rexpr
.indexIn(str
, i
)) != -1))
268 int len
= rexpr
.matchedLength();
269 if(rexpr
.pos(1) != -1)
271 //qDebug("%i (%s): escape", i, CSTR(rexpr.cap(1)));
272 str
.replace(i
, len
, "\\");
275 else if(rexpr
.pos(2) != -1)
277 QString num_str
= rexpr
.cap(2);
278 num_str
.remove(0, 1);
279 int num
= num_str
.toInt();
280 //qDebug("%i (%s): backref = %i", i, CSTR(rexpr.cap(2)), num);
281 if(num
<= expr
.captureCount())
283 QString cap
= expr
.cap(num
);
284 //qDebug("resolving ref to: %s", CSTR(cap));
285 str
.replace(i
, len
, cap
);
290 //qDebug("ref out of range", i, num);
296 //qDebug("%i (%s): unknown match", i, CSTR(rexpr.cap(0)));
299 //qDebug(">> [%s] %i", CSTR(str), i);
305 bool CodeEditor::replace( const QRegExp
&expr
, const QString
&replacement
, QTextDocument::FindFlags options
)
307 if(expr
.isEmpty()) return true;
309 QTextCursor cursor
= textCursor();
310 if (cursor
.hasSelection() && expr
.exactMatch(cursor
.selectedText()))
312 QString rstr
= replacement
;
313 if(expr
.patternSyntax() != QRegExp::FixedString
)
314 rstr
= resolvedReplacement(rstr
, expr
);
315 cursor
.insertText(rstr
);
318 return find(expr
, options
);
321 int CodeEditor::replaceAll( const QRegExp
&expr
, const QString
&replacement
, QTextDocument::FindFlags options
)
323 mSearchSelections
.clear();
324 updateExtraSelections();
326 if(expr
.isEmpty()) return 0;
328 int replacements
= 0;
329 bool caps
= expr
.patternSyntax() != QRegExp::FixedString
;
331 QTextDocument
*doc
= QPlainTextEdit::document();
332 QTextBlock block
= doc
->begin();
335 QTextCursor(doc
).beginEditBlock();
337 while (block
.isValid())
339 int blockPos
= block
.position();
341 while(findInBlock(doc
, block
, expr
, offset
, options
, cursor
))
343 QString rstr
= replacement
;
345 rstr
= resolvedReplacement(rstr
, expr
);
346 cursor
.insertText(rstr
);
348 offset
= cursor
.selectionEnd() - blockPos
;
350 block
= block
.next();
353 QTextCursor(doc
).endEditBlock();
358 QTextCursor
CodeEditor::currentRegion()
360 return regionAtCursor(textCursor());
364 QTextCursor
CodeEditor::regionAtCursor(QTextCursor c
)
366 QTextBlock
block(c
.block());
368 int positionInBlock
= c
.position() - block
.position();
374 // search unmatched opening bracket
375 TokenIterator it
= TokenIterator::leftOf( block
, positionInBlock
);
378 char chr
= it
->character
;
381 if(level
> topLevel
) {
386 else if(chr
== ')') {
393 // no unmatched opening bracket
394 return QTextCursor();
396 // match the found opening bracket
397 it
= TokenIterator::rightOf( block
, positionInBlock
);
400 char chr
= it
->character
;
415 if(start
.isValid() && end
.isValid())
417 // only care about brackets at beginning of a line
418 if(start
->positionInBlock
!= 0)
419 return QTextCursor();
421 // check whether the bracket makes part of an event
424 if (it
->type
== Token::SymbolArg
)
425 return QTextCursor();
428 if (it
.isValid() && it
->character
== ':')
429 return QTextCursor();
433 // ok, this is is a real top-level region
434 QTextCursor
c(QPlainTextEdit::document());
435 c
.setPosition(start
.position() + 1);
436 c
.setPosition(end
.position(), QTextCursor::KeepAnchor
);
440 return QTextCursor();
443 QTextCursor
CodeEditor::blockAtCursor(QTextCursor cursor
)
445 TokenIterator
it (cursor
.block(), cursor
.positionInBlock());
449 case Token::OpeningBracket
:
450 case Token::ClosingBracket
:
453 matchBracket(it
, match
);
455 if (match
.first
.isValid()) {
456 QTextCursor
selection(textDocument());
457 selection
.setPosition(match
.first
.position());
458 selection
.setPosition(match
.second
.position() + 1, QTextCursor::KeepAnchor
);
469 return QTextCursor();
473 void CodeEditor::showPosition( int pos
)
477 QTextDocument
*doc
= QPlainTextEdit::document();
480 int lineNumber
= doc
->findBlock(pos
).firstLineNumber();
481 verticalScrollBar()->setValue(lineNumber
);
483 QTextCursor
cursor(doc
);
484 cursor
.setPosition(pos
);
485 setTextCursor(cursor
);
488 QString
CodeEditor::symbolUnderCursor()
490 QTextCursor cursor
= textCursor();
491 if (!cursor
.hasSelection())
492 cursor
.select(QTextCursor::WordUnderCursor
);
493 return cursor
.selectedText();
496 void CodeEditor::clearSearchHighlighting()
498 mSearchSelections
.clear();
499 updateExtraSelections();
502 void CodeEditor::zoomIn(int steps
)
507 void CodeEditor::zoomOut(int steps
)
512 void CodeEditor::resetFontSize()
514 mDoc
->resetDefaultFont();
517 void CodeEditor::zoomFont(int steps
)
519 QFont currentFont
= mDoc
->defaultFont();
520 const int newSize
= currentFont
.pointSize() + steps
;
523 currentFont
.setPointSize(newSize
);
524 mDoc
->setDefaultFont(currentFont
);
527 void CodeEditor::applySettings( Settings::Manager
*settings
)
529 settings
->beginGroup("IDE/editor");
531 mSpaceIndent
= settings
->value("spaceIndent").toBool();
533 mBlinkDuration
= settings
->value("blinkDuration").toInt();
537 settings
->beginGroup("colors");
539 if (settings
->contains("text")) {
540 QTextCharFormat format
= settings
->value("text").value
<QTextCharFormat
>();
541 QBrush bg
= format
.background();
542 QBrush fg
= format
.foreground();
543 if (bg
.style() != Qt::NoBrush
)
544 palette
.setBrush(QPalette::Base
, bg
);
545 if (fg
.style() != Qt::NoBrush
)
546 palette
.setBrush(QPalette::Text
, fg
);
549 if (settings
->contains("lineNumbers")) {
551 QTextCharFormat format
= settings
->value("lineNumbers").value
<QTextCharFormat
>();
552 QBrush bg
= format
.background();
553 QBrush fg
= format
.foreground();
554 if (bg
.style() != Qt::NoBrush
)
555 palette
.setBrush(QPalette::Button
, bg
);
556 if (fg
.style() != Qt::NoBrush
)
557 palette
.setBrush(QPalette::ButtonText
, fg
);
558 mLineIndicator
->setPalette(lineNumPlt
);
561 mBracketHighlight
= settings
->value("matchingBrackets").value
<QTextCharFormat
>();
563 settings
->endGroup(); // colors
567 settings
->endGroup();
570 void CodeEditor::onDocumentFontChanged()
572 QFont font
= mDoc
->defaultFont();
576 void CodeEditor::deleteTrailingSpaces()
578 mDoc
->deleteTrailingSpaces();
581 bool CodeEditor::event( QEvent
*e
)
585 case QEvent::KeyPress
:
587 QKeyEvent
*ke
= static_cast<QKeyEvent
*>(e
);
601 return QPlainTextEdit::event(e
);
604 void CodeEditor::keyPressEvent( QKeyEvent
*e
)
611 // override to avoid entering a "soft" new line when certain modifier is held
612 textCursor().insertBlock();
613 ensureCursorVisible();
617 Qt::KeyboardModifiers
mods(e
->modifiers());
618 if (mods
&& mods
!= Qt::ShiftModifier
) {
619 QPlainTextEdit::keyPressEvent(e
);
623 QTextCursor::MoveMode mode
=
624 mods
& Qt::ShiftModifier
? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor
;
626 QTextCursor
c(textCursor());
627 QTextBlock
b(c
.block());
629 int pos
= indentedStartOfLine(b
);
632 if (c
.position() == pos
)
633 c
.movePosition(QTextCursor::StartOfLine
, mode
);
635 c
.setPosition(pos
, mode
);
642 case Qt::Key_Backtab
:
644 QTextCursor cursor
= textCursor();
645 cursor
.insertText("\t");
646 ensureCursorVisible();
651 QPlainTextEdit::keyPressEvent(e
);
657 case Qt::Key_BraceRight
:
658 case Qt::Key_BracketRight
:
659 case Qt::Key_ParenRight
:
666 mAutoCompleter
->keyPress(e
);
669 void CodeEditor::mouseReleaseEvent ( QMouseEvent
*e
)
671 // Prevent deselection of bracket match:
672 if(!mMouseBracketMatch
)
673 QPlainTextEdit::mouseReleaseEvent(e
);
675 mMouseBracketMatch
= false;
678 void CodeEditor::mouseDoubleClickEvent( QMouseEvent
* e
)
680 QTextCursor cursor
= cursorForPosition(e
->pos());
681 QTextCursor selection
= blockAtCursor(cursor
);
683 if (!selection
.isNull()) {
684 setTextCursor(selection
);
688 QPlainTextEdit::mouseDoubleClickEvent(e
);
691 void CodeEditor::mouseMoveEvent( QMouseEvent
*e
)
693 // Prevent initiating a text drag:
694 if(!mMouseBracketMatch
)
695 QPlainTextEdit::mouseMoveEvent(e
);
698 void CodeEditor::wheelEvent( QWheelEvent
* e
)
700 if (e
->modifiers() == Qt::ControlModifier
) {
708 QPlainTextEdit::wheelEvent(e
);
711 void CodeEditor::onOverlayChanged ( const QList
<QRectF
> & region
)
713 foreach(QRectF r
, region
)
715 viewport()->update(r
.toRect());
719 void CodeEditor::paintEvent( QPaintEvent
*e
)
721 QPlainTextEdit::paintEvent(e
);
723 QPainter
p(viewport());
724 mOverlay
->render(&p
, e
->rect(), e
->rect());
727 void CodeEditor::dragEnterEvent( QDragEnterEvent
* event
)
729 foreach (QUrl url
, event
->mimeData()->urls()) {
730 if (url
.scheme() == QString("file")) { // LATER: use isLocalFile
731 // LATER: check mime type ?
732 event
->acceptProposedAction();
738 void CodeEditor::dropEvent( QDropEvent
* event
)
740 foreach (QUrl url
, event
->mimeData()->urls()) {
741 if (url
.scheme() == QString("file")) // LATER: use isLocalFile
742 Main::documentManager()->open(url
.toLocalFile());
746 void CodeEditor::updateLayout()
748 setViewportMargins( mLineIndicator
->width(), 0, 0, 0 );
751 void CodeEditor::updateLineIndicator( QRect r
, int dy
)
754 mLineIndicator
->scroll(0, dy
);
756 mLineIndicator
->update(0, r
.y(), mLineIndicator
->width(), r
.height() );
759 void CodeEditor::matchBrackets()
761 mBracketSelections
.clear();
763 QTextCursor
cursor(textCursor());
764 QTextBlock
block( cursor
.block() );
765 int posInBlock
= cursor
.positionInBlock();
766 TokenIterator
it(block
);
767 while (it
.isValid() && it
.block() == block
)
769 const Token
& token
= *it
;
770 if (token
.positionInBlock
> posInBlock
) {
771 it
= TokenIterator();
774 (token
.positionInBlock
== posInBlock
&& token
.type
== Token::OpeningBracket
) ||
775 (token
.positionInBlock
== posInBlock
- 1 && token
.type
== Token::ClosingBracket
)
782 if( it
.isValid() && it
.block() == block
)
783 matchBracket( it
, match
);
785 if( match
.first
.isValid() && match
.second
.isValid() )
787 const Token
& tok1
= *match
.first
;
788 const Token
& tok2
= *match
.second
;
791 (tok1
.character
== '(' && tok2
.character
== ')')
792 || (tok1
.character
== '[' && tok2
.character
== ']')
793 || (tok1
.character
== '{' && tok2
.character
== '}')
795 QTextEdit::ExtraSelection selection
;
796 selection
.format
= mBracketHighlight
;
798 cursor
.setPosition(match
.first
.position());
799 cursor
.movePosition(QTextCursor::NextCharacter
, QTextCursor::KeepAnchor
);
800 selection
.cursor
= cursor
;
801 mBracketSelections
.append(selection
);
803 cursor
.setPosition(match
.second
.position());
804 cursor
.movePosition(QTextCursor::NextCharacter
, QTextCursor::KeepAnchor
);
805 selection
.cursor
= cursor
;
806 mBracketSelections
.append(selection
);
809 QTextEdit::ExtraSelection selection
;
810 selection
.format
.setBackground(Qt::red
);
811 cursor
.setPosition(match
.first
.position());
812 cursor
.setPosition(match
.second
.position()+1, QTextCursor::KeepAnchor
);
813 selection
.cursor
= cursor
;
814 mBracketSelections
.append(selection
);
818 updateExtraSelections();
821 void CodeEditor::matchBracket( const TokenIterator
& bracket
, BracketMatch
& match
)
823 TokenIterator
it(bracket
);
825 if(it
->type
== Token::OpeningBracket
)
829 while((++it
).isValid())
831 Token::Type type
= it
->type
;
832 if(type
== Token::ClosingBracket
)
834 else if(type
== Token::OpeningBracket
)
842 else if(it
->type
== Token::ClosingBracket
)
846 while((--it
).isValid())
848 Token::Type type
= it
->type
;
849 if(type
== Token::OpeningBracket
)
851 else if(type
== Token::ClosingBracket
)
861 int CodeEditor::indentedStartOfLine( const QTextBlock
&b
)
868 if (c
!= ' ' && c
!= '\t')
876 void CodeEditor::updateExtraSelections()
878 QList
<QTextEdit::ExtraSelection
> selections
;
879 selections
.append(mBracketSelections
);
880 selections
.append(mSearchSelections
);
881 setExtraSelections(selections
);
884 void CodeEditor::resizeEvent( QResizeEvent
*e
)
886 QPlainTextEdit::resizeEvent( e
);
888 QRect cr
= contentsRect();
889 mLineIndicator
->resize( mLineIndicator
->width(), cr
.height() );
892 void CodeEditor::paintLineIndicator( QPaintEvent
*e
)
894 QPalette
plt( mLineIndicator
->palette() );
895 QRect
r( e
->rect() );
896 QPainter
p( mLineIndicator
);
898 p
.fillRect( r
, plt
.color( QPalette::Button
) );
899 p
.setPen( plt
.color(QPalette::ButtonText
) );
900 p
.drawLine( r
.topRight(), r
.bottomRight() );
902 QTextDocument
*doc
= QPlainTextEdit::document();
903 QTextCursor
cursor(textCursor());
904 int selStartBlock
, selEndBlock
;
905 if (cursor
.hasSelection()) {
906 selStartBlock
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
907 selEndBlock
= doc
->findBlock(cursor
.selectionEnd()).blockNumber();
910 selStartBlock
= selEndBlock
= -1;
912 QTextBlock block
= firstVisibleBlock();
913 int blockNumber
= block
.blockNumber();
914 int top
= (int) blockBoundingGeometry(block
).translated(contentOffset()).top();
915 int bottom
= top
+ (int) blockBoundingRect(block
).height();
917 while (block
.isValid() && top
<= e
->rect().bottom()) {
918 if (block
.isVisible() && bottom
>= e
->rect().top()) {
921 QRect
numRect( 0, top
, mLineIndicator
->width() - 1, bottom
- top
);
923 int num
= blockNumber
;
924 if (num
>= selStartBlock
&& num
<= selEndBlock
) {
925 num
-= selStartBlock
;
927 p
.setBrush(plt
.color(QPalette::Highlight
));
929 p
.setPen(plt
.color(QPalette::HighlightedText
));
932 QString number
= QString::number(num
+ 1);
933 p
.drawText(0, top
, mLineIndicator
->width() - 4, bottom
- top
,
934 Qt::AlignRight
, number
);
939 block
= block
.next();
941 bottom
= top
+ (int) blockBoundingRect(block
).height();
946 void CodeEditor::indent()
948 indent(textCursor());
951 void CodeEditor::indent( const QTextCursor
& selection
)
953 QTextCursor
cursor(selection
);
955 cursor
.beginEditBlock();
957 QTextDocument
*doc
= QPlainTextEdit::document();
958 int startBlockNum
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
959 int endBlockNum
= cursor
.hasSelection() ?
960 doc
->findBlock(cursor
.selectionEnd()).blockNumber() : startBlockNum
;
965 QTextBlock block
= QPlainTextEdit::document()->begin();
966 while (block
.isValid())
973 int initialStackSize
= stack
.size();
975 TextBlockData
*data
= static_cast<TextBlockData
*>(block
.userData());
978 int count
= data
->tokens
.size();
979 for (int idx
= 0; idx
< count
; ++idx
)
981 const Token
& token
= data
->tokens
[idx
];
984 case Token::OpeningBracket
:
985 if (token
.character
!= '(' || stack
.size() || token
.positionInBlock
)
989 case Token::ClosingBracket
:
992 else if(!stack
.isEmpty()) {
994 if (stack
.top() <= 0)
1005 if(blockNum
>= startBlockNum
) {
1007 if (data
&& data
->tokens
.size() && data
->tokens
[0].type
== Token::ClosingBracket
)
1008 indentLevel
= stack
.size();
1010 indentLevel
= initialStackSize
;
1011 block
= indent(block
, indentLevel
);
1014 if(blockNum
== endBlockNum
)
1017 block
= block
.next();
1021 cursor
.endEditBlock();
1024 QString
CodeEditor::makeIndentationString(int level
)
1029 if ( mSpaceIndent
) {
1030 const int spaces
= mDoc
->indentWidth() * level
;
1031 QString
indentationString (spaces
, QChar(' '));
1032 return indentationString
;
1034 const int tabs
= level
;
1035 QString
indentationString (tabs
, QChar('\t'));
1036 return indentationString
;
1040 QTextBlock
CodeEditor::indent( const QTextBlock
& block
, int level
)
1042 QTextCursor
cursor(block
);
1043 cursor
.movePosition(QTextCursor::StartOfBlock
);
1044 cursor
.setPosition(cursor
.position() + indentedStartOfLine(block
), QTextCursor::KeepAnchor
);
1046 cursor
.insertText(makeIndentationString(level
));
1048 // modification has invalidated the block, so return a new one
1049 return cursor
.block();
1052 int CodeEditor::indentationLevel(const QTextCursor
& cursor
)
1054 QTextDocument
*doc
= QPlainTextEdit::document();
1055 int startBlockNum
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
1060 QTextBlock block
= QPlainTextEdit::document()->begin();
1061 while (block
.isValid()) {
1067 TextBlockData
*data
= static_cast<TextBlockData
*>(block
.userData());
1069 int count
= data
->tokens
.size();
1070 for (int idx
= 0; idx
< count
; ++idx
) {
1071 const Token
& token
= data
->tokens
[idx
];
1072 switch (token
.type
) {
1073 case Token::OpeningBracket
:
1074 if (token
.character
!= '(' || stack
.size() || token
.positionInBlock
)
1078 case Token::ClosingBracket
:
1081 else if(!stack
.isEmpty()) {
1083 if (stack
.top() <= 0)
1094 if (blockNum
== startBlockNum
)
1095 return stack
.size();
1097 block
= block
.next();
1104 void CodeEditor::triggerAutoCompletion()
1106 mAutoCompleter
->triggerCompletion();
1109 void CodeEditor::triggerMethodCallAid()
1111 mAutoCompleter
->triggerMethodCallAid();
1114 static bool isSingleLineComment(QTextBlock
const & block
)
1116 static QRegExp
commentRegex("^\\s*//.*");
1117 return commentRegex
.exactMatch(block
.text());
1120 static bool isSelectionComment(QString
const & text
)
1122 QString trimmed
= text
.trimmed();
1123 if ( trimmed
.startsWith(QString("/*")) && trimmed
.endsWith(QString("*/")) )
1129 void CodeEditor::toggleComment()
1131 QTextCursor cursor
= textCursor();
1133 if (cursor
.hasSelection())
1134 toggleCommentSelection();
1136 toggleCommentSingleLine();
1139 void CodeEditor::toggleCommentSingleLine()
1141 QTextCursor cursor
= textCursor();
1142 cursor
.beginEditBlock();
1144 toggleCommentSingleLine( cursor
);
1146 cursor
.endEditBlock();
1149 void CodeEditor::addSingleLineComment(QTextCursor cursor
, int indentation
)
1151 QTextBlock
currentBlock(cursor
.block());
1152 int blockIndentationLevel
= indentationLevel(cursor
);
1154 cursor
.movePosition(QTextCursor::StartOfBlock
);
1155 cursor
.setPosition(cursor
.position() + indentedStartOfLine(currentBlock
), QTextCursor::KeepAnchor
);
1157 QString commentString
= makeIndentationString(indentation
) + QString("// ")
1158 + makeIndentationString(blockIndentationLevel
- indentation
);
1160 cursor
.insertText(commentString
);
1162 cursor
.movePosition(QTextCursor::StartOfBlock
);
1165 void CodeEditor::removeSingleLineComment(QTextCursor cursor
)
1167 QTextBlock
currentBlock(cursor
.block());
1168 cursor
.movePosition(QTextCursor::StartOfBlock
);
1169 cursor
.setPosition(cursor
.position() + indentedStartOfLine(currentBlock
) + 2, QTextCursor::KeepAnchor
);
1171 if (!cursor
.selectedText().endsWith(QString("//")))
1172 cursor
.setPosition(cursor
.anchor() + indentedStartOfLine(currentBlock
), QTextCursor::KeepAnchor
);
1174 cursor
.insertText("");
1177 void CodeEditor::toggleCommentSingleLine(QTextCursor cursor
)
1179 QTextBlock
currentBlock(cursor
.block());
1181 cursor
.beginEditBlock();
1183 if (!isSingleLineComment(currentBlock
)) {
1184 int blockIndentation
= indentationLevel(cursor
);
1185 addSingleLineComment(cursor
, blockIndentation
);
1187 removeSingleLineComment(cursor
);
1189 cursor
.endEditBlock();
1193 static bool isBlockOnlySelection(QTextCursor
)
1198 void CodeEditor::toggleCommentSelection()
1200 QTextCursor cursor
= textCursor();
1201 cursor
.beginEditBlock();
1203 if (isBlockOnlySelection(cursor
)) {
1204 QTextCursor
selectionCursor(cursor
);
1205 selectionCursor
.setPosition(cursor
.selectionStart());
1207 QTextBlock currentBlock
= selectionCursor
.block();
1208 bool isComment
= isSingleLineComment(currentBlock
);
1209 int firstBlockIndentation
= isComment
? 0
1210 : indentationLevel(selectionCursor
);
1213 QTextCursor
blockCursor(currentBlock
);
1215 addSingleLineComment(blockCursor
, firstBlockIndentation
);
1217 removeSingleLineComment(blockCursor
);
1218 currentBlock
= currentBlock
.next();
1219 } while (currentBlock
.isValid() && currentBlock
.position() < cursor
.selectionEnd());
1221 QString selectionText
= cursor
.selectedText();
1222 QTextCursor
selectionCursor(cursor
);
1224 if (isSelectionComment(selectionText
)) {
1225 selectionText
= selectionText
.trimmed().remove(2);
1226 selectionText
.chop(2);
1227 selectionCursor
.insertText(selectionText
);
1229 selectionText
= QString("/* ") + selectionText
+ QString(" */");
1230 selectionCursor
.insertText(selectionText
);
1234 cursor
.endEditBlock();
1235 cursor
.beginEditBlock();
1236 indent(currentRegion());
1237 cursor
.endEditBlock();
1240 void CodeEditor::copyUpDown(bool up
)
1242 // directly taken from qtcreator
1243 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
1244 // GNU Lesser General Public License
1245 QTextCursor cursor
= textCursor();
1246 QTextCursor move
= cursor
;
1247 move
.beginEditBlock();
1249 bool hasSelection
= cursor
.hasSelection();
1252 move
.setPosition(cursor
.selectionStart());
1253 move
.movePosition(QTextCursor::StartOfBlock
);
1254 move
.setPosition(cursor
.selectionEnd(), QTextCursor::KeepAnchor
);
1255 move
.movePosition(move
.atBlockStart() ? QTextCursor::Left
: QTextCursor::EndOfBlock
,
1256 QTextCursor::KeepAnchor
);
1258 move
.movePosition(QTextCursor::StartOfBlock
);
1259 move
.movePosition(QTextCursor::EndOfBlock
, QTextCursor::KeepAnchor
);
1262 QString text
= move
.selectedText();
1265 move
.setPosition(cursor
.selectionStart());
1266 move
.movePosition(QTextCursor::StartOfBlock
);
1268 move
.movePosition(QTextCursor::Left
);
1270 move
.movePosition(QTextCursor::EndOfBlock
);
1271 if (move
.atBlockStart()) {
1272 move
.movePosition(QTextCursor::NextBlock
);
1274 move
.movePosition(QTextCursor::Left
);
1280 int start
= move
.position();
1281 move
.clearSelection();
1282 move
.insertText(text
);
1283 int end
= move
.position();
1285 move
.setPosition(start
);
1286 move
.setPosition(end
, QTextCursor::KeepAnchor
);
1288 move
.endEditBlock();
1289 indent(currentRegion());
1291 setTextCursor(move
);
1295 void CodeEditor::toggleOverwriteMode()
1297 setOverwriteMode(!overwriteMode());
1301 void CodeEditor::copyLineDown()
1306 void CodeEditor::copyLineUp()
1311 void CodeEditor::moveLineUpDown(bool up
)
1313 // directly taken from qtcreator
1314 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
1315 // GNU Lesser General Public License
1316 QTextCursor cursor
= textCursor();
1317 QTextCursor move
= cursor
;
1319 move
.setVisualNavigation(false); // this opens folded items instead of destroying them
1321 move
.beginEditBlock();
1323 bool hasSelection
= cursor
.hasSelection();
1325 if (cursor
.hasSelection()) {
1326 move
.setPosition(cursor
.selectionStart());
1327 move
.movePosition(QTextCursor::StartOfBlock
);
1328 move
.setPosition(cursor
.selectionEnd(), QTextCursor::KeepAnchor
);
1329 move
.movePosition(move
.atBlockStart() ? QTextCursor::Left
: QTextCursor::EndOfBlock
,
1330 QTextCursor::KeepAnchor
);
1332 move
.movePosition(QTextCursor::StartOfBlock
);
1333 move
.movePosition(QTextCursor::EndOfBlock
, QTextCursor::KeepAnchor
);
1335 QString text
= move
.selectedText();
1337 move
.movePosition(QTextCursor::Right
, QTextCursor::KeepAnchor
);
1338 move
.removeSelectedText();
1341 move
.movePosition(QTextCursor::PreviousBlock
);
1343 move
.movePosition(QTextCursor::Left
);
1345 move
.movePosition(QTextCursor::EndOfBlock
);
1346 if (move
.atBlockStart()) { // empty block
1347 move
.movePosition(QTextCursor::NextBlock
);
1349 move
.movePosition(QTextCursor::Left
);
1355 int start
= move
.position();
1356 move
.clearSelection();
1357 move
.insertText(text
);
1358 int end
= move
.position();
1361 move
.setPosition(start
);
1362 move
.setPosition(end
, QTextCursor::KeepAnchor
);
1365 move
.endEditBlock();
1366 indent(currentRegion());
1368 setTextCursor(move
);
1371 void CodeEditor::moveLineUp()
1373 moveLineUpDown(true);
1376 void CodeEditor::moveLineDown()
1378 moveLineUpDown(false);
1381 // taking nested brackets into account
1382 static TokenIterator
previousOpeningBracket(TokenIterator it
)
1385 while (it
.isValid()) {
1387 case Token::OpeningBracket
:
1393 case Token::ClosingBracket
:
1404 // taking nested brackets into account
1405 static TokenIterator
nextClosingBracket(TokenIterator it
)
1408 while (it
.isValid()) {
1410 case Token::ClosingBracket
:
1416 case Token::OpeningBracket
:
1428 void CodeEditor::gotoNextBlock()
1430 QTextCursor cursor
= textCursor();
1432 TokenIterator
startIt (cursor
.block(), cursor
.positionInBlock());
1433 TokenIterator previousBracket
;
1435 if (startIt
.type() != Token::OpeningBracket
) {
1436 startIt
= TokenIterator::leftOf(cursor
.block(), cursor
.positionInBlock());
1437 previousBracket
= previousOpeningBracket(startIt
);
1439 previousBracket
= startIt
;
1441 if (previousBracket
.isValid()) {
1442 TokenIterator nextBracket
= nextClosingBracket(previousBracket
.next());
1444 if (nextBracket
.isValid()) {
1445 setTextCursor(cursorAt(nextBracket
, 1));
1453 void CodeEditor::gotoPreviousBlock()
1455 QTextCursor cursor
= textCursor();
1457 TokenIterator startIt
;
1458 if (cursor
.positionInBlock())
1459 startIt
= TokenIterator(cursor
.block(), cursor
.positionInBlock() - 1);
1461 QTextBlock previousBlock
= cursor
.block().previous();
1462 startIt
= TokenIterator(previousBlock
, previousBlock
.length() - 1);
1465 TokenIterator nextBracket
;
1467 if (startIt
.type() != Token::ClosingBracket
) {
1468 startIt
= TokenIterator::rightOf(cursor
.block(), cursor
.positionInBlock());
1469 nextBracket
= nextClosingBracket(startIt
);
1471 nextBracket
= startIt
;
1473 if (nextBracket
.isValid()) {
1474 TokenIterator previousBracket
= previousOpeningBracket(nextBracket
.previous());
1476 if (previousBracket
.isValid()) {
1477 setTextCursor(cursorAt(previousBracket
));
1482 gotoPreviousRegion();
1485 void CodeEditor::gotoPreviousEmptyLine()
1487 gotoEmptyLineUpDown(true);
1490 void CodeEditor::gotoNextEmptyLine()
1492 gotoEmptyLineUpDown(false);
1495 void CodeEditor::gotoEmptyLineUpDown(bool up
)
1497 static const QRegExp
whiteSpaceLine("^\\s*$");
1499 const QTextCursor::MoveOperation direction
= up
? QTextCursor::PreviousBlock
1500 : QTextCursor::NextBlock
;
1502 QTextCursor cursor
= textCursor();
1503 cursor
.beginEditBlock();
1505 bool cursorMoved
= false;
1507 // find first non-whitespace line
1508 while ( cursor
.movePosition(direction
) ) {
1509 if ( !whiteSpaceLine
.exactMatch(cursor
.block().text()) )
1513 // find first whitespace line
1514 while ( cursor
.movePosition(direction
) ) {
1515 if ( whiteSpaceLine
.exactMatch(cursor
.block().text()) ) {
1516 setTextCursor(cursor
);
1523 const QTextCursor::MoveOperation startOrEnd
= up
? QTextCursor::Start
1526 cursor
.movePosition(startOrEnd
);
1527 setTextCursor(cursor
);
1530 cursor
.endEditBlock();
1534 void CodeEditor::selectCurrentRegion()
1536 QTextCursor selectedRegionCursor
= currentRegion();
1537 if (!selectedRegionCursor
.isNull() && selectedRegionCursor
.hasSelection())
1538 setTextCursor(selectedRegionCursor
);
1541 void CodeEditor::gotoNextRegion()
1543 QTextCursor cursor
= textCursor();
1544 cursor
.movePosition(QTextCursor::NextCharacter
);
1546 QTextCursor regionCursor
= regionAtCursor(cursor
);
1548 QTextCursor cursorBehindRegion
;
1549 if (!regionCursor
.isNull() && regionCursor
.hasSelection()) {
1550 cursorBehindRegion
= regionCursor
;
1551 cursorBehindRegion
.movePosition(QTextCursor::NextCharacter
);
1553 cursorBehindRegion
= textCursor();
1554 cursorBehindRegion
.movePosition(QTextCursor::NextCharacter
);
1557 TokenIterator it
= TokenIterator(cursorBehindRegion
.block(), cursorBehindRegion
.positionInBlock());
1558 if (!it
.isValid() || it
->type
== Token::OpeningBracket
)
1559 it
= TokenIterator::rightOf(cursorBehindRegion
.block(), cursorBehindRegion
.positionInBlock());
1561 while (it
.isValid()) {
1562 if ( (it
->type
== Token::OpeningBracket
) && (it
->character
== '(') &&
1563 (it
->positionInBlock
== 0) ) {
1564 setTextCursor( cursorAt(it
) );
1570 cursor
= textCursor();
1571 cursor
.movePosition(QTextCursor::End
);
1572 setTextCursor(cursor
);
1575 void CodeEditor::gotoPreviousRegion()
1577 QTextCursor cursor
= textCursor();
1579 QTextCursor regionCursor
= regionAtCursor(cursor
);
1581 QTextCursor cursorBeforeRegion
;
1582 if (!regionCursor
.isNull() && regionCursor
.hasSelection()) {
1583 cursorBeforeRegion
= regionCursor
;
1584 cursorBeforeRegion
.setPosition(cursorBeforeRegion
.anchor());
1585 cursorBeforeRegion
.movePosition(QTextCursor::PreviousCharacter
);
1587 cursorBeforeRegion
= textCursor();
1588 cursorBeforeRegion
.movePosition(QTextCursor::PreviousCharacter
);
1591 TokenIterator it
= TokenIterator(cursorBeforeRegion
.block(), cursorBeforeRegion
.positionInBlock());
1592 if (!it
.isValid() || it
->type
== Token::ClosingBracket
)
1593 it
= TokenIterator::leftOf(cursorBeforeRegion
.block(), cursorBeforeRegion
.positionInBlock());
1595 while (it
.isValid()) {
1596 if ( (it
->type
== Token::ClosingBracket
) && (it
->character
== ')') &&
1597 (it
->positionInBlock
== 0) ) {
1598 setTextCursor( cursorAt(it
) );
1604 cursor
= textCursor();
1605 cursor
.movePosition(QTextCursor::Start
);
1606 setTextCursor(cursor
);
1609 bool CodeEditor::openDocumentation()
1611 return Main::openDocumentation(symbolUnderCursor());
1614 void CodeEditor::openDefinition()
1616 Main::openDefinition(symbolUnderCursor(), this);
1619 void CodeEditor::lookupReferences()
1621 QString symbol
= symbolUnderCursor();
1622 if (symbol
.size() != 0)
1623 new SymbolReferenceRequest(symbol
, Main::scProcess(), this);
1626 void CodeEditor::hideMouseCursor()
1628 QCursor
* overrideCursor
= QApplication::overrideCursor();
1629 if (!overrideCursor
|| overrideCursor
->shape() != Qt::BlankCursor
)
1630 QApplication::setOverrideCursor( Qt::BlankCursor
);
1633 QTextCursor
CodeEditor::cursorAt(const TokenIterator it
, int offset
)
1635 Q_ASSERT(it
.isValid());
1637 QTextCursor
textCursor(textDocument());
1638 textCursor
.setPosition(it
.position() + offset
);
1643 } // namespace ScIDE