1 /*
2 SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
3 SPDX-FileCopyrightText: 2008 Vladimir Prus <ghost@cs.msu.su>
4 SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "variablecollection.h"
10
11 #include <QFont>
12 #include <QApplication>
13
14 #include <KColorScheme>
15 #include <KLocalizedString>
16 #include <KTextEditor/TextHintInterface>
17 #include <KTextEditor/Document>
18 #include <KTextEditor/View>
19
20 #include "../../interfaces/icore.h"
21 #include "../../interfaces/idocumentcontroller.h"
22 #include "../../interfaces/iuicontroller.h"
23 #include "../../sublime/controller.h"
24 #include "../../sublime/view.h"
25 #include "../../interfaces/idebugcontroller.h"
26 #include "../interfaces/idebugsession.h"
27 #include "../interfaces/ivariablecontroller.h"
28 #include <debug.h>
29 #include "util/texteditorhelpers.h"
30 #include "variabletooltip.h"
31 #include <sublime/area.h>
32
33 namespace KDevelop {
34
currentSession()35 IDebugSession* currentSession()
36 {
37 return ICore::self()->debugController()->currentSession();
38 }
39
currentSessionState()40 IDebugSession::DebuggerState currentSessionState()
41 {
42 if (!currentSession()) return IDebugSession::NotStartedState;
43 return currentSession()->state();
44 }
45
hasStartedSession()46 bool hasStartedSession()
47 {
48 IDebugSession::DebuggerState s = currentSessionState();
49 return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState;
50 }
51
Variable(TreeModel * model,TreeItem * parent,const QString & expression,const QString & display)52 Variable::Variable(TreeModel* model, TreeItem* parent,
53 const QString& expression,
54 const QString& display)
55 : TreeItem(model, parent)
56 , m_expression(expression)
57 , m_inScope(true)
58 , m_topLevel(true)
59 , m_changed(false)
60 , m_showError(false)
61 , m_format(Natural)
62 {
63 // FIXME: should not duplicate the data, instead overload 'data'
64 // and return expression_ directly.
65 if (display.isEmpty())
66 setData(QVector<QVariant>{expression, QString(), QString()});
67 else
68 setData(QVector<QVariant>{display, QString(), QString()});
69 }
70
expression() const71 QString Variable::expression() const
72 {
73 return m_expression;
74 }
75
inScope() const76 bool Variable::inScope() const
77 {
78 return m_inScope;
79 }
80
setValue(const QString & v)81 void Variable::setValue(const QString& v)
82 {
83 itemData[VariableCollection::ValueColumn] = v;
84 reportChange();
85 }
86
value() const87 QString Variable::value() const
88 {
89 return itemData[VariableCollection::ValueColumn].toString();
90 }
91
setType(const QString & type)92 void Variable::setType(const QString& type)
93 {
94 itemData[VariableCollection::TypeColumn] = type;
95 reportChange();
96 }
97
type() const98 QString Variable::type() const
99 {
100 return itemData[VariableCollection::TypeColumn].toString();
101 }
102
setTopLevel(bool v)103 void Variable::setTopLevel(bool v)
104 {
105 m_topLevel = v;
106 }
107
setInScope(bool v)108 void Variable::setInScope(bool v)
109 {
110 m_inScope = v;
111 for (int i=0; i < childCount(); ++i) {
112 if (auto *var = qobject_cast<Variable*>(child(i))) {
113 var->setInScope(v);
114 }
115 }
116 reportChange();
117 }
118
setShowError(bool v)119 void Variable::setShowError (bool v)
120 {
121 m_showError = v;
122 reportChange();
123 }
124
showError()125 bool Variable::showError()
126 {
127 return m_showError;
128 }
129
130
~Variable()131 Variable::~Variable()
132 {
133 }
134
die()135 void Variable::die()
136 {
137 removeSelf();
138 deleteLater();
139 }
140
141
setChanged(bool c)142 void Variable::setChanged(bool c)
143 {
144 m_changed=c;
145 reportChange();
146 }
147
resetChanged()148 void Variable::resetChanged()
149 {
150 setChanged(false);
151 for (int i=0; i<childCount(); ++i) {
152 TreeItem* childItem = child(i);
153 if (qobject_cast<Variable*>(childItem)) {
154 static_cast<Variable*>(childItem)->resetChanged();
155 }
156 }
157 }
158
str2format(const QString & str)159 Variable::format_t Variable::str2format(const QString& str)
160 {
161 if(str==QLatin1String("Binary") || str==QLatin1String("binary")) return Binary;
162 if(str==QLatin1String("Octal") || str==QLatin1String("octal")) return Octal;
163 if(str==QLatin1String("Decimal") || str==QLatin1String("decimal")) return Decimal;
164 if(str==QLatin1String("Hexadecimal") || str==QLatin1String("hexadecimal"))return Hexadecimal;
165
166 return Natural; // maybe most reasonable default
167 }
168
format2str(format_t format)169 QString Variable::format2str(format_t format)
170 {
171 switch(format) {
172 case Natural: return QStringLiteral("natural");
173 case Binary: return QStringLiteral("binary");
174 case Octal: return QStringLiteral("octal");
175 case Decimal: return QStringLiteral("decimal");
176 case Hexadecimal: return QStringLiteral("hexadecimal");
177 }
178 return QString();
179 }
180
181
setFormat(Variable::format_t format)182 void Variable::setFormat(Variable::format_t format)
183 {
184 if (m_format != format) {
185 m_format = format;
186 formatChanged();
187 }
188 }
189
formatChanged()190 void Variable::formatChanged()
191 {
192 }
193
isPotentialProblematicValue() const194 bool Variable::isPotentialProblematicValue() const
195 {
196 const auto value = data(VariableCollection::ValueColumn, Qt::DisplayRole).toString();
197 return value == QLatin1String("0x0");
198 }
199
data(int column,int role) const200 QVariant Variable::data(int column, int role) const
201 {
202 if (m_showError) {
203 if (role == Qt::FontRole) {
204 QVariant ret = TreeItem::data(column, role);
205 QFont font = ret.value<QFont>();
206 font.setStyle(QFont::StyleItalic);
207 return font;
208 } else if (column == 1 && role == Qt::DisplayRole) {
209 return i18n("Error");
210 }
211 }
212 if (column == 1 && role == Qt::ForegroundRole)
213 {
214 KColorScheme scheme(QPalette::Active);
215 if (!m_inScope) {
216 return scheme.foreground(KColorScheme::InactiveText).color();
217 } else if (isPotentialProblematicValue()) {
218 return scheme.foreground(KColorScheme::NegativeText).color();
219 } else if (m_changed) {
220 return scheme.foreground(KColorScheme::NeutralText).color();
221 }
222 }
223 if (role == Qt::ToolTipRole) {
224 return TreeItem::data(column, Qt::DisplayRole);
225 }
226
227 return TreeItem::data(column, role);
228 }
229
Watches(TreeModel * model,TreeItem * parent)230 Watches::Watches(TreeModel* model, TreeItem* parent)
231 : TreeItem(model, parent), finishResult_(nullptr)
232 {
233 setData(QVector<QVariant>{i18n("Auto"), QString()});
234 }
235
add(const QString & expression)236 Variable* Watches::add(const QString& expression)
237 {
238 if (!hasStartedSession()) return nullptr;
239
240 Variable* v = currentSession()->variableController()->createVariable(
241 model(), this, expression);
242 appendChild(v);
243 v->attachMaybe();
244 if (childCount() == 1 && !isExpanded()) {
245 setExpanded(true);
246 }
247 return v;
248 }
249
addFinishResult(const QString & convenienceVarible)250 Variable *Watches::addFinishResult(const QString& convenienceVarible)
251 {
252 if( finishResult_ )
253 {
254 removeFinishResult();
255 }
256 finishResult_ = currentSession()->variableController()->createVariable(
257 model(), this, convenienceVarible, QStringLiteral("$ret"));
258 appendChild(finishResult_);
259 finishResult_->attachMaybe();
260 if (childCount() == 1 && !isExpanded()) {
261 setExpanded(true);
262 }
263 return finishResult_;
264 }
265
removeFinishResult()266 void Watches::removeFinishResult()
267 {
268 if (finishResult_)
269 {
270 finishResult_->die();
271 finishResult_ = nullptr;
272 }
273 }
274
resetChanged()275 void Watches::resetChanged()
276 {
277 for (int i=0; i<childCount(); ++i) {
278 TreeItem* childItem = child(i);
279 if (qobject_cast<Variable*>(childItem)) {
280 static_cast<Variable*>(childItem)->resetChanged();
281 }
282 }
283 }
284
data(int column,int role) const285 QVariant Watches::data(int column, int role) const
286 {
287 #if 0
288 if (column == 0 && role == Qt::FontRole)
289 {
290 /* FIXME: is creating font again and again efficient? */
291 QFont f = font();
292 f.setBold(true);
293 return f;
294 }
295 #endif
296 return TreeItem::data(column, role);
297 }
298
reinstall()299 void Watches::reinstall()
300 {
301 for (int i = 0; i < childItems.size(); ++i)
302 {
303 auto* v = static_cast<Variable*>(child(i));
304 v->attachMaybe();
305 }
306 }
307
Locals(TreeModel * model,TreeItem * parent,const QString & name)308 Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name)
309 : TreeItem(model, parent)
310 {
311 setData(QVector<QVariant>{name, QString()});
312 }
313
updateLocals(const QStringList & locals)314 QList<Variable*> Locals::updateLocals(const QStringList& locals)
315 {
316 QSet<QString> existing, current;
317 for (int i = 0; i < childItems.size(); i++)
318 {
319 Q_ASSERT(qobject_cast<KDevelop::Variable*>(child(i)));
320 auto* var= static_cast<KDevelop::Variable*>(child(i));
321 existing << var->expression();
322 }
323
324 for (const QString& var : locals) {
325 current << var;
326 // If we currently don't display this local var, add it.
327 if( !existing.contains( var ) ) {
328 // FIXME: passing variableCollection this way is awkward.
329 // In future, variableCollection probably should get a
330 // method to create variable.
331 Variable* v =
332 currentSession()->variableController()->createVariable(
333 ICore::self()->debugController()->variableCollection(),
334 this, var );
335 appendChild( v, false );
336 }
337 }
338
339 for (int i = 0; i < childItems.size(); ++i) {
340 auto* v = static_cast<KDevelop::Variable*>(child(i));
341 if (!current.contains(v->expression())) {
342 removeChild(i);
343 --i;
344 // FIXME: check that -var-delete is sent.
345 delete v;
346 }
347 }
348
349
350 if (hasMore()) {
351 setHasMore(false);
352 }
353
354 // Variables which changed just value are updated by a call to -var-update.
355 // Variables that changed type -- likewise.
356
357 QList<Variable*> ret;
358 ret.reserve(childItems.size());
359 for (TreeItem* i : qAsConst(childItems)) {
360 Q_ASSERT(qobject_cast<Variable*>(i));
361 ret << static_cast<Variable*>(i);
362 }
363 return ret;
364 }
365
resetChanged()366 void Locals::resetChanged()
367 {
368 for (int i=0; i<childCount(); ++i) {
369 TreeItem* childItem = child(i);
370 if (qobject_cast<Variable*>(childItem)) {
371 static_cast<Variable*>(childItem)->resetChanged();
372 }
373 }
374 }
375
VariablesRoot(TreeModel * model)376 VariablesRoot::VariablesRoot(TreeModel* model)
377 : TreeItem(model)
378 , m_watches(new Watches(model, this))
379 {
380 appendChild(m_watches, true);
381 }
382
383
locals(const QString & name)384 Locals* VariablesRoot::locals(const QString& name)
385 {
386 auto localsIt = m_locals.find(name);
387 if (localsIt == m_locals.end()) {
388 localsIt = m_locals.insert(name, new Locals(model(), this, name));
389 appendChild(*localsIt);
390 }
391 return *localsIt;
392 }
393
allLocals() const394 QHash<QString, Locals*> VariablesRoot::allLocals() const
395 {
396 return m_locals;
397 }
398
resetChanged()399 void VariablesRoot::resetChanged()
400 {
401 m_watches->resetChanged();
402 for (Locals* l : qAsConst(m_locals)) {
403 l->resetChanged();
404 }
405 }
406
VariableCollection(IDebugController * controller)407 VariableCollection::VariableCollection(IDebugController* controller)
408 : TreeModel({i18n("Name"), i18n("Value"), i18n("Type")}, controller)
409 , m_widgetVisible(false)
410 , m_textHintProvider(this)
411 {
412 m_universe = new VariablesRoot(this);
413 setRootItem(m_universe);
414
415 //new ModelTest(this);
416
417 connect (ICore::self()->documentController(),
418 &IDocumentController::textDocumentCreated,
419 this,
420 &VariableCollection::textDocumentCreated );
421
422 connect(controller, &IDebugController::currentSessionChanged,
423 this, &VariableCollection::updateAutoUpdate);
424
425 // Qt5 signal slot syntax does not support default arguments
__anon64739e870102null426 auto callUpdateAutoUpdate = [&] { updateAutoUpdate(); };
427
428 connect(locals(), &Locals::expanded, this, callUpdateAutoUpdate);
429 connect(locals(), &Locals::collapsed, this, callUpdateAutoUpdate);
430 connect(watches(), &Watches::expanded, this, callUpdateAutoUpdate);
431 connect(watches(), &Watches::collapsed, this, callUpdateAutoUpdate);
432 }
433
variableWidgetHidden()434 void VariableCollection::variableWidgetHidden()
435 {
436 m_widgetVisible = false;
437 updateAutoUpdate();
438 }
439
variableWidgetShown()440 void VariableCollection::variableWidgetShown()
441 {
442 m_widgetVisible = true;
443 updateAutoUpdate();
444 }
445
updateAutoUpdate(IDebugSession * session)446 void VariableCollection::updateAutoUpdate(IDebugSession* session)
447 {
448 if (!session) session = currentSession();
449 qCDebug(DEBUGGER) << session;
450 if (!session) return;
451
452 if (!m_widgetVisible) {
453 session->variableController()->setAutoUpdate(IVariableController::UpdateNone);
454 } else {
455 QFlags<IVariableController::UpdateType> t = IVariableController::UpdateNone;
456 if (locals()->isExpanded()) t |= IVariableController::UpdateLocals;
457 if (watches()->isExpanded()) t |= IVariableController::UpdateWatches;
458 session->variableController()->setAutoUpdate(t);
459 }
460 }
461
~VariableCollection()462 VariableCollection::~ VariableCollection()
463 {
464 for (auto* view : qAsConst(m_textHintProvidedViews)) {
465 auto* iface = qobject_cast<KTextEditor::TextHintInterface*>(view);
466 iface->unregisterTextHintProvider(&m_textHintProvider);
467 }
468 }
469
textDocumentCreated(IDocument * doc)470 void VariableCollection::textDocumentCreated(IDocument* doc)
471 {
472 connect( doc->textDocument(),
473 &KTextEditor::Document::viewCreated,
474 this, &VariableCollection::viewCreated );
475
476 const auto views = doc->textDocument()->views();
477 for (KTextEditor::View* view : views) {
478 viewCreated( doc->textDocument(), view );
479 }
480 }
481
viewCreated(KTextEditor::Document * doc,KTextEditor::View * view)482 void VariableCollection::viewCreated(KTextEditor::Document* doc,
483 KTextEditor::View* view)
484 {
485 Q_UNUSED(doc);
486 using namespace KTextEditor;
487 auto* iface = qobject_cast<TextHintInterface*>(view);
488 if( !iface )
489 return;
490
491 if (m_textHintProvidedViews.contains(view)) {
492 return;
493 }
494 connect(view, &View::destroyed, this, [this, view](QObject* obj) {
495 Q_ASSERT(obj == view);
496 m_textHintProvidedViews.removeOne(view);
497 });
498
499 iface->registerTextHintProvider(&m_textHintProvider);
500 m_textHintProvidedViews.append(view);
501 }
502
locals(const QString & name) const503 Locals* VariableCollection::locals(const QString &name) const
504 {
505 return m_universe->locals(name.isEmpty() ? i18n("Locals") : name);
506 }
507
VariableProvider(VariableCollection * collection)508 VariableProvider::VariableProvider(VariableCollection* collection)
509 : KTextEditor::TextHintProvider()
510 , m_collection(collection)
511 {
512 }
513
textHint(KTextEditor::View * view,const KTextEditor::Cursor & cursor)514 QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor)
515 {
516 if (!hasStartedSession())
517 return QString();
518
519 if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("debug"))
520 return QString();
521
522 //TODO: These keyboardModifiers should also hide already opened tooltip, and show another one for code area.
523 if (QApplication::keyboardModifiers() == Qt::ControlModifier ||
524 QApplication::keyboardModifiers() == Qt::AltModifier){
525 return QString();
526 }
527
528 KTextEditor::Document* doc = view->document();
529
530 KTextEditor::Range expressionRange = currentSession()->variableController()->expressionRangeUnderCursor(doc, cursor);
531
532 if (!expressionRange.isValid())
533 return QString();
534 QString expression = doc->text(expressionRange).trimmed();
535
536 // Don't do anything if there's already an open tooltip with matching range
537 if (m_collection->m_activeTooltip && m_collection->m_activeTooltip->variable()->expression() == expression)
538 return QString();
539 if (expression.isEmpty())
540 return QString();
541
542 QPoint local = view->cursorToCoordinate(cursor);
543 QPoint global = view->mapToGlobal(local);
544 QWidget* w = view->childAt(local);
545 if (!w)
546 w = view;
547
548 m_collection->m_activeTooltip = new VariableToolTip(w, global+QPoint(30,30), expression);
549 m_collection->m_activeTooltip->setHandleRect(KTextEditorHelpers::itemBoundingRect(view, expressionRange));
550 return QString();
551 }
552
553 }
554
555