Merge branch 'maint'
[kdbg.git] / kdbg / sourcewnd.cpp
blob49ee428a7bb149ec8bdc002b076fa7251eb7b3a1
1 /*
2 * Copyright Johannes Sixt
3 * This file is licensed under the GNU General Public License Version 2.
4 * See the file COPYING in the toplevel directory of the source directory.
5 */
7 #include "debugger.h"
8 #include "sourcewnd.h"
9 #include "dbgdriver.h"
10 #include <QTextStream>
11 #include <QPainter>
12 #include <QFile>
13 #include <QFileInfo>
14 #include <QMenu>
15 #include <QContextMenuEvent>
16 #include <QKeyEvent>
17 #include <QMouseEvent>
18 #include <kiconloader.h>
19 #include <kglobalsettings.h>
20 #include <kxmlguiwindow.h>
21 #include <kxmlguifactory.h>
22 #include <algorithm>
23 #include "mydebug.h"
26 SourceWindow::SourceWindow(const QString& fileName, QWidget* parent) :
27 QPlainTextEdit(parent),
28 m_fileName(fileName),
29 m_highlighter(0),
30 m_widthItems(16),
31 m_widthPlus(12),
32 m_widthLineNo(30),
33 m_lineInfoArea(new LineInfoArea(this))
35 // load pixmaps
36 m_pcinner = UserIcon("pcinner");
37 m_pcup = UserIcon("pcup");
38 m_brkena = UserIcon("brkena");
39 m_brkdis = UserIcon("brkdis");
40 m_brktmp = UserIcon("brktmp");
41 m_brkcond = UserIcon("brkcond");
42 m_brkorph = UserIcon("brkorph");
43 setFont(KGlobalSettings::fixedFont());
44 setReadOnly(true);
45 setViewportMargins(lineInfoAreaWidth(), 0, 0 ,0);
46 setWordWrapMode(QTextOption::NoWrap);
47 connect(this, SIGNAL(updateRequest(const QRect&, int)),
48 m_lineInfoArea, SLOT(update()));
49 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorChanged()));
51 // add a syntax highlighter
52 if (QRegExp("\\.(c(pp|c|\\+\\+)?|CC?|h(\\+\\+|h|pp)?|HH?)$").indexIn(m_fileName) >= 0)
54 m_highlighter = new HighlightCpp(this);
58 SourceWindow::~SourceWindow()
60 delete m_highlighter;
63 int SourceWindow::lineInfoAreaWidth() const
65 return 3 + m_widthItems + m_widthPlus + m_widthLineNo;
68 bool SourceWindow::loadFile()
70 QFile f(m_fileName);
71 if (!f.open(QIODevice::ReadOnly)) {
72 return false;
75 QTextStream t(&f);
76 setPlainText(t.readAll());
77 f.close();
79 int n = blockCount();
80 m_sourceCode.resize(n);
81 m_rowToLine.resize(n);
82 for (int i = 0; i < n; i++) {
83 m_rowToLine[i] = i;
85 m_lineItems.resize(n, 0);
87 // set a font for line numbers
88 m_lineNoFont = currentCharFormat().font();
89 m_lineNoFont.setPixelSize(11);
91 return true;
94 void SourceWindow::reloadFile()
96 QFile f(m_fileName);
97 if (!f.open(QIODevice::ReadOnly)) {
98 // open failed; leave alone
99 return;
102 m_sourceCode.clear(); /* clear old text */
104 QTextStream t(&f);
105 setPlainText(t.readAll());
106 f.close();
108 m_sourceCode.resize(blockCount());
109 // expanded lines are collapsed: move existing line items up
110 for (size_t i = 0; i < m_lineItems.size(); i++) {
111 if (m_rowToLine[i] != int(i)) {
112 m_lineItems[m_rowToLine[i]] |= m_lineItems[i];
113 m_lineItems[i] = 0;
116 // allocate line items
117 m_lineItems.resize(m_sourceCode.size(), 0);
119 m_rowToLine.resize(m_sourceCode.size());
120 for (size_t i = 0; i < m_sourceCode.size(); i++)
121 m_rowToLine[i] = i;
123 // Highlighting was applied above when the text was inserted into widget,
124 // but at that time m_rowToLine was not corrected, yet, so that lines
125 // that previously were assembly were painted incorrectly.
126 if (m_highlighter)
127 m_highlighter->rehighlight();
130 void SourceWindow::scrollTo(int lineNo, const DbgAddr& address)
132 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
133 return;
135 int row = lineToRow(lineNo, address);
136 scrollToRow(row);
139 void SourceWindow::scrollToRow(int row)
141 QTextCursor cursor(document()->findBlockByNumber(row));
142 setTextCursor(cursor);
143 ensureCursorVisible();
146 void SourceWindow::resizeEvent(QResizeEvent* e)
148 QPlainTextEdit::resizeEvent(e);
150 QRect cr = contentsRect();
151 cr.setRight(lineInfoAreaWidth());
152 m_lineInfoArea->setGeometry(cr);
155 void SourceWindow::drawLineInfoArea(QPainter* p, QPaintEvent* event)
157 QTextBlock block = firstVisibleBlock();
159 p->setFont(m_lineNoFont);
161 for (; block.isValid(); block = block.next())
163 if (!block.isVisible())
164 continue;
166 QRect r = blockBoundingGeometry(block).translated(contentOffset()).toRect();
167 if (r.bottom() < event->rect().top())
168 continue; // skip blocks that are higher than the region being updated
169 else if (r.top() > event->rect().bottom())
170 break; // all the following blocks are lower then the region being updated
172 int row = block.blockNumber();
173 uchar item = m_lineItems[row];
175 int h = r.height();
176 p->save();
177 p->translate(0, r.top());
179 if (item & liBP) {
180 // enabled breakpoint
181 int y = (h - m_brkena.height())/2;
182 if (y < 0) y = 0;
183 p->drawPixmap(0,y,m_brkena);
185 if (item & liBPdisabled) {
186 // disabled breakpoint
187 int y = (h - m_brkdis.height())/2;
188 if (y < 0) y = 0;
189 p->drawPixmap(0,y,m_brkdis);
191 if (item & liBPtemporary) {
192 // temporary breakpoint marker
193 int y = (h - m_brktmp.height())/2;
194 if (y < 0) y = 0;
195 p->drawPixmap(0,y,m_brktmp);
197 if (item & liBPconditional) {
198 // conditional breakpoint marker
199 int y = (h - m_brkcond.height())/2;
200 if (y < 0) y = 0;
201 p->drawPixmap(0,y,m_brkcond);
203 if (item & liBPorphan) {
204 // orphaned breakpoint marker
205 int y = (h - m_brkcond.height())/2;
206 if (y < 0) y = 0;
207 p->drawPixmap(0,y,m_brkorph);
209 if (item & liPC) {
210 // program counter in innermost frame
211 int y = (h - m_pcinner.height())/2;
212 if (y < 0) y = 0;
213 p->drawPixmap(0,y,m_pcinner);
215 if (item & liPCup) {
216 // program counter somewhere up the stack
217 int y = (h - m_pcup.height())/2;
218 if (y < 0) y = 0;
219 p->drawPixmap(0,y,m_pcup);
221 p->translate(m_widthItems, 0);
222 if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) {
223 int w = m_widthPlus;
224 int x = w/2;
225 int y = h/2;
226 p->drawLine(x-2, y, x+2, y);
227 if (!isRowExpanded(row)) {
228 p->drawLine(x, y-2, x, y+2);
231 p->translate(m_widthPlus, 0);
232 if (!isRowDisassCode(row)) {
233 p->drawText(0, 0, m_widthLineNo, h, Qt::AlignRight|Qt::AlignVCenter,
234 QString().setNum(rowToLine(row)+1));
236 p->restore();
240 void SourceWindow::updateLineItems(const KDebugger* dbg)
242 // clear outdated breakpoints
243 for (int i = m_lineItems.size()-1; i >= 0; i--) {
244 if (m_lineItems[i] & liBPany) {
245 // check if this breakpoint still exists
246 int line = rowToLine(i);
247 TRACE(QString().sprintf("checking for bp at %d", line));
248 KDebugger::BrkptROIterator bp = dbg->breakpointsBegin();
249 for (; bp != dbg->breakpointsEnd(); ++bp)
251 if (bp->lineNo == line &&
252 fileNameMatches(bp->fileName) &&
253 lineToRow(line, bp->address) == i)
255 // yes it exists; mode is changed below
256 break;
259 if (bp == dbg->breakpointsEnd()) {
260 /* doesn't exist anymore, remove it */
261 m_lineItems[i] &= ~liBPany;
266 // add new breakpoints
267 for (KDebugger::BrkptROIterator bp = dbg->breakpointsBegin(); bp != dbg->breakpointsEnd(); ++bp)
269 if (fileNameMatches(bp->fileName)) {
270 TRACE(QString("updating %2:%1").arg(bp->lineNo).arg(bp->fileName));
271 int i = bp->lineNo;
272 if (i < 0 || i >= int(m_sourceCode.size()))
273 continue;
274 // compute new line item flags for breakpoint
275 uchar flags = bp->enabled ? liBP : liBPdisabled;
276 if (bp->temporary)
277 flags |= liBPtemporary;
278 if (!bp->condition.isEmpty() || bp->ignoreCount != 0)
279 flags |= liBPconditional;
280 if (bp->isOrphaned())
281 flags |= liBPorphan;
282 // update if changed
283 int row = lineToRow(i, bp->address);
284 if ((m_lineItems[row] & liBPany) != flags) {
285 m_lineItems[row] &= ~liBPany;
286 m_lineItems[row] |= flags;
290 m_lineInfoArea->update();
293 void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
295 if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
296 return;
299 int row = lineToRow(lineNo, address);
301 uchar flag = frameNo == 0 ? liPC : liPCup;
302 if (set) {
303 // set only if not already set
304 if ((m_lineItems[row] & flag) == 0) {
305 m_lineItems[row] |= flag;
306 m_lineInfoArea->update();
308 } else {
309 // clear only if not set
310 if ((m_lineItems[row] & flag) != 0) {
311 m_lineItems[row] &= ~flag;
312 m_lineInfoArea->update();
317 void SourceWindow::find(const QString& text, bool caseSensitive, FindDirection dir)
319 ASSERT(dir == 1 || dir == -1);
320 QTextDocument::FindFlags flags = 0;
321 if (caseSensitive)
322 flags |= QTextDocument::FindCaseSensitively;
323 if (dir < 0)
324 flags |= QTextDocument::FindBackward;
325 if (QPlainTextEdit::find(text, flags))
326 return;
327 // not found; wrap around
328 QTextCursor cursor(document());
329 if (dir < 0)
330 cursor.movePosition(QTextCursor::End);
331 cursor = document()->find(text, cursor, flags);
332 if (!cursor.isNull())
333 setTextCursor(cursor);
336 void SourceWindow::infoMousePress(QMouseEvent* ev)
338 // we handle left and middle button
339 if (ev->button() != Qt::LeftButton && ev->button() != Qt::MidButton)
341 return;
344 // get row
345 int row = cursorForPosition(QPoint(0, ev->y())).blockNumber();
346 if (row < 0)
347 return;
349 if (ev->x() > m_widthItems)
351 if (isRowExpanded(row)) {
352 actionCollapseRow(row);
353 } else {
354 actionExpandRow(row);
356 return;
359 int sourceRow;
360 int line = rowToLine(row, &sourceRow);
362 // find address if row is disassembled code
363 DbgAddr address;
364 if (row > sourceRow) {
365 // get offset from source code line
366 int off = row - sourceRow;
367 address = m_sourceCode[line].disassAddr[off-1];
370 switch (ev->button()) {
371 case Qt::LeftButton:
372 TRACE(QString().sprintf("left-clicked line %d", line));
373 emit clickedLeft(m_fileName, line, address,
374 (ev->modifiers() & Qt::ShiftModifier) != 0);
375 break;
376 case Qt::MidButton:
377 TRACE(QString().sprintf("mid-clicked row %d", line));
378 emit clickedMid(m_fileName, line, address);
379 break;
380 default:;
384 void SourceWindow::keyPressEvent(QKeyEvent* ev)
386 int top1 = 0, top2;
387 switch (ev->key()) {
388 case Qt::Key_Plus:
389 actionExpandRow(textCursor().blockNumber());
390 return;
391 case Qt::Key_Minus:
392 actionCollapseRow(textCursor().blockNumber());
393 return;
394 case Qt::Key_Up:
395 case Qt::Key_K:
396 moveCursor(QTextCursor::PreviousBlock);
397 return;
398 case Qt::Key_Down:
399 case Qt::Key_J:
400 moveCursor(QTextCursor::NextBlock);
401 return;
402 case Qt::Key_Home:
403 moveCursor(QTextCursor::Start);
404 return;
405 case Qt::Key_End:
406 moveCursor(QTextCursor::End);
407 return;
408 case Qt::Key_PageUp:
409 case Qt::Key_PageDown:
410 top1 = firstVisibleBlock().blockNumber();
413 QPlainTextEdit::keyPressEvent(ev);
415 switch (ev->key()) {
416 case Qt::Key_PageUp:
417 case Qt::Key_PageDown:
418 top2 = firstVisibleBlock().blockNumber();
420 QTextCursor cursor = textCursor();
421 if (top1 < top2)
422 cursor.movePosition(QTextCursor::NextBlock,
423 QTextCursor::MoveAnchor, top2-top1);
424 else
425 cursor.movePosition(QTextCursor::PreviousBlock,
426 QTextCursor::MoveAnchor, top1-top2);
427 setTextCursor(cursor);
432 QString SourceWindow::extendExpr(const QString &plainText,
433 int wordStart,
434 int wordEnd)
436 QString document = plainText.left(wordEnd);
437 QString word = plainText.mid(wordStart, wordEnd - wordStart);
438 QRegExp regex = QRegExp("(::)?([A-Za-z_]{1}\\w*\\s*(->|\\.|::)\\s*)*" + word + "$");
440 #define IDENTIFIER_MAX_SIZE 256
441 // cut the document to reduce size of string to scan
442 // because of this only identifiefs of length <= IDENTIFIER_MAX_SIZE are supported
443 if (document.length() > IDENTIFIER_MAX_SIZE) {
444 document.right(IDENTIFIER_MAX_SIZE);
447 const int index = regex.indexIn(document);
449 if (index == -1)
451 TRACE("No match, returning " + word);
453 else
455 const int length = regex.matchedLength();
457 word = plainText.mid(index, length);
458 TRACE("Matched, returning " + word);
461 return word;
464 bool SourceWindow::wordAtPoint(const QPoint& p, QString& word, QRect& r)
466 QTextCursor cursor = cursorForPosition(viewport()->mapFrom(this, p));
467 if (cursor.isNull())
468 return false;
470 cursor.select(QTextCursor::WordUnderCursor);
471 word = cursor.selectedText();
473 if (word.isEmpty())
474 return false;
476 // keep only letters and digits
477 QRegExp w("[A-Za-z_]{1}[\\dA-Za-z_]*");
478 if (w.indexIn(word) < 0)
479 return false;
481 word = w.cap();
483 if (m_highlighter)
485 // if cpp highlighter is enabled - c/c++ file is being displayed
487 // check that word is not a c/c++ keyword
488 if (m_highlighter->isCppKeyword(word))
489 return false;
491 // TODO check that cursor is on top of a string literal
492 // and don't display any tooltips in this case
494 // try to extend selected word under the cursor to get a full variable name
495 word = extendExpr(cursor.document()->toPlainText(),
496 cursor.selectionStart(),
497 cursor.selectionEnd());
499 if (word.isEmpty())
500 return false;
503 r = QRect(p, p);
504 r.adjust(-5,-5,5,5);
505 return true;
508 void SourceWindow::paletteChange(const QPalette& oldPal)
510 setFont(KGlobalSettings::fixedFont());
511 QPlainTextEdit::paletteChange(oldPal);
515 * Two file names (possibly full paths) match if the last parts - the file
516 * names - match.
518 bool SourceWindow::fileNameMatches(const QString& other)
520 return QFileInfo(other).fileName() == QFileInfo(m_fileName).fileName();
523 void SourceWindow::disassembled(int lineNo, const std::list<DisassembledCode>& disass)
525 TRACE("disassembled line " + QString().setNum(lineNo));
526 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
527 return;
529 SourceLine& sl = m_sourceCode[lineNo];
531 // copy disassembled code and its addresses
532 sl.disass.resize(disass.size());
533 sl.disassAddr.resize(disass.size());
534 sl.canDisass = !disass.empty();
535 int i = 0;
536 for (std::list<DisassembledCode>::const_iterator c = disass.begin(); c != disass.end(); ++c, ++i)
538 QString code = c->code;
539 while (code.endsWith("\n"))
540 code.truncate(code.length()-1);
541 sl.disass[i] = c->address.asString() + ' ' + code;
542 sl.disassAddr[i] = c->address;
545 int row = lineToRow(lineNo);
546 if (sl.canDisass) {
547 expandRow(row);
548 } else {
549 // clear expansion marker
550 m_lineInfoArea->update();
554 int SourceWindow::rowToLine(int row, int* sourceRow)
556 int line = row >= 0 ? m_rowToLine[row] : -1;
557 if (sourceRow != 0) {
558 // search back until we hit the first entry with the current line number
559 while (row > 0 && m_rowToLine[row-1] == line)
560 row--;
561 *sourceRow = row;
563 return line;
567 * Rows showing diassembled code have the same line number as the
568 * corresponding source code line number. Therefore, the line numbers in
569 * m_rowToLine are monotonically increasing with blocks of equal line
570 * numbers for a source line and its disassembled code that follows it.
572 * Hence, m_rowToLine always obeys the following condition:
574 * m_rowToLine[i] <= i
577 int SourceWindow::lineToRow(int line)
579 // line is zero-based!
581 assert(line < int(m_rowToLine.size()));
583 // quick test for common case
584 if (line < 0 || m_rowToLine[line] == line)
585 return line;
587 assert(m_rowToLine[line] < line);
590 * Binary search between row == line and end of list. In the loop below
591 * we use the fact that the line numbers m_rowToLine do not contain
592 * holes.
594 int l = line;
595 int h = m_rowToLine.size();
596 while (l < h && m_rowToLine[l] != line)
598 assert(h == int(m_rowToLine.size()) || m_rowToLine[l] < m_rowToLine[h]);
601 * We want to round down the midpoint so that we find the
602 * lowest row that belongs to the line we seek.
604 int mid = (l+h)/2;
605 if (m_rowToLine[mid] <= line)
606 l = mid;
607 else
608 h = mid;
610 // Found! Result is in l:
611 assert(m_rowToLine[l] == line);
614 * We might not have hit the lowest index for the line.
616 while (l > 0 && m_rowToLine[l-1] == line)
617 --l;
619 return l;
622 int SourceWindow::lineToRow(int line, const DbgAddr& address)
624 int row = lineToRow(line);
625 if (isRowExpanded(row)) {
626 row += m_sourceCode[line].findAddressRowOffset(address);
628 return row;
631 bool SourceWindow::isRowExpanded(int row)
633 assert(row >= 0);
634 return row < int(m_rowToLine.size())-1 &&
635 m_rowToLine[row] == m_rowToLine[row+1];
638 bool SourceWindow::isRowDisassCode(int row)
640 return row > 0 && row < int(m_rowToLine.size()) &&
641 m_rowToLine[row] == m_rowToLine[row-1];
644 void SourceWindow::expandRow(int row)
646 TRACE("expanding row " + QString().setNum(row));
647 // get disassembled code
648 int line = rowToLine(row);
649 const std::vector<QString>& disass = m_sourceCode[line].disass;
651 // remove PC (must be set again in slot of signal expanded())
652 m_lineItems[row] &= ~(liPC|liPCup);
654 // insert new lines
655 setUpdatesEnabled(false);
656 ++row;
657 m_rowToLine.insert(m_rowToLine.begin()+row, disass.size(), line);
658 m_lineItems.insert(m_lineItems.begin()+row, disass.size(), 0);
660 QTextCursor cursor(document()->findBlockByNumber(row));
661 for (size_t i = 0; i < disass.size(); i++) {
662 cursor.insertText(disass[i]);
663 cursor.insertBlock();
665 setUpdatesEnabled(true);
667 emit expanded(line); /* must set PC */
670 void SourceWindow::collapseRow(int row)
672 TRACE("collapsing row " + QString().setNum(row));
673 int line = rowToLine(row);
675 // find end of this block
676 int end = row+1;
677 while (end < int(m_rowToLine.size()) && m_rowToLine[end] == m_rowToLine[row]) {
678 end++;
680 ++row;
681 setUpdatesEnabled(false);
682 QTextCursor cursor(document()->findBlockByNumber(end-1));
683 while (--end >= row) {
684 m_rowToLine.erase(m_rowToLine.begin()+end);
685 m_lineItems.erase(m_lineItems.begin()+end);
686 cursor.select(QTextCursor::BlockUnderCursor);
687 cursor.removeSelectedText();
689 setUpdatesEnabled(true);
691 emit collapsed(line);
694 void SourceWindow::activeLine(int& line, DbgAddr& address)
696 int row = textCursor().blockNumber();
698 int sourceRow;
699 line = rowToLine(row, &sourceRow);
700 if (row > sourceRow) {
701 int off = row - sourceRow; /* offset from source line */
702 address = m_sourceCode[line].disassAddr[off-1];
707 * Returns the offset from the line displaying the source code to
708 * the line containing the specified address. If the address is not
709 * found, 0 is returned.
711 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
713 if (address.isEmpty())
714 return 0;
716 for (size_t i = 0; i < disassAddr.size(); i++) {
717 if (disassAddr[i] == address) {
718 // found exact address
719 return i+1;
721 if (disassAddr[i] > address) {
723 * We have already advanced too far; the address is before this
724 * index, but obviously we haven't found an exact match
725 * earlier. address is somewhere between the displayed
726 * addresses. We return the previous line.
728 return i;
731 // not found
732 return 0;
735 void SourceWindow::actionExpandRow(int row)
737 if (row < 0 || isRowExpanded(row) || isRowDisassCode(row))
738 return;
740 // disassemble
741 int line = rowToLine(row);
742 const SourceLine& sl = m_sourceCode[line];
743 if (!sl.canDisass)
744 return;
745 if (sl.disass.size() == 0) {
746 emit disassemble(m_fileName, line);
747 } else {
748 expandRow(row);
752 void SourceWindow::actionCollapseRow(int row)
754 if (row < 0 || !isRowExpanded(row) || isRowDisassCode(row))
755 return;
757 collapseRow(row);
760 void SourceWindow::setTabWidth(int numChars)
762 if (numChars <= 0)
763 numChars = 8;
764 QFontMetrics fm(document()->defaultFont());
765 QString s;
766 int w = fm.width(s.fill('x', numChars));
767 setTabStopWidth(w);
770 void SourceWindow::cursorChanged()
772 QList<QTextEdit::ExtraSelection> extraSelections;
773 QTextEdit::ExtraSelection selection;
775 selection.format.setBackground(QColor("#E7E7E7"));
776 selection.format.setProperty(QTextFormat::FullWidthSelection, true);
777 selection.cursor = textCursor();
778 selection.cursor.clearSelection();
779 extraSelections.append(selection);
780 setExtraSelections(extraSelections);
784 * Show our own context menu.
786 void SourceWindow::contextMenuEvent(QContextMenuEvent* e)
788 // get the context menu from the GUI factory
789 QWidget* top = this;
791 top = top->parentWidget();
792 while (!top->isTopLevel());
793 KXmlGuiWindow* mw = static_cast<KXmlGuiWindow*>(top);
794 QMenu* m = static_cast<QMenu*>(mw->factory()->container("popup_files", mw));
795 if (m)
796 m->exec(e->globalPos());
799 void LineInfoArea::paintEvent(QPaintEvent* e)
801 QPainter p(this);
802 static_cast<SourceWindow*>(parent())->drawLineInfoArea(&p, e);
805 void LineInfoArea::mousePressEvent(QMouseEvent* ev)
807 static_cast<SourceWindow*>(parent())->infoMousePress(ev);
810 void LineInfoArea::contextMenuEvent(QContextMenuEvent* e)
812 static_cast<SourceWindow*>(parent())->contextMenuEvent(e);
815 HighlightCpp::HighlightCpp(SourceWindow* srcWnd) :
816 QSyntaxHighlighter(srcWnd->document()),
817 m_srcWnd(srcWnd)
821 enum HLState {
822 hlCommentLine = 1,
823 hlCommentBlock,
824 hlIdent,
825 hlString
828 static const QString ckw[] =
830 "and",
831 "and_eq",
832 "asm",
833 "auto",
834 "bitand",
835 "bitor",
836 "bool",
837 "break",
838 "case",
839 "catch",
840 "char",
841 "class",
842 "compl",
843 "const",
844 "const_cast",
845 "continue",
846 "default",
847 "delete",
848 "do",
849 "double",
850 "dynamic_cast",
851 "else",
852 "enum",
853 "explicit",
854 "export",
855 "extern",
856 "false",
857 "float",
858 "for",
859 "friend",
860 "goto",
861 "if",
862 "inline",
863 "int",
864 "long",
865 "mutable",
866 "namespace",
867 "new",
868 "not",
869 "not_eq",
870 "operator",
871 "or",
872 "or_eq",
873 "private",
874 "protected",
875 "public",
876 "reinterpret_cast",
877 "register",
878 "return",
879 "short",
880 "signed",
881 "sizeof",
882 "static",
883 "static_cast",
884 "struct",
885 "switch",
886 "template",
887 "this",
888 "throw",
889 "true",
890 "try",
891 "typedef",
892 "typeid",
893 "typename",
894 "using",
895 "union",
896 "unsigned",
897 "virtual",
898 "void",
899 "volatile",
900 "wchar_t",
901 "while",
902 "xor",
903 "xor_eq"
906 void HighlightCpp::highlightBlock(const QString& text)
908 int state = previousBlockState();
909 state = highlight(text, state);
910 setCurrentBlockState(state);
913 int HighlightCpp::highlight(const QString& text, int state)
915 // highlight assembly lines
916 if (m_srcWnd->isRowDisassCode(currentBlock().blockNumber()))
918 setFormat(0, text.length(), Qt::blue);
919 return state;
922 if (state == -2) // initial state
923 state = 0;
925 // check for preprocessor line
926 if (state == 0 && text.trimmed().startsWith("#"))
928 setFormat(0, text.length(), QColor("darkgreen"));
929 return 0;
932 // a font for keywords
933 QTextCharFormat identFont;
934 identFont.setFontWeight(QFont::Bold);
936 int start = 0;
937 while (start < text.length())
939 int end;
940 switch (state) {
941 case hlCommentLine:
942 end = text.length();
943 state = 0;
944 setFormat(start, end-start, QColor("gray"));
945 break;
946 case hlCommentBlock:
947 end = text.indexOf("*/", start);
948 if (end >= 0)
949 end += 2, state = 0;
950 else
951 end = text.length();
952 setFormat(start, end-start, QColor("gray"));
953 break;
954 case hlString:
955 for (end = start+1; end < int(text.length()); end++) {
956 if (text[end] == '\\') {
957 if (end < int(text.length()))
958 ++end;
959 } else if (text[end] == text[start]) {
960 ++end;
961 break;
964 state = 0;
965 setFormat(start, end-start, QColor("darkred"));
966 break;
967 case hlIdent:
968 for (end = start+1; end < int(text.length()); end++) {
969 if (!text[end].isLetterOrNumber() && text[end] != '_')
970 break;
972 state = 0;
973 if (isCppKeyword(text.mid(start, end-start)))
975 setFormat(start, end-start, identFont);
976 } else {
977 setFormat(start, end-start, m_srcWnd->palette().color(QPalette::WindowText));
979 break;
980 default:
981 for (end = start; end < int(text.length()); end++)
983 if (text[end] == '/')
985 if (end+1 < int(text.length())) {
986 if (text[end+1] == '/') {
987 state = hlCommentLine;
988 break;
989 } else if (text[end+1] == '*') {
990 state = hlCommentBlock;
991 break;
995 else if (text[end] == '"' || text[end] == '\'')
997 state = hlString;
998 break;
1000 else if ((text[end] >= 'A' && text[end] <= 'Z') ||
1001 (text[end] >= 'a' && text[end] <= 'z') ||
1002 text[end] == '_')
1004 state = hlIdent;
1005 break;
1008 setFormat(start, end-start, m_srcWnd->palette().color(QPalette::WindowText));
1010 start = end;
1012 return state;
1015 bool HighlightCpp::isCppKeyword(const QString& word)
1017 return std::binary_search(ckw, ckw + sizeof(ckw)/sizeof(ckw[0]), word);
1020 #include "sourcewnd.moc"