Extend copyright to 2018.
[kdbg.git] / kdbg / sourcewnd.cpp
blobcde9f2c02bbb117af2fa7f8c679d3ea09140de3b
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 // Center the cursor when moving it into view
36 setCenterOnScroll(true);
38 // load pixmaps
39 m_pcinner = UserIcon("pcinner");
40 m_pcup = UserIcon("pcup");
41 m_brkena = UserIcon("brkena");
42 m_brkdis = UserIcon("brkdis");
43 m_brktmp = UserIcon("brktmp");
44 m_brkcond = UserIcon("brkcond");
45 m_brkorph = UserIcon("brkorph");
46 setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
47 setReadOnly(true);
48 setViewportMargins(lineInfoAreaWidth(), 0, 0 ,0);
49 setWordWrapMode(QTextOption::NoWrap);
50 connect(this, SIGNAL(updateRequest(const QRect&, int)),
51 m_lineInfoArea, SLOT(update()));
52 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(cursorChanged()));
54 // add a syntax highlighter
55 if (QRegExp("\\.(c(pp|c|\\+\\+)?|CC?|h(\\+\\+|h|pp)?|HH?)$").indexIn(m_fileName) >= 0)
57 m_highlighter = new HighlightCpp(this);
61 SourceWindow::~SourceWindow()
63 delete m_highlighter;
66 int SourceWindow::lineInfoAreaWidth() const
68 return 3 + m_widthItems + m_widthPlus + m_widthLineNo;
71 bool SourceWindow::loadFile()
73 QFile f(m_fileName);
74 if (!f.open(QIODevice::ReadOnly)) {
75 return false;
78 QTextStream t(&f);
79 setPlainText(t.readAll());
80 f.close();
82 int n = blockCount();
83 m_sourceCode.resize(n);
84 m_rowToLine.resize(n);
85 for (int i = 0; i < n; i++) {
86 m_rowToLine[i] = i;
88 m_lineItems.resize(n, 0);
90 // set a font for line numbers
91 m_lineNoFont = currentCharFormat().font();
92 m_lineNoFont.setPixelSize(11);
94 return true;
97 void SourceWindow::reloadFile()
99 QFile f(m_fileName);
100 if (!f.open(QIODevice::ReadOnly)) {
101 // open failed; leave alone
102 return;
105 m_sourceCode.clear(); /* clear old text */
107 QTextStream t(&f);
108 setPlainText(t.readAll());
109 f.close();
111 m_sourceCode.resize(blockCount());
112 // expanded lines are collapsed: move existing line items up
113 for (size_t i = 0; i < m_lineItems.size(); i++) {
114 if (m_rowToLine[i] != int(i)) {
115 m_lineItems[m_rowToLine[i]] |= m_lineItems[i];
116 m_lineItems[i] = 0;
119 // allocate line items
120 m_lineItems.resize(m_sourceCode.size(), 0);
122 m_rowToLine.resize(m_sourceCode.size());
123 for (size_t i = 0; i < m_sourceCode.size(); i++)
124 m_rowToLine[i] = i;
126 // Highlighting was applied above when the text was inserted into widget,
127 // but at that time m_rowToLine was not corrected, yet, so that lines
128 // that previously were assembly were painted incorrectly.
129 if (m_highlighter)
130 m_highlighter->rehighlight();
133 void SourceWindow::scrollTo(int lineNo, const DbgAddr& address)
135 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
136 return;
138 int row = lineToRow(lineNo, address);
139 scrollToRow(row);
142 void SourceWindow::scrollToRow(int row)
144 QTextCursor cursor(document()->findBlockByNumber(row));
145 setTextCursor(cursor);
146 ensureCursorVisible();
149 void SourceWindow::resizeEvent(QResizeEvent* e)
151 QPlainTextEdit::resizeEvent(e);
153 QRect cr = contentsRect();
154 cr.setRight(lineInfoAreaWidth());
155 m_lineInfoArea->setGeometry(cr);
158 void SourceWindow::drawLineInfoArea(QPainter* p, QPaintEvent* event)
160 QTextBlock block = firstVisibleBlock();
162 p->setFont(m_lineNoFont);
164 for (; block.isValid(); block = block.next())
166 if (!block.isVisible())
167 continue;
169 QRect r = blockBoundingGeometry(block).translated(contentOffset()).toRect();
170 if (r.bottom() < event->rect().top())
171 continue; // skip blocks that are higher than the region being updated
172 else if (r.top() > event->rect().bottom())
173 break; // all the following blocks are lower then the region being updated
175 int row = block.blockNumber();
176 uchar item = m_lineItems[row];
178 int h = r.height();
179 p->save();
180 p->translate(0, r.top());
182 if (item & liBP) {
183 // enabled breakpoint
184 int y = (h - m_brkena.height())/2;
185 if (y < 0) y = 0;
186 p->drawPixmap(0,y,m_brkena);
188 if (item & liBPdisabled) {
189 // disabled breakpoint
190 int y = (h - m_brkdis.height())/2;
191 if (y < 0) y = 0;
192 p->drawPixmap(0,y,m_brkdis);
194 if (item & liBPtemporary) {
195 // temporary breakpoint marker
196 int y = (h - m_brktmp.height())/2;
197 if (y < 0) y = 0;
198 p->drawPixmap(0,y,m_brktmp);
200 if (item & liBPconditional) {
201 // conditional breakpoint marker
202 int y = (h - m_brkcond.height())/2;
203 if (y < 0) y = 0;
204 p->drawPixmap(0,y,m_brkcond);
206 if (item & liBPorphan) {
207 // orphaned breakpoint marker
208 int y = (h - m_brkcond.height())/2;
209 if (y < 0) y = 0;
210 p->drawPixmap(0,y,m_brkorph);
212 if (item & liPC) {
213 // program counter in innermost frame
214 int y = (h - m_pcinner.height())/2;
215 if (y < 0) y = 0;
216 p->drawPixmap(0,y,m_pcinner);
218 if (item & liPCup) {
219 // program counter somewhere up the stack
220 int y = (h - m_pcup.height())/2;
221 if (y < 0) y = 0;
222 p->drawPixmap(0,y,m_pcup);
224 p->translate(m_widthItems, 0);
225 if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) {
226 int w = m_widthPlus;
227 int x = w/2;
228 int y = h/2;
229 p->drawLine(x-2, y, x+2, y);
230 if (!isRowExpanded(row)) {
231 p->drawLine(x, y-2, x, y+2);
234 p->translate(m_widthPlus, 0);
235 if (!isRowDisassCode(row)) {
236 p->drawText(0, 0, m_widthLineNo, h, Qt::AlignRight|Qt::AlignVCenter,
237 QString().setNum(rowToLine(row)+1));
239 p->restore();
243 void SourceWindow::updateLineItems(const KDebugger* dbg)
245 // clear outdated breakpoints
246 for (int i = m_lineItems.size()-1; i >= 0; i--) {
247 if (m_lineItems[i] & liBPany) {
248 // check if this breakpoint still exists
249 int line = rowToLine(i);
250 TRACE(QString().sprintf("checking for bp at %d", line));
251 KDebugger::BrkptROIterator bp = dbg->breakpointsBegin();
252 for (; bp != dbg->breakpointsEnd(); ++bp)
254 if (bp->lineNo == line &&
255 fileNameMatches(bp->fileName) &&
256 lineToRow(line, bp->address) == i)
258 // yes it exists; mode is changed below
259 break;
262 if (bp == dbg->breakpointsEnd()) {
263 /* doesn't exist anymore, remove it */
264 m_lineItems[i] &= ~liBPany;
269 // add new breakpoints
270 for (KDebugger::BrkptROIterator bp = dbg->breakpointsBegin(); bp != dbg->breakpointsEnd(); ++bp)
272 if (fileNameMatches(bp->fileName)) {
273 TRACE(QString("updating %2:%1").arg(bp->lineNo).arg(bp->fileName));
274 int i = bp->lineNo;
275 if (i < 0 || i >= int(m_sourceCode.size()))
276 continue;
277 // compute new line item flags for breakpoint
278 uchar flags = bp->enabled ? liBP : liBPdisabled;
279 if (bp->temporary)
280 flags |= liBPtemporary;
281 if (!bp->condition.isEmpty() || bp->ignoreCount != 0)
282 flags |= liBPconditional;
283 if (bp->isOrphaned())
284 flags |= liBPorphan;
285 // update if changed
286 int row = lineToRow(i, bp->address);
287 if ((m_lineItems[row] & liBPany) != flags) {
288 m_lineItems[row] &= ~liBPany;
289 m_lineItems[row] |= flags;
293 m_lineInfoArea->update();
296 void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
298 if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
299 return;
302 int row = lineToRow(lineNo, address);
304 uchar flag = frameNo == 0 ? liPC : liPCup;
305 if (set) {
306 // set only if not already set
307 if ((m_lineItems[row] & flag) == 0) {
308 m_lineItems[row] |= flag;
309 m_lineInfoArea->update();
311 } else {
312 // clear only if not set
313 if ((m_lineItems[row] & flag) != 0) {
314 m_lineItems[row] &= ~flag;
315 m_lineInfoArea->update();
320 void SourceWindow::find(const QString& text, bool caseSensitive, FindDirection dir)
322 ASSERT(dir == 1 || dir == -1);
323 QTextDocument::FindFlags flags = 0;
324 if (caseSensitive)
325 flags |= QTextDocument::FindCaseSensitively;
326 if (dir < 0)
327 flags |= QTextDocument::FindBackward;
328 if (QPlainTextEdit::find(text, flags))
329 return;
330 // not found; wrap around
331 QTextCursor cursor(document());
332 if (dir < 0)
333 cursor.movePosition(QTextCursor::End);
334 cursor = document()->find(text, cursor, flags);
335 if (!cursor.isNull())
336 setTextCursor(cursor);
339 void SourceWindow::infoMousePress(QMouseEvent* ev)
341 // we handle left and middle button
342 if (ev->button() != Qt::LeftButton && ev->button() != Qt::MidButton)
344 return;
347 // get row
348 int row = cursorForPosition(QPoint(0, ev->y())).blockNumber();
349 if (row < 0)
350 return;
352 if (ev->x() > m_widthItems)
354 if (isRowExpanded(row)) {
355 actionCollapseRow(row);
356 } else {
357 actionExpandRow(row);
359 return;
362 int sourceRow;
363 int line = rowToLine(row, &sourceRow);
365 // find address if row is disassembled code
366 DbgAddr address;
367 if (row > sourceRow) {
368 // get offset from source code line
369 int off = row - sourceRow;
370 address = m_sourceCode[line].disassAddr[off-1];
373 switch (ev->button()) {
374 case Qt::LeftButton:
375 TRACE(QString().sprintf("left-clicked line %d", line));
376 emit clickedLeft(m_fileName, line, address,
377 (ev->modifiers() & Qt::ShiftModifier) != 0);
378 break;
379 case Qt::MidButton:
380 TRACE(QString().sprintf("mid-clicked row %d", line));
381 emit clickedMid(m_fileName, line, address);
382 break;
383 default:;
387 void SourceWindow::keyPressEvent(QKeyEvent* ev)
389 int top1 = 0, top2;
390 switch (ev->key()) {
391 case Qt::Key_Plus:
392 actionExpandRow(textCursor().blockNumber());
393 return;
394 case Qt::Key_Minus:
395 actionCollapseRow(textCursor().blockNumber());
396 return;
397 case Qt::Key_Up:
398 case Qt::Key_K:
399 moveCursor(QTextCursor::PreviousBlock);
400 return;
401 case Qt::Key_Down:
402 case Qt::Key_J:
403 moveCursor(QTextCursor::NextBlock);
404 return;
405 case Qt::Key_Home:
406 moveCursor(QTextCursor::Start);
407 return;
408 case Qt::Key_End:
409 moveCursor(QTextCursor::End);
410 return;
411 case Qt::Key_PageUp:
412 case Qt::Key_PageDown:
413 top1 = firstVisibleBlock().blockNumber();
416 QPlainTextEdit::keyPressEvent(ev);
418 switch (ev->key()) {
419 case Qt::Key_PageUp:
420 case Qt::Key_PageDown:
421 top2 = firstVisibleBlock().blockNumber();
423 QTextCursor cursor = textCursor();
424 if (top1 < top2)
425 cursor.movePosition(QTextCursor::NextBlock,
426 QTextCursor::MoveAnchor, top2-top1);
427 else
428 cursor.movePosition(QTextCursor::PreviousBlock,
429 QTextCursor::MoveAnchor, top1-top2);
430 setTextCursor(cursor);
435 QString SourceWindow::extendExpr(const QString &plainText,
436 int wordStart,
437 int wordEnd)
439 QString document = plainText.left(wordEnd);
440 QString word = plainText.mid(wordStart, wordEnd - wordStart);
441 QRegExp regex = QRegExp("(::)?([A-Za-z_]{1}\\w*\\s*(->|\\.|::)\\s*)*" + word + "$");
443 #define IDENTIFIER_MAX_SIZE 256
444 // cut the document to reduce size of string to scan
445 // because of this only identifiefs of length <= IDENTIFIER_MAX_SIZE are supported
446 if (document.length() > IDENTIFIER_MAX_SIZE) {
447 document.right(IDENTIFIER_MAX_SIZE);
450 const int index = regex.indexIn(document);
452 if (index == -1)
454 TRACE("No match, returning " + word);
456 else
458 const int length = regex.matchedLength();
460 word = plainText.mid(index, length);
461 TRACE("Matched, returning " + word);
464 return word;
467 bool SourceWindow::wordAtPoint(const QPoint& p, QString& word, QRect& r)
469 QTextCursor cursor = cursorForPosition(viewport()->mapFrom(this, p));
470 if (cursor.isNull())
471 return false;
473 cursor.select(QTextCursor::WordUnderCursor);
474 word = cursor.selectedText();
476 if (word.isEmpty())
477 return false;
479 // keep only letters and digits
480 QRegExp w("[A-Za-z_]{1}[\\dA-Za-z_]*");
481 if (w.indexIn(word) < 0)
482 return false;
484 word = w.cap();
486 if (m_highlighter)
488 // if cpp highlighter is enabled - c/c++ file is being displayed
490 // check that word is not a c/c++ keyword
491 if (m_highlighter->isCppKeyword(word))
492 return false;
494 // TODO check that cursor is on top of a string literal
495 // and don't display any tooltips in this case
497 // try to extend selected word under the cursor to get a full variable name
498 word = extendExpr(cursor.document()->toPlainText(),
499 cursor.selectionStart(),
500 cursor.selectionEnd());
502 if (word.isEmpty())
503 return false;
506 r = QRect(p, p);
507 r.adjust(-5,-5,5,5);
508 return true;
511 void SourceWindow::changeEvent(QEvent* ev)
513 switch (ev->type()) {
514 case QEvent::ApplicationFontChange:
515 case QEvent::FontChange:
516 setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
517 break;
518 default:
519 break;
521 QPlainTextEdit::changeEvent(ev);
525 * Two file names (possibly full paths) match if the last parts - the file
526 * names - match.
528 bool SourceWindow::fileNameMatches(const QString& other)
530 return QFileInfo(other).fileName() == QFileInfo(m_fileName).fileName();
533 void SourceWindow::disassembled(int lineNo, const std::list<DisassembledCode>& disass)
535 TRACE("disassembled line " + QString().setNum(lineNo));
536 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
537 return;
539 SourceLine& sl = m_sourceCode[lineNo];
541 // copy disassembled code and its addresses
542 sl.disass.resize(disass.size());
543 sl.disassAddr.resize(disass.size());
544 sl.canDisass = !disass.empty();
545 int i = 0;
546 for (std::list<DisassembledCode>::const_iterator c = disass.begin(); c != disass.end(); ++c, ++i)
548 QString code = c->code;
549 while (code.endsWith("\n"))
550 code.truncate(code.length()-1);
551 sl.disass[i] = c->address.asString() + ' ' + code;
552 sl.disassAddr[i] = c->address;
555 int row = lineToRow(lineNo);
556 if (sl.canDisass) {
557 expandRow(row);
558 } else {
559 // clear expansion marker
560 m_lineInfoArea->update();
564 int SourceWindow::rowToLine(int row, int* sourceRow)
566 int line = row >= 0 ? m_rowToLine[row] : -1;
567 if (sourceRow != 0) {
568 // search back until we hit the first entry with the current line number
569 while (row > 0 && m_rowToLine[row-1] == line)
570 row--;
571 *sourceRow = row;
573 return line;
577 * Rows showing diassembled code have the same line number as the
578 * corresponding source code line number. Therefore, the line numbers in
579 * m_rowToLine are monotonically increasing with blocks of equal line
580 * numbers for a source line and its disassembled code that follows it.
582 * Hence, m_rowToLine always obeys the following condition:
584 * m_rowToLine[i] <= i
587 int SourceWindow::lineToRow(int line)
589 // line is zero-based!
591 assert(line < int(m_rowToLine.size()));
593 // quick test for common case
594 if (line < 0 || m_rowToLine[line] == line)
595 return line;
597 assert(m_rowToLine[line] < line);
600 * Binary search between row == line and end of list. In the loop below
601 * we use the fact that the line numbers m_rowToLine do not contain
602 * holes.
604 int l = line;
605 int h = m_rowToLine.size();
606 while (l < h && m_rowToLine[l] != line)
608 assert(h == int(m_rowToLine.size()) || m_rowToLine[l] < m_rowToLine[h]);
611 * We want to round down the midpoint so that we find the
612 * lowest row that belongs to the line we seek.
614 int mid = (l+h)/2;
615 if (m_rowToLine[mid] <= line)
616 l = mid;
617 else
618 h = mid;
620 // Found! Result is in l:
621 assert(m_rowToLine[l] == line);
624 * We might not have hit the lowest index for the line.
626 while (l > 0 && m_rowToLine[l-1] == line)
627 --l;
629 return l;
632 int SourceWindow::lineToRow(int line, const DbgAddr& address)
634 int row = lineToRow(line);
635 if (isRowExpanded(row)) {
636 row += m_sourceCode[line].findAddressRowOffset(address);
638 return row;
641 bool SourceWindow::isRowExpanded(int row)
643 assert(row >= 0);
644 return row < int(m_rowToLine.size())-1 &&
645 m_rowToLine[row] == m_rowToLine[row+1];
648 bool SourceWindow::isRowDisassCode(int row)
650 return row > 0 && row < int(m_rowToLine.size()) &&
651 m_rowToLine[row] == m_rowToLine[row-1];
654 void SourceWindow::expandRow(int row)
656 TRACE("expanding row " + QString().setNum(row));
657 // get disassembled code
658 int line = rowToLine(row);
659 const std::vector<QString>& disass = m_sourceCode[line].disass;
661 // remove PC (must be set again in slot of signal expanded())
662 m_lineItems[row] &= ~(liPC|liPCup);
664 // insert new lines
665 setUpdatesEnabled(false);
666 ++row;
667 m_rowToLine.insert(m_rowToLine.begin()+row, disass.size(), line);
668 m_lineItems.insert(m_lineItems.begin()+row, disass.size(), 0);
670 QTextCursor cursor(document()->findBlockByNumber(row));
671 for (size_t i = 0; i < disass.size(); i++) {
672 cursor.insertText(disass[i]);
673 cursor.insertBlock();
675 setUpdatesEnabled(true);
677 emit expanded(line); /* must set PC */
680 void SourceWindow::collapseRow(int row)
682 TRACE("collapsing row " + QString().setNum(row));
683 int line = rowToLine(row);
685 // find end of this block
686 int end = row+1;
687 while (end < int(m_rowToLine.size()) && m_rowToLine[end] == m_rowToLine[row]) {
688 end++;
690 ++row;
691 setUpdatesEnabled(false);
692 QTextCursor cursor(document()->findBlockByNumber(end-1));
693 while (--end >= row) {
694 m_rowToLine.erase(m_rowToLine.begin()+end);
695 m_lineItems.erase(m_lineItems.begin()+end);
696 cursor.select(QTextCursor::BlockUnderCursor);
697 cursor.removeSelectedText();
699 setUpdatesEnabled(true);
701 emit collapsed(line);
704 void SourceWindow::activeLine(int& line, DbgAddr& address)
706 int row = textCursor().blockNumber();
708 int sourceRow;
709 line = rowToLine(row, &sourceRow);
710 if (row > sourceRow) {
711 int off = row - sourceRow; /* offset from source line */
712 address = m_sourceCode[line].disassAddr[off-1];
717 * Returns the offset from the line displaying the source code to
718 * the line containing the specified address. If the address is not
719 * found, 0 is returned.
721 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
723 if (address.isEmpty())
724 return 0;
726 for (size_t i = 0; i < disassAddr.size(); i++) {
727 if (disassAddr[i] == address) {
728 // found exact address
729 return i+1;
731 if (disassAddr[i] > address) {
733 * We have already advanced too far; the address is before this
734 * index, but obviously we haven't found an exact match
735 * earlier. address is somewhere between the displayed
736 * addresses. We return the previous line.
738 return i;
741 // not found
742 return 0;
745 void SourceWindow::actionExpandRow(int row)
747 if (row < 0 || isRowExpanded(row) || isRowDisassCode(row))
748 return;
750 // disassemble
751 int line = rowToLine(row);
752 const SourceLine& sl = m_sourceCode[line];
753 if (!sl.canDisass)
754 return;
755 if (sl.disass.size() == 0) {
756 emit disassemble(m_fileName, line);
757 } else {
758 expandRow(row);
762 void SourceWindow::actionCollapseRow(int row)
764 if (row < 0 || !isRowExpanded(row) || isRowDisassCode(row))
765 return;
767 collapseRow(row);
770 void SourceWindow::setTabWidth(int numChars)
772 if (numChars <= 0)
773 numChars = 8;
774 QFontMetrics fm(document()->defaultFont());
775 QString s;
776 int w = fm.width(s.fill('x', numChars));
777 setTabStopWidth(w);
780 void SourceWindow::cursorChanged()
782 QList<QTextEdit::ExtraSelection> extraSelections;
783 QTextEdit::ExtraSelection selection;
785 selection.format.setBackground(QColor("#E7E7E7"));
786 selection.format.setProperty(QTextFormat::FullWidthSelection, true);
787 selection.cursor = textCursor();
788 selection.cursor.clearSelection();
789 extraSelections.append(selection);
790 setExtraSelections(extraSelections);
794 * Show our own context menu.
796 void SourceWindow::contextMenuEvent(QContextMenuEvent* e)
798 // get the context menu from the GUI factory
799 QWidget* top = this;
801 top = top->parentWidget();
802 while (!top->isTopLevel());
803 KXmlGuiWindow* mw = static_cast<KXmlGuiWindow*>(top);
804 QMenu* m = static_cast<QMenu*>(mw->factory()->container("popup_files", mw));
805 if (m)
806 m->exec(e->globalPos());
809 void LineInfoArea::paintEvent(QPaintEvent* e)
811 QPainter p(this);
812 static_cast<SourceWindow*>(parent())->drawLineInfoArea(&p, e);
815 void LineInfoArea::mousePressEvent(QMouseEvent* ev)
817 static_cast<SourceWindow*>(parent())->infoMousePress(ev);
820 void LineInfoArea::contextMenuEvent(QContextMenuEvent* e)
822 static_cast<SourceWindow*>(parent())->contextMenuEvent(e);
825 HighlightCpp::HighlightCpp(SourceWindow* srcWnd) :
826 QSyntaxHighlighter(srcWnd->document()),
827 m_srcWnd(srcWnd)
831 enum HLState {
832 hlCommentLine = 1,
833 hlCommentBlock,
834 hlIdent,
835 hlString
838 static const QString ckw[] =
840 "alignas",
841 "alignof",
842 "and",
843 "and_eq",
844 "asm",
845 "auto",
846 "bitand",
847 "bitor",
848 "bool",
849 "break",
850 "case",
851 "catch",
852 "char",
853 "char16_t",
854 "char32_t",
855 "class",
856 "compl",
857 "const",
858 "const_cast",
859 "constexpr",
860 "continue",
861 "decltype",
862 "default",
863 "delete",
864 "do",
865 "double",
866 "dynamic_cast",
867 "else",
868 "enum",
869 "explicit",
870 "export",
871 "extern",
872 "false",
873 "float",
874 "for",
875 "friend",
876 "goto",
877 "if",
878 "inline",
879 "int",
880 "long",
881 "mutable",
882 "namespace",
883 "new",
884 "noexcept",
885 "not",
886 "not_eq",
887 "nullptr",
888 "operator",
889 "or",
890 "or_eq",
891 "private",
892 "protected",
893 "public",
894 "register",
895 "reinterpret_cast",
896 "return",
897 "short",
898 "signed",
899 "sizeof",
900 "static",
901 "static_assert",
902 "static_cast",
903 "struct",
904 "switch",
905 "template",
906 "this",
907 "thread_local",
908 "throw",
909 "true",
910 "try",
911 "typedef",
912 "typeid",
913 "typename",
914 "union",
915 "unsigned",
916 "using",
917 "virtual",
918 "void",
919 "volatile",
920 "wchar_t",
921 "while",
922 "xor",
923 "xor_eq"
926 void HighlightCpp::highlightBlock(const QString& text)
928 int state = previousBlockState();
929 state = highlight(text, state);
930 setCurrentBlockState(state);
933 int HighlightCpp::highlight(const QString& text, int state)
935 // highlight assembly lines
936 if (m_srcWnd->isRowDisassCode(currentBlock().blockNumber()))
938 setFormat(0, text.length(), Qt::blue);
939 return state;
942 if (state == -2) // initial state
943 state = 0;
945 // check for preprocessor line
946 if (state == 0 && text.trimmed().startsWith("#"))
948 setFormat(0, text.length(), QColor("darkgreen"));
949 return 0;
952 // a font for keywords
953 QTextCharFormat identFont;
954 identFont.setFontWeight(QFont::Bold);
956 int start = 0;
957 while (start < text.length())
959 int end;
960 switch (state) {
961 case hlCommentLine:
962 end = text.length();
963 state = 0;
964 setFormat(start, end-start, QColor("gray"));
965 break;
966 case hlCommentBlock:
967 end = text.indexOf("*/", start);
968 if (end >= 0)
969 end += 2, state = 0;
970 else
971 end = text.length();
972 setFormat(start, end-start, QColor("gray"));
973 break;
974 case hlString:
975 for (end = start+1; end < int(text.length()); end++) {
976 if (text[end] == '\\') {
977 if (end < int(text.length()))
978 ++end;
979 } else if (text[end] == text[start]) {
980 ++end;
981 break;
984 state = 0;
985 setFormat(start, end-start, QColor("darkred"));
986 break;
987 case hlIdent:
988 for (end = start+1; end < int(text.length()); end++) {
989 if (!text[end].isLetterOrNumber() && text[end] != '_')
990 break;
992 state = 0;
993 if (isCppKeyword(text.mid(start, end-start)))
995 setFormat(start, end-start, identFont);
996 } else {
997 setFormat(start, end-start, m_srcWnd->palette().color(QPalette::WindowText));
999 break;
1000 default:
1001 for (end = start; end < int(text.length()); end++)
1003 if (text[end] == '/')
1005 if (end+1 < int(text.length())) {
1006 if (text[end+1] == '/') {
1007 state = hlCommentLine;
1008 break;
1009 } else if (text[end+1] == '*') {
1010 state = hlCommentBlock;
1011 break;
1015 else if (text[end] == '"' || text[end] == '\'')
1017 state = hlString;
1018 break;
1020 else if ((text[end] >= 'A' && text[end] <= 'Z') ||
1021 (text[end] >= 'a' && text[end] <= 'z') ||
1022 text[end] == '_')
1024 state = hlIdent;
1025 break;
1028 setFormat(start, end-start, m_srcWnd->palette().color(QPalette::WindowText));
1030 start = end;
1032 return state;
1035 bool HighlightCpp::isCppKeyword(const QString& word)
1037 #ifndef NDEBUG
1038 // std::binary_search requires the search list to be sorted
1039 static bool keyword_order_verified = false;
1040 if (!keyword_order_verified) {
1041 for (size_t i = 1; i < sizeof(ckw)/sizeof(ckw[0]); ++i) {
1042 if (ckw[i-1] > ckw[i]) {
1043 qDebug("\"%s\" > \"%s\"",
1044 qPrintable(ckw[i-1]), qPrintable(ckw[i]));
1045 assert(0);
1048 keyword_order_verified = true;
1050 #endif
1052 return std::binary_search(ckw, ckw + sizeof(ckw)/sizeof(ckw[0]), word);
1055 #include "sourcewnd.moc"