/* * Copyright Johannes Sixt * This file is licensed under the GNU General Public License Version 2. * See the file COPYING in the toplevel directory of the source directory. */ #include "debugger.h" #include "sourcewnd.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "mydebug.h" SourceWindow::SourceWindow(const TQString& fileName, TQWidget* parent, const char* name) : TQTextEdit(parent, name), m_fileName(fileName), m_curRow(-1), m_widthItems(16), m_widthPlus(12), m_widthLineNo(30) { // load pixmaps m_pcinner = UserIcon("pcinner"); m_pcup = UserIcon("pcup"); m_brkena = UserIcon("brkena"); m_brkdis = UserIcon("brkdis"); m_brktmp = UserIcon("brktmp"); m_brkcond = UserIcon("brkcond"); m_brkorph = UserIcon("brkorph"); setFont(TDEGlobalSettings::fixedFont()); setReadOnly(true); setMargins(m_widthItems+m_widthPlus+m_widthLineNo, 0, 0 ,0); setAutoFormatting(AutoNone); setTextFormat(PlainText); setWordWrap(NoWrap); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(update())); connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(cursorChanged(int))); viewport()->installEventFilter(this); // add a syntax highlighter if (TQRegExp("\\.(c(pp|c|\\+\\+)?|CC?|h(\\+\\+|h)?|HH?)$").search(m_fileName) >= 0) { new HighlightCpp(this); } } SourceWindow::~SourceWindow() { delete syntaxHighlighter(); } bool SourceWindow::loadFile() { // first we load the code into TQTextEdit TQFile f(m_fileName); if (!f.open(IO_ReadOnly)) { return false; } TQTextStream t(&f); setText(t.read()); f.close(); // then we copy it into our own m_sourceCode int n = paragraphs(); m_sourceCode.resize(n); m_rowToLine.resize(n); for (int i = 0; i < n; i++) { m_sourceCode[i].code = text(i); m_rowToLine[i] = i; } m_lineItems.resize(n, 0); // set a font for line numbers m_lineNoFont = currentFont(); m_lineNoFont.setPixelSize(11); return true; } void SourceWindow::reloadFile() { TQFile f(m_fileName); if (!f.open(IO_ReadOnly)) { // open failed; leave alone return; } // read text into m_sourceCode m_sourceCode.clear(); /* clear old text */ TQTextStream t(&f); setText(t.read()); f.close(); m_sourceCode.resize(paragraphs()); for (size_t i = 0; i < m_sourceCode.size(); i++) { m_sourceCode[i].code = text(i); } // expanded lines are collapsed: move existing line items up for (size_t i = 0; i < m_lineItems.size(); i++) { if (m_rowToLine[i] != i) { m_lineItems[m_rowToLine[i]] |= m_lineItems[i]; m_lineItems[i] = 0; } } // allocate line items m_lineItems.resize(m_sourceCode.size(), 0); m_rowToLine.resize(m_sourceCode.size()); for (size_t i = 0; i < m_sourceCode.size(); i++) m_rowToLine[i] = i; // Highlighting was applied above when the text was inserted into widget, // but at that time m_rowToLine was not corrected, yet, so that lines // that previously were assembly were painted incorrectly. if (syntaxHighlighter()) syntaxHighlighter()->rehighlight(); update(); // line numbers } void SourceWindow::scrollTo(int lineNo, const DbgAddr& address) { if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) return; int row = lineToRow(lineNo, address); scrollToRow(row); } void SourceWindow::scrollToRow(int row) { setCursorPosition(row, 0); ensureCursorVisible(); } void SourceWindow::drawFrame(TQPainter* p) { TQTextEdit::drawFrame(p); // and paragraph at the top is... int top = paragraphAt(TQPoint(0,contentsY())); int bot = paragraphAt(TQPoint(0,contentsY()+visibleHeight()-1)); if (bot < 0) bot = paragraphs()-1; p->save(); // set a clip rectangle int fw = frameWidth(); TQRect inside = rect(); inside.addCoords(fw,fw,-fw,-fw); TQRegion clip = p->clipRegion(); clip &= TQRegion(inside); p->setClipRegion(clip); p->setFont(m_lineNoFont); p->setPen(colorGroup().text()); p->eraseRect(inside); for (int row = top; row <= bot; row++) { uchar item = m_lineItems[row]; p->save(); TQRect r = paragraphRect(row); TQPoint pt = contentsToViewport(r.topLeft()); int h = r.height(); p->translate(fw, pt.y()+viewport()->y()); if (item & liBP) { // enabled breakpoint int y = (h - m_brkena.height())/2; if (y < 0) y = 0; p->drawPixmap(0,y,m_brkena); } if (item & liBPdisabled) { // disabled breakpoint int y = (h - m_brkdis.height())/2; if (y < 0) y = 0; p->drawPixmap(0,y,m_brkdis); } if (item & liBPtemporary) { // temporary breakpoint marker int y = (h - m_brktmp.height())/2; if (y < 0) y = 0; p->drawPixmap(0,y,m_brktmp); } if (item & liBPconditional) { // conditional breakpoint marker int y = (h - m_brkcond.height())/2; if (y < 0) y = 0; p->drawPixmap(0,y,m_brkcond); } if (item & liBPorphan) { // orphaned breakpoint marker int y = (h - m_brkcond.height())/2; if (y < 0) y = 0; p->drawPixmap(0,y,m_brkorph); } if (item & liPC) { // program counter in innermost frame int y = (h - m_pcinner.height())/2; if (y < 0) y = 0; p->drawPixmap(0,y,m_pcinner); } if (item & liPCup) { // program counter somewhere up the stack int y = (h - m_pcup.height())/2; if (y < 0) y = 0; p->drawPixmap(0,y,m_pcup); } p->translate(m_widthItems, 0); if (!isRowDisassCode(row) && m_sourceCode[rowToLine(row)].canDisass) { int w = m_widthPlus; int x = w/2; int y = h/2; p->drawLine(x-2, y, x+2, y); if (!isRowExpanded(row)) { p->drawLine(x, y-2, x, y+2); } } p->translate(m_widthPlus, 0); if (!isRowDisassCode(row)) { p->drawText(0, 0, m_widthLineNo, h, AlignRight|AlignVCenter, TQString().setNum(rowToLine(row)+1)); } p->restore(); } p->restore(); } void SourceWindow::updateLineItems(const KDebugger* dbg) { // clear outdated breakpoints for (int i = m_lineItems.size()-1; i >= 0; i--) { if (m_lineItems[i] & liBPany) { // check if this breakpoint still exists int line = rowToLine(i); TRACE(TQString().sprintf("checking for bp at %d", line)); KDebugger::BrkptROIterator bp = dbg->breakpointsBegin(); for (; bp != dbg->breakpointsEnd(); ++bp) { if (bp->lineNo == line && fileNameMatches(bp->fileName) && lineToRow(line, bp->address) == i) { // yes it exists; mode is changed below break; } } if (bp == dbg->breakpointsEnd()) { /* doesn't exist anymore, remove it */ m_lineItems[i] &= ~liBPany; update(); } } } // add new breakpoints for (KDebugger::BrkptROIterator bp = dbg->breakpointsBegin(); bp != dbg->breakpointsEnd(); ++bp) { if (fileNameMatches(bp->fileName)) { TRACE(TQString().sprintf("updating %s:%d", bp->fileName.data(), bp->lineNo)); int i = bp->lineNo; if (i < 0 || i >= int(m_sourceCode.size())) continue; // compute new line item flags for breakpoint uchar flags = bp->enabled ? liBP : liBPdisabled; if (bp->temporary) flags |= liBPtemporary; if (!bp->condition.isEmpty() || bp->ignoreCount != 0) flags |= liBPconditional; if (bp->isOrphaned()) flags |= liBPorphan; // update if changed int row = lineToRow(i, bp->address); if ((m_lineItems[row] & liBPany) != flags) { m_lineItems[row] &= ~liBPany; m_lineItems[row] |= flags; update(); } } } } void SourceWindow::setPC(bool set, int lineNo, const DbgAddr& address, int frameNo) { if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) { return; } int row = lineToRow(lineNo, address); uchar flag = frameNo == 0 ? liPC : liPCup; if (set) { // set only if not already set if ((m_lineItems[row] & flag) == 0) { m_lineItems[row] |= flag; update(); } } else { // clear only if not set if ((m_lineItems[row] & flag) != 0) { m_lineItems[row] &= ~flag; update(); } } } void SourceWindow::find(const TQString& text, bool caseSensitive, FindDirection dir) { ASSERT(dir == 1 || dir == -1); if (TQTextEdit::find(text, caseSensitive, false, dir > 0)) return; // not found; wrap around int para = dir > 0 ? 0 : paragraphs(), index = 0; TQTextEdit::find(text, caseSensitive, false, dir > 0, ¶, &index); } void SourceWindow::mousePressEvent(TQMouseEvent* ev) { // we handle left and middle button if (ev->button() != LeftButton && ev->button() != MidButton) { TQTextEdit::mousePressEvent(ev); return; } // get row TQPoint p = viewportToContents(TQPoint(0, ev->y() - viewport()->y())); int row = paragraphAt(p); if (row < 0) return; if (ev->x() > m_widthItems+frameWidth()) { if (isRowExpanded(row)) { actionCollapseRow(row); } else { actionExpandRow(row); } return; } int sourceRow; int line = rowToLine(row, &sourceRow); // find address if row is disassembled code DbgAddr address; if (row > sourceRow) { // get offset from source code line int off = row - sourceRow; address = m_sourceCode[line].disassAddr[off-1]; } switch (ev->button()) { case LeftButton: TRACE(TQString().sprintf("left-clicked line %d", line)); emit clickedLeft(m_fileName, line, address, (ev->state() & ShiftButton) != 0); break; case MidButton: TRACE(TQString().sprintf("mid-clicked row %d", line)); emit clickedMid(m_fileName, line, address); break; default:; } } void SourceWindow::keyPressEvent(TQKeyEvent* ev) { int top1, top2; TQPoint top; switch (ev->key()) { case Key_Plus: actionExpandRow(m_curRow); return; case Key_Minus: actionCollapseRow(m_curRow); return; case Key_Up: if (m_curRow > 0) { setCursorPosition(m_curRow-1, 0); } return; case Key_Down: if (m_curRow < paragraphs()-1) { setCursorPosition(m_curRow+1, 0); } return; case Key_Home: setCursorPosition(0, 0); return; case Key_End: setCursorPosition(paragraphs()-1, 0); return; case Key_Next: case Key_Prior: top = viewportToContents(TQPoint(0,0)); top1 = paragraphAt(top); } TQTextEdit::keyPressEvent(ev); switch (ev->key()) { case Key_Next: case Key_Prior: top = viewportToContents(TQPoint(0,0)); top2 = paragraphAt(top); setCursorPosition(m_curRow+(top2-top1), 0); } } static inline bool isident(TQChar c) { return c.isLetterOrNumber() || c.latin1() == '_'; } bool SourceWindow::wordAtPoint(const TQPoint& p, TQString& word, TQRect& r) { TQPoint pv = viewportToContents(p - viewport()->pos()); int row, col = charAt(pv, &row); if (row < 0 || col < 0) return false; // isolate the word at row, col TQString line = text(row); if (!isident(line[col])) return false; int begin = col; while (begin > 0 && isident(line[begin-1])) --begin; do ++col; while (col < int(line.length()) && isident(line[col])); r = TQRect(p, p); r.addCoords(-5,-5,5,5); word = line.mid(begin, col-begin); return true; } void SourceWindow::paletteChange(const TQPalette& oldPal) { setFont(TDEGlobalSettings::fixedFont()); TQTextEdit::paletteChange(oldPal); } /* * Two file names (possibly full paths) match if the last parts - the file * names - match. */ bool SourceWindow::fileNameMatches(const TQString& other) { return TQFileInfo(other).fileName() == TQFileInfo(m_fileName).fileName(); } void SourceWindow::disassembled(int lineNo, const std::list& disass) { TRACE("disassembled line " + TQString().setNum(lineNo)); if (lineNo < 0 || lineNo >= int(m_sourceCode.size())) return; SourceLine& sl = m_sourceCode[lineNo]; // copy disassembled code and its addresses sl.disass.resize(disass.size()); sl.disassAddr.resize(disass.size()); sl.canDisass = !disass.empty(); int i = 0; for (std::list::const_iterator c = disass.begin(); c != disass.end(); ++c, ++i) { TQString code = c->code; while (code.endsWith("\n")) code.truncate(code.length()-1); sl.disass[i] = c->address.asString() + ' ' + code; sl.disassAddr[i] = c->address; } int row = lineToRow(lineNo); if (sl.canDisass) { expandRow(row); } else { // clear expansion marker update(); } } int SourceWindow::rowToLine(int row, int* sourceRow) { int line = row >= 0 ? m_rowToLine[row] : -1; if (sourceRow != 0) { // search back until we hit the first entry with the current line number while (row > 0 && m_rowToLine[row-1] == line) row--; *sourceRow = row; } return line; } /* * Rows showing diassembled code have the same line number as the * corresponding source code line number. Therefore, the line numbers in * m_rowToLine are monotonically increasing with blocks of equal line * numbers for a source line and its disassembled code that follows it. * * Hence, m_rowToLine always obeys the following condition: * * m_rowToLine[i] <= i */ int SourceWindow::lineToRow(int line) { // line is zero-based! assert(line < int(m_rowToLine.size())); // quick test for common case if (line < 0 || m_rowToLine[line] == line) return line; assert(m_rowToLine[line] < line); /* * Binary search between row == line and end of list. In the loop below * we use the fact that the line numbers m_rowToLine do not contain * holes. */ int l = line; int h = m_rowToLine.size(); while (l < h && m_rowToLine[l] != line) { assert(h == int(m_rowToLine.size()) || m_rowToLine[l] < m_rowToLine[h]); /* * We want to round down the midpoint so that we find the * lowest row that belongs to the line we seek. */ int mid = (l+h)/2; if (m_rowToLine[mid] <= line) l = mid; else h = mid; } // Found! Result is in l: assert(m_rowToLine[l] == line); /* * We might not have hit the lowest index for the line. */ while (l > 0 && m_rowToLine[l-1] == line) --l; return l; } int SourceWindow::lineToRow(int line, const DbgAddr& address) { int row = lineToRow(line); if (isRowExpanded(row)) { row += m_sourceCode[line].findAddressRowOffset(address); } return row; } bool SourceWindow::isRowExpanded(int row) { assert(row >= 0); return row < int(m_rowToLine.size())-1 && m_rowToLine[row] == m_rowToLine[row+1]; } bool SourceWindow::isRowDisassCode(int row) { return row > 0 && row < int(m_rowToLine.size()) && m_rowToLine[row] == m_rowToLine[row-1]; } void SourceWindow::expandRow(int row) { TRACE("expanding row " + TQString().setNum(row)); // get disassembled code int line = rowToLine(row); const std::vector& disass = m_sourceCode[line].disass; // remove PC (must be set again in slot of signal expanded()) m_lineItems[row] &= ~(liPC|liPCup); // adjust current row if (m_curRow > row) { m_curRow += disass.size(); // highlight is moved automatically } // insert new lines setUpdatesEnabled(false); ++row; for (size_t i = 0; i < disass.size(); i++) { m_rowToLine.insert(m_rowToLine.begin()+row, line); m_lineItems.insert(m_lineItems.begin()+row, 0); insertParagraph(disass[i], row++); } setUpdatesEnabled(true); viewport()->update(); update(); // line items emit expanded(line); /* must set PC */ } void SourceWindow::collapseRow(int row) { TRACE("collapsing row " + TQString().setNum(row)); int line = rowToLine(row); // find end of this block int end = row+1; while (end < int(m_rowToLine.size()) && m_rowToLine[end] == m_rowToLine[row]) { end++; } ++row; // adjust current row if (m_curRow >= row) { m_curRow -= end-row; if (m_curRow < row) // was m_curRow in disassembled code? m_curRow = -1; } setUpdatesEnabled(false); while (--end >= row) { m_rowToLine.erase(m_rowToLine.begin()+end); m_lineItems.erase(m_lineItems.begin()+end); removeParagraph(end); } setUpdatesEnabled(true); viewport()->update(); update(); // line items emit collapsed(line); } void SourceWindow::activeLine(int& line, DbgAddr& address) { int row = m_curRow; int sourceRow; line = rowToLine(row, &sourceRow); if (row > sourceRow) { int off = row - sourceRow; /* offset from source line */ address = m_sourceCode[line].disassAddr[off-1]; } } /** * Returns the offset from the line displaying the source code to * the line containing the specified address. If the address is not * found, 0 is returned. */ int SourceWindow::SourceLine::findAddressRowOffset(const DbgAddr& address) const { if (address.isEmpty()) return 0; for (size_t i = 0; i < disassAddr.size(); i++) { if (disassAddr[i] == address) { // found exact address return i+1; } if (disassAddr[i] > address) { /* * We have already advanced too far; the address is before this * index, but obviously we haven't found an exact match * earlier. address is somewhere between the displayed * addresses. We return the previous line. */ return i; } } // not found return 0; } void SourceWindow::actionExpandRow(int row) { if (row < 0 || isRowExpanded(row) || isRowDisassCode(row)) return; // disassemble int line = rowToLine(row); const SourceLine& sl = m_sourceCode[line]; if (!sl.canDisass) return; if (sl.disass.size() == 0) { emit disassemble(m_fileName, line); } else { expandRow(row); } } void SourceWindow::actionCollapseRow(int row) { if (row < 0 || !isRowExpanded(row) || isRowDisassCode(row)) return; collapseRow(row); } void SourceWindow::setTabWidth(int numChars) { if (numChars <= 0) numChars = 8; TQFontMetrics fm(currentFont()); TQString s; int w = fm.width(s.fill('x', numChars)); setTabStopWidth(w); } void SourceWindow::cursorChanged(int row) { if (row == m_curRow) return; if (m_curRow >= 0 && m_curRow < paragraphs()) clearParagraphBackground(m_curRow); m_curRow = row; setParagraphBackgroundColor(row, colorGroup().background()); } /* * We must override the context menu handling because TQTextEdit's handling * requires that it receives ownership of the popup menu; but the popup menu * returned from the GUI factory is owned by the factory. */ void SourceWindow::contextMenuEvent(TQContextMenuEvent* e) { // get the context menu from the GUI factory TQWidget* top = this; do top = top->parentWidget(); while (!top->isTopLevel()); TDEMainWindow* mw = static_cast(top); TQPopupMenu* m = static_cast(mw->factory()->container("popup_files", mw)); m->exec(e->globalPos()); } bool SourceWindow::eventFilter(TQObject* watched, TQEvent* e) { if (e->type() == TQEvent::ContextMenu && watched == viewport()) { contextMenuEvent(static_cast(e)); return true; } return TQTextEdit::eventFilter(watched, e); } HighlightCpp::HighlightCpp(SourceWindow* srcWnd) : TQSyntaxHighlighter(srcWnd), m_srcWnd(srcWnd) { } enum HLState { hlCommentLine = 1, hlCommentBlock, hlIdent, hlString }; static const TQString ckw[] = { "and", "and_eq", "asm", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "class", "compl", "const", "const_cast", "continue", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "not", "not_eq", "operator", "or", "or_eq", "private", "protected", "public", "reinterpret_cast", "register", "return", "short", "signed", "sizeof", "static", "static_cast", "struct", "switch", "template", "this", "throw", "true", "try", "typedef", "typeid", "typename", "using", "union", "unsigned", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" }; int HighlightCpp::highlightParagraph(const TQString& text, int state) { int row = currentParagraph(); // highlight assembly lines if (m_srcWnd->isRowDisassCode(row)) { setFormat(0, text.length(), blue); return state; } if (state == -2) // initial state state = 0; // check for preprocessor line if (state == 0 && text.stripWhiteSpace().startsWith("#")) { setFormat(0, text.length(), TQColor("dark green")); return 0; } // a font for keywords TQFont identFont = textEdit()->currentFont(); identFont.setBold(!identFont.bold()); unsigned start = 0; while (start < text.length()) { int end; switch (state) { case hlCommentLine: end = text.length(); state = 0; setFormat(start, end-start, TQColor("gray50")); break; case hlCommentBlock: end = text.find("*/", start); if (end >= 0) end += 2, state = 0; else end = text.length(); setFormat(start, end-start, TQColor("gray50")); break; case hlString: for (end = start+1; end < int(text.length()); end++) { if (text[end] == '\\') { if (end < int(text.length())) ++end; } else if (text[end] == text[start]) { ++end; break; } } state = 0; setFormat(start, end-start, TQColor("dark red")); break; case hlIdent: for (end = start+1; end < int(text.length()); end++) { if (!text[end].isLetterOrNumber() && text[end] != '_') break; } state = 0; if (std::binary_search(ckw, ckw + sizeof(ckw)/sizeof(ckw[0]), text.mid(start, end-start))) { setFormat(start, end-start, identFont); } else { setFormat(start, end-start, m_srcWnd->colorGroup().text()); } break; default: for (end = start; end < int(text.length()); end++) { if (text[end] == '/') { if (end+1 < int(text.length())) { if (text[end+1] == '/') { state = hlCommentLine; break; } else if (text[end+1] == '*') { state = hlCommentBlock; break; } } } else if (text[end] == '"' || text[end] == '\'') { state = hlString; break; } else if (text[end] >= 'A' && text[end] <= 'Z' || text[end] >= 'a' && text[end] <= 'z' || text[end] == '_') { state = hlIdent; break; } } setFormat(start, end-start, m_srcWnd->colorGroup().text()); } start = end; } return state; } #include "sourcewnd.moc"