File indexing completed on 2024-05-12 04:37:37
0001 /* 0002 SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org> 0003 SPDX-FileCopyrightText: 2008 Vladimir Prus <ghost@cs.msu.su> 0004 SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "variablecollection.h" 0010 0011 #include <QFont> 0012 #include <QApplication> 0013 0014 #include <KColorScheme> 0015 #include <KLocalizedString> 0016 #include <KTextEditor/TextHintInterface> 0017 #include <KTextEditor/Document> 0018 #include <KTextEditor/View> 0019 0020 #include "../../interfaces/icore.h" 0021 #include "../../interfaces/idocumentcontroller.h" 0022 #include "../../interfaces/iuicontroller.h" 0023 #include "../../sublime/controller.h" 0024 #include "../../sublime/view.h" 0025 #include "../../interfaces/idebugcontroller.h" 0026 #include "../interfaces/idebugsession.h" 0027 #include "../interfaces/ivariablecontroller.h" 0028 #include <debug.h> 0029 #include "util/texteditorhelpers.h" 0030 #include "variabletooltip.h" 0031 #include <sublime/area.h> 0032 0033 namespace KDevelop { 0034 0035 IDebugSession* currentSession() 0036 { 0037 return ICore::self()->debugController()->currentSession(); 0038 } 0039 0040 IDebugSession::DebuggerState currentSessionState() 0041 { 0042 if (!currentSession()) return IDebugSession::NotStartedState; 0043 return currentSession()->state(); 0044 } 0045 0046 bool hasStartedSession() 0047 { 0048 IDebugSession::DebuggerState s = currentSessionState(); 0049 return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState; 0050 } 0051 0052 Variable::Variable(TreeModel* model, TreeItem* parent, 0053 const QString& expression, 0054 const QString& display) 0055 : TreeItem(model, parent) 0056 , m_expression(expression) 0057 , m_inScope(true) 0058 , m_topLevel(true) 0059 , m_changed(false) 0060 , m_showError(false) 0061 , m_format(Natural) 0062 { 0063 // FIXME: should not duplicate the data, instead overload 'data' 0064 // and return expression_ directly. 0065 if (display.isEmpty()) 0066 setData(QVector<QVariant>{expression, QString(), QString()}); 0067 else 0068 setData(QVector<QVariant>{display, QString(), QString()}); 0069 } 0070 0071 QString Variable::expression() const 0072 { 0073 return m_expression; 0074 } 0075 0076 bool Variable::inScope() const 0077 { 0078 return m_inScope; 0079 } 0080 0081 void Variable::setValue(const QString& v) 0082 { 0083 itemData[VariableCollection::ValueColumn] = v; 0084 reportChange(); 0085 } 0086 0087 QString Variable::value() const 0088 { 0089 return itemData[VariableCollection::ValueColumn].toString(); 0090 } 0091 0092 void Variable::setType(const QString& type) 0093 { 0094 itemData[VariableCollection::TypeColumn] = type; 0095 reportChange(); 0096 } 0097 0098 QString Variable::type() const 0099 { 0100 return itemData[VariableCollection::TypeColumn].toString(); 0101 } 0102 0103 void Variable::setTopLevel(bool v) 0104 { 0105 m_topLevel = v; 0106 } 0107 0108 void Variable::setInScope(bool v) 0109 { 0110 m_inScope = v; 0111 for (int i=0; i < childCount(); ++i) { 0112 if (auto *var = qobject_cast<Variable*>(child(i))) { 0113 var->setInScope(v); 0114 } 0115 } 0116 reportChange(); 0117 } 0118 0119 void Variable::setShowError (bool v) 0120 { 0121 m_showError = v; 0122 reportChange(); 0123 } 0124 0125 bool Variable::showError() 0126 { 0127 return m_showError; 0128 } 0129 0130 0131 Variable::~Variable() 0132 { 0133 } 0134 0135 void Variable::die() 0136 { 0137 removeSelf(); 0138 deleteLater(); 0139 } 0140 0141 0142 void Variable::setChanged(bool c) 0143 { 0144 m_changed=c; 0145 reportChange(); 0146 } 0147 0148 void Variable::resetChanged() 0149 { 0150 setChanged(false); 0151 for (int i=0; i<childCount(); ++i) { 0152 TreeItem* childItem = child(i); 0153 if (qobject_cast<Variable*>(childItem)) { 0154 static_cast<Variable*>(childItem)->resetChanged(); 0155 } 0156 } 0157 } 0158 0159 Variable::format_t Variable::str2format(const QString& str) 0160 { 0161 if(str==QLatin1String("Binary") || str==QLatin1String("binary")) return Binary; 0162 if(str==QLatin1String("Octal") || str==QLatin1String("octal")) return Octal; 0163 if(str==QLatin1String("Decimal") || str==QLatin1String("decimal")) return Decimal; 0164 if(str==QLatin1String("Hexadecimal") || str==QLatin1String("hexadecimal"))return Hexadecimal; 0165 0166 return Natural; // maybe most reasonable default 0167 } 0168 0169 QString Variable::format2str(format_t format) 0170 { 0171 switch(format) { 0172 case Natural: return QStringLiteral("natural"); 0173 case Binary: return QStringLiteral("binary"); 0174 case Octal: return QStringLiteral("octal"); 0175 case Decimal: return QStringLiteral("decimal"); 0176 case Hexadecimal: return QStringLiteral("hexadecimal"); 0177 } 0178 return QString(); 0179 } 0180 0181 0182 void Variable::setFormat(Variable::format_t format) 0183 { 0184 if (m_format != format) { 0185 m_format = format; 0186 formatChanged(); 0187 } 0188 } 0189 0190 void Variable::formatChanged() 0191 { 0192 } 0193 0194 bool Variable::isPotentialProblematicValue() const 0195 { 0196 const auto value = data(VariableCollection::ValueColumn, Qt::DisplayRole).toString(); 0197 return value == QLatin1String("0x0"); 0198 } 0199 0200 QVariant Variable::data(int column, int role) const 0201 { 0202 if (m_showError) { 0203 if (role == Qt::FontRole) { 0204 QVariant ret = TreeItem::data(column, role); 0205 QFont font = ret.value<QFont>(); 0206 font.setStyle(QFont::StyleItalic); 0207 return font; 0208 } else if (column == 1 && role == Qt::DisplayRole) { 0209 return i18n("Error"); 0210 } 0211 } 0212 if (column == 1 && role == Qt::ForegroundRole) 0213 { 0214 KColorScheme scheme(QPalette::Active); 0215 if (!m_inScope) { 0216 return scheme.foreground(KColorScheme::InactiveText).color(); 0217 } else if (isPotentialProblematicValue()) { 0218 return scheme.foreground(KColorScheme::NegativeText).color(); 0219 } else if (m_changed) { 0220 return scheme.foreground(KColorScheme::NeutralText).color(); 0221 } 0222 } 0223 if (role == Qt::ToolTipRole) { 0224 return TreeItem::data(column, Qt::DisplayRole); 0225 } 0226 0227 return TreeItem::data(column, role); 0228 } 0229 0230 Watches::Watches(TreeModel* model, TreeItem* parent) 0231 : TreeItem(model, parent), finishResult_(nullptr) 0232 { 0233 setData(QVector<QVariant>{i18n("Auto"), QString()}); 0234 } 0235 0236 Variable* Watches::add(const QString& expression) 0237 { 0238 if (!hasStartedSession()) return nullptr; 0239 0240 Variable* v = currentSession()->variableController()->createVariable( 0241 model(), this, expression); 0242 appendChild(v); 0243 v->attachMaybe(); 0244 if (childCount() == 1 && !isExpanded()) { 0245 setExpanded(true); 0246 } 0247 return v; 0248 } 0249 0250 Variable *Watches::addFinishResult(const QString& convenienceVarible) 0251 { 0252 if( finishResult_ ) 0253 { 0254 removeFinishResult(); 0255 } 0256 finishResult_ = currentSession()->variableController()->createVariable( 0257 model(), this, convenienceVarible, QStringLiteral("$ret")); 0258 appendChild(finishResult_); 0259 finishResult_->attachMaybe(); 0260 if (childCount() == 1 && !isExpanded()) { 0261 setExpanded(true); 0262 } 0263 return finishResult_; 0264 } 0265 0266 void Watches::removeFinishResult() 0267 { 0268 if (finishResult_) 0269 { 0270 finishResult_->die(); 0271 finishResult_ = nullptr; 0272 } 0273 } 0274 0275 void Watches::resetChanged() 0276 { 0277 for (int i=0; i<childCount(); ++i) { 0278 TreeItem* childItem = child(i); 0279 if (qobject_cast<Variable*>(childItem)) { 0280 static_cast<Variable*>(childItem)->resetChanged(); 0281 } 0282 } 0283 } 0284 0285 QVariant Watches::data(int column, int role) const 0286 { 0287 #if 0 0288 if (column == 0 && role == Qt::FontRole) 0289 { 0290 /* FIXME: is creating font again and again efficient? */ 0291 QFont f = font(); 0292 f.setBold(true); 0293 return f; 0294 } 0295 #endif 0296 return TreeItem::data(column, role); 0297 } 0298 0299 void Watches::reinstall() 0300 { 0301 for (int i = 0; i < childItems.size(); ++i) 0302 { 0303 auto* v = static_cast<Variable*>(child(i)); 0304 v->attachMaybe(); 0305 } 0306 } 0307 0308 Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name) 0309 : TreeItem(model, parent) 0310 { 0311 setData(QVector<QVariant>{name, QString()}); 0312 } 0313 0314 QList<Variable*> Locals::updateLocals(const QStringList& locals) 0315 { 0316 QSet<QString> existing, current; 0317 for (int i = 0; i < childItems.size(); i++) 0318 { 0319 Q_ASSERT(qobject_cast<KDevelop::Variable*>(child(i))); 0320 auto* var= static_cast<KDevelop::Variable*>(child(i)); 0321 existing << var->expression(); 0322 } 0323 0324 for (const QString& var : locals) { 0325 current << var; 0326 // If we currently don't display this local var, add it. 0327 if( !existing.contains( var ) ) { 0328 // FIXME: passing variableCollection this way is awkward. 0329 // In future, variableCollection probably should get a 0330 // method to create variable. 0331 Variable* v = 0332 currentSession()->variableController()->createVariable( 0333 ICore::self()->debugController()->variableCollection(), 0334 this, var ); 0335 appendChild( v, false ); 0336 } 0337 } 0338 0339 for (int i = 0; i < childItems.size(); ++i) { 0340 auto* v = static_cast<KDevelop::Variable*>(child(i)); 0341 if (!current.contains(v->expression())) { 0342 removeChild(i); 0343 --i; 0344 // FIXME: check that -var-delete is sent. 0345 delete v; 0346 } 0347 } 0348 0349 0350 if (hasMore()) { 0351 setHasMore(false); 0352 } 0353 0354 // Variables which changed just value are updated by a call to -var-update. 0355 // Variables that changed type -- likewise. 0356 0357 QList<Variable*> ret; 0358 ret.reserve(childItems.size()); 0359 for (TreeItem* i : qAsConst(childItems)) { 0360 Q_ASSERT(qobject_cast<Variable*>(i)); 0361 ret << static_cast<Variable*>(i); 0362 } 0363 return ret; 0364 } 0365 0366 void Locals::resetChanged() 0367 { 0368 for (int i=0; i<childCount(); ++i) { 0369 TreeItem* childItem = child(i); 0370 if (qobject_cast<Variable*>(childItem)) { 0371 static_cast<Variable*>(childItem)->resetChanged(); 0372 } 0373 } 0374 } 0375 0376 VariablesRoot::VariablesRoot(TreeModel* model) 0377 : TreeItem(model) 0378 , m_watches(new Watches(model, this)) 0379 { 0380 appendChild(m_watches, true); 0381 } 0382 0383 0384 Locals* VariablesRoot::locals(const QString& name) 0385 { 0386 auto localsIt = m_locals.find(name); 0387 if (localsIt == m_locals.end()) { 0388 localsIt = m_locals.insert(name, new Locals(model(), this, name)); 0389 appendChild(*localsIt); 0390 } 0391 return *localsIt; 0392 } 0393 0394 QHash<QString, Locals*> VariablesRoot::allLocals() const 0395 { 0396 return m_locals; 0397 } 0398 0399 void VariablesRoot::resetChanged() 0400 { 0401 m_watches->resetChanged(); 0402 for (Locals* l : qAsConst(m_locals)) { 0403 l->resetChanged(); 0404 } 0405 } 0406 0407 VariableCollection::VariableCollection(IDebugController* controller) 0408 : TreeModel({i18n("Name"), i18n("Value"), i18n("Type")}, controller) 0409 , m_widgetVisible(false) 0410 , m_textHintProvider(this) 0411 { 0412 m_universe = new VariablesRoot(this); 0413 setRootItem(m_universe); 0414 0415 //new ModelTest(this); 0416 0417 connect (ICore::self()->documentController(), 0418 &IDocumentController::textDocumentCreated, 0419 this, 0420 &VariableCollection::textDocumentCreated ); 0421 0422 connect(controller, &IDebugController::currentSessionChanged, 0423 this, &VariableCollection::updateAutoUpdate); 0424 0425 // Qt5 signal slot syntax does not support default arguments 0426 auto callUpdateAutoUpdate = [&] { updateAutoUpdate(); }; 0427 0428 connect(locals(), &Locals::expanded, this, callUpdateAutoUpdate); 0429 connect(locals(), &Locals::collapsed, this, callUpdateAutoUpdate); 0430 connect(watches(), &Watches::expanded, this, callUpdateAutoUpdate); 0431 connect(watches(), &Watches::collapsed, this, callUpdateAutoUpdate); 0432 } 0433 0434 void VariableCollection::variableWidgetHidden() 0435 { 0436 m_widgetVisible = false; 0437 updateAutoUpdate(); 0438 } 0439 0440 void VariableCollection::variableWidgetShown() 0441 { 0442 m_widgetVisible = true; 0443 updateAutoUpdate(); 0444 } 0445 0446 void VariableCollection::updateAutoUpdate(IDebugSession* session) 0447 { 0448 if (!session) session = currentSession(); 0449 qCDebug(DEBUGGER) << session; 0450 if (!session) return; 0451 0452 if (!m_widgetVisible) { 0453 session->variableController()->setAutoUpdate(IVariableController::UpdateNone); 0454 } else { 0455 QFlags<IVariableController::UpdateType> t = IVariableController::UpdateNone; 0456 if (locals()->isExpanded()) t |= IVariableController::UpdateLocals; 0457 if (watches()->isExpanded()) t |= IVariableController::UpdateWatches; 0458 session->variableController()->setAutoUpdate(t); 0459 } 0460 } 0461 0462 VariableCollection::~ VariableCollection() 0463 { 0464 for (auto* view : qAsConst(m_textHintProvidedViews)) { 0465 auto* iface = qobject_cast<KTextEditor::TextHintInterface*>(view); 0466 iface->unregisterTextHintProvider(&m_textHintProvider); 0467 } 0468 } 0469 0470 void VariableCollection::textDocumentCreated(IDocument* doc) 0471 { 0472 connect( doc->textDocument(), 0473 &KTextEditor::Document::viewCreated, 0474 this, &VariableCollection::viewCreated ); 0475 0476 const auto views = doc->textDocument()->views(); 0477 for (KTextEditor::View* view : views) { 0478 viewCreated( doc->textDocument(), view ); 0479 } 0480 } 0481 0482 void VariableCollection::viewCreated(KTextEditor::Document* doc, 0483 KTextEditor::View* view) 0484 { 0485 Q_UNUSED(doc); 0486 using namespace KTextEditor; 0487 auto* iface = qobject_cast<TextHintInterface*>(view); 0488 if( !iface ) 0489 return; 0490 0491 if (m_textHintProvidedViews.contains(view)) { 0492 return; 0493 } 0494 connect(view, &View::destroyed, this, [this, view](QObject* obj) { 0495 Q_ASSERT(obj == view); 0496 m_textHintProvidedViews.removeOne(view); 0497 }); 0498 0499 iface->registerTextHintProvider(&m_textHintProvider); 0500 m_textHintProvidedViews.append(view); 0501 } 0502 0503 Locals* VariableCollection::locals(const QString &name) const 0504 { 0505 return m_universe->locals(name.isEmpty() ? i18n("Locals") : name); 0506 } 0507 0508 VariableProvider::VariableProvider(VariableCollection* collection) 0509 : KTextEditor::TextHintProvider() 0510 , m_collection(collection) 0511 { 0512 } 0513 0514 QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor) 0515 { 0516 if (!hasStartedSession()) 0517 return QString(); 0518 0519 if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("debug")) 0520 return QString(); 0521 0522 //TODO: These keyboardModifiers should also hide already opened tooltip, and show another one for code area. 0523 if (QApplication::keyboardModifiers() == Qt::ControlModifier || 0524 QApplication::keyboardModifiers() == Qt::AltModifier){ 0525 return QString(); 0526 } 0527 0528 KTextEditor::Document* doc = view->document(); 0529 0530 KTextEditor::Range expressionRange = currentSession()->variableController()->expressionRangeUnderCursor(doc, cursor); 0531 0532 if (!expressionRange.isValid()) 0533 return QString(); 0534 QString expression = doc->text(expressionRange).trimmed(); 0535 0536 // Don't do anything if there's already an open tooltip with matching range 0537 if (m_collection->m_activeTooltip && m_collection->m_activeTooltip->variable()->expression() == expression) 0538 return QString(); 0539 if (expression.isEmpty()) 0540 return QString(); 0541 0542 QPoint local = view->cursorToCoordinate(cursor); 0543 QPoint global = view->mapToGlobal(local); 0544 QWidget* w = view->childAt(local); 0545 if (!w) 0546 w = view; 0547 0548 m_collection->m_activeTooltip = new VariableToolTip(w, global+QPoint(30,30), expression); 0549 m_collection->m_activeTooltip->setHandleRect(KTextEditorHelpers::itemBoundingRect(view, expressionRange)); 0550 return QString(); 0551 } 0552 0553 } 0554 0555 #include "moc_variablecollection.cpp"