scide: code editor - better handling on indentation for specific keys
[supercollider.git] / editors / sc-ide / widgets / code_editor / editor.cpp
blob2fab98d329e87deb02e58c126b7c0c00872040d0
1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "editor.hpp"
22 #include "highlighter.hpp"
23 #include "autocompleter.hpp"
24 #include "../../core/doc_manager.hpp"
25 #include "../../core/settings/manager.hpp"
27 #include <QDebug>
28 #include <QKeyEvent>
29 #include <QPainter>
30 #include <QPaintEvent>
31 #include <QScrollBar>
32 #include <QTextBlock>
33 #include <QTextDocumentFragment>
35 namespace ScIDE
38 LineIndicator::LineIndicator( CodeEditor *editor )
39 : QWidget( editor ), mEditor(editor)
42 void LineIndicator::setLineCount( int count )
44 mLineCount = count;
45 setFixedWidth( widthForLineCount(count) );
46 Q_EMIT( widthChanged() );
49 void LineIndicator::changeEvent( QEvent *e )
51 if( e->type() == QEvent::FontChange ) {
52 setFixedWidth( widthForLineCount(mLineCount) );
53 Q_EMIT( widthChanged() );
55 else
56 QWidget::changeEvent(e);
59 void LineIndicator::paintEvent( QPaintEvent *e )
60 { mEditor->paintLineIndicator(e); }
62 int LineIndicator::widthForLineCount( int lineCount )
64 int digits = 2;
65 while( lineCount >= 100 ) {
66 lineCount /= 10;
67 ++digits;
70 return 6 + fontMetrics().width('9') * digits;
73 CodeEditor::CodeEditor( QWidget *parent ) :
74 QPlainTextEdit( parent ),
75 mLineIndicator( new LineIndicator(this) ),
76 mDoc(0),
77 mIndentWidth(4),
78 mSpaceIndent(true),
79 mShowWhitespace(false),
80 mMouseBracketMatch(false),
81 mOverlay( new QGraphicsScene(this) ),
82 mAutoCompleter( new AutoCompleter(this) )
84 mLineIndicator->move( contentsRect().topLeft() );
86 connect( this, SIGNAL(blockCountChanged(int)),
87 mLineIndicator, SLOT(setLineCount(int)) );
89 connect( mLineIndicator, SIGNAL( widthChanged() ),
90 this, SLOT( updateLayout() ) );
92 connect( this, SIGNAL(updateRequest(QRect,int)),
93 this, SLOT(updateLineIndicator(QRect,int)) );
95 connect( this, SIGNAL(cursorPositionChanged()),
96 this, SLOT(matchBrackets()) );
98 connect( mOverlay, SIGNAL(changed(const QList<QRectF>&)),
99 this, SLOT(onOverlayChanged(const QList<QRectF>&)) );
101 mLineIndicator->setLineCount(1);
104 void CodeEditor::setDocument( Document *doc )
106 QTextDocument *tdoc = doc->textDocument();
107 new SyntaxHighlighter(tdoc);
109 QFontMetricsF fm(font());
111 QTextOption opt;
112 opt.setTabStop( fm.width(' ') * mIndentWidth );
113 if(mShowWhitespace)
114 opt.setFlags( QTextOption::ShowTabsAndSpaces );
116 tdoc->setDefaultTextOption(opt);
117 tdoc->setDefaultFont(font());
118 tdoc->setDocumentLayout( new QPlainTextDocumentLayout(tdoc) );
120 QPlainTextEdit::setDocument(tdoc);
122 mLineIndicator->setLineCount( tdoc->blockCount() );
124 mDoc = doc;
126 mAutoCompleter->documentChanged(tdoc);
129 void CodeEditor::setIndentWidth( int width )
131 mIndentWidth = width;
133 QTextDocument *tdoc = QPlainTextEdit::document();
135 QFontMetricsF fm(font());
137 QTextOption opt;
138 opt.setTabStop( fm.width(' ') * mIndentWidth );
139 if(mShowWhitespace)
140 opt.setFlags( QTextOption::ShowTabsAndSpaces );
142 tdoc->setDefaultTextOption(opt);
145 static bool findInBlock(QTextDocument *doc, const QTextBlock &block, const QRegExp &expr, int offset,
146 QTextDocument::FindFlags options, QTextCursor &cursor)
148 QString text = block.text();
149 if(options & QTextDocument::FindBackward)
150 text.truncate(offset);
151 text.replace(QChar::Nbsp, QLatin1Char(' '));
153 int idx = -1;
154 while (offset >=0 && offset <= text.length()) {
155 idx = (options & QTextDocument::FindBackward) ?
156 expr.lastIndexIn(text, offset) : expr.indexIn(text, offset);
157 if (idx == -1)
158 return false;
160 if (options & QTextDocument::FindWholeWords) {
161 const int start = idx;
162 const int end = start + expr.matchedLength();
163 if ((start != 0 && text.at(start - 1).isLetterOrNumber())
164 || (end != text.length() && text.at(end).isLetterOrNumber())) {
165 //if this is not a whole word, continue the search in the string
166 offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1;
167 idx = -1;
168 continue;
171 //we have a hit, return the cursor for that.
172 break;
175 if (idx == -1)
176 return false;
178 cursor = QTextCursor(doc);
179 cursor.setPosition(block.position() + idx);
180 cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor);
181 return true;
184 bool CodeEditor::find( const QRegExp &expr, QTextDocument::FindFlags options )
186 // Although QTextDocument provides a find() method, we implement
187 // our own, because the former one is not adequate.
189 if(expr.isEmpty()) return true;
191 bool backwards = options & QTextDocument::FindBackward;
193 QTextCursor c( textCursor() );
194 int pos;
195 if (c.hasSelection())
197 bool matching = expr.exactMatch(c.selectedText());
199 if( backwards == matching )
200 pos = c.selectionStart();
201 else
202 pos = c.selectionEnd();
204 else
205 pos = c.position();
207 QTextDocument *doc = QPlainTextEdit::document();
208 QTextBlock startBlock = doc->findBlock(pos);
209 int startBlockOffset = pos - startBlock.position();
211 QTextCursor cursor;
213 if (!backwards) {
214 int blockOffset = startBlockOffset;
215 QTextBlock block = startBlock;
216 while (block.isValid()) {
217 if (findInBlock(doc, block, expr, blockOffset, options, cursor))
218 break;
219 blockOffset = 0;
220 block = block.next();
222 if(cursor.isNull())
224 blockOffset = 0;
225 block = doc->begin();
226 while(true) {
227 if (findInBlock(doc, block, expr, blockOffset, options, cursor)
228 || block == startBlock)
229 break;
230 block = block.next();
233 } else {
234 int blockOffset = startBlockOffset;
235 QTextBlock block = startBlock;
236 while (block.isValid()) {
237 if (findInBlock(doc, block, expr, blockOffset, options, cursor))
238 break;
239 block = block.previous();
240 blockOffset = block.length() - 1;
242 if(cursor.isNull())
244 block = doc->end();
245 while(true) {
246 blockOffset = block.length() - 1;
247 if (findInBlock(doc, block, expr, blockOffset, options, cursor)
248 || block == startBlock)
249 break;
250 block = block.previous();
255 if(!cursor.isNull()) {
256 QTextEdit::ExtraSelection extraSelection;
257 extraSelection.cursor = cursor;
258 extraSelection.format.setBackground(Qt::yellow);
260 cursor.setPosition(cursor.selectionEnd());
261 setTextCursor(cursor);
263 QList<QTextEdit::ExtraSelection> selections = extraSelections();
264 selections.append(extraSelection);
265 setExtraSelections(selections);
267 return true;
269 else
270 return false;
273 int CodeEditor::findAll( const QRegExp &expr, QTextDocument::FindFlags options )
275 mSearchSelections.clear();
277 if(expr.isEmpty()) {
278 updateExtraSelections();
279 return 0;
282 QTextEdit::ExtraSelection selection;
283 selection.format.setBackground(Qt::darkYellow);
285 QTextDocument *doc = QPlainTextEdit::document();
286 QTextBlock block = doc->begin();
287 QTextCursor cursor;
289 while (block.isValid()) {
290 int blockPos = block.position();
291 int offset = 0;
292 while(findInBlock(doc, block, expr, offset, options, cursor))
294 offset = cursor.selectionEnd() - blockPos;
295 selection.cursor = cursor;
296 mSearchSelections.append(selection);
298 block = block.next();
301 updateExtraSelections();
303 return mSearchSelections.count();
306 //#define CSTR(QSTR) QSTR.toStdString().c_str()
308 static QString resolvedReplacement( const QString &replacement, const QRegExp &expr )
310 //qDebug("START");
311 static const QRegExp rexpr("(\\\\\\\\)|(\\\\[0-9]+)");
312 QString str( replacement );
313 int i=0;
314 while(i < str.size() && ((i = rexpr.indexIn(str, i)) != -1))
316 int len = rexpr.matchedLength();
317 if(rexpr.pos(1) != -1)
319 //qDebug("%i (%s): escape", i, CSTR(rexpr.cap(1)));
320 str.replace(i, len, "\\");
321 i += 1;
323 else if(rexpr.pos(2) != -1)
325 QString num_str = rexpr.cap(2);
326 num_str.remove(0, 1);
327 int num = num_str.toInt();
328 //qDebug("%i (%s): backref = %i", i, CSTR(rexpr.cap(2)), num);
329 if(num <= expr.captureCount())
331 QString cap = expr.cap(num);
332 //qDebug("resolving ref to: %s", CSTR(cap));
333 str.replace(i, len, cap);
334 i += cap.size();
336 else
338 //qDebug("ref out of range", i, num);
339 str.remove(i, len);
342 else
344 //qDebug("%i (%s): unknown match", i, CSTR(rexpr.cap(0)));
345 str.remove(i, len);
347 //qDebug(">> [%s] %i", CSTR(str), i);
349 //qDebug("END");
350 return str;
353 bool CodeEditor::replace( const QRegExp &expr, const QString &replacement, QTextDocument::FindFlags options )
355 if(expr.isEmpty()) return true;
357 QTextCursor cursor = textCursor();
358 if (cursor.hasSelection() && expr.exactMatch(cursor.selectedText()))
360 QString rstr = replacement;
361 if(expr.patternSyntax() != QRegExp::FixedString)
362 rstr = resolvedReplacement(rstr, expr);
363 cursor.insertText(rstr);
366 return find(expr, options);
369 int CodeEditor::replaceAll( const QRegExp &expr, const QString &replacement, QTextDocument::FindFlags options )
371 mSearchSelections.clear();
372 updateExtraSelections();
374 if(expr.isEmpty()) return 0;
376 int replacements = 0;
377 bool caps = expr.patternSyntax() != QRegExp::FixedString;
379 QTextDocument *doc = QPlainTextEdit::document();
380 QTextBlock block = doc->begin();
381 QTextCursor cursor;
383 QTextCursor(doc).beginEditBlock();
385 while (block.isValid())
387 int blockPos = block.position();
388 int offset = 0;
389 while(findInBlock(doc, block, expr, offset, options, cursor))
391 QString rstr = replacement;
392 if(caps)
393 rstr = resolvedReplacement(rstr, expr);
394 cursor.insertText(rstr);
395 ++replacements;
396 offset = cursor.selectionEnd() - blockPos;
398 block = block.next();
401 QTextCursor(doc).endEditBlock();
403 return replacements;
406 QTextCursor CodeEditor::currentRegion()
408 QTextCursor c(textCursor());
409 QTextBlock b(c.block());
411 int pos = c.position() - b.position();
412 TokenIterator start;
413 TokenIterator end;
414 int topLevel = 0;
415 int level = 0;
417 // search unmatched opening bracket
418 TokenIterator it = TokenIterator::leftOf( b, pos );
419 while(it.isValid())
421 char chr = it->character;
422 if(chr == '(') {
423 ++level;
424 if(level > topLevel) {
425 topLevel = level;
426 start = it;
429 else if(chr == ')') {
430 --level;
432 --it;
435 if(topLevel < 1)
436 // no unmatched opening bracket
437 return QTextCursor();
439 // match the found opening bracket
440 it = TokenIterator::rightOf( b, pos );
441 while(it.isValid())
443 char chr = it->character;
444 if(chr == '(')
445 ++topLevel;
446 else if(chr == ')')
448 --topLevel;
449 if(topLevel == 0)
451 end = it;
452 break;
455 ++it;
458 if(start.isValid() && end.isValid())
460 // only care about brackets at beginning of a line
461 if(start->position != 0)
462 return QTextCursor();
464 // check whether the bracket makes part of an event
465 it = start.next();
466 if (it.isValid()) {
467 if (it->type == Token::SymbolArg)
468 return QTextCursor();
469 else {
470 ++it;
471 if (it.isValid() && it->character == ':')
472 return QTextCursor();
476 // ok, this is is a real top-level region
477 QTextCursor c(QPlainTextEdit::document());
478 c.setPosition(start.position() + 1);
479 c.setPosition(end.position(), QTextCursor::KeepAnchor);
480 return c;
483 return QTextCursor();
486 void CodeEditor::showPosition( int pos )
488 if (pos < 0) return;
490 QTextDocument *doc = QPlainTextEdit::document();
491 if (!doc) return;
493 int lineNumber = doc->findBlock(pos).firstLineNumber();
494 verticalScrollBar()->setValue(lineNumber);
496 QTextCursor cursor(doc);
497 cursor.setPosition(pos);
498 setTextCursor(cursor);
501 void CodeEditor::clearSearchHighlighting()
503 mSearchSelections.clear();
504 updateExtraSelections();
507 void CodeEditor::zoomIn(int steps)
509 QFont f = font();
510 qreal size = f.pointSizeF();
511 if( size != -1 )
512 f.setPointSizeF( size + steps );
513 else
514 f.setPixelSize( f.pixelSize() + steps );
516 setFont(f);
519 void CodeEditor::zoomOut(int steps)
521 QFont f = font();
522 qreal size = f.pointSizeF();
523 if( size != -1 )
524 f.setPointSizeF( qMax(1.0, size - steps) );
525 else
526 f.setPixelSize( qMax(1, f.pixelSize() - steps) );
528 setFont(f);
532 void CodeEditor::setShowWhitespace(bool show)
534 mShowWhitespace = show;
536 QTextDocument *doc = QPlainTextEdit::document();
537 QTextOption opt( doc->defaultTextOption() );
538 if( show )
539 opt.setFlags( opt.flags() | QTextOption::ShowTabsAndSpaces );
540 else
541 opt.setFlags( opt.flags() & ~QTextOption::ShowTabsAndSpaces );
542 doc->setDefaultTextOption(opt);
545 void CodeEditor::applySettings( Settings::Manager *s )
547 s->beginGroup("IDE/editor");
549 mSpaceIndent = s->value("spaceIndent").toBool();
551 setIndentWidth( s->value("indentWidth").toInt() );
553 QPalette plt;
555 QFont fnt;
556 fnt.fromString( s->value("font").toString() );
558 s->beginGroup("colors");
560 if (s->contains("background"))
561 plt.setColor(QPalette::Base, s->value("background").value<QColor>());
563 if (s->contains("text"))
564 plt.setColor(QPalette::Text, s->value("text").value<QColor>());
566 QPalette lineNumPlt;
567 if (s->contains("lineNumbersBackground"))
568 lineNumPlt.setColor(QPalette::Button, s->value("lineNumbersBackground").value<QColor>());
569 if (s->contains("lineNumbers"))
570 lineNumPlt.setColor(QPalette::ButtonText, s->value("lineNumbers").value<QColor>());
571 mLineIndicator->setPalette(lineNumPlt);
573 mBracketHighlight = s->value("matchingBrackets").value<QColor>();
575 s->endGroup(); // colors
577 setPalette(plt);
578 setFont(fnt);
580 s->endGroup();
583 void CodeEditor::deleteTrailingSpaces()
585 document()->deleteTrailingSpaces();
588 bool CodeEditor::event( QEvent *e )
590 switch (e->type())
592 case QEvent::KeyPress:
594 QKeyEvent *ke = static_cast<QKeyEvent*>(e);
595 int key = ke->key();
596 switch (key)
598 case Qt::Key_Tab:
599 case Qt::Key_Backtab:
600 indent();
601 e->accept();
602 return true;
603 default:;
605 break;
607 default:;
609 return QPlainTextEdit::event(e);
612 void CodeEditor::changeEvent( QEvent *e )
614 if( e->type() == QEvent::FontChange ) {
615 // adjust tab stop to match mIndentWidth * width of space
616 QTextDocument *doc = QPlainTextEdit::document();
617 QFontMetricsF fm(font());
618 QTextOption opt( doc->defaultTextOption() );
619 opt.setTabStop( fm.width(' ') * mIndentWidth );
620 doc->setDefaultTextOption(opt);
623 QPlainTextEdit::changeEvent(e);
626 void CodeEditor::keyPressEvent( QKeyEvent *e )
628 switch (e->key()) {
629 case Qt::Key_Home:
631 Qt::KeyboardModifiers mods(e->modifiers());
632 if (mods && mods != Qt::ShiftModifier) break;
634 QTextCursor::MoveMode mode =
635 mods & Qt::ShiftModifier ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
637 QTextCursor c(textCursor());
638 QTextBlock b(c.block());
640 int pos = indentedStartOfLine(b);
641 pos += b.position();
643 if (c.position() == pos)
644 c.movePosition(QTextCursor::StartOfLine, mode);
645 else
646 c.setPosition(pos, mode);
648 setTextCursor(c);
650 return;
652 default:;
655 QPlainTextEdit::keyPressEvent(e);
657 switch (e->key()) {
658 case Qt::Key_Enter:
659 case Qt::Key_Return:
660 case Qt::Key_BraceRight:
661 case Qt::Key_BracketRight:
662 indentCurrentLine();
663 break;
665 default:;
668 mAutoCompleter->keyPress(e);
671 void CodeEditor::mouseReleaseEvent ( QMouseEvent *e )
673 // Prevent deselection of bracket match:
674 if(!mMouseBracketMatch)
675 QPlainTextEdit::mouseReleaseEvent(e);
677 mMouseBracketMatch = false;
680 void CodeEditor::mouseDoubleClickEvent ( QMouseEvent *e )
682 QTextCursor c(textCursor());
684 BracketMatch m;
685 matchBracket( c.position(), m );
686 if(m.first.isValid() && m.second.isValid())
688 c.setPosition(m.first.position());
689 c.setPosition(m.second.position() + 1, QTextCursor::KeepAnchor);
690 setTextCursor(c);
691 mMouseBracketMatch = true;
693 else
695 QPlainTextEdit::mouseDoubleClickEvent(e);
699 void CodeEditor::mouseMoveEvent( QMouseEvent *e )
701 // Prevent initiating a text drag:
702 if(!mMouseBracketMatch)
703 QPlainTextEdit::mouseMoveEvent(e);
706 void CodeEditor::onOverlayChanged ( const QList<QRectF> & region )
708 foreach(QRectF r, region)
710 viewport()->update(r.toRect());
714 void CodeEditor::paintEvent( QPaintEvent *e )
716 QPlainTextEdit::paintEvent(e);
718 QPainter p(viewport());
719 mOverlay->render(&p, e->rect(), e->rect());
722 void CodeEditor::updateLayout()
724 setViewportMargins( mLineIndicator->width(), 0, 0, 0 );
727 void CodeEditor::updateLineIndicator( QRect r, int dy )
729 if (dy)
730 mLineIndicator->scroll(0, dy);
731 else
732 mLineIndicator->update(0, r.y(), mLineIndicator->width(), r.height() );
735 void CodeEditor::matchBrackets()
737 mBracketSelections.clear();
739 QTextCursor cursor(textCursor());
741 BracketMatch match;
742 matchBracket( cursor.position(), match );
744 if( match.first.isValid() && match.second.isValid() )
746 const Token & tok1 = *match.first;
747 const Token & tok2 = *match.second;
749 if (
750 (tok1.character == '(' && tok2.character == ')')
751 || (tok1.character == '[' && tok2.character == ']')
752 || (tok1.character == '{' && tok2.character == '}')
754 QTextEdit::ExtraSelection selection;
755 selection.format.setFontWeight(QFont::Bold);
756 selection.format.setBackground(mBracketHighlight);
758 cursor.setPosition(match.first.position());
759 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
760 selection.cursor = cursor;
761 mBracketSelections.append(selection);
763 cursor.setPosition(match.second.position());
764 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
765 selection.cursor = cursor;
766 mBracketSelections.append(selection);
768 else {
769 QTextEdit::ExtraSelection selection;
770 selection.format.setBackground(Qt::red);
771 cursor.setPosition(match.first.position());
772 cursor.setPosition(match.second.position()+1, QTextCursor::KeepAnchor);
773 selection.cursor = cursor;
774 mBracketSelections.append(selection);
778 updateExtraSelections();
781 void CodeEditor::matchBracket( int pos, BracketMatch & match )
783 QTextBlock block( textDocument()->findBlock(pos) );
784 if (!block.isValid())
785 return;
787 int posInBlock = pos - block.position();
788 TokenIterator it(block);
790 while (it.isValid() && it.block() == block)
792 const Token & token = *it;
793 if (token.position > posInBlock)
794 return;
795 else if (
796 (token.position == posInBlock && token.type == Token::OpeningBracket) ||
797 (token.position == posInBlock - 1 && token.type == Token::ClosingBracket)
799 break;
800 ++it;
803 if( !it.isValid() || it.block() != block)
804 return;
806 if(it->type == Token::OpeningBracket)
808 match.first = it;
809 int level = 1;
810 while((++it).isValid())
812 Token::Type type = it->type;
813 if(type == Token::ClosingBracket)
814 --level;
815 else if(type == Token::OpeningBracket)
816 ++level;
817 if(level == 0) {
818 match.second = it;
819 return;
823 else if(it->type == Token::ClosingBracket)
825 match.second = it;
826 int level = 1;
827 while((--it).isValid())
829 Token::Type type = it->type;
830 if(type == Token::OpeningBracket)
831 --level;
832 else if(type == Token::ClosingBracket)
833 ++level;
834 if(level == 0) {
835 match.first = it;
836 return;
842 int CodeEditor::indentedStartOfLine( const QTextBlock &b )
844 QString t(b.text());
845 int n = t.size();
846 int i = 0;
847 while (i < n) {
848 QChar c(t[i]);
849 if (c != ' ' && c != '\t')
850 break;
851 ++i;
854 return i;
857 void CodeEditor::updateExtraSelections()
859 QList<QTextEdit::ExtraSelection> selections;
860 selections.append(mBracketSelections);
861 selections.append(mSearchSelections);
862 setExtraSelections(selections);
865 void CodeEditor::resizeEvent( QResizeEvent *e )
867 QPlainTextEdit::resizeEvent( e );
869 QRect cr = contentsRect();
870 mLineIndicator->resize( mLineIndicator->width(), cr.height() );
873 void CodeEditor::paintLineIndicator( QPaintEvent *e )
875 QPalette plt( mLineIndicator->palette() );
876 QRect r( e->rect() );
877 QPainter p( mLineIndicator );
879 p.fillRect( r, plt.color( QPalette::Button ) );
880 p.setPen( plt.color(QPalette::ButtonText) );
881 p.drawLine( r.topRight(), r.bottomRight() );
883 QTextBlock block = firstVisibleBlock();
884 int blockNumber = block.blockNumber();
885 int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
886 int bottom = top + (int) blockBoundingRect(block).height();
888 while (block.isValid() && top <= e->rect().bottom()) {
889 if (block.isVisible() && bottom >= e->rect().top()) {
890 QString number = QString::number(blockNumber + 1);
891 p.drawText(0, top, mLineIndicator->width() - 4, fontMetrics().height(),
892 Qt::AlignRight, number);
895 block = block.next();
896 top = bottom;
897 bottom = top + (int) blockBoundingRect(block).height();
898 ++blockNumber;
902 void CodeEditor::indent()
904 QTextCursor cursor = textCursor();
905 if (cursor.selection().isEmpty())
906 indentCurrentLine();
907 else
908 indentCurrentSelection();
911 int CodeEditor::findIndentationLevel(QTextBlock const & block)
913 if (!block.isValid())
914 return 0;
916 TokenIterator it = TokenIterator::leftOf(block, 0);
918 int level = 0;
919 int offset = 0;
921 TokenIterator next(block);
922 if (next.isValid() && next->type == Token::ClosingBracket)
923 offset = -1;
925 while (it.isValid()) {
926 if (it->type == Token::OpeningBracket && it->character != '(')
927 level += 1;
928 else if (it->type == Token::ClosingBracket && it->character != ')')
929 level -= 1;
931 --it;
934 level += offset;
936 return qMax(level, 0);
939 void CodeEditor::indentCurrentSelection()
941 QTextCursor cursor = textCursor();
943 const int position = cursor.position();
944 const int anchor = cursor.anchor();
946 QTextDocument * doc = QPlainTextEdit::document();
947 QTextBlock block = doc->findBlock(qMin(position, anchor));
948 QTextBlock endBlock = anchor != position ? doc->findBlock(qMax(position, anchor))
949 : block;
951 cursor.beginEditBlock();
953 for (;;) {
954 cursor.setPosition(block.position());
955 indentLineAtCursor(cursor);
957 if ( block != endBlock )
958 block = block.next();
959 else
960 break;
963 cursor.endEditBlock();
966 void CodeEditor::indentCurrentLine()
968 QTextCursor cursor = textCursor();
969 cursor.beginEditBlock();
970 indentLineAtCursor(textCursor());
971 cursor.endEditBlock();
974 void CodeEditor::indentLineAtCursor(QTextCursor cursor)
976 const int indentationLevel = findIndentationLevel(cursor.block());
978 cursor.movePosition(QTextCursor::StartOfBlock);
979 cursor.setPosition(cursor.position() + indentedStartOfLine(cursor.block()), QTextCursor::KeepAnchor);
981 if ( mSpaceIndent ) {
982 const int spaces = mIndentWidth * indentationLevel;
983 QString replacement (spaces, QChar(' '));
984 cursor.insertText(replacement);
985 } else {
986 const int tabs = indentationLevel;
987 QString replacement (tabs, QChar('\t'));
988 cursor.insertText(replacement);
992 } // namespace ScIDE