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>
38 #include <QGraphicsView>
42 GenericCodeEditor::GenericCodeEditor( Document
*doc
, QWidget
*parent
):
43 QPlainTextEdit( parent
),
48 setFrameShape( QFrame::NoFrame
);
50 mLineIndicator
= new LineIndicator(this);
51 mLineIndicator
->move( contentsRect().topLeft() );
53 mOverlay
= new QGraphicsScene(this);
55 QGraphicsView
*overlayView
= new QGraphicsView(mOverlay
, this);
56 overlayView
->setFrameShape( QFrame::NoFrame
);
57 overlayView
->setBackgroundBrush( Qt::NoBrush
);
58 overlayView
->setStyleSheet("background: transparent");
59 overlayView
->setFocusPolicy( Qt::NoFocus
);
60 overlayView
->setAttribute(Qt::WA_TransparentForMouseEvents
, true);
61 overlayView
->setSceneRect(QRectF(0,0,1,1));
62 overlayView
->setAlignment(Qt::AlignLeft
| Qt::AlignTop
);
64 mOverlayWidget
= overlayView
;
66 connect( mDoc
, SIGNAL(defaultFontChanged()), this, SLOT(onDocumentFontChanged()) );
68 connect( this, SIGNAL(blockCountChanged(int)),
69 mLineIndicator
, SLOT(setLineCount(int)) );
71 connect( mLineIndicator
, SIGNAL( widthChanged() ),
72 this, SLOT( updateLayout() ) );
74 connect( this, SIGNAL(updateRequest(QRect
,int)),
75 this, SLOT(updateLineIndicator(QRect
,int)) );
77 connect( this, SIGNAL(selectionChanged()),
78 mLineIndicator
, SLOT(update()) );
80 QTextDocument
*tdoc
= doc
->textDocument();
81 QPlainTextEdit::setDocument(tdoc
);
82 onDocumentFontChanged();
83 mLineIndicator
->setLineCount(blockCount());
86 bool GenericCodeEditor::showWhitespace()
88 QTextOption
options( textDocument()->defaultTextOption() );
89 return options
.flags().testFlag( QTextOption::ShowTabsAndSpaces
);
92 void GenericCodeEditor::setShowWhitespace(bool show
)
94 QTextDocument
*doc
= textDocument();
95 QTextOption
opt( doc
->defaultTextOption() );
97 opt
.setFlags( opt
.flags() | QTextOption::ShowTabsAndSpaces
);
99 opt
.setFlags( opt
.flags() & ~QTextOption::ShowTabsAndSpaces
);
100 doc
->setDefaultTextOption(opt
);
103 static bool findInBlock(QTextDocument
*doc
, const QTextBlock
&block
, const QRegExp
&expr
, int offset
,
104 QTextDocument::FindFlags options
, QTextCursor
&cursor
)
106 QString text
= block
.text();
107 if(options
& QTextDocument::FindBackward
)
108 text
.truncate(offset
);
109 text
.replace(QChar::Nbsp
, QLatin1Char(' '));
112 while (offset
>=0 && offset
<= text
.length()) {
113 idx
= (options
& QTextDocument::FindBackward
) ?
114 expr
.lastIndexIn(text
, offset
) : expr
.indexIn(text
, offset
);
118 if (options
& QTextDocument::FindWholeWords
) {
119 const int start
= idx
;
120 const int end
= start
+ expr
.matchedLength();
121 if ((start
!= 0 && text
.at(start
- 1).isLetterOrNumber())
122 || (end
!= text
.length() && text
.at(end
).isLetterOrNumber())) {
123 //if this is not a whole word, continue the search in the string
124 offset
= (options
& QTextDocument::FindBackward
) ? idx
-1 : end
+1;
129 //we have a hit, return the cursor for that.
136 cursor
= QTextCursor(doc
);
137 cursor
.setPosition(block
.position() + idx
);
138 cursor
.setPosition(cursor
.position() + expr
.matchedLength(), QTextCursor::KeepAnchor
);
142 bool GenericCodeEditor::find( const QRegExp
&expr
, QTextDocument::FindFlags options
)
144 // Although QTextDocument provides a find() method, we implement
145 // our own, because the former one is not adequate.
147 if(expr
.isEmpty()) return true;
149 bool backwards
= options
& QTextDocument::FindBackward
;
151 QTextCursor
c( textCursor() );
153 if (c
.hasSelection())
155 bool matching
= expr
.exactMatch(c
.selectedText());
157 if( backwards
== matching
)
158 pos
= c
.selectionStart();
160 pos
= c
.selectionEnd();
165 QTextDocument
*doc
= QPlainTextEdit::document();
166 QTextBlock startBlock
= doc
->findBlock(pos
);
167 int startBlockOffset
= pos
- startBlock
.position();
172 int blockOffset
= startBlockOffset
;
173 QTextBlock block
= startBlock
;
174 while (block
.isValid()) {
175 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
))
178 block
= block
.next();
183 block
= doc
->begin();
185 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
)
186 || block
== startBlock
)
188 block
= block
.next();
192 int blockOffset
= startBlockOffset
;
193 QTextBlock block
= startBlock
;
194 while (block
.isValid()) {
195 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
))
197 block
= block
.previous();
198 blockOffset
= block
.length() - 1;
204 blockOffset
= block
.length() - 1;
205 if (findInBlock(doc
, block
, expr
, blockOffset
, options
, cursor
)
206 || block
== startBlock
)
208 block
= block
.previous();
213 if(!cursor
.isNull()) {
214 setTextCursor(cursor
);
221 int GenericCodeEditor::findAll( const QRegExp
&expr
, QTextDocument::FindFlags options
)
223 mSearchSelections
.clear();
226 this->updateExtraSelections();
230 QTextEdit::ExtraSelection selection
;
231 selection
.format
.setBackground(Qt::darkYellow
);
233 QTextDocument
*doc
= QPlainTextEdit::document();
234 QTextBlock block
= doc
->begin();
237 while (block
.isValid()) {
238 int blockPos
= block
.position();
240 while(findInBlock(doc
, block
, expr
, offset
, options
, cursor
))
242 offset
= cursor
.selectionEnd() - blockPos
;
243 selection
.cursor
= cursor
;
244 mSearchSelections
.append(selection
);
246 block
= block
.next();
249 this->updateExtraSelections();
251 return mSearchSelections
.count();
254 //#define CSTR(QSTR) QSTR.toStdString().c_str()
256 static QString
resolvedReplacement( const QString
&replacement
, const QRegExp
&expr
)
259 static const QRegExp
rexpr("(\\\\\\\\)|(\\\\[0-9]+)");
260 QString
str( replacement
);
262 while(i
< str
.size() && ((i
= rexpr
.indexIn(str
, i
)) != -1))
264 int len
= rexpr
.matchedLength();
265 if(rexpr
.pos(1) != -1)
267 //qDebug("%i (%s): escape", i, CSTR(rexpr.cap(1)));
268 str
.replace(i
, len
, "\\");
271 else if(rexpr
.pos(2) != -1)
273 QString num_str
= rexpr
.cap(2);
274 num_str
.remove(0, 1);
275 int num
= num_str
.toInt();
276 //qDebug("%i (%s): backref = %i", i, CSTR(rexpr.cap(2)), num);
277 if(num
<= expr
.captureCount())
279 QString cap
= expr
.cap(num
);
280 //qDebug("resolving ref to: %s", CSTR(cap));
281 str
.replace(i
, len
, cap
);
286 //qDebug("ref out of range", i, num);
292 //qDebug("%i (%s): unknown match", i, CSTR(rexpr.cap(0)));
295 //qDebug(">> [%s] %i", CSTR(str), i);
301 bool GenericCodeEditor::replace( const QRegExp
&expr
, const QString
&replacement
, QTextDocument::FindFlags options
)
303 if(expr
.isEmpty()) return true;
305 QTextCursor cursor
= textCursor();
306 if (cursor
.hasSelection() && expr
.exactMatch(cursor
.selectedText()))
308 QString rstr
= replacement
;
309 if(expr
.patternSyntax() != QRegExp::FixedString
)
310 rstr
= resolvedReplacement(rstr
, expr
);
311 cursor
.insertText(rstr
);
314 return find(expr
, options
);
317 int GenericCodeEditor::replaceAll( const QRegExp
&expr
, const QString
&replacement
, QTextDocument::FindFlags options
)
319 mSearchSelections
.clear();
320 updateExtraSelections();
322 if(expr
.isEmpty()) return 0;
324 int replacements
= 0;
325 bool caps
= expr
.patternSyntax() != QRegExp::FixedString
;
327 QTextDocument
*doc
= QPlainTextEdit::document();
328 QTextBlock block
= doc
->begin();
331 QTextCursor(doc
).beginEditBlock();
333 while (block
.isValid())
335 int blockPos
= block
.position();
337 while(findInBlock(doc
, block
, expr
, offset
, options
, cursor
))
339 QString rstr
= replacement
;
341 rstr
= resolvedReplacement(rstr
, expr
);
342 cursor
.insertText(rstr
);
344 offset
= cursor
.selectionEnd() - blockPos
;
346 block
= block
.next();
349 QTextCursor(doc
).endEditBlock();
354 void GenericCodeEditor::showPosition( int pos
, int selectionLength
)
358 QTextDocument
*doc
= QPlainTextEdit::document();
361 int lineNumber
= doc
->findBlock(pos
).firstLineNumber();
362 verticalScrollBar()->setValue(lineNumber
);
364 QTextCursor
cursor(doc
);
365 cursor
.setPosition(pos
);
367 cursor
.setPosition(pos
+ selectionLength
, QTextCursor::KeepAnchor
);
369 setTextCursor(cursor
);
372 QString
GenericCodeEditor::symbolUnderCursor()
374 QTextCursor cursor
= textCursor();
375 if (!cursor
.hasSelection())
376 cursor
.select(QTextCursor::WordUnderCursor
);
377 return cursor
.selectedText();
380 void GenericCodeEditor::keyPressEvent(QKeyEvent
* e
)
384 QTextCursor
cursor( textCursor() );
389 // override to avoid entering a "soft" new line when certain modifier is held
390 cursor
.insertBlock();
394 QPlainTextEdit::keyPressEvent(e
);
400 case Qt::Key_Backspace
:
401 cursor
.setVerticalMovementX(-1);
402 setTextCursor( cursor
);
403 ensureCursorVisible();
409 void GenericCodeEditor::wheelEvent( QWheelEvent
* e
)
411 if (e
->modifiers() == Qt::ControlModifier
) {
419 QPlainTextEdit::wheelEvent(e
);
422 void GenericCodeEditor::dragEnterEvent( QDragEnterEvent
* event
)
424 const QMimeData
* data
= event
->mimeData();
425 if (data
->hasUrls()) {
426 // Propagate event to parent.
427 // URL drops are ultimately handled by MainWindow.
432 QPlainTextEdit::dragEnterEvent(event
);
435 void GenericCodeEditor::clearSearchHighlighting()
437 mSearchSelections
.clear();
438 this->updateExtraSelections();
441 void GenericCodeEditor::zoomIn(int steps
)
446 void GenericCodeEditor::zoomOut(int steps
)
451 void GenericCodeEditor::resetFontSize()
453 mDoc
->resetDefaultFont();
456 void GenericCodeEditor::zoomFont(int steps
)
458 QFont currentFont
= mDoc
->defaultFont();
459 const int newSize
= currentFont
.pointSize() + steps
;
462 currentFont
.setPointSize(newSize
);
463 mDoc
->setDefaultFont(currentFont
);
466 void GenericCodeEditor::onDocumentFontChanged()
468 QFont font
= mDoc
->defaultFont();
472 void GenericCodeEditor::updateLayout()
474 setViewportMargins( mLineIndicator
->width(), 0, 0, 0 );
475 mOverlayWidget
->setGeometry( viewport()->geometry() );
478 void GenericCodeEditor::updateLineIndicator( QRect r
, int dy
)
481 mLineIndicator
->scroll(0, dy
);
483 mLineIndicator
->update(0, r
.y(), mLineIndicator
->width(), r
.height() );
486 void GenericCodeEditor::updateExtraSelections()
488 QList
<QTextEdit::ExtraSelection
> selections
;
489 selections
.append(mSearchSelections
);
490 setExtraSelections(selections
);
493 void GenericCodeEditor::resizeEvent( QResizeEvent
*e
)
495 QPlainTextEdit::resizeEvent( e
);
497 QRect cr
= contentsRect();
498 mLineIndicator
->resize( mLineIndicator
->width(), cr
.height() );
500 mOverlayWidget
->setGeometry( viewport()->geometry() );
503 void GenericCodeEditor::paintLineIndicator( QPaintEvent
*e
)
505 QPalette
plt( mLineIndicator
->palette() );
506 QRect
r( e
->rect() );
507 QPainter
p( mLineIndicator
);
509 p
.fillRect( r
, plt
.color( QPalette::Button
) );
510 p
.setPen( plt
.color(QPalette::ButtonText
) );
511 p
.drawLine( r
.topRight(), r
.bottomRight() );
513 QTextDocument
*doc
= QPlainTextEdit::document();
514 QTextCursor
cursor(textCursor());
515 int selStartBlock
, selEndBlock
;
516 if (cursor
.hasSelection()) {
517 selStartBlock
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
518 selEndBlock
= doc
->findBlock(cursor
.selectionEnd()).blockNumber();
521 selStartBlock
= selEndBlock
= -1;
523 QTextBlock block
= firstVisibleBlock();
524 int blockNumber
= block
.blockNumber();
525 int top
= (int) blockBoundingGeometry(block
).translated(contentOffset()).top();
526 int bottom
= top
+ (int) blockBoundingRect(block
).height();
528 while (block
.isValid() && top
<= e
->rect().bottom()) {
529 if (block
.isVisible() && bottom
>= e
->rect().top()) {
532 QRect
numRect( 0, top
, mLineIndicator
->width() - 1, bottom
- top
);
534 int num
= blockNumber
;
535 if (num
>= selStartBlock
&& num
<= selEndBlock
) {
536 num
-= selStartBlock
;
538 p
.setBrush(plt
.color(QPalette::Highlight
));
540 p
.setPen(plt
.color(QPalette::HighlightedText
));
543 QString number
= QString::number(num
+ 1);
544 p
.drawText(0, top
, mLineIndicator
->width() - 4, bottom
- top
,
545 Qt::AlignRight
, number
);
550 block
= block
.next();
552 bottom
= top
+ (int) blockBoundingRect(block
).height();
557 void GenericCodeEditor::copyUpDown(bool up
)
559 // directly taken from qtcreator
560 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
561 // GNU Lesser General Public License
562 QTextCursor cursor
= textCursor();
563 QTextCursor move
= cursor
;
564 move
.beginEditBlock();
566 bool hasSelection
= cursor
.hasSelection();
569 move
.setPosition(cursor
.selectionStart());
570 move
.movePosition(QTextCursor::StartOfBlock
);
571 move
.setPosition(cursor
.selectionEnd(), QTextCursor::KeepAnchor
);
572 move
.movePosition(move
.atBlockStart() ? QTextCursor::Left
: QTextCursor::EndOfBlock
,
573 QTextCursor::KeepAnchor
);
575 move
.movePosition(QTextCursor::StartOfBlock
);
576 move
.movePosition(QTextCursor::EndOfBlock
, QTextCursor::KeepAnchor
);
579 QString text
= move
.selectedText();
582 move
.setPosition(cursor
.selectionStart());
583 move
.movePosition(QTextCursor::StartOfBlock
);
585 move
.movePosition(QTextCursor::Left
);
587 move
.movePosition(QTextCursor::EndOfBlock
);
588 if (move
.atBlockStart()) {
589 move
.movePosition(QTextCursor::NextBlock
);
591 move
.movePosition(QTextCursor::Left
);
597 int start
= move
.position();
598 move
.clearSelection();
599 move
.insertText(text
);
600 int end
= move
.position();
602 move
.setPosition(start
);
603 move
.setPosition(end
, QTextCursor::KeepAnchor
);
611 void GenericCodeEditor::toggleOverwriteMode()
613 setOverwriteMode(!overwriteMode());
617 void GenericCodeEditor::copyLineDown()
622 void GenericCodeEditor::copyLineUp()
627 void GenericCodeEditor::moveLineUpDown(bool up
)
629 // directly taken from qtcreator
630 // Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
631 // GNU Lesser General Public License
632 QTextCursor cursor
= textCursor();
633 QTextCursor move
= cursor
;
635 move
.setVisualNavigation(false); // this opens folded items instead of destroying them
637 move
.beginEditBlock();
639 bool hasSelection
= cursor
.hasSelection();
641 if (cursor
.hasSelection()) {
642 move
.setPosition(cursor
.selectionStart());
643 move
.movePosition(QTextCursor::StartOfBlock
);
644 move
.setPosition(cursor
.selectionEnd(), QTextCursor::KeepAnchor
);
645 move
.movePosition(move
.atBlockStart() ? QTextCursor::Left
: QTextCursor::EndOfBlock
,
646 QTextCursor::KeepAnchor
);
648 move
.movePosition(QTextCursor::StartOfBlock
);
649 move
.movePosition(QTextCursor::EndOfBlock
, QTextCursor::KeepAnchor
);
651 QString text
= move
.selectedText();
653 move
.movePosition(QTextCursor::Right
, QTextCursor::KeepAnchor
);
654 move
.removeSelectedText();
657 move
.movePosition(QTextCursor::PreviousBlock
);
659 move
.movePosition(QTextCursor::Left
);
661 move
.movePosition(QTextCursor::EndOfBlock
);
662 if (move
.atBlockStart()) { // empty block
663 move
.movePosition(QTextCursor::NextBlock
);
665 move
.movePosition(QTextCursor::Left
);
671 int start
= move
.position();
672 move
.clearSelection();
673 move
.insertText(text
);
674 int end
= move
.position();
677 move
.setPosition(start
);
678 move
.setPosition(end
, QTextCursor::KeepAnchor
);
686 void GenericCodeEditor::moveLineUp()
688 moveLineUpDown(true);
691 void GenericCodeEditor::moveLineDown()
693 moveLineUpDown(false);
696 void GenericCodeEditor::gotoPreviousEmptyLine()
698 gotoEmptyLineUpDown(true);
701 void GenericCodeEditor::gotoNextEmptyLine()
703 gotoEmptyLineUpDown(false);
706 void GenericCodeEditor::gotoEmptyLineUpDown(bool up
)
708 static const QRegExp
whiteSpaceLine("^\\s*$");
710 const QTextCursor::MoveOperation direction
= up
? QTextCursor::PreviousBlock
711 : QTextCursor::NextBlock
;
713 QTextCursor cursor
= textCursor();
714 cursor
.beginEditBlock();
716 bool cursorMoved
= false;
718 // find first non-whitespace line
719 while ( cursor
.movePosition(direction
) ) {
720 if ( !whiteSpaceLine
.exactMatch(cursor
.block().text()) )
724 // find first whitespace line
725 while ( cursor
.movePosition(direction
) ) {
726 if ( whiteSpaceLine
.exactMatch(cursor
.block().text()) ) {
727 setTextCursor(cursor
);
734 const QTextCursor::MoveOperation startOrEnd
= up
? QTextCursor::Start
737 cursor
.movePosition(startOrEnd
);
738 setTextCursor(cursor
);
741 cursor
.endEditBlock();
744 void GenericCodeEditor::hideMouseCursor()
746 QCursor
* overrideCursor
= QApplication::overrideCursor();
747 if (!overrideCursor
|| overrideCursor
->shape() != Qt::BlankCursor
)
748 QApplication::setOverrideCursor( Qt::BlankCursor
);
751 inline static bool bracketDefinesRegion( const TokenIterator
& it
)
753 Q_ASSERT(it
.isValid());
754 bool result
= it
->positionInBlock
== 0;
755 result
= result
&& static_cast<TextBlockData
*>(it
.block().userData())->tokens
.size() == 1;
759 CodeEditor::CodeEditor( Document
*doc
, QWidget
*parent
) :
760 GenericCodeEditor( doc
, parent
),
763 mMouseBracketMatch(false),
764 mAutoCompleter( new AutoCompleter(this) )
768 setFrameShape( QFrame::NoFrame
);
770 mLineIndicator
->move( contentsRect().topLeft() );
772 connect( this, SIGNAL(cursorPositionChanged()),
773 this, SLOT(matchBrackets()) );
775 connect( Main::instance(), SIGNAL(applySettingsRequest(Settings::Manager
*)),
776 this, SLOT(applySettings(Settings::Manager
*)) );
778 QTextDocument
*tdoc
= doc
->textDocument();
779 mAutoCompleter
->documentChanged(tdoc
);
780 mLineIndicator
->setLineCount(blockCount());
782 applySettings(Main::settings());
785 void CodeEditor::applySettings( Settings::Manager
*settings
)
787 settings
->beginGroup("IDE/editor");
789 mSpaceIndent
= settings
->value("spaceIndent").toBool();
791 mBlinkDuration
= settings
->value("blinkDuration").toInt();
795 settings
->beginGroup("colors");
797 if (settings
->contains("text")) {
798 QTextCharFormat format
= settings
->value("text").value
<QTextCharFormat
>();
799 QBrush bg
= format
.background();
800 QBrush fg
= format
.foreground();
801 if (bg
.style() != Qt::NoBrush
)
802 palette
.setBrush(QPalette::Base
, bg
);
803 if (fg
.style() != Qt::NoBrush
)
804 palette
.setBrush(QPalette::Text
, fg
);
807 if (settings
->contains("lineNumbers")) {
809 QTextCharFormat format
= settings
->value("lineNumbers").value
<QTextCharFormat
>();
810 QBrush bg
= format
.background();
811 QBrush fg
= format
.foreground();
812 if (bg
.style() != Qt::NoBrush
)
813 palette
.setBrush(QPalette::Button
, bg
);
814 if (fg
.style() != Qt::NoBrush
)
815 palette
.setBrush(QPalette::ButtonText
, fg
);
816 mLineIndicator
->setPalette(lineNumPlt
);
819 mBracketHighlight
= settings
->value("matchingBrackets").value
<QTextCharFormat
>();
821 settings
->endGroup(); // colors
825 settings
->endGroup();
828 bool CodeEditor::event( QEvent
*e
)
832 case QEvent::KeyPress
:
834 QKeyEvent
*ke
= static_cast<QKeyEvent
*>(e
);
848 return QPlainTextEdit::event(e
);
851 void CodeEditor::keyPressEvent( QKeyEvent
*e
)
856 Qt::KeyboardModifiers
mods(e
->modifiers());
857 if (mods
&& mods
!= Qt::ShiftModifier
) {
858 GenericCodeEditor::keyPressEvent(e
);
864 QTextCursor::MoveMode mode
=
865 mods
& Qt::ShiftModifier
? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor
;
867 QTextCursor
c(textCursor());
868 QTextBlock
b(c
.block());
870 int pos
= indentedStartOfLine(b
);
873 if (c
.position() == pos
)
874 c
.movePosition(QTextCursor::StartOfLine
, mode
);
876 c
.setPosition(pos
, mode
);
883 case Qt::Key_Backtab
:
886 QTextCursor cursor
= textCursor();
887 cursor
.insertText("\t");
888 ensureCursorVisible();
898 case Qt::Key_BraceRight
:
899 case Qt::Key_BracketRight
:
900 case Qt::Key_ParenRight
: {
901 // Wrap superclass' implementation into an edit block,
902 // so it can be joined with indentation later:
904 QTextCursor cursor
= textCursor();
906 cursor
.beginEditBlock();
907 GenericCodeEditor::keyPressEvent(e
);
908 cursor
.endEditBlock();
910 cursor
.joinPreviousEditBlock();
912 cursor
.endEditBlock();
914 cursor
.setVerticalMovementX(-1);
915 setTextCursor(cursor
);
920 GenericCodeEditor::keyPressEvent(e
);
923 mAutoCompleter
->keyPress(e
);
926 void CodeEditor::mouseReleaseEvent ( QMouseEvent
*e
)
928 // Prevent deselection of bracket match:
929 if(!mMouseBracketMatch
)
930 GenericCodeEditor::mouseReleaseEvent(e
);
932 mMouseBracketMatch
= false;
935 void CodeEditor::mouseDoubleClickEvent( QMouseEvent
* e
)
937 QTextCursor cursor
= cursorForPosition(e
->pos());
938 QTextCursor selection
= blockAtCursor(cursor
);
940 if (!selection
.isNull()) {
941 setTextCursor(selection
);
945 GenericCodeEditor::mouseDoubleClickEvent(e
);
948 void CodeEditor::mouseMoveEvent( QMouseEvent
*e
)
950 // Prevent initiating a text drag:
951 if(!mMouseBracketMatch
)
952 GenericCodeEditor::mouseMoveEvent(e
);
955 void CodeEditor::matchBrackets()
957 mBracketSelections
.clear();
959 QTextCursor
cursor(textCursor());
960 QTextBlock
block( cursor
.block() );
961 int posInBlock
= cursor
.positionInBlock();
962 TokenIterator
it(block
);
963 while (it
.isValid() && it
.block() == block
)
965 const Token
& token
= *it
;
966 if (token
.positionInBlock
> posInBlock
) {
967 it
= TokenIterator();
970 (token
.positionInBlock
== posInBlock
&& token
.type
== Token::OpeningBracket
) ||
971 (token
.positionInBlock
== posInBlock
- 1 && token
.type
== Token::ClosingBracket
)
978 if( it
.isValid() && it
.block() == block
)
979 matchBracket( it
, match
);
981 if( match
.first
.isValid() && match
.second
.isValid() )
983 const Token
& tok1
= *match
.first
;
984 const Token
& tok2
= *match
.second
;
987 (tok1
.character
== '(' && tok2
.character
== ')')
988 || (tok1
.character
== '[' && tok2
.character
== ']')
989 || (tok1
.character
== '{' && tok2
.character
== '}')
991 QTextEdit::ExtraSelection selection
;
992 selection
.format
= mBracketHighlight
;
994 cursor
.setPosition(match
.first
.position());
995 cursor
.movePosition(QTextCursor::NextCharacter
, QTextCursor::KeepAnchor
);
996 selection
.cursor
= cursor
;
997 mBracketSelections
.append(selection
);
999 cursor
.setPosition(match
.second
.position());
1000 cursor
.movePosition(QTextCursor::NextCharacter
, QTextCursor::KeepAnchor
);
1001 selection
.cursor
= cursor
;
1002 mBracketSelections
.append(selection
);
1005 QTextEdit::ExtraSelection selection
;
1006 selection
.format
.setBackground(Qt::red
);
1007 cursor
.setPosition(match
.first
.position());
1008 cursor
.setPosition(match
.second
.position()+1, QTextCursor::KeepAnchor
);
1009 selection
.cursor
= cursor
;
1010 mBracketSelections
.append(selection
);
1014 updateExtraSelections();
1017 void CodeEditor::matchBracket( const TokenIterator
& bracket
, BracketMatch
& match
)
1019 TokenIterator
it(bracket
);
1021 if(it
->type
== Token::OpeningBracket
)
1025 while((++it
).isValid())
1027 Token::Type type
= it
->type
;
1028 if(type
== Token::ClosingBracket
)
1030 else if(type
== Token::OpeningBracket
)
1038 else if(it
->type
== Token::ClosingBracket
)
1042 while((--it
).isValid())
1044 Token::Type type
= it
->type
;
1045 if(type
== Token::OpeningBracket
)
1047 else if(type
== Token::ClosingBracket
)
1057 int CodeEditor::indentedStartOfLine( const QTextBlock
&b
)
1059 QString
t(b
.text());
1064 if (c
!= ' ' && c
!= '\t')
1072 void CodeEditor::updateExtraSelections()
1074 QList
<QTextEdit::ExtraSelection
> selections
;
1075 selections
.append(mBracketSelections
);
1076 selections
.append(mSearchSelections
);
1077 setExtraSelections(selections
);
1080 void CodeEditor::indentCurrentRegion()
1082 indent(currentRegion());
1085 void CodeEditor::indent()
1087 indent(textCursor());
1090 void CodeEditor::indent( const QTextCursor
& selection
)
1092 if (selection
.isNull())
1095 QTextCursor
cursor(selection
);
1097 cursor
.beginEditBlock();
1099 QTextDocument
*doc
= QPlainTextEdit::document();
1100 int startBlockNum
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
1101 int endBlockNum
= cursor
.hasSelection() ?
1102 doc
->findBlock(cursor
.selectionEnd()).blockNumber() : startBlockNum
;
1107 QTextBlock block
= QPlainTextEdit::document()->begin();
1108 while (block
.isValid())
1115 int initialStackSize
= stack
.size();
1117 TextBlockData
*data
= static_cast<TextBlockData
*>(block
.userData());
1120 int count
= data
->tokens
.size();
1121 for (int idx
= 0; idx
< count
; ++idx
)
1123 const Token
& token
= data
->tokens
[idx
];
1126 case Token::OpeningBracket
:
1127 if (token
.character
!= '(' || stack
.size() || token
.positionInBlock
)
1131 case Token::ClosingBracket
:
1134 else if(!stack
.isEmpty()) {
1136 if (stack
.top() <= 0)
1147 if(blockNum
>= startBlockNum
) {
1149 if (data
&& data
->tokens
.size() && data
->tokens
[0].type
== Token::ClosingBracket
)
1150 indentLevel
= stack
.size();
1152 indentLevel
= initialStackSize
;
1153 block
= indent(block
, indentLevel
);
1156 if(blockNum
== endBlockNum
)
1159 block
= block
.next();
1163 cursor
.endEditBlock();
1166 QString
CodeEditor::makeIndentationString(int level
)
1171 if ( mSpaceIndent
) {
1172 const int spaces
= mDoc
->indentWidth() * level
;
1173 QString
indentationString (spaces
, QChar(' '));
1174 return indentationString
;
1176 const int tabs
= level
;
1177 QString
indentationString (tabs
, QChar('\t'));
1178 return indentationString
;
1182 QTextBlock
CodeEditor::indent( const QTextBlock
& block
, int level
)
1184 QTextCursor
cursor(block
);
1185 cursor
.movePosition(QTextCursor::StartOfBlock
);
1186 cursor
.setPosition(cursor
.position() + indentedStartOfLine(block
), QTextCursor::KeepAnchor
);
1188 cursor
.insertText(makeIndentationString(level
));
1190 // modification has invalidated the block, so return a new one
1191 return cursor
.block();
1194 int CodeEditor::indentationLevel(const QTextCursor
& cursor
)
1196 QTextDocument
*doc
= QPlainTextEdit::document();
1197 int startBlockNum
= doc
->findBlock(cursor
.selectionStart()).blockNumber();
1202 QTextBlock block
= QPlainTextEdit::document()->begin();
1203 while (block
.isValid()) {
1209 TextBlockData
*data
= static_cast<TextBlockData
*>(block
.userData());
1211 int count
= data
->tokens
.size();
1212 for (int idx
= 0; idx
< count
; ++idx
) {
1213 const Token
& token
= data
->tokens
[idx
];
1214 switch (token
.type
) {
1215 case Token::OpeningBracket
:
1216 if (token
.character
!= '(' || stack
.size() || token
.positionInBlock
)
1220 case Token::ClosingBracket
:
1223 else if(!stack
.isEmpty()) {
1225 if (stack
.top() <= 0)
1236 if (blockNum
== startBlockNum
)
1237 return stack
.size();
1239 block
= block
.next();
1246 void CodeEditor::triggerAutoCompletion()
1248 mAutoCompleter
->triggerCompletion();
1251 void CodeEditor::triggerMethodCallAid()
1253 mAutoCompleter
->triggerMethodCallAid();
1256 static bool isSingleLineComment(QTextBlock
const & block
)
1258 static QRegExp
commentRegex("^\\s*//.*");
1259 return commentRegex
.exactMatch(block
.text());
1262 static bool isSelectionComment(QString
const & text
)
1264 QString trimmed
= text
.trimmed();
1265 if ( trimmed
.startsWith(QString("/*")) && trimmed
.endsWith(QString("*/")) )
1271 void CodeEditor::toggleComment()
1273 QTextCursor cursor
= textCursor();
1275 if (cursor
.hasSelection())
1276 toggleCommentSelection();
1278 toggleCommentSingleLine();
1281 void CodeEditor::toggleCommentSingleLine()
1283 QTextCursor cursor
= textCursor();
1284 cursor
.beginEditBlock();
1286 toggleCommentSingleLine( cursor
);
1288 cursor
.endEditBlock();
1291 void CodeEditor::addSingleLineComment(QTextCursor cursor
, int indentation
)
1293 QTextBlock
currentBlock(cursor
.block());
1294 int blockIndentationLevel
= indentationLevel(cursor
);
1296 cursor
.movePosition(QTextCursor::StartOfBlock
);
1297 cursor
.setPosition(cursor
.position() + indentedStartOfLine(currentBlock
), QTextCursor::KeepAnchor
);
1299 QString commentString
= makeIndentationString(indentation
) + QString("// ")
1300 + makeIndentationString(blockIndentationLevel
- indentation
);
1302 cursor
.insertText(commentString
);
1304 cursor
.movePosition(QTextCursor::StartOfBlock
);
1307 void CodeEditor::removeSingleLineComment(QTextCursor cursor
)
1309 QTextBlock
currentBlock(cursor
.block());
1310 cursor
.movePosition(QTextCursor::StartOfBlock
);
1311 cursor
.setPosition(cursor
.position() + indentedStartOfLine(currentBlock
) + 2, QTextCursor::KeepAnchor
);
1313 if (!cursor
.selectedText().endsWith(QString("//")))
1314 cursor
.setPosition(cursor
.anchor() + indentedStartOfLine(currentBlock
), QTextCursor::KeepAnchor
);
1316 cursor
.insertText("");
1319 void CodeEditor::toggleCommentSingleLine(QTextCursor cursor
)
1321 QTextBlock
currentBlock(cursor
.block());
1323 cursor
.beginEditBlock();
1325 if (!isSingleLineComment(currentBlock
)) {
1326 int blockIndentation
= indentationLevel(cursor
);
1327 addSingleLineComment(cursor
, blockIndentation
);
1329 removeSingleLineComment(cursor
);
1331 cursor
.endEditBlock();
1335 static bool isBlockOnlySelection(QTextCursor cursor
)
1337 Q_ASSERT(cursor
.hasSelection());
1339 QTextCursor
begin(cursor
);
1340 begin
.setPosition(begin
.anchor());
1342 if (begin
.atBlockStart() && (cursor
.atBlockStart() || cursor
.atBlockEnd()))
1348 void CodeEditor::toggleCommentSelection()
1350 QTextCursor cursor
= textCursor();
1351 cursor
.beginEditBlock();
1353 if (isBlockOnlySelection(cursor
)) {
1354 QTextCursor
selectionCursor(cursor
);
1355 selectionCursor
.setPosition(cursor
.selectionStart());
1357 QTextBlock currentBlock
= selectionCursor
.block();
1358 bool isComment
= isSingleLineComment(currentBlock
);
1359 int firstBlockIndentation
= isComment
? 0
1360 : indentationLevel(selectionCursor
);
1363 QTextCursor
blockCursor(currentBlock
);
1365 addSingleLineComment(blockCursor
, firstBlockIndentation
);
1367 removeSingleLineComment(blockCursor
);
1368 currentBlock
= currentBlock
.next();
1369 } while (currentBlock
.isValid() && currentBlock
.position() < cursor
.selectionEnd());
1371 QString selectionText
= cursor
.selectedText();
1372 QTextCursor
selectionCursor(cursor
);
1373 if (isSelectionComment(selectionText
)) {
1374 selectionText
= selectionText
.trimmed().remove(0, 2);
1375 selectionText
.chop(2);
1376 selectionCursor
.insertText(selectionText
);
1378 selectionText
= QString("/* ") + selectionText
+ QString(" */");
1379 selectionCursor
.insertText(selectionText
);
1381 int position
= selectionCursor
.position();
1382 cursor
.setPosition(position
- selectionText
.size());
1383 cursor
.setPosition(position
, QTextCursor::KeepAnchor
);
1384 setTextCursor(cursor
);
1388 cursor
.endEditBlock();
1391 // taking nested brackets into account
1392 static TokenIterator
previousOpeningBracket(TokenIterator it
)
1395 while (it
.isValid()) {
1397 case Token::OpeningBracket
:
1403 case Token::ClosingBracket
:
1414 // taking nested brackets into account
1415 static TokenIterator
nextClosingBracket(TokenIterator it
)
1418 while (it
.isValid()) {
1420 case Token::ClosingBracket
:
1426 case Token::OpeningBracket
:
1437 QTextCursor
CodeEditor::blockAtCursor(const QTextCursor
& cursor
)
1439 TokenIterator
it (cursor
.block(), cursor
.positionInBlock());
1443 case Token::OpeningBracket
:
1444 case Token::ClosingBracket
:
1447 matchBracket(it
, match
);
1449 if (match
.first
.isValid()) {
1450 QTextCursor
selection(textDocument());
1451 selection
.setPosition(match
.first
.position());
1452 selection
.setPosition(match
.second
.position() + 1, QTextCursor::KeepAnchor
);
1463 return QTextCursor();
1466 void CodeEditor::gotoNextBlock()
1468 QTextCursor cursor
= textCursor();
1470 TokenIterator tokenIt
= TokenIterator::rightOf( cursor
.block(), cursor
.positionInBlock() );
1471 if (tokenIt
.type() == Token::OpeningBracket
1472 && tokenIt
.block() == cursor
.block()
1473 && tokenIt
->positionInBlock
== cursor
.positionInBlock())
1476 tokenIt
= nextClosingBracket( tokenIt
);
1478 if (tokenIt
.isValid())
1479 setTextCursor( cursorAt(tokenIt
, 1) );
1481 cursor
.movePosition( QTextCursor::End
);
1482 setTextCursor( cursor
);
1486 void CodeEditor::gotoPreviousBlock()
1488 QTextCursor cursor
= textCursor();
1490 TokenIterator tokenIt
= TokenIterator::leftOf(cursor
.block(), cursor
.positionInBlock());
1491 if (tokenIt
.type() == Token::ClosingBracket
1492 && tokenIt
.block() == cursor
.block()
1493 && tokenIt
->positionInBlock
== cursor
.positionInBlock() - 1)
1497 tokenIt
= previousOpeningBracket( tokenIt
);
1499 if (tokenIt
.isValid())
1500 setTextCursor( cursorAt(tokenIt
) );
1502 cursor
.movePosition( QTextCursor::Start
);
1503 setTextCursor( cursor
);
1507 QTextCursor
CodeEditor::regionAtCursor(const QTextCursor
& cursor
)
1509 QTextBlock
block(cursor
.block());
1510 int positionInBlock
= cursor
.positionInBlock();
1512 TokenIterator start
;
1517 // search suitable opening bracket
1518 TokenIterator it
= TokenIterator::leftOf( block
, positionInBlock
);
1521 char chr
= it
->character
;
1524 if(level
> topLevel
) {
1526 if (bracketDefinesRegion(it
))
1530 else if(chr
== ')') {
1536 if (!start
.isValid())
1537 return QTextCursor();
1539 // match the found opening bracket
1540 it
= TokenIterator::rightOf( block
, positionInBlock
);
1543 char chr
= it
->character
;
1551 if (bracketDefinesRegion(it
))
1559 if(start
.isValid() && end
.isValid())
1562 FIXME: the following should be checked for every candidate opening bracket,
1563 and continue searching if check fails.
1567 // check whether the bracket makes part of an event
1570 if (it
->type
== Token::SymbolArg
)
1571 return QTextCursor();
1574 if (it
.isValid() && it
->character
== ':')
1575 return QTextCursor();
1579 // ok, this is is a real top-level region
1580 QTextCursor
c(QPlainTextEdit::document());
1581 c
.setPosition(start
.position() + 1);
1582 c
.setPosition(end
.position(), QTextCursor::KeepAnchor
);
1586 return QTextCursor();
1589 QTextCursor
CodeEditor::currentRegion()
1591 QTextCursor cursor
= textCursor();
1592 QTextBlock block
= cursor
.block();
1593 int positionInBlock
= cursor
.positionInBlock();
1595 if (TokenIterator(block
, positionInBlock
- 1).type() == Token::ClosingBracket
)
1596 cursor
.movePosition( QTextCursor::PreviousCharacter
);
1597 else if (TokenIterator(block
, positionInBlock
).type() == Token::OpeningBracket
)
1598 cursor
.movePosition( QTextCursor::NextCharacter
);
1600 return regionAtCursor( cursor
);
1603 void CodeEditor::selectCurrentRegion()
1605 QTextCursor selectedRegionCursor
= currentRegion();
1606 if (!selectedRegionCursor
.isNull() && selectedRegionCursor
.hasSelection())
1607 setTextCursor(selectedRegionCursor
);
1610 void CodeEditor::gotoNextRegion()
1612 QTextCursor cursor
= textCursor();
1614 QTextCursor regionCursor
= regionAtCursor(cursor
);
1615 if (!regionCursor
.isNull()) {
1616 cursor
= regionCursor
;
1617 // regionCursor does not include region's closing bracket, so skip it
1618 cursor
.movePosition(QTextCursor::NextCharacter
);
1621 // Skip potential opening bracket immediately right of cursor:
1622 cursor
.movePosition(QTextCursor::NextCharacter
);
1624 TokenIterator it
= TokenIterator::rightOf(cursor
.block(), cursor
.positionInBlock());
1626 while (it
.isValid()) {
1627 if ( (it
->type
== Token::OpeningBracket
) && (it
->character
== '(') &&
1628 (it
->positionInBlock
== 0) ) {
1629 setTextCursor( cursorAt(it
, 1) );
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