Generate and install scalable icons.
[kdbg.git] / kdbg / sourcewnd.cpp
blobc7da663455a2c271b566a2933bfc6fecf14115d1
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 <QFontDatabase>
15 #include <QMenu>
16 #include <QContextMenuEvent>
17 #include <QKeyEvent>
18 #include <QMouseEvent>
19 #include <kiconloader.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(QFontDatabase::systemFont(QFontDatabase::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::changeEvent(QEvent* ev)
510 switch (ev->type()) {
511 case QEvent::ApplicationFontChange:
512 case QEvent::FontChange:
513 setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
514 break;
515 default:
516 break;
518 QPlainTextEdit::changeEvent(ev);
522 * Two file names (possibly full paths) match if the last parts - the file
523 * names - match.
525 bool SourceWindow::fileNameMatches(const QString& other)
527 return QFileInfo(other).fileName() == QFileInfo(m_fileName).fileName();
530 void SourceWindow::disassembled(int lineNo, const std::list<DisassembledCode>& disass)
532 TRACE("disassembled line " + QString().setNum(lineNo));
533 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
534 return;
536 SourceLine& sl = m_sourceCode[lineNo];
538 // copy disassembled code and its addresses
539 sl.disass.resize(disass.size());
540 sl.disassAddr.resize(disass.size());
541 sl.canDisass = !disass.empty();
542 int i = 0;
543 for (std::list<DisassembledCode>::const_iterator c = disass.begin(); c != disass.end(); ++c, ++i)
545 QString code = c->code;
546 while (code.endsWith("\n"))
547 code.truncate(code.length()-1);
548 sl.disass[i] = c->address.asString() + ' ' + code;
549 sl.disassAddr[i] = c->address;
552 int row = lineToRow(lineNo);
553 if (sl.canDisass) {
554 expandRow(row);
555 } else {
556 // clear expansion marker
557 m_lineInfoArea->update();
561 int SourceWindow::rowToLine(int row, int* sourceRow)
563 int line = row >= 0 ? m_rowToLine[row] : -1;
564 if (sourceRow != 0) {
565 // search back until we hit the first entry with the current line number
566 while (row > 0 && m_rowToLine[row-1] == line)
567 row--;
568 *sourceRow = row;
570 return line;
574 * Rows showing diassembled code have the same line number as the
575 * corresponding source code line number. Therefore, the line numbers in
576 * m_rowToLine are monotonically increasing with blocks of equal line
577 * numbers for a source line and its disassembled code that follows it.
579 * Hence, m_rowToLine always obeys the following condition:
581 * m_rowToLine[i] <= i
584 int SourceWindow::lineToRow(int line)
586 // line is zero-based!
588 assert(line < int(m_rowToLine.size()));
590 // quick test for common case
591 if (line < 0 || m_rowToLine[line] == line)
592 return line;
594 assert(m_rowToLine[line] < line);
597 * Binary search between row == line and end of list. In the loop below
598 * we use the fact that the line numbers m_rowToLine do not contain
599 * holes.
601 int l = line;
602 int h = m_rowToLine.size();
603 while (l < h && m_rowToLine[l] != line)
605 assert(h == int(m_rowToLine.size()) || m_rowToLine[l] < m_rowToLine[h]);
608 * We want to round down the midpoint so that we find the
609 * lowest row that belongs to the line we seek.
611 int mid = (l+h)/2;
612 if (m_rowToLine[mid] <= line)
613 l = mid;
614 else
615 h = mid;
617 // Found! Result is in l:
618 assert(m_rowToLine[l] == line);
621 * We might not have hit the lowest index for the line.
623 while (l > 0 && m_rowToLine[l-1] == line)
624 --l;
626 return l;
629 int SourceWindow::lineToRow(int line, const DbgAddr& address)
631 int row = lineToRow(line);
632 if (isRowExpanded(row)) {
633 row += m_sourceCode[line].findAddressRowOffset(address);
635 return row;
638 bool SourceWindow::isRowExpanded(int row)
640 assert(row >= 0);
641 return row < int(m_rowToLine.size())-1 &&
642 m_rowToLine[row] == m_rowToLine[row+1];
645 bool SourceWindow::isRowDisassCode(int row)
647 return row > 0 && row < int(m_rowToLine.size()) &&
648 m_rowToLine[row] == m_rowToLine[row-1];
651 void SourceWindow::expandRow(int row)
653 TRACE("expanding row " + QString().setNum(row));
654 // get disassembled code
655 int line = rowToLine(row);
656 const std::vector<QString>& disass = m_sourceCode[line].disass;
658 // remove PC (must be set again in slot of signal expanded())
659 m_lineItems[row] &= ~(liPC|liPCup);
661 // insert new lines
662 setUpdatesEnabled(false);
663 ++row;
664 m_rowToLine.insert(m_rowToLine.begin()+row, disass.size(), line);
665 m_lineItems.insert(m_lineItems.begin()+row, disass.size(), 0);
667 QTextCursor cursor(document()->findBlockByNumber(row));
668 for (size_t i = 0; i < disass.size(); i++) {
669 cursor.insertText(disass[i]);
670 cursor.insertBlock();
672 setUpdatesEnabled(true);
674 emit expanded(line); /* must set PC */
677 void SourceWindow::collapseRow(int row)
679 TRACE("collapsing row " + QString().setNum(row));
680 int line = rowToLine(row);
682 // find end of this block
683 int end = row+1;
684 while (end < int(m_rowToLine.size()) && m_rowToLine[end] == m_rowToLine[row]) {
685 end++;
687 ++row;
688 setUpdatesEnabled(false);
689 QTextCursor cursor(document()->findBlockByNumber(end-1));
690 while (--end >= row) {
691 m_rowToLine.erase(m_rowToLine.begin()+end);
692 m_lineItems.erase(m_lineItems.begin()+end);
693 cursor.select(QTextCursor::BlockUnderCursor);
694 cursor.removeSelectedText();
696 setUpdatesEnabled(true);
698 emit collapsed(line);
701 void SourceWindow::activeLine(int& line, DbgAddr& address)
703 int row = textCursor().blockNumber();
705 int sourceRow;
706 line = rowToLine(row, &sourceRow);
707 if (row > sourceRow) {
708 int off = row - sourceRow; /* offset from source line */
709 address = m_sourceCode[line].disassAddr[off-1];
714 * Returns the offset from the line displaying the source code to
715 * the line containing the specified address. If the address is not
716 * found, 0 is returned.
718 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
720 if (address.isEmpty())
721 return 0;
723 for (size_t i = 0; i < disassAddr.size(); i++) {
724 if (disassAddr[i] == address) {
725 // found exact address
726 return i+1;
728 if (disassAddr[i] > address) {
730 * We have already advanced too far; the address is before this
731 * index, but obviously we haven't found an exact match
732 * earlier. address is somewhere between the displayed
733 * addresses. We return the previous line.
735 return i;
738 // not found
739 return 0;
742 void SourceWindow::actionExpandRow(int row)
744 if (row < 0 || isRowExpanded(row) || isRowDisassCode(row))
745 return;
747 // disassemble
748 int line = rowToLine(row);
749 const SourceLine& sl = m_sourceCode[line];
750 if (!sl.canDisass)
751 return;
752 if (sl.disass.size() == 0) {
753 emit disassemble(m_fileName, line);
754 } else {
755 expandRow(row);
759 void SourceWindow::actionCollapseRow(int row)
761 if (row < 0 || !isRowExpanded(row) || isRowDisassCode(row))
762 return;
764 collapseRow(row);
767 void SourceWindow::setTabWidth(int numChars)
769 if (numChars <= 0)
770 numChars = 8;
771 QFontMetrics fm(document()->defaultFont());
772 QString s;
773 int w = fm.width(s.fill('x', numChars));
774 setTabStopWidth(w);
777 void SourceWindow::cursorChanged()
779 QList<QTextEdit::ExtraSelection> extraSelections;
780 QTextEdit::ExtraSelection selection;
782 selection.format.setBackground(QColor("#E7E7E7"));
783 selection.format.setProperty(QTextFormat::FullWidthSelection, true);
784 selection.cursor = textCursor();
785 selection.cursor.clearSelection();
786 extraSelections.append(selection);
787 setExtraSelections(extraSelections);
791 * Show our own context menu.
793 void SourceWindow::contextMenuEvent(QContextMenuEvent* e)
795 // get the context menu from the GUI factory
796 QWidget* top = this;
798 top = top->parentWidget();
799 while (!top->isTopLevel());
800 KXmlGuiWindow* mw = static_cast<KXmlGuiWindow*>(top);
801 QMenu* m = static_cast<QMenu*>(mw->factory()->container("popup_files", mw));
802 if (m)
803 m->exec(e->globalPos());
806 void LineInfoArea::paintEvent(QPaintEvent* e)
808 QPainter p(this);
809 static_cast<SourceWindow*>(parent())->drawLineInfoArea(&p, e);
812 void LineInfoArea::mousePressEvent(QMouseEvent* ev)
814 static_cast<SourceWindow*>(parent())->infoMousePress(ev);
817 void LineInfoArea::contextMenuEvent(QContextMenuEvent* e)
819 static_cast<SourceWindow*>(parent())->contextMenuEvent(e);
822 HighlightCpp::HighlightCpp(SourceWindow* srcWnd) :
823 QSyntaxHighlighter(srcWnd->document()),
824 m_srcWnd(srcWnd)
828 enum HLState {
829 hlCommentLine = 1,
830 hlCommentBlock,
831 hlIdent,
832 hlString
835 static const QString ckw[] =
837 "alignas",
838 "alignof",
839 "and",
840 "and_eq",
841 "asm",
842 "auto",
843 "bitand",
844 "bitor",
845 "bool",
846 "break",
847 "case",
848 "catch",
849 "char",
850 "char16_t",
851 "char32_t",
852 "class",
853 "compl",
854 "const",
855 "const_cast",
856 "constexpr",
857 "continue",
858 "decltype",
859 "default",
860 "delete",
861 "do",
862 "double",
863 "dynamic_cast",
864 "else",
865 "enum",
866 "explicit",
867 "export",
868 "extern",
869 "false",
870 "float",
871 "for",
872 "friend",
873 "goto",
874 "if",
875 "inline",
876 "int",
877 "long",
878 "mutable",
879 "namespace",
880 "new",
881 "noexcept",
882 "not",
883 "not_eq",
884 "nullptr",
885 "operator",
886 "or",
887 "or_eq",
888 "private",
889 "protected",
890 "public",
891 "register",
892 "reinterpret_cast",
893 "return",
894 "short",
895 "signed",
896 "sizeof",
897 "static",
898 "static_assert",
899 "static_cast",
900 "struct",
901 "switch",
902 "template",
903 "this",
904 "thread_local",
905 "throw",
906 "true",
907 "try",
908 "typedef",
909 "typeid",
910 "typename",
911 "union",
912 "unsigned",
913 "using",
914 "virtual",
915 "void",
916 "volatile",
917 "wchar_t",
918 "while",
919 "xor",
920 "xor_eq"
923 void HighlightCpp::highlightBlock(const QString& text)
925 int state = previousBlockState();
926 state = highlight(text, state);
927 setCurrentBlockState(state);
930 int HighlightCpp::highlight(const QString& text, int state)
932 // highlight assembly lines
933 if (m_srcWnd->isRowDisassCode(currentBlock().blockNumber()))
935 setFormat(0, text.length(), Qt::blue);
936 return state;
939 if (state == -2) // initial state
940 state = 0;
942 // check for preprocessor line
943 if (state == 0 && text.trimmed().startsWith("#"))
945 setFormat(0, text.length(), QColor("darkgreen"));
946 return 0;
949 // a font for keywords
950 QTextCharFormat identFont;
951 identFont.setFontWeight(QFont::Bold);
953 int start = 0;
954 while (start < text.length())
956 int end;
957 switch (state) {
958 case hlCommentLine:
959 end = text.length();
960 state = 0;
961 setFormat(start, end-start, QColor("gray"));
962 break;
963 case hlCommentBlock:
964 end = text.indexOf("*/", start);
965 if (end >= 0)
966 end += 2, state = 0;
967 else
968 end = text.length();
969 setFormat(start, end-start, QColor("gray"));
970 break;
971 case hlString:
972 for (end = start+1; end < int(text.length()); end++) {
973 if (text[end] == '\\') {
974 if (end < int(text.length()))
975 ++end;
976 } else if (text[end] == text[start]) {
977 ++end;
978 break;
981 state = 0;
982 setFormat(start, end-start, QColor("darkred"));
983 break;
984 case hlIdent:
985 for (end = start+1; end < int(text.length()); end++) {
986 if (!text[end].isLetterOrNumber() && text[end] != '_')
987 break;
989 state = 0;
990 if (isCppKeyword(text.mid(start, end-start)))
992 setFormat(start, end-start, identFont);
993 } else {
994 setFormat(start, end-start, m_srcWnd->palette().color(QPalette::WindowText));
996 break;
997 default:
998 for (end = start; end < int(text.length()); end++)
1000 if (text[end] == '/')
1002 if (end+1 < int(text.length())) {
1003 if (text[end+1] == '/') {
1004 state = hlCommentLine;
1005 break;
1006 } else if (text[end+1] == '*') {
1007 state = hlCommentBlock;
1008 break;
1012 else if (text[end] == '"' || text[end] == '\'')
1014 state = hlString;
1015 break;
1017 else if ((text[end] >= 'A' && text[end] <= 'Z') ||
1018 (text[end] >= 'a' && text[end] <= 'z') ||
1019 text[end] == '_')
1021 state = hlIdent;
1022 break;
1025 setFormat(start, end-start, m_srcWnd->palette().color(QPalette::WindowText));
1027 start = end;
1029 return state;
1032 bool HighlightCpp::isCppKeyword(const QString& word)
1034 #ifndef NDEBUG
1035 // std::binary_search requires the search list to be sorted
1036 static bool keyword_order_verified = false;
1037 if (!keyword_order_verified) {
1038 for (size_t i = 1; i < sizeof(ckw)/sizeof(ckw[0]); ++i) {
1039 if (ckw[i-1] > ckw[i]) {
1040 qDebug("\"%s\" > \"%s\"",
1041 qPrintable(ckw[i-1]), qPrintable(ckw[i]));
1042 assert(0);
1045 keyword_order_verified = true;
1047 #endif
1049 return std::binary_search(ckw, ckw + sizeof(ckw)/sizeof(ckw[0]), word);
1052 #include "sourcewnd.moc"