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