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