1 #include "debugdocument.h"
2 #include "debugwindow.h"
3 
4 #include <QApplication>
5 
6 #include "kjs_binding.h"
7 #include "khtml_part.h"
8 
9 #include <ktexteditor/document.h>
10 #include <ktexteditor/view.h>
11 #include <ktexteditor/editor.h>
12 #include <ktexteditor/configinterface.h>
13 #include <ktexteditor/markinterface.h>
14 #include <kiconloader.h>
15 #include <kmessagebox.h>
16 #include <qcryptographichash.h>
17 
18 using namespace KJS;
19 using namespace KJSDebugger;
20 
DebugDocument(KJS::Interpreter * intp,const QString & url,int sourceId,int baseLine,const QString & source)21 DebugDocument::DebugDocument(KJS::Interpreter *intp, const QString &url,
22                              int sourceId, int baseLine, const QString &source)
23 {
24     m_interpreter = intp;
25     m_url   = url;
26 
27     m_firstLine = baseLine;
28     m_sourceId  = sourceId;
29     m_sourceLines = source.split('\n');
30 
31     QUrl kurl(url);
32     m_name = kurl.fileName();
33 
34     // Might have to fall back in case of query-like things;
35     // ad scripts tend to do that
36     while (m_name.contains("=") || m_name.contains("&")) {
37         kurl = kurl.upUrl();
38         m_name = kurl.fileName();
39     }
40 
41     if (m_name.isEmpty()) {
42         m_name = kurl.host();
43     }
44 
45     if (m_name.isEmpty()) {
46         m_name = "????";    //Probably better than un-i18n'd 'undefined'...
47     }
48 
49     m_kteDoc  = 0;
50     m_kteView = 0;
51     m_rebuilding    = false;
52     m_reload        = false;
53     m_hasFunctions  = false;
54 }
55 
~DebugDocument()56 DebugDocument::~DebugDocument()
57 {
58     emit documentDestroyed(this);
59 
60     // View has an another parent for UI purposes, so we have to clean it up
61     delete m_kteView;
62 }
63 
interpreter()64 KJS::Interpreter *DebugDocument::interpreter()
65 {
66     return m_interpreter;
67 }
68 
hasFunctions()69 bool DebugDocument::hasFunctions()
70 {
71     return m_hasFunctions;
72 }
73 
setHasFunctions()74 void DebugDocument::setHasFunctions()
75 {
76     m_hasFunctions = true;
77 }
78 
name() const79 QString DebugDocument::name() const
80 {
81     return m_name;
82 }
83 
sid() const84 int DebugDocument::sid() const
85 {
86     return m_sourceId;
87 }
88 
baseLine() const89 int DebugDocument::baseLine() const
90 {
91     return m_firstLine;
92 }
93 
length() const94 int DebugDocument::length() const
95 {
96     return m_sourceLines.size();
97 }
98 
url() const99 QString DebugDocument::url() const
100 {
101     return m_url;
102 }
103 
reloaded(int sourceId,const QString & source)104 void DebugDocument::reloaded(int sourceId, const QString &source)
105 {
106     assert(m_reload);
107     m_reload = false;
108 
109     m_sourceLines = source.split('\n');
110     m_sourceId    = sourceId;
111     m_md5.clear();
112 
113     if (m_kteDoc) { // Update docu if needed
114         rebuildViewerDocument();
115     }
116 }
117 
markReload()118 void DebugDocument::markReload()
119 {
120     m_reload = true;
121 }
122 
isMarkedReload() const123 bool DebugDocument::isMarkedReload() const
124 {
125     return m_reload;
126 }
127 
setBreakpoint(int lineNumber)128 void DebugDocument::setBreakpoint(int lineNumber)
129 {
130     if (m_rebuilding) {
131         return;
132     }
133 
134     breakpoints().append(lineNumber);
135 }
136 
removeBreakpoint(int lineNumber)137 void DebugDocument::removeBreakpoint(int lineNumber)
138 {
139     if (m_rebuilding) {
140         return;
141     }
142 
143     QVector<int> &br = breakpoints();
144     int idx = breakpoints().indexOf(lineNumber);
145     if (idx != -1) {
146         br.remove(idx);
147         if (br.isEmpty() && !m_url.isEmpty()) {
148             // We just removed the last breakpoint per URL,
149             // so we can kill the entire list
150             s_perUrlBreakPoints->remove(url());
151         }
152     }
153 }
154 
hasBreakpoint(int lineNumber)155 bool DebugDocument::hasBreakpoint(int lineNumber)
156 {
157     return breakpoints().contains(lineNumber);
158 }
159 
160 QHash<QString, QVector<int> > *DebugDocument::s_perUrlBreakPoints = 0;
161 QHash<QString, QVector<int> > *DebugDocument::s_perHashBreakPoints = 0;
162 
breakpoints()163 QVector<int> &DebugDocument::breakpoints()
164 {
165     if (m_url.isEmpty()) {
166         if (!s_perHashBreakPoints) {
167             s_perHashBreakPoints = new QHash<QString, QVector<int> >;
168         }
169 
170         if (m_md5.isEmpty()) {
171             QCryptographicHash hash(QCryptographicHash::Md5);
172             hash.addData(m_sourceLines.join("\n").toUtf8());
173             m_md5 = QString::fromLatin1(hash.result().toHex());
174         }
175 
176         return (*s_perHashBreakPoints)[m_md5];
177     } else {
178         if (!s_perUrlBreakPoints) {
179             s_perUrlBreakPoints = new QHash<QString, QVector<int> >;
180         }
181 
182         return (*s_perUrlBreakPoints)[m_url];
183     }
184 }
185 
viewerDocument()186 KTextEditor::Document *DebugDocument::viewerDocument()
187 {
188     if (!m_kteDoc) {
189         rebuildViewerDocument();
190     }
191     return m_kteDoc;
192 }
193 
194 KTextEditor::Editor *DebugDocument::s_kate = 0;
195 
kate()196 KTextEditor::Editor *DebugDocument::kate()
197 {
198     if (!s_kate) {
199         s_kate = KTextEditor::editor("katepart");
200     }
201 
202     if (!s_kate) {
203         KMessageBox::error(DebugWindow::window(), i18n("Unable to find the Kate editor component;\n"
204                            "please check your KDE installation."));
205         qApp->exit(1);
206     }
207 
208     return s_kate;
209 }
210 
rebuildViewerDocument()211 void DebugDocument::rebuildViewerDocument()
212 {
213     m_rebuilding = true;
214 
215     if (!m_kteDoc) {
216         m_kteDoc = kate()->createDocument(this);
217         setupViewerDocument();
218     }
219 
220     KTextEditor::Cursor oldPos;
221     if (m_kteView) {
222         oldPos = m_kteView->cursorPosition();
223     }
224 
225     m_kteDoc->setReadWrite(true);
226     m_kteDoc->setText(m_sourceLines.join("\n"));
227 
228     // Restore cursor pos, if there is a view
229     if (m_kteView) {
230         m_kteView->setCursorPosition(oldPos);
231     }
232 
233     // Check off the pending/URL-based breakpoints. We have to do even
234     // when the document is being updated as they may be on later lines
235     // Note that we have to fiddle with them based on our base line, as
236     // views will always start at 0, even for later fragments
237     KTextEditor::MarkInterface *imark = qobject_cast<KTextEditor::MarkInterface *>(m_kteDoc);
238     if (imark) {
239         QVector<int> &bps = breakpoints();
240         foreach (int bpLine, bps) {
241             int relLine = bpLine - m_firstLine;
242             if (0 <= relLine && relLine < length()) {
243                 imark->addMark(relLine, KTextEditor::MarkInterface::BreakpointActive);
244             }
245         }
246     }
247 
248     m_kteDoc->setReadWrite(false);
249     m_rebuilding = false;
250 }
251 
setupViewerDocument()252 void DebugDocument::setupViewerDocument()
253 {
254     // Highlight as JS..
255     m_kteDoc->setMode("JavaScript");
256 
257     // Configure all the breakpoint/execution point marker stuff.
258     // ### there is an odd split of mark use between here and DebugWindow.
259     // Perhaps we should just emit a single and let it do it, and
260     // limit ourselves to ownership?
261     KTextEditor::MarkInterface *imark = qobject_cast<KTextEditor::MarkInterface *>(m_kteDoc);
262     assert(imark);
263 
264     imark->setEditableMarks(KTextEditor::MarkInterface::BreakpointActive);
265     connect(m_kteDoc, SIGNAL(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)),
266             DebugWindow::window(), SLOT(markSet(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)));
267 
268     imark->setMarkDescription(KTextEditor::MarkInterface::BreakpointActive,
269                               i18n("Breakpoint"));
270     imark->setMarkPixmap(KTextEditor::MarkInterface::BreakpointActive,
271                          SmallIcon("flag-red"));
272     imark->setMarkPixmap(KTextEditor::MarkInterface::Execution,
273                          SmallIcon("arrow-right"));
274 }
275 
viewerView()276 KTextEditor::View *DebugDocument::viewerView()
277 {
278     if (m_kteView) {
279         return m_kteView;
280     }
281 
282     // Ensure document is created
283     viewerDocument();
284 
285     m_kteView = m_kteDoc->createView(DebugWindow::window());
286     KTextEditor::ConfigInterface *iconf = qobject_cast<KTextEditor::ConfigInterface *>(m_kteView);
287     assert(iconf);
288     if (iconf->configKeys().contains("line-numbers")) {
289         iconf->setConfigValue("line-numbers", false);
290     }
291     if (iconf->configKeys().contains("icon-bar")) {
292         iconf->setConfigValue("icon-bar", true);
293     }
294     if (iconf->configKeys().contains("dynamic-word-wrap")) {
295         iconf->setConfigValue("dynamic-word-wrap", true);
296     }
297 
298     return m_kteView;
299 }
300 
301