File indexing completed on 2024-12-01 09:50:25

0001 #include "debugdocument.h"
0002 #include "debugwindow.h"
0003 
0004 #include <QApplication>
0005 
0006 #include "kjs_binding.h"
0007 #include "khtml_part.h"
0008 
0009 #include <ktexteditor/document.h>
0010 #include <ktexteditor/view.h>
0011 #include <ktexteditor/editor.h>
0012 #include <ktexteditor/configinterface.h>
0013 #include <ktexteditor/markinterface.h>
0014 #include <kiconloader.h>
0015 #include <kmessagebox.h>
0016 #include <qcryptographichash.h>
0017 
0018 using namespace KJS;
0019 using namespace KJSDebugger;
0020 
0021 DebugDocument::DebugDocument(KJS::Interpreter *intp, const QString &url,
0022                              int sourceId, int baseLine, const QString &source)
0023 {
0024     m_interpreter = intp;
0025     m_url   = url;
0026 
0027     m_firstLine = baseLine;
0028     m_sourceId  = sourceId;
0029     m_sourceLines = source.split('\n');
0030 
0031     QUrl kurl(url);
0032     m_name = kurl.fileName();
0033 
0034     // Might have to fall back in case of query-like things;
0035     // ad scripts tend to do that
0036     while (m_name.contains("=") || m_name.contains("&")) {
0037         kurl = kurl.upUrl();
0038         m_name = kurl.fileName();
0039     }
0040 
0041     if (m_name.isEmpty()) {
0042         m_name = kurl.host();
0043     }
0044 
0045     if (m_name.isEmpty()) {
0046         m_name = "????";    //Probably better than un-i18n'd 'undefined'...
0047     }
0048 
0049     m_kteDoc  = 0;
0050     m_kteView = 0;
0051     m_rebuilding    = false;
0052     m_reload        = false;
0053     m_hasFunctions  = false;
0054 }
0055 
0056 DebugDocument::~DebugDocument()
0057 {
0058     emit documentDestroyed(this);
0059 
0060     // View has an another parent for UI purposes, so we have to clean it up
0061     delete m_kteView;
0062 }
0063 
0064 KJS::Interpreter *DebugDocument::interpreter()
0065 {
0066     return m_interpreter;
0067 }
0068 
0069 bool DebugDocument::hasFunctions()
0070 {
0071     return m_hasFunctions;
0072 }
0073 
0074 void DebugDocument::setHasFunctions()
0075 {
0076     m_hasFunctions = true;
0077 }
0078 
0079 QString DebugDocument::name() const
0080 {
0081     return m_name;
0082 }
0083 
0084 int DebugDocument::sid() const
0085 {
0086     return m_sourceId;
0087 }
0088 
0089 int DebugDocument::baseLine() const
0090 {
0091     return m_firstLine;
0092 }
0093 
0094 int DebugDocument::length() const
0095 {
0096     return m_sourceLines.size();
0097 }
0098 
0099 QString DebugDocument::url() const
0100 {
0101     return m_url;
0102 }
0103 
0104 void DebugDocument::reloaded(int sourceId, const QString &source)
0105 {
0106     assert(m_reload);
0107     m_reload = false;
0108 
0109     m_sourceLines = source.split('\n');
0110     m_sourceId    = sourceId;
0111     m_md5.clear();
0112 
0113     if (m_kteDoc) { // Update docu if needed
0114         rebuildViewerDocument();
0115     }
0116 }
0117 
0118 void DebugDocument::markReload()
0119 {
0120     m_reload = true;
0121 }
0122 
0123 bool DebugDocument::isMarkedReload() const
0124 {
0125     return m_reload;
0126 }
0127 
0128 void DebugDocument::setBreakpoint(int lineNumber)
0129 {
0130     if (m_rebuilding) {
0131         return;
0132     }
0133 
0134     breakpoints().append(lineNumber);
0135 }
0136 
0137 void DebugDocument::removeBreakpoint(int lineNumber)
0138 {
0139     if (m_rebuilding) {
0140         return;
0141     }
0142 
0143     QVector<int> &br = breakpoints();
0144     int idx = breakpoints().indexOf(lineNumber);
0145     if (idx != -1) {
0146         br.remove(idx);
0147         if (br.isEmpty() && !m_url.isEmpty()) {
0148             // We just removed the last breakpoint per URL,
0149             // so we can kill the entire list
0150             s_perUrlBreakPoints->remove(url());
0151         }
0152     }
0153 }
0154 
0155 bool DebugDocument::hasBreakpoint(int lineNumber)
0156 {
0157     return breakpoints().contains(lineNumber);
0158 }
0159 
0160 QHash<QString, QVector<int> > *DebugDocument::s_perUrlBreakPoints = 0;
0161 QHash<QString, QVector<int> > *DebugDocument::s_perHashBreakPoints = 0;
0162 
0163 QVector<int> &DebugDocument::breakpoints()
0164 {
0165     if (m_url.isEmpty()) {
0166         if (!s_perHashBreakPoints) {
0167             s_perHashBreakPoints = new QHash<QString, QVector<int> >;
0168         }
0169 
0170         if (m_md5.isEmpty()) {
0171             QCryptographicHash hash(QCryptographicHash::Md5);
0172             hash.addData(m_sourceLines.join("\n").toUtf8());
0173             m_md5 = QString::fromLatin1(hash.result().toHex());
0174         }
0175 
0176         return (*s_perHashBreakPoints)[m_md5];
0177     } else {
0178         if (!s_perUrlBreakPoints) {
0179             s_perUrlBreakPoints = new QHash<QString, QVector<int> >;
0180         }
0181 
0182         return (*s_perUrlBreakPoints)[m_url];
0183     }
0184 }
0185 
0186 KTextEditor::Document *DebugDocument::viewerDocument()
0187 {
0188     if (!m_kteDoc) {
0189         rebuildViewerDocument();
0190     }
0191     return m_kteDoc;
0192 }
0193 
0194 KTextEditor::Editor *DebugDocument::s_kate = 0;
0195 
0196 KTextEditor::Editor *DebugDocument::kate()
0197 {
0198     if (!s_kate) {
0199         s_kate = KTextEditor::editor("katepart");
0200     }
0201 
0202     if (!s_kate) {
0203         KMessageBox::error(DebugWindow::window(), i18n("Unable to find the Kate editor component;\n"
0204                            "please check your KDE installation."));
0205         qApp->exit(1);
0206     }
0207 
0208     return s_kate;
0209 }
0210 
0211 void DebugDocument::rebuildViewerDocument()
0212 {
0213     m_rebuilding = true;
0214 
0215     if (!m_kteDoc) {
0216         m_kteDoc = kate()->createDocument(this);
0217         setupViewerDocument();
0218     }
0219 
0220     KTextEditor::Cursor oldPos;
0221     if (m_kteView) {
0222         oldPos = m_kteView->cursorPosition();
0223     }
0224 
0225     m_kteDoc->setReadWrite(true);
0226     m_kteDoc->setText(m_sourceLines.join("\n"));
0227 
0228     // Restore cursor pos, if there is a view
0229     if (m_kteView) {
0230         m_kteView->setCursorPosition(oldPos);
0231     }
0232 
0233     // Check off the pending/URL-based breakpoints. We have to do even
0234     // when the document is being updated as they may be on later lines
0235     // Note that we have to fiddle with them based on our base line, as
0236     // views will always start at 0, even for later fragments
0237     KTextEditor::MarkInterface *imark = qobject_cast<KTextEditor::MarkInterface *>(m_kteDoc);
0238     if (imark) {
0239         QVector<int> &bps = breakpoints();
0240         foreach (int bpLine, bps) {
0241             int relLine = bpLine - m_firstLine;
0242             if (0 <= relLine && relLine < length()) {
0243                 imark->addMark(relLine, KTextEditor::MarkInterface::BreakpointActive);
0244             }
0245         }
0246     }
0247 
0248     m_kteDoc->setReadWrite(false);
0249     m_rebuilding = false;
0250 }
0251 
0252 void DebugDocument::setupViewerDocument()
0253 {
0254     // Highlight as JS..
0255     m_kteDoc->setMode("JavaScript");
0256 
0257     // Configure all the breakpoint/execution point marker stuff.
0258     // ### there is an odd split of mark use between here and DebugWindow.
0259     // Perhaps we should just emit a single and let it do it, and
0260     // limit ourselves to ownership?
0261     KTextEditor::MarkInterface *imark = qobject_cast<KTextEditor::MarkInterface *>(m_kteDoc);
0262     assert(imark);
0263 
0264     imark->setEditableMarks(KTextEditor::MarkInterface::BreakpointActive);
0265     connect(m_kteDoc, SIGNAL(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)),
0266             DebugWindow::window(), SLOT(markSet(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)));
0267 
0268     imark->setMarkDescription(KTextEditor::MarkInterface::BreakpointActive,
0269                               i18n("Breakpoint"));
0270     imark->setMarkPixmap(KTextEditor::MarkInterface::BreakpointActive,
0271                          SmallIcon("flag-red"));
0272     imark->setMarkPixmap(KTextEditor::MarkInterface::Execution,
0273                          SmallIcon("arrow-right"));
0274 }
0275 
0276 KTextEditor::View *DebugDocument::viewerView()
0277 {
0278     if (m_kteView) {
0279         return m_kteView;
0280     }
0281 
0282     // Ensure document is created
0283     viewerDocument();
0284 
0285     m_kteView = m_kteDoc->createView(DebugWindow::window());
0286     KTextEditor::ConfigInterface *iconf = qobject_cast<KTextEditor::ConfigInterface *>(m_kteView);
0287     assert(iconf);
0288     if (iconf->configKeys().contains("line-numbers")) {
0289         iconf->setConfigValue("line-numbers", false);
0290     }
0291     if (iconf->configKeys().contains("icon-bar")) {
0292         iconf->setConfigValue("icon-bar", true);
0293     }
0294     if (iconf->configKeys().contains("dynamic-word-wrap")) {
0295         iconf->setConfigValue("dynamic-word-wrap", true);
0296     }
0297 
0298     return m_kteView;
0299 }
0300 
0301 #include "moc_debugdocument.cpp"