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 */
6
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"
24
25
SourceWindow(const QString & fileName,QWidget * parent)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))
34 {
35 // Center the cursor when moving it into view
36 setCenterOnScroll(true);
37
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()));
53
54 // add a syntax highlighter
55 if (QRegExp("\\.(c(pp|c|\\+\\+)?|CC?|h(\\+\\+|h|pp)?|HH?)$").indexIn(m_fileName) >= 0)
56 {
57 m_highlighter = new HighlightCpp(this);
58 }
59 }
60
~SourceWindow()61 SourceWindow::~SourceWindow()
62 {
63 delete m_highlighter;
64 }
65
lineInfoAreaWidth() const66 int SourceWindow::lineInfoAreaWidth() const
67 {
68 return 3 + m_widthItems + m_widthPlus + m_widthLineNo;
69 }
70
loadFile()71 bool SourceWindow::loadFile()
72 {
73 QFile f(m_fileName);
74 if (!f.open(QIODevice::ReadOnly)) {
75 return false;
76 }
77
78 QTextStream t(&f);
79 setPlainText(t.readAll());
80 f.close();
81
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;
87 }
88 m_lineItems.resize(n, 0);
89
90 // set a font for line numbers
91 m_lineNoFont = currentCharFormat().font();
92 m_lineNoFont.setPixelSize(11);
93
94 return true;
95 }
96
reloadFile()97 void SourceWindow::reloadFile()
98 {
99 QFile f(m_fileName);
100 if (!f.open(QIODevice::ReadOnly)) {
101 // open failed; leave alone
102 return;
103 }
104
105 m_sourceCode.clear(); /* clear old text */
106
107 QTextStream t(&f);
108 setPlainText(t.readAll());
109 f.close();
110
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;
117 }
118 }
119 // allocate line items
120 m_lineItems.resize(m_sourceCode.size(), 0);
121
122 m_rowToLine.resize(m_sourceCode.size());
123 for (size_t i = 0; i < m_sourceCode.size(); i++)
124 m_rowToLine[i] = i;
125
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();
131 }
132
scrollTo(int lineNo,const DbgAddr & address)133 void SourceWindow::scrollTo(int lineNo, const DbgAddr& address)
134 {
135 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
136 return;
137
138 int row = lineToRow(lineNo, address);
139 scrollToRow(row);
140 }
141
scrollToRow(int row)142 void SourceWindow::scrollToRow(int row)
143 {
144 QTextCursor cursor(document()->findBlockByNumber(row));
145 setTextCursor(cursor);
146 ensureCursorVisible();
147 }
148
resizeEvent(QResizeEvent * e)149 void SourceWindow::resizeEvent(QResizeEvent* e)
150 {
151 QPlainTextEdit::resizeEvent(e);
152
153 QRect cr = contentsRect();
154 cr.setRight(lineInfoAreaWidth());
155 m_lineInfoArea->setGeometry(cr);
156 }
157
drawLineInfoArea(QPainter * p,QPaintEvent * event)158 void SourceWindow::drawLineInfoArea(QPainter* p, QPaintEvent* event)
159 {
160 QTextBlock block = firstVisibleBlock();
161
162 p->setFont(m_lineNoFont);
163
164 for (; block.isValid(); block = block.next())
165 {
166 if (!block.isVisible())
167 continue;
168
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
174
175 int row = block.blockNumber();
176 uchar item = m_lineItems[row];
177
178 int h = r.height();
179 p->save();
180 p->translate(0, r.top());
181
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);
187 }
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);
193 }
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);
199 }
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);
205 }
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);
211 }
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);
217 }
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);
223 }
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);
232 }
233 }
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));
238 }
239 p->restore();
240 }
241 }
242
updateLineItems(const KDebugger * dbg)243 void SourceWindow::updateLineItems(const KDebugger* dbg)
244 {
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)
253 {
254 if (bp->lineNo == line &&
255 fileNameMatches(bp->fileName) &&
256 lineToRow(line, bp->address) == i)
257 {
258 // yes it exists; mode is changed below
259 break;
260 }
261 }
262 if (bp == dbg->breakpointsEnd()) {
263 /* doesn't exist anymore, remove it */
264 m_lineItems[i] &= ~liBPany;
265 }
266 }
267 }
268
269 // add new breakpoints
270 for (KDebugger::BrkptROIterator bp = dbg->breakpointsBegin(); bp != dbg->breakpointsEnd(); ++bp)
271 {
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;
290 }
291 }
292 }
293 m_lineInfoArea->update();
294 }
295
setPC(bool set,int lineNo,const DbgAddr & address,int frameNo)296 void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo)
297 {
298 if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) {
299 return;
300 }
301
302 int row = lineToRow(lineNo, address);
303
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();
310 }
311 } else {
312 // clear only if not set
313 if ((m_lineItems[row] & flag) != 0) {
314 m_lineItems[row] &= ~flag;
315 m_lineInfoArea->update();
316 }
317 }
318 }
319
find(const QString & text,bool caseSensitive,FindDirection dir)320 void SourceWindow::find(const QString& text, bool caseSensitive, FindDirection dir)
321 {
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);
337 }
338
infoMousePress(QMouseEvent * ev)339 void SourceWindow::infoMousePress(QMouseEvent* ev)
340 {
341 // we handle left and middle button
342 if (ev->button() != Qt::LeftButton && ev->button() != Qt::MidButton)
343 {
344 return;
345 }
346
347 // get row
348 int row = cursorForPosition(QPoint(0, ev->y())).blockNumber();
349 if (row < 0)
350 return;
351
352 if (ev->x() > m_widthItems)
353 {
354 if (isRowExpanded(row)) {
355 actionCollapseRow(row);
356 } else {
357 actionExpandRow(row);
358 }
359 return;
360 }
361
362 int sourceRow;
363 int line = rowToLine(row, &sourceRow);
364
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];
371 }
372
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:;
384 }
385 }
386
keyPressEvent(QKeyEvent * ev)387 void SourceWindow::keyPressEvent(QKeyEvent* ev)
388 {
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();
414 }
415
416 QPlainTextEdit::keyPressEvent(ev);
417
418 switch (ev->key()) {
419 case Qt::Key_PageUp:
420 case Qt::Key_PageDown:
421 top2 = firstVisibleBlock().blockNumber();
422 {
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);
431 }
432 }
433 }
434
extendExpr(const QString & plainText,int wordStart,int wordEnd)435 QString SourceWindow::extendExpr(const QString &plainText,
436 int wordStart,
437 int wordEnd)
438 {
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 + "$");
442
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);
448 }
449
450 const int index = regex.indexIn(document);
451
452 if (index == -1)
453 {
454 TRACE("No match, returning " + word);
455 }
456 else
457 {
458 const int length = regex.matchedLength();
459
460 word = plainText.mid(index, length);
461 TRACE("Matched, returning " + word);
462 }
463
464 return word;
465 }
466
wordAtPoint(const QPoint & p,QString & word,QRect & r)467 bool SourceWindow::wordAtPoint(const QPoint& p, QString& word, QRect& r)
468 {
469 QTextCursor cursor = cursorForPosition(viewport()->mapFrom(this, p));
470 if (cursor.isNull())
471 return false;
472
473 cursor.select(QTextCursor::WordUnderCursor);
474 word = cursor.selectedText();
475
476 if (word.isEmpty())
477 return false;
478
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;
483
484 word = w.cap();
485
486 if (m_highlighter)
487 {
488 // if cpp highlighter is enabled - c/c++ file is being displayed
489
490 // check that word is not a c/c++ keyword
491 if (m_highlighter->isCppKeyword(word))
492 return false;
493
494 // TODO check that cursor is on top of a string literal
495 // and don't display any tooltips in this case
496
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());
501
502 if (word.isEmpty())
503 return false;
504 }
505
506 r = QRect(p, p);
507 r.adjust(-5,-5,5,5);
508 return true;
509 }
510
changeEvent(QEvent * ev)511 void SourceWindow::changeEvent(QEvent* ev)
512 {
513 switch (ev->type()) {
514 case QEvent::ApplicationFontChange:
515 case QEvent::FontChange:
516 setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
517 break;
518 default:
519 break;
520 }
521 QPlainTextEdit::changeEvent(ev);
522 }
523
524 /*
525 * Two file names (possibly full paths) match if the last parts - the file
526 * names - match.
527 */
fileNameMatches(const QString & other)528 bool SourceWindow::fileNameMatches(const QString& other)
529 {
530 return QFileInfo(other).fileName() == QFileInfo(m_fileName).fileName();
531 }
532
disassembled(int lineNo,const std::list<DisassembledCode> & disass)533 void SourceWindow::disassembled(int lineNo, const std::list<DisassembledCode>& disass)
534 {
535 TRACE("disassembled line " + QString().setNum(lineNo));
536 if (lineNo < 0 || lineNo >= int(m_sourceCode.size()))
537 return;
538
539 SourceLine& sl = m_sourceCode[lineNo];
540
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)
547 {
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;
553 }
554
555 int row = lineToRow(lineNo);
556 if (sl.canDisass) {
557 expandRow(row);
558 } else {
559 // clear expansion marker
560 m_lineInfoArea->update();
561 }
562 }
563
rowToLine(int row,int * sourceRow)564 int SourceWindow::rowToLine(int row, int* sourceRow)
565 {
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;
572 }
573 return line;
574 }
575
576 /*
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.
581 *
582 * Hence, m_rowToLine always obeys the following condition:
583 *
584 * m_rowToLine[i] <= i
585 */
586
lineToRow(int line)587 int SourceWindow::lineToRow(int line)
588 {
589 // line is zero-based!
590
591 assert(line < int(m_rowToLine.size()));
592
593 // quick test for common case
594 if (line < 0 || m_rowToLine[line] == line)
595 return line;
596
597 assert(m_rowToLine[line] < line);
598
599 /*
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.
603 */
604 int l = line;
605 int h = m_rowToLine.size();
606 while (l < h && m_rowToLine[l] != line)
607 {
608 assert(h == int(m_rowToLine.size()) || m_rowToLine[l] < m_rowToLine[h]);
609
610 /*
611 * We want to round down the midpoint so that we find the
612 * lowest row that belongs to the line we seek.
613 */
614 int mid = (l+h)/2;
615 if (m_rowToLine[mid] <= line)
616 l = mid;
617 else
618 h = mid;
619 }
620 // Found! Result is in l:
621 assert(m_rowToLine[l] == line);
622
623 /*
624 * We might not have hit the lowest index for the line.
625 */
626 while (l > 0 && m_rowToLine[l-1] == line)
627 --l;
628
629 return l;
630 }
631
lineToRow(int line,const DbgAddr & address)632 int SourceWindow::lineToRow(int line, const DbgAddr& address)
633 {
634 int row = lineToRow(line);
635 if (isRowExpanded(row)) {
636 row += m_sourceCode[line].findAddressRowOffset(address);
637 }
638 return row;
639 }
640
isRowExpanded(int row)641 bool SourceWindow::isRowExpanded(int row)
642 {
643 assert(row >= 0);
644 return row < int(m_rowToLine.size())-1 &&
645 m_rowToLine[row] == m_rowToLine[row+1];
646 }
647
isRowDisassCode(int row)648 bool SourceWindow::isRowDisassCode(int row)
649 {
650 return row > 0 && row < int(m_rowToLine.size()) &&
651 m_rowToLine[row] == m_rowToLine[row-1];
652 }
653
expandRow(int row)654 void SourceWindow::expandRow(int row)
655 {
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;
660
661 // remove PC (must be set again in slot of signal expanded())
662 m_lineItems[row] &= ~(liPC|liPCup);
663
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);
669
670 QTextCursor cursor(document()->findBlockByNumber(row));
671 for (size_t i = 0; i < disass.size(); i++) {
672 cursor.insertText(disass[i]);
673 cursor.insertBlock();
674 }
675 setUpdatesEnabled(true);
676
677 emit expanded(line); /* must set PC */
678 }
679
collapseRow(int row)680 void SourceWindow::collapseRow(int row)
681 {
682 TRACE("collapsing row " + QString().setNum(row));
683 int line = rowToLine(row);
684
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++;
689 }
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();
698 }
699 setUpdatesEnabled(true);
700
701 emit collapsed(line);
702 }
703
activeLine(int & line,DbgAddr & address)704 void SourceWindow::activeLine(int& line, DbgAddr& address)
705 {
706 int row = textCursor().blockNumber();
707
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];
713 }
714 }
715
716 /**
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.
720 */
findAddressRowOffset(const DbgAddr & address) const721 int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const
722 {
723 if (address.isEmpty())
724 return 0;
725
726 for (size_t i = 0; i < disassAddr.size(); i++) {
727 if (disassAddr[i] == address) {
728 // found exact address
729 return i+1;
730 }
731 if (disassAddr[i] > address) {
732 /*
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.
737 */
738 return i;
739 }
740 }
741 // not found
742 return 0;
743 }
744
actionExpandRow(int row)745 void SourceWindow::actionExpandRow(int row)
746 {
747 if (row < 0 || isRowExpanded(row) || isRowDisassCode(row))
748 return;
749
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);
759 }
760 }
761
actionCollapseRow(int row)762 void SourceWindow::actionCollapseRow(int row)
763 {
764 if (row < 0 || !isRowExpanded(row) || isRowDisassCode(row))
765 return;
766
767 collapseRow(row);
768 }
769
setTabWidth(int numChars)770 void SourceWindow::setTabWidth(int numChars)
771 {
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);
778 }
779
cursorChanged()780 void SourceWindow::cursorChanged()
781 {
782 QList<QTextEdit::ExtraSelection> extraSelections;
783 QTextEdit::ExtraSelection selection;
784
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);
791 }
792
793 /*
794 * Show our own context menu.
795 */
contextMenuEvent(QContextMenuEvent * e)796 void SourceWindow::contextMenuEvent(QContextMenuEvent* e)
797 {
798 // get the context menu from the GUI factory
799 QWidget* top = this;
800 do
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());
807 }
808
paintEvent(QPaintEvent * e)809 void LineInfoArea::paintEvent(QPaintEvent* e)
810 {
811 QPainter p(this);
812 static_cast<SourceWindow*>(parent())->drawLineInfoArea(&p, e);
813 }
814
mousePressEvent(QMouseEvent * ev)815 void LineInfoArea::mousePressEvent(QMouseEvent* ev)
816 {
817 static_cast<SourceWindow*>(parent())->infoMousePress(ev);
818 }
819
contextMenuEvent(QContextMenuEvent * e)820 void LineInfoArea::contextMenuEvent(QContextMenuEvent* e)
821 {
822 static_cast<SourceWindow*>(parent())->contextMenuEvent(e);
823 }
824
HighlightCpp(SourceWindow * srcWnd)825 HighlightCpp::HighlightCpp(SourceWindow* srcWnd) :
826 QSyntaxHighlighter(srcWnd->document()),
827 m_srcWnd(srcWnd)
828 {
829 }
830
831 enum HLState {
832 hlCommentLine = 1,
833 hlCommentBlock,
834 hlIdent,
835 hlString
836 };
837
838 static const QString ckw[] =
839 {
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"
924 };
925
highlightBlock(const QString & text)926 void HighlightCpp::highlightBlock(const QString& text)
927 {
928 int state = previousBlockState();
929 state = highlight(text, state);
930 setCurrentBlockState(state);
931 }
932
highlight(const QString & text,int state)933 int HighlightCpp::highlight(const QString& text, int state)
934 {
935 // highlight assembly lines
936 if (m_srcWnd->isRowDisassCode(currentBlock().blockNumber()))
937 {
938 setFormat(0, text.length(), Qt::blue);
939 return state;
940 }
941
942 if (state == -2) // initial state
943 state = 0;
944
945 // check for preprocessor line
946 if (state == 0 && text.trimmed().startsWith("#"))
947 {
948 setFormat(0, text.length(), QColor("darkgreen"));
949 return 0;
950 }
951
952 // a font for keywords
953 QTextCharFormat identFont;
954 identFont.setFontWeight(QFont::Bold);
955
956 int start = 0;
957 while (start < text.length())
958 {
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;
982 }
983 }
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;
991 }
992 state = 0;
993 if (isCppKeyword(text.mid(start, end-start)))
994 {
995 setFormat(start, end-start, identFont);
996 } else {
997 setFormat(start, end-start, m_srcWnd->palette().color(QPalette::WindowText));
998 }
999 break;
1000 default:
1001 for (end = start; end < int(text.length()); end++)
1002 {
1003 if (text[end] == '/')
1004 {
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;
1012 }
1013 }
1014 }
1015 else if (text[end] == '"' || text[end] == '\'')
1016 {
1017 state = hlString;
1018 break;
1019 }
1020 else if ((text[end] >= 'A' && text[end] <= 'Z') ||
1021 (text[end] >= 'a' && text[end] <= 'z') ||
1022 text[end] == '_')
1023 {
1024 state = hlIdent;
1025 break;
1026 }
1027 }
1028 setFormat(start, end-start, m_srcWnd->palette().color(QPalette::WindowText));
1029 }
1030 start = end;
1031 }
1032 return state;
1033 }
1034
isCppKeyword(const QString & word)1035 bool HighlightCpp::isCppKeyword(const QString& word)
1036 {
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);
1046 }
1047 }
1048 keyword_order_verified = true;
1049 }
1050 #endif
1051
1052 return std::binary_search(ckw, ckw + sizeof(ckw)/sizeof(ckw[0]), word);
1053 }
1054
1055 #include "sourcewnd.moc"
1056