diff --git a/kdevplatform/debugger/breakpoint/breakpointmodel.cpp b/kdevplatform/debugger/breakpoint/breakpointmodel.cpp index dc69945fd4..f93a1bc03f 100644 --- a/kdevplatform/debugger/breakpoint/breakpointmodel.cpp +++ b/kdevplatform/debugger/breakpoint/breakpointmodel.cpp @@ -1,713 +1,714 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, see . */ #include "breakpointmodel.h" #include #include #include #include #include #include #include "../interfaces/icore.h" #include "../interfaces/idebugcontroller.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/idocument.h" #include "../interfaces/ipartcontroller.h" #include #include #include #include #include "breakpoint.h" #include #include #include #include #define IF_DEBUG(x) using namespace KDevelop; using namespace KTextEditor; namespace { IBreakpointController* breakpointController() { KDevelop::ICore* core = KDevelop::ICore::self(); if (!core) { return nullptr; } IDebugController* controller = core->debugController(); if (!controller) { return nullptr; } IDebugSession* session = controller->currentSession(); return session ? session->breakpointController() : nullptr; } } // anonymous namespace class KDevelop::BreakpointModelPrivate { public: bool dirty = false; bool dontUpdateMarks = false; QList breakpoints; }; BreakpointModel::BreakpointModel(QObject* parent) : QAbstractTableModel(parent), d_ptr(new BreakpointModelPrivate) { connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::updateMarks); if (KDevelop::ICore::self()->partController()) { //TODO remove if const auto parts = KDevelop::ICore::self()->partController()->parts(); for (KParts::Part* p : parts) { slotPartAdded(p); } connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &BreakpointModel::slotPartAdded); } connect (KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &BreakpointModel::textDocumentCreated); connect (KDevelop::ICore::self()->documentController(), &IDocumentController::documentSaved, this, &BreakpointModel::documentSaved); } BreakpointModel::~BreakpointModel() { Q_D(BreakpointModel); qDeleteAll(d->breakpoints); } void BreakpointModel::slotPartAdded(KParts::Part* part) { if (auto doc = qobject_cast(part)) { auto *iface = qobject_cast(doc); if( !iface ) return; iface->setMarkDescription((MarkInterface::MarkTypes)BreakpointMark, i18n("Breakpoint")); iface->setMarkPixmap((MarkInterface::MarkTypes)BreakpointMark, *breakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)PendingBreakpointMark, *pendingBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)ReachedBreakpointMark, *reachedBreakpointPixmap()); iface->setMarkPixmap((MarkInterface::MarkTypes)DisabledBreakpointMark, *disabledBreakpointPixmap()); iface->setEditableMarks( MarkInterface::Bookmark | BreakpointMark ); updateMarks(); } } void BreakpointModel::textDocumentCreated(KDevelop::IDocument* doc) { KTextEditor::MarkInterface *iface = qobject_cast(doc->textDocument()); if (iface) { // can't use new signal slot syntax here, MarkInterface is not a QObject connect(doc->textDocument(), SIGNAL(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction)), this, SLOT(markChanged(KTextEditor::Document*,KTextEditor::Mark,KTextEditor::MarkInterface::MarkChangeAction))); connect(doc->textDocument(), SIGNAL(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), SLOT(markContextMenuRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&))); } } void BreakpointModel::markContextMenuRequested(Document* document, Mark mark, const QPoint &pos, bool& handled) { int type = mark.type; qCDebug(DEBUGGER) << type; Breakpoint *b = nullptr; if ((type & AllBreakpointMarks)) { b = breakpoint(document->url(), mark.line); if (!b) { QMessageBox::critical(nullptr, i18n("Breakpoint not found"), i18n("Couldn't find breakpoint at %1:%2", document->url().toString(), mark.line)); } } else if (!(type & MarkInterface::Bookmark)) // neither breakpoint nor bookmark return; QMenu menu; // TODO: needs qwidget QAction* breakpointAction = menu.addAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18n("&Breakpoint")); breakpointAction->setCheckable(true); breakpointAction->setChecked(b); QAction* enableAction = nullptr; if (b) { enableAction = b->enabled() ? menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("&Disable Breakpoint")) : menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("&Enable Breakpoint")); } menu.addSeparator(); QAction* bookmarkAction = menu.addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("&Bookmark")); bookmarkAction->setCheckable(true); bookmarkAction->setChecked((type & MarkInterface::Bookmark)); QAction* triggeredAction = menu.exec(pos); if (triggeredAction) { if (triggeredAction == bookmarkAction) { KTextEditor::MarkInterface *iface = qobject_cast(document); if ((type & MarkInterface::Bookmark)) iface->removeMark(mark.line, MarkInterface::Bookmark); else iface->addMark(mark.line, MarkInterface::Bookmark); } else if (triggeredAction == breakpointAction) { if (b) { b->setDeleted(); } else { Breakpoint *breakpoint = addCodeBreakpoint(document->url(), mark.line); MovingInterface *moving = qobject_cast(document); if (moving) { MovingCursor* cursor = moving->newMovingCursor(Cursor(mark.line, 0)); // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(Document*)), Qt::UniqueConnection); breakpoint->setMovingCursor(cursor); } } } else if (triggeredAction == enableAction) { b->setData(Breakpoint::EnableColumn, b->enabled() ? Qt::Unchecked : Qt::Checked); } } handled = true; } QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); if (role == Qt::DecorationRole ) { if (section == 0) return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); else if (section == 1) return QIcon::fromTheme(QStringLiteral("system-switch-user")); } if (role == Qt::DisplayRole) { if (section == 0 || section == 1) return QString(); if (section == 2) return i18n("Type"); if (section == 3) return i18n("Location"); if (section == 4) return i18n("Condition"); } if (role == Qt::ToolTipRole) { if (section == 0) return i18n("Active status"); if (section == 1) return i18n("State"); return headerData(section, orientation, Qt::DisplayRole); } return QVariant(); } Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const { /* FIXME: all this logic must be in item */ if (!index.isValid()) return Qt::NoItemFlags; if (index.column() == 0) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); if (index.column() == Breakpoint::ConditionColumn) return static_cast( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); return static_cast(Qt::ItemIsEnabled | Qt::ItemIsSelectable); } QModelIndex BreakpointModel::breakpointIndex(KDevelop::Breakpoint* b, int column) { Q_D(BreakpointModel); int row = d->breakpoints.indexOf(b); if (row == -1) return QModelIndex(); return index(row, column); } bool KDevelop::BreakpointModel::removeRows(int row, int count, const QModelIndex& parent) { Q_D(BreakpointModel); if (count < 1 || (row < 0) || (row + count) > rowCount(parent)) return false; IBreakpointController* controller = breakpointController(); beginRemoveRows(parent, row, row+count-1); for (int i=0; i < count; ++i) { Breakpoint* b = d->breakpoints.at(row); b->m_deleted = true; if (controller) controller->breakpointAboutToBeDeleted(row); d->breakpoints.removeAt(row); b->m_model = nullptr; // To be changed: the controller is currently still responsible for deleting the breakpoint // object } endRemoveRows(); updateMarks(); scheduleSave(); return true; } int KDevelop::BreakpointModel::rowCount(const QModelIndex& parent) const { Q_D(const BreakpointModel); if (!parent.isValid()) { return d->breakpoints.count(); } return 0; } int KDevelop::BreakpointModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 5; } QVariant BreakpointModel::data(const QModelIndex& index, int role) const { Q_D(const BreakpointModel); if (!index.parent().isValid() && index.row() < d->breakpoints.count()) { return d->breakpoints.at(index.row())->data(index.column(), role); } return QVariant(); } bool KDevelop::BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role) { Q_D(const BreakpointModel); if (!index.parent().isValid() && index.row() < d->breakpoints.count() && (role == Qt::EditRole || role == Qt::CheckStateRole)) { return d->breakpoints.at(index.row())->setData(index.column(), value); } return false; } void BreakpointModel::updateState(int row, Breakpoint::BreakpointState state) { Q_D(BreakpointModel); Breakpoint* breakpoint = d->breakpoints.at(row); if (state != breakpoint->m_state) { breakpoint->m_state = state; reportChange(breakpoint, Breakpoint::StateColumn); } } void BreakpointModel::updateHitCount(int row, int hitCount) { Q_D(BreakpointModel); Breakpoint* breakpoint = d->breakpoints.at(row); if (hitCount != breakpoint->m_hitCount) { breakpoint->m_hitCount = hitCount; reportChange(breakpoint, Breakpoint::HitCountColumn); } } void BreakpointModel::updateErrorText(int row, const QString& errorText) { Q_D(BreakpointModel); Breakpoint* breakpoint = d->breakpoints.at(row); if (breakpoint->m_errorText != errorText) { breakpoint->m_errorText = errorText; reportChange(breakpoint, Breakpoint::StateColumn); } if (!errorText.isEmpty()) { emit error(row, errorText); } } void BreakpointModel::notifyHit(int row) { emit hit(row); } void BreakpointModel::markChanged( KTextEditor::Document *document, KTextEditor::Mark mark, KTextEditor::MarkInterface::MarkChangeAction action) { int type = mark.type; /* Is this a breakpoint mark, to begin with? */ if (!(type & AllBreakpointMarks)) return; if (action == KTextEditor::MarkInterface::MarkAdded) { Breakpoint *b = breakpoint(document->url(), mark.line); if (b) { //there was already a breakpoint, so delete instead of adding b->setDeleted(); return; } Breakpoint *breakpoint = addCodeBreakpoint(document->url(), mark.line); KTextEditor::MovingInterface *moving = qobject_cast(document); if (moving) { KTextEditor::MovingCursor* cursor = moving->newMovingCursor(KTextEditor::Cursor(mark.line, 0)); // can't use new signal/slot syntax here, MovingInterface is not a QObject connect(document, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), Qt::UniqueConnection); breakpoint->setMovingCursor(cursor); } } else { // Find this breakpoint and delete it Breakpoint *b = breakpoint(document->url(), mark.line); if (b) { b->setDeleted(); } } #if 0 if ( KDevelop::ICore::self()->documentController()->activeDocument() && KDevelop::ICore::self()->documentController()->activeDocument()->textDocument() == document ) { //bring focus back to the editor // TODO probably want a different command here KDevelop::ICore::self()->documentController()->activateDocument(KDevelop::ICore::self()->documentController()->activeDocument()); } #endif } const QPixmap* BreakpointModel::breakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Active, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::pendingBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Normal, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::reachedBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Selected, QIcon::Off); return &pixmap; } const QPixmap* BreakpointModel::disabledBreakpointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("breakpoint")).pixmap(QSize(16,16), QIcon::Disabled, QIcon::Off); return &pixmap; } void BreakpointModel::toggleBreakpoint(const QUrl& url, const KTextEditor::Cursor& cursor) { Breakpoint *b = breakpoint(url, cursor.line()); if (b) { b->setDeleted(); } else { addCodeBreakpoint(url, cursor.line()); } } void BreakpointModel::reportChange(Breakpoint* breakpoint, Breakpoint::Column column) { Q_D(BreakpointModel); // note: just a portion of Breakpoint::Column is displayed in this model! if (column >= 0 && column < columnCount()) { QModelIndex idx = breakpointIndex(breakpoint, column); Q_ASSERT(idx.isValid()); // make sure we don't pass invalid indices to dataChanged() emit dataChanged(idx, idx); } if (IBreakpointController* controller = breakpointController()) { int row = d->breakpoints.indexOf(breakpoint); Q_ASSERT(row != -1); controller->breakpointModelChanged(row, ColumnFlags(1 << column)); } scheduleSave(); } uint BreakpointModel::breakpointType(Breakpoint *breakpoint) const { uint type = BreakpointMark; if (!breakpoint->enabled()) { type = DisabledBreakpointMark; } else if (breakpoint->hitCount() > 0) { type = ReachedBreakpointMark; } else if (breakpoint->state() == Breakpoint::PendingState) { type = PendingBreakpointMark; } return type; } void KDevelop::BreakpointModel::updateMarks() { Q_D(BreakpointModel); if (d->dontUpdateMarks) return; //add marks for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (breakpoint->line() == -1) continue; IDocument *doc = ICore::self()->documentController()->documentForUrl(breakpoint->url()); if (!doc) continue; KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; uint type = breakpointType(breakpoint); IF_DEBUG( qCDebug(DEBUGGER) << type << breakpoint->url() << mark->mark(breakpoint->line()); ) { QSignalBlocker blocker(doc->textDocument()); if (mark->mark(breakpoint->line()) & AllBreakpointMarks) { if (!(mark->mark(breakpoint->line()) & type)) { mark->removeMark(breakpoint->line(), AllBreakpointMarks); mark->addMark(breakpoint->line(), type); } } else { mark->addMark(breakpoint->line(), type); } } } //remove marks const auto documents = ICore::self()->documentController()->openDocuments(); for (IDocument* doc : documents) { KTextEditor::MarkInterface *mark = qobject_cast(doc->textDocument()); if (!mark) continue; { QSignalBlocker blocker(doc->textDocument()); - for (KTextEditor::Mark* m : mark->marks()) { + const auto oldMarks = mark->marks(); + for (KTextEditor::Mark* m : oldMarks) { if (!(m->type & AllBreakpointMarks)) continue; IF_DEBUG( qCDebug(DEBUGGER) << m->line << m->type; ) for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) { if (breakpoint->kind() != Breakpoint::CodeBreakpoint) continue; if (doc->url() == breakpoint->url() && m->line == breakpoint->line()) { goto continueNextMark; } } mark->removeMark(m->line, AllBreakpointMarks); continueNextMark:; } } } } void BreakpointModel::documentSaved(KDevelop::IDocument* doc) { Q_D(BreakpointModel); IF_DEBUG( qCDebug(DEBUGGER); ) for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) { if (breakpoint->movingCursor()) { if (breakpoint->movingCursor()->document() != doc->textDocument()) continue; if (breakpoint->movingCursor()->line() == breakpoint->line()) continue; d->dontUpdateMarks = true; breakpoint->setLine(breakpoint->movingCursor()->line()); d->dontUpdateMarks = false; } } } void BreakpointModel::aboutToDeleteMovingInterfaceContent(KTextEditor::Document* document) { Q_D(BreakpointModel); for (Breakpoint* breakpoint : qAsConst(d->breakpoints)) { if (breakpoint->movingCursor() && breakpoint->movingCursor()->document() == document) { breakpoint->setMovingCursor(nullptr); } } } void BreakpointModel::load() { KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); int count = breakpoints.readEntry("number", 0); if (count == 0) return; beginInsertRows(QModelIndex(), 0, count - 1); for (int i = 0; i < count; ++i) { if (!breakpoints.group(QString::number(i)).readEntry("kind", "").isEmpty()) { new Breakpoint(this, breakpoints.group(QString::number(i))); } } endInsertRows(); } void BreakpointModel::save() { Q_D(BreakpointModel); d->dirty = false; KConfigGroup breakpoints = ICore::self()->activeSession()->config()->group("Breakpoints"); breakpoints.writeEntry("number", d->breakpoints.count()); int i = 0; for (Breakpoint* b : qAsConst(d->breakpoints)) { KConfigGroup g = breakpoints.group(QString::number(i)); b->save(g); ++i; } breakpoints.sync(); } void BreakpointModel::scheduleSave() { Q_D(BreakpointModel); if (d->dirty) return; d->dirty = true; QTimer::singleShot(0, this, &BreakpointModel::save); } QList KDevelop::BreakpointModel::breakpoints() const { Q_D(const BreakpointModel); return d->breakpoints; } Breakpoint* BreakpointModel::breakpoint(int row) const { Q_D(const BreakpointModel); if (row >= d->breakpoints.count()) return nullptr; return d->breakpoints.at(row); } Breakpoint* BreakpointModel::addCodeBreakpoint() { Q_D(BreakpointModel); beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count()); auto* n = new Breakpoint(this, Breakpoint::CodeBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QUrl& url, int line) { Breakpoint* n = addCodeBreakpoint(); n->setLocation(url, line); return n; } Breakpoint* BreakpointModel::addCodeBreakpoint(const QString& expression) { Breakpoint* n = addCodeBreakpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addWatchpoint() { Q_D(BreakpointModel); beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count()); auto* n = new Breakpoint(this, Breakpoint::WriteBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addWatchpoint(const QString& expression) { Breakpoint* n = addWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addReadWatchpoint() { Q_D(BreakpointModel); beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count()); auto* n = new Breakpoint(this, Breakpoint::ReadBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addReadWatchpoint(const QString& expression) { Breakpoint* n = addReadWatchpoint(); n->setExpression(expression); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint() { Q_D(BreakpointModel); beginInsertRows(QModelIndex(), d->breakpoints.count(), d->breakpoints.count()); auto* n = new Breakpoint(this, Breakpoint::AccessBreakpoint); endInsertRows(); return n; } Breakpoint* BreakpointModel::addAccessWatchpoint(const QString& expression) { Breakpoint* n = addAccessWatchpoint(); n->setExpression(expression); return n; } void BreakpointModel::registerBreakpoint(Breakpoint* breakpoint) { Q_D(BreakpointModel); Q_ASSERT(!d->breakpoints.contains(breakpoint)); int row = d->breakpoints.size(); d->breakpoints << breakpoint; if (IBreakpointController* controller = breakpointController()) { controller->breakpointAdded(row); } scheduleSave(); } Breakpoint* BreakpointModel::breakpoint(const QUrl& url, int line) const { Q_D(const BreakpointModel); auto it = std::find_if(d->breakpoints.constBegin(), d->breakpoints.constEnd(), [&](Breakpoint* b) { return (b->url() == url && b->line() == line); }); return (it != d->breakpoints.constEnd()) ? *it : nullptr; } diff --git a/kdevplatform/shell/debugcontroller.cpp b/kdevplatform/shell/debugcontroller.cpp index 73769e981c..502707d502 100644 --- a/kdevplatform/shell/debugcontroller.cpp +++ b/kdevplatform/shell/debugcontroller.cpp @@ -1,577 +1,576 @@ /* This file is part of KDevelop * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "debugcontroller.h" #include #include #include #include #include #include #include "../interfaces/idocument.h" #include "../interfaces/icore.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/ipartcontroller.h" #include "../interfaces/contextmenuextension.h" #include "../interfaces/context.h" #include "../language/interfaces/editorcontext.h" #include "../sublime/view.h" #include "../sublime/mainwindow.h" #include "../sublime/area.h" #include "../debugger/breakpoint/breakpointmodel.h" #include "../debugger/breakpoint/breakpointwidget.h" #include "../debugger/variable/variablewidget.h" #include "../debugger/framestack/framestackmodel.h" #include "../debugger/framestack/framestackwidget.h" #include "core.h" #include "debug.h" #include "uicontroller.h" #include "iruncontroller.h" namespace KDevelop { template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(DebugController* controller, const QString &id, Qt::DockWidgetArea defaultArea) : m_controller(controller), m_id(id), m_defaultArea(defaultArea) {} QWidget* create(QWidget *parent = nullptr) override { return new T(m_controller, parent); } QString id() const override { return m_id; } Qt::DockWidgetArea defaultPosition() override { return m_defaultArea; } void viewCreated(Sublime::View* view) override { if (view->widget()->metaObject()->indexOfSignal("requestRaise()") != -1) QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise())); } /* At present, some debugger widgets (e.g. breakpoint) contain actions so that shortcuts work, but they don't need any toolbar. So, suppress toolbar action. */ QList toolBarActions( QWidget* viewWidget ) const override { Q_UNUSED(viewWidget); return QList(); } private: DebugController* const m_controller; const QString m_id; const Qt::DockWidgetArea m_defaultArea; }; DebugController::DebugController(QObject *parent) : IDebugController(parent), KXMLGUIClient(), m_breakpointModel(new BreakpointModel(this)), m_variableCollection(new VariableCollection(this)) { setComponentName(QStringLiteral("kdevdebugger"), i18n("Debugger")); setXMLFile(QStringLiteral("kdevdebuggershellui.rc")); } void DebugController::initialize() { m_breakpointModel->load(); } void DebugController::initializeUi() { if (m_uiInitialized) return; m_uiInitialized = true; if((Core::self()->setupFlags() & Core::NoUi)) return; setupActions(); ICore::self()->uiController()->addToolView( i18n("Frame Stack"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.StackView"), Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Breakpoints"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.BreakpointsView"), Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Variables"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.VariablesView"), Qt::LeftDockWidgetArea)); const auto parts = KDevelop::ICore::self()->partController()->parts(); for (KParts::Part* p : parts) { partAdded(p); } connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &DebugController::partAdded); ICore::self()->uiController()->activeMainWindow()->guiFactory()->addClient(this); stateChanged(QStringLiteral("ended")); } void DebugController::cleanup() { if (m_currentSession) m_currentSession.data()->stopDebugger(); } DebugController::~DebugController() { } BreakpointModel* DebugController::breakpointModel() { return m_breakpointModel; } VariableCollection* DebugController::variableCollection() { return m_variableCollection; } void DebugController::partAdded(KParts::Part* part) { if (auto* doc = qobject_cast(part)) { auto* iface = qobject_cast(doc); if( !iface ) return; iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, *executionPointPixmap()); } } IDebugSession* DebugController::currentSession() { return m_currentSession.data(); } void DebugController::setupActions() { KActionCollection* ac = actionCollection(); QAction* action = m_continueDebugger = new QAction(this); setContinueStartsDebug(true); ac->addAction(QStringLiteral("debug_continue"), action); connect(action, &QAction::triggered, this, &DebugController::run); #if 0 m_restartDebugger = action = new QAction(QIcon::fromTheme("media-seek-backward"), i18n("&Restart"), this); action->setToolTip( i18n("Restart program") ); action->setWhatsThis( i18n("Restarts applications from the beginning.") ); action->setEnabled(false); connect(action, SIGNAL(triggered(bool)), this, SLOT(restartDebugger())); ac->addAction("debug_restart", action); #endif m_interruptDebugger = action = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-pause")), i18n("Interrupt"), this); action->setToolTip( i18n("Interrupt application") ); action->setWhatsThis(i18n("Interrupts the debugged process or current debugger command.")); connect(action, &QAction::triggered, this, &DebugController::interruptDebugger); ac->addAction(QStringLiteral("debug_pause"), action); m_runToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-run-cursor")), i18n("Run to &Cursor"), this); action->setToolTip( i18n("Run to cursor") ); action->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(action, &QAction::triggered, this, &DebugController::runToCursor); ac->addAction(QStringLiteral("debug_runtocursor"), action); m_jumpToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-execute-to-cursor")), i18n("Set E&xecution Position to Cursor"), this); action->setToolTip( i18n("Jump to cursor") ); action->setWhatsThis(i18n("Continue execution from the current cursor position.")); connect(action, &QAction::triggered, this, &DebugController::jumpToCursor); ac->addAction(QStringLiteral("debug_jumptocursor"), action); m_stepOver = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-over")), i18n("Step &Over"), this); ac->setDefaultShortcut( action, Qt::Key_F10); action->setToolTip( i18n("Step over the next line") ); action->setWhatsThis( i18n("Executes one line of source in the current source file. " "If the source line is a call to a function the whole " "function is executed and the app will stop at the line " "following the function call.") ); connect(action, &QAction::triggered, this, &DebugController::stepOver); ac->addAction(QStringLiteral("debug_stepover"), action); m_stepOverInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-instruction")), i18n("Step over Ins&truction"), this); action->setToolTip( i18n("Step over instruction") ); action->setWhatsThis(i18n("Steps over the next assembly instruction.")); connect(action, &QAction::triggered, this, &DebugController::stepOverInstruction); ac->addAction(QStringLiteral("debug_stepoverinst"), action); m_stepInto = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into")), i18n("Step &Into"), this); ac->setDefaultShortcut( action, Qt::Key_F11); action->setToolTip( i18n("Step into the next statement") ); action->setWhatsThis( i18n("Executes exactly one line of source. If the source line " "is a call to a function then execution will stop after " "the function has been entered.") ); connect(action, &QAction::triggered, this, &DebugController::stepInto); ac->addAction(QStringLiteral("debug_stepinto"), action); m_stepIntoInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into-instruction")), i18n("Step into I&nstruction"), this); action->setToolTip( i18n("Step into instruction") ); action->setWhatsThis(i18n("Steps into the next assembly instruction.")); connect(action, &QAction::triggered, this, &DebugController::stepIntoInstruction); ac->addAction(QStringLiteral("debug_stepintoinst"), action); m_stepOut = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-out")), i18n("Step O&ut"), this); ac->setDefaultShortcut( action, Qt::Key_F12); action->setToolTip( i18n("Step out of the current function") ); action->setWhatsThis( i18n("Executes the application until the currently executing " "function is completed. The debugger will then display " "the line after the original call to that function. If " "program execution is in the outermost frame (i.e. in " "main()) then this operation has no effect.") ); connect(action, &QAction::triggered, this, &DebugController::stepOut); ac->addAction(QStringLiteral("debug_stepout"), action); m_toggleBreakpoint = action = new QAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18n("Toggle Breakpoint"), this); ac->setDefaultShortcut( action, i18n("Ctrl+Alt+B") ); action->setToolTip(i18n("Toggle breakpoint")); action->setWhatsThis(i18n("Toggles the breakpoint at the current line in editor.")); connect(action, &QAction::triggered, this, &DebugController::toggleBreakpoint); ac->addAction(QStringLiteral("debug_toggle_breakpoint"), action); m_showCurrentLine = action = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("Show Current Line"), this); action->setToolTip(i18n("Show the current execution position")); action->setWhatsThis(i18n("Jumps to the execution line in the editor.")); connect(action, &QAction::triggered, this, &DebugController::showCurrentLine); ac->addAction(QStringLiteral("debug_showcurrentline"), action); } void DebugController::addSession(IDebugSession* session) { qCDebug(SHELL) << session; Q_ASSERT(session->variableController()); Q_ASSERT(session->breakpointController()); Q_ASSERT(session->frameStackModel()); //TODO support multiple sessions if (m_currentSession) { m_currentSession.data()->stopDebugger(); } m_currentSession = session; connect(session, &IDebugSession::stateChanged, this, &DebugController::debuggerStateChanged); connect(session, &IDebugSession::showStepInSource, this, &DebugController::showStepInSource); connect(session, &IDebugSession::clearExecutionPoint, this, &DebugController::clearExecutionPoint); connect(session, &IDebugSession::raiseFramestackViews, this, &DebugController::raiseFramestackViews); updateDebuggerState(session->state(), session); emit currentSessionChanged(session); if((Core::self()->setupFlags() & Core::NoUi)) return; Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow->area()->objectName() != QLatin1String("debug")) { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea(QStringLiteral("debug"), IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); connect(mainWindow, &Sublime::MainWindow::areaChanged, this, &DebugController::areaChanged); } } void DebugController::clearExecutionPoint() { qCDebug(SHELL); const auto documents = KDevelop::ICore::self()->documentController()->openDocuments(); for (KDevelop::IDocument* document : documents) { auto* iface = qobject_cast(document->textDocument()); if (!iface) continue; - QHashIterator it = iface->marks(); - while (it.hasNext()) - { - KTextEditor::Mark* mark = it.next().value(); - if( mark->type & KTextEditor::MarkInterface::Execution ) + const auto oldMarks = iface->marks(); + for (KTextEditor::Mark* mark : oldMarks) { + if (mark->type & KTextEditor::MarkInterface::Execution) { iface->removeMark( mark->line, KTextEditor::MarkInterface::Execution ); + } } } } void DebugController::showStepInSource(const QUrl &url, int lineNum) { if((Core::self()->setupFlags() & Core::NoUi)) return; clearExecutionPoint(); qCDebug(SHELL) << url << lineNum; Q_ASSERT(qobject_cast(sender())); QPair openUrl = static_cast(sender())->convertToLocalUrl(qMakePair( url, lineNum )); KDevelop::IDocument* document = KDevelop::ICore::self() ->documentController() ->openDocument(openUrl.first, KTextEditor::Cursor(openUrl.second, 0), IDocumentController::DoNotFocus); if( !document ) return; auto* iface = qobject_cast(document->textDocument()); if( !iface ) return; { QSignalBlocker blocker(document->textDocument()); iface->addMark( lineNum, KTextEditor::MarkInterface::Execution ); } } void DebugController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state) { Q_ASSERT(qobject_cast(sender())); auto* session = static_cast(sender()); qCDebug(SHELL) << session << state << "current" << m_currentSession.data(); if (session == m_currentSession.data()) { updateDebuggerState(state, session); } if (state == IDebugSession::EndedState) { if (session == m_currentSession.data()) { m_currentSession.clear(); emit currentSessionChanged(nullptr); if (!Core::self()->shuttingDown()) { Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow && mainWindow->area()->objectName() != QLatin1String("code")) { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea(QStringLiteral("code"), IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); } ICore::self()->uiController()->findToolView(i18n("Debug"), nullptr, IUiController::Raise); } } session->deleteLater(); } } void DebugController::updateDebuggerState(IDebugSession::DebuggerState state, IDebugSession *session) { Q_UNUSED(session); if((Core::self()->setupFlags() & Core::NoUi)) return; qCDebug(SHELL) << state; switch (state) { case IDebugSession::StoppedState: case IDebugSession::NotStartedState: case IDebugSession::StoppingState: qCDebug(SHELL) << "new state: stopped"; stateChanged(QStringLiteral("stopped")); setContinueStartsDebug(true); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::StartingState: case IDebugSession::PausedState: qCDebug(SHELL) << "new state: paused"; stateChanged(QStringLiteral("paused")); setContinueStartsDebug(false); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::ActiveState: qCDebug(SHELL) << "new state: active"; stateChanged(QStringLiteral("active")); setContinueStartsDebug(false); //m_restartDebugger->setEnabled(false); break; case IDebugSession::EndedState: qCDebug(SHELL) << "new state: ended"; stateChanged(QStringLiteral("ended")); setContinueStartsDebug(true); //m_restartDebugger->setEnabled(false); break; } if (state == IDebugSession::PausedState && ICore::self()->uiController()->activeMainWindow()) { ICore::self()->uiController()->activeMainWindow()->activateWindow(); } } void DebugController::setContinueStartsDebug(bool startsDebug) { if (startsDebug) { m_continueDebugger->setText(i18n("Debug Launch")); m_continueDebugger->setIcon(QIcon::fromTheme(QStringLiteral("debug-run"))); m_continueDebugger->setToolTip(i18n("Debug current launch")); m_continueDebugger->setWhatsThis(i18n("Executes the target or the program specified in " "currently active launch configuration inside a Debugger.")); } else { m_continueDebugger->setText(i18n("&Continue")); m_continueDebugger->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); m_continueDebugger->setToolTip(i18n("Continue application execution") ); m_continueDebugger->setWhatsThis(i18n("Continues the execution of your application in the " "debugger. This only takes effect when the application " "has been halted by the debugger (i.e. a breakpoint has " "been activated or the interrupt was pressed).") ); } } ContextMenuExtension DebugController::contextMenuExtension(Context* context, QWidget* parent) { Q_UNUSED(parent); ContextMenuExtension menuExt; if( context->type() != Context::EditorContext ) return menuExt; auto *econtext = dynamic_cast(context); if (!econtext) return menuExt; if (m_currentSession && m_currentSession.data()->isRunning()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_runToCursor); } if (econtext->url().isLocalFile()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_toggleBreakpoint); } return menuExt; } #if 0 void DebugController::restartDebugger() { if (m_currentSession) { m_currentSession.data()->restartDebugger(); } } #endif void DebugController::stopDebugger() { if (m_currentSession) { m_currentSession.data()->stopDebugger(); } } void DebugController::interruptDebugger() { if (m_currentSession) { m_currentSession.data()->interruptDebugger(); } } void DebugController::run() { if (m_currentSession) { m_currentSession.data()->run(); } else { auto runController = ICore::self()->runController(); if (runController->launchConfigurations().isEmpty()) { runController->showConfigurationDialog(); } runController->executeDefaultLaunch(QStringLiteral("debug")); } } void DebugController::runToCursor() { if (m_currentSession) { m_currentSession.data()->runToCursor(); } } void DebugController::jumpToCursor() { if (m_currentSession) { m_currentSession.data()->jumpToCursor(); } } void DebugController::stepOver() { if (m_currentSession) { m_currentSession.data()->stepOver(); } } void DebugController::stepIntoInstruction() { if (m_currentSession) { m_currentSession.data()->stepIntoInstruction(); } } void DebugController::stepInto() { if (m_currentSession) { m_currentSession.data()->stepInto(); } } void DebugController::stepOverInstruction() { if (m_currentSession) { m_currentSession.data()->stepOverInstruction(); } } void DebugController::stepOut() { if (m_currentSession) { m_currentSession.data()->stepOut(); } } void DebugController::areaChanged(Sublime::Area* newArea) { if (newArea->objectName()!=QLatin1String("debug")) { stopDebugger(); } } void DebugController::toggleBreakpoint() { if (KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = document->cursorPosition(); if (!cursor.isValid()) return; breakpointModel()->toggleBreakpoint(document->url(), cursor); } } void DebugController::showCurrentLine() { const auto location = qMakePair(m_currentSession->currentUrl(), m_currentSession->currentLine()); if (location.second != -1) { const auto localLocation = m_currentSession->convertToLocalUrl(location); ICore::self()->documentController()->openDocument(localLocation.first, KTextEditor::Cursor(localLocation.second, 0), IDocumentController::DefaultMode); } } const QPixmap* DebugController::executionPointPixmap() { static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("go-next")).pixmap(QSize(22,22), QIcon::Normal, QIcon::Off); return &pixmap; } } diff --git a/plugins/patchreview/patchhighlighter.cpp b/plugins/patchreview/patchhighlighter.cpp index 8d3f22dc48..82dbf438f2 100644 --- a/plugins/patchreview/patchhighlighter.cpp +++ b/plugins/patchreview/patchhighlighter.cpp @@ -1,696 +1,696 @@ /*************************************************************************** Copyright 2006 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "patchhighlighter.h" #include #include #include "patchreview.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QPointer currentTooltip; KTextEditor::MovingRange* currentTooltipMark; QSize sizeHintForHtml( const QString& html, QSize maxSize ) { QTextDocument doc; doc.setHtml( html ); QSize ret; if( doc.idealWidth() > maxSize.width() ) { doc.setPageSize( QSize( maxSize.width(), 30 ) ); ret.setWidth( maxSize.width() ); }else{ ret.setWidth( doc.idealWidth() ); } ret.setHeight( doc.size().height() ); if( ret.height() > maxSize.height() ) ret.setHeight( maxSize.height() ); return ret; } } const unsigned int PatchHighlighter::m_allmarks = KTextEditor::MarkInterface::markType22 | KTextEditor::MarkInterface::markType23 | KTextEditor::MarkInterface::markType24 | KTextEditor::MarkInterface::markType25 | KTextEditor::MarkInterface::markType26 | KTextEditor::MarkInterface::markType27; void PatchHighlighter::showToolTipForMark(const QPoint& pos, KTextEditor::MovingRange* markRange) { if( currentTooltipMark == markRange && currentTooltip ) return; delete currentTooltip; //Got the difference Diff2::Difference* diff = m_ranges[markRange]; QString html; #if 0 if( diff->hasConflict() ) html += i18n( "Conflict
" ); #endif Diff2::DifferenceStringList lines; html += QLatin1String(""); if( diff->applied() ) { if( !m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Applied.
" ); if( isInsertion( diff ) ) { html += i18n( "Insertion
" ); } else { if( isRemoval( diff ) ) html += i18n( "Removal
" ); html += i18n( "Previous:
" ); lines = diff->sourceLines(); } } else { if( m_plugin->patch()->isAlreadyApplied() ) html += i18n( "Reverted.
" ); if( isRemoval( diff ) ) { html += i18n( "Removal
" ); } else { if( isInsertion( diff ) ) html += i18n( "Insertion
" ); html += i18n( "Alternative:
" ); lines = diff->destinationLines(); } } html += QLatin1String("
"); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; uint currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { QString spanText = string.mid( currentPos, markers[b]->offset() - currentPos ).toHtmlEscaped(); if( markers[b]->type() == Diff2::Marker::End && ( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) ) { html += QLatin1String("") + spanText + QLatin1String(""); }else{ html += spanText; } currentPos = markers[b]->offset(); } html += string.mid(currentPos, string.length()-currentPos).toHtmlEscaped() + QLatin1String("
"); } auto browser = new QTextBrowser; browser->setPalette( QApplication::palette() ); browser->setHtml( html ); int maxHeight = 500; browser->setMinimumSize( sizeHintForHtml( html, QSize( ( ICore::self()->uiController()->activeMainWindow()->width()*2 )/3, maxHeight ) ) ); browser->setMaximumSize( browser->minimumSize() + QSize( 10, 10 ) ); if( browser->minimumHeight() != maxHeight ) browser->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); auto* layout = new QVBoxLayout; layout->setMargin( 0 ); layout->addWidget( browser ); KDevelop::ActiveToolTip* tooltip = new KDevelop::ActiveToolTip( ICore::self()->uiController()->activeMainWindow(), pos + QPoint( 5, -browser->sizeHint().height() - 30 ) ); tooltip->setLayout( layout ); tooltip->resize( tooltip->sizeHint() + QSize( 10, 10 ) ); tooltip->move( pos - QPoint( 0, 20 + tooltip->height() ) ); tooltip->setHandleRect( QRect( pos - QPoint( 15, 15 ), pos + QPoint( 15, 15 ) ) ); currentTooltip = tooltip; currentTooltipMark = markRange; ActiveToolTip::showToolTip( tooltip ); } void PatchHighlighter::markClicked( KTextEditor::Document* doc, const KTextEditor::Mark& mark, bool& handled ) { if( handled || !(mark.type & m_allmarks) ) return; auto range_diff = rangeForMark(mark); m_applying = true; if (range_diff.first) { handled = true; KTextEditor::MovingRange *&range = range_diff.first; Diff2::Difference *&diff = range_diff.second; QString currentText = doc->text( range->toRange() ); removeLineMarker( range ); QString sourceText; QString targetText; for( int a = 0; a < diff->sourceLineCount(); ++a ) { sourceText += diff->sourceLineAt( a )->string(); if (!sourceText.endsWith(QLatin1Char('\n'))) sourceText += QLatin1Char('\n'); } for( int a = 0; a < diff->destinationLineCount(); ++a ) { targetText += diff->destinationLineAt( a )->string(); if (!targetText.endsWith(QLatin1Char('\n'))) targetText += QLatin1Char('\n'); } bool applied = diff->applied(); QString &replace(applied ? targetText : sourceText); QString &replaceWith(applied ? sourceText : targetText); if( currentText.simplified() != replace.simplified() ) { KMessageBox::error( ICore::self()->uiController()->activeMainWindow(), i18n( "Could not apply the change: Text should be \"%1\", but is \"%2\".", replace, currentText ) ); m_applying = false; return; } diff->apply(!applied); KTextEditor::Cursor start = range->start().toCursor(); range->document()->replaceText( range->toRange(), replaceWith ); const uint replaceWithLines = replaceWith.count(QLatin1Char('\n')); KTextEditor::Range newRange( start, KTextEditor::Cursor(start.line() + replaceWithLines, start.column()) ); range->setRange( newRange ); addLineMarker( range, diff ); { // After applying the change, show the tooltip again, mainly to update an old tooltip delete currentTooltip; currentTooltip = nullptr; bool h = false; markToolTipRequested( doc, mark, QCursor::pos(), h ); } } m_applying = false; } QPair PatchHighlighter::rangeForMark( const KTextEditor::Mark &mark ) { if (!m_applying) { for( QMap::const_iterator it = m_ranges.constBegin(); it != m_ranges.constEnd(); ++it ) { if (it.value() && it.key()->start().line() <= mark.line && mark.line <= it.key()->end().line()) { return qMakePair(it.key(), it.value()); } } } return qMakePair(nullptr, nullptr); } void PatchHighlighter::markToolTipRequested( KTextEditor::Document*, const KTextEditor::Mark& mark, QPoint pos, bool& handled ) { if( handled ) return; if( mark.type & m_allmarks ) { //There is a mark in this line. Show the old text. auto range = rangeForMark(mark); if( range.first ) { showToolTipForMark( pos, range.first ); handled = true; } } } bool PatchHighlighter::isInsertion( Diff2::Difference* diff ) { return diff->sourceLineCount() == 0; } bool PatchHighlighter::isRemoval( Diff2::Difference* diff ) { return diff->destinationLineCount() == 0; } void PatchHighlighter::performContentChange( KTextEditor::Document* doc, const QStringList& oldLines, const QStringList& newLines, int editLineNumber ) { QPair, QList > diffChange = m_model->linesChanged( oldLines, newLines, editLineNumber ); const QList& inserted = diffChange.first; const QList& removed = diffChange.second; for (Diff2::Difference* d : removed) { const auto sourceLines = d->sourceLines(); for (Diff2::DifferenceString* s : sourceLines) qCDebug(PLUGIN_PATCHREVIEW) << "removed source" << s->string(); const auto destinationLines = d->destinationLines(); for (Diff2::DifferenceString* s : destinationLines) qCDebug(PLUGIN_PATCHREVIEW) << "removed destination" << s->string(); } for (Diff2::Difference* d : inserted) { const auto sourceLines = d->sourceLines(); for (Diff2::DifferenceString* s : sourceLines) qCDebug(PLUGIN_PATCHREVIEW) << "inserted source" << s->string(); const auto destinationLines = d->destinationLines(); for (Diff2::DifferenceString* s : destinationLines) qCDebug(PLUGIN_PATCHREVIEW) << "inserted destination" << s->string(); } // Remove all ranges that are in the same line (the line markers) for (auto it = m_ranges.begin(); it != m_ranges.end();) { if (removed.contains(it.value())) { KTextEditor::MovingRange* r = it.key(); removeLineMarker(r); // is altering m_ranges it = m_ranges.erase(it); delete r; } else { ++it; } } qDeleteAll(removed); auto* moving = qobject_cast(doc); if ( !moving ) return; for (Diff2::Difference* diff : inserted) { int lineStart = diff->destinationLineNumber(); if ( lineStart > 0 ) { --lineStart; } int lineEnd = diff->destinationLineEnd(); if ( lineEnd > 0 ) { --lineEnd; } KTextEditor::Range newRange( lineStart, 0, lineEnd, 0 ); KTextEditor::MovingRange * r = moving->newMovingRange( newRange ); m_ranges[r] = diff; addLineMarker( r, diff ); } } void PatchHighlighter::textRemoved( KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& oldText ) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "removal range" << range; qCDebug(PLUGIN_PATCHREVIEW) << "removed text" << oldText; KTextEditor::Cursor cursor = range.start(); int startLine = cursor.line(); QStringList removedLines; QStringList remainingLines; if (startLine > 0) { QString above = doc->line(--startLine); removedLines << above; remainingLines << above; } const QString changed = doc->line(cursor.line()) + QLatin1Char('\n'); removedLines << changed.midRef(0, cursor.column()) + oldText + changed.midRef(cursor.column()); remainingLines << changed; if (doc->documentRange().end().line() > cursor.line()) { QString below = doc->line(cursor.line() + 1); removedLines << below; remainingLines << below; } performContentChange(doc, removedLines, remainingLines, startLine + 1); } void PatchHighlighter::newlineRemoved(KTextEditor::Document* doc, int line) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "remove newline" << line; KTextEditor::Cursor cursor = m_doc->cursorPosition(); int startLine = line - 1; QStringList removedLines; QStringList remainingLines; if (startLine > 0) { QString above = doc->line(--startLine); removedLines << above; remainingLines << above; } QString changed = doc->line(line - 1); if (cursor.line() == line - 1) { removedLines << changed.mid(0, cursor.column()); removedLines << changed.mid(cursor.column()); } else { removedLines << changed; removedLines << QString(); } remainingLines << changed; if (doc->documentRange().end().line() >= line) { QString below = doc->line(line); removedLines << below; remainingLines << below; } performContentChange(doc, removedLines, remainingLines, startLine + 1); } void PatchHighlighter::documentReloaded(KTextEditor::Document* doc) { qCDebug(PLUGIN_PATCHREVIEW) << "re-doing"; //The document was loaded / reloaded if ( !m_model->differences() ) return; auto* moving = qobject_cast(doc); if ( !moving ) return; auto* markIface = qobject_cast(doc); if( !markIface ) return; clear(); KColorScheme scheme( QPalette::Active ); QImage tintedInsertion = QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedInsertion, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); QImage tintedRemoval = QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedRemoval, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); QImage tintedChange = QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ).toImage(); KIconEffect::colorize( tintedChange, scheme.foreground( KColorScheme::NegativeText ).color(), 1.0 ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType22, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType22, QPixmap::fromImage( tintedInsertion ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType23, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType23, QPixmap::fromImage( tintedRemoval ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType24, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType24, QPixmap::fromImage( tintedChange ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType25, i18n( "Insertion" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType25, QIcon::fromTheme( QStringLiteral("insert-text") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType26, i18n( "Removal" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType26, QIcon::fromTheme( QStringLiteral("edit-delete") ).pixmap( 16, 16 ) ); markIface->setMarkDescription( KTextEditor::MarkInterface::markType27, i18n( "Change" ) ); markIface->setMarkPixmap( KTextEditor::MarkInterface::markType27, QIcon::fromTheme( QStringLiteral("text-field") ).pixmap( 16, 16 ) ); for ( Diff2::DifferenceList::const_iterator it = m_model->differences()->constBegin(); it != m_model->differences()->constEnd(); ++it ) { Diff2::Difference* diff = *it; int line, lineCount; Diff2::DifferenceStringList lines; if( diff->applied() ) { line = diff->destinationLineNumber(); lineCount = diff->destinationLineCount(); lines = diff->destinationLines(); } else { line = diff->sourceLineNumber(); lineCount = diff->sourceLineCount(); lines = diff->sourceLines(); } if ( line > 0 ) line -= 1; KTextEditor::Cursor c( line, 0 ); KTextEditor::Cursor endC( line + lineCount, 0 ); if ( doc->lines() <= c.line() ) c.setLine( doc->lines() - 1 ); if ( doc->lines() <= endC.line() ) endC.setLine( doc->lines() ); if ( endC.isValid() && c.isValid() ) { KTextEditor::MovingRange * r = moving->newMovingRange( KTextEditor::Range( c, endC ) ); m_ranges[r] = diff; addLineMarker( r, diff ); } } } void PatchHighlighter::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { if ( m_applying ) { // Do not interfere with patch application return; } int startLine = cursor.line(); int endColumn = cursor.column() + text.length(); qCDebug(PLUGIN_PATCHREVIEW) << "insertion range" << KTextEditor::Range(cursor, KTextEditor::Cursor(startLine, endColumn)); qCDebug(PLUGIN_PATCHREVIEW) << "inserted text" << text; QStringList removedLines; QStringList insertedLines; if (startLine > 0) { const QString above = doc->line(--startLine) + QLatin1Char('\n'); removedLines << above; insertedLines << above; } const QString changed = doc->line(cursor.line()) + QLatin1Char('\n'); removedLines << changed.midRef(0, cursor.column()) + changed.midRef(endColumn); insertedLines << changed; if (doc->documentRange().end().line() > cursor.line()) { const QString below = doc->line(cursor.line() + 1) + QLatin1Char('\n'); removedLines << below; insertedLines << below; } performContentChange(doc, removedLines, insertedLines, startLine + 1); } void PatchHighlighter::newlineInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor) { if ( m_applying ) { // Do not interfere with patch application return; } qCDebug(PLUGIN_PATCHREVIEW) << "newline range" << KTextEditor::Range(cursor, KTextEditor::Cursor(cursor.line() + 1, 0)); int startLine = cursor.line(); QStringList removedLines; QStringList insertedLines; if (startLine > 0) { const QString above = doc->line(--startLine) + QLatin1Char('\n'); removedLines << above; insertedLines << above; } insertedLines << QStringLiteral("\n"); if (doc->documentRange().end().line() > cursor.line()) { const QString below = doc->line(cursor.line() + 1) + QLatin1Char('\n'); removedLines << below; insertedLines << below; } performContentChange(doc, removedLines, insertedLines, startLine + 1); } PatchHighlighter::PatchHighlighter( Diff2::DiffModel* model, IDocument* kdoc, PatchReviewPlugin* plugin, bool updatePatchFromEdits ) : m_doc( kdoc ), m_plugin( plugin ), m_model( model ), m_applying( false ) { KTextEditor::Document* doc = kdoc->textDocument(); // connect( kdoc, SIGNAL(destroyed(QObject*)), this, SLOT(documentDestroyed()) ); if (updatePatchFromEdits) { connect(doc, &KTextEditor::Document::textInserted, this, &PatchHighlighter::textInserted); connect(doc, &KTextEditor::Document::lineWrapped, this, &PatchHighlighter::newlineInserted); connect(doc, &KTextEditor::Document::textRemoved, this, &PatchHighlighter::textRemoved); connect(doc, &KTextEditor::Document::lineUnwrapped, this, &PatchHighlighter::newlineRemoved); } connect(doc, &KTextEditor::Document::reloaded, this, &PatchHighlighter::documentReloaded); connect(doc, &KTextEditor::Document::destroyed, this, &PatchHighlighter::documentDestroyed); if ( doc->lines() == 0 ) return; if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MarkInterface is not a QObject connect(doc, SIGNAL(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&)), this, SLOT(markToolTipRequested(KTextEditor::Document*,KTextEditor::Mark,QPoint,bool&))); connect(doc, SIGNAL(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&)), this, SLOT(markClicked(KTextEditor::Document*,KTextEditor::Mark,bool&))); } if (qobject_cast(doc)) { //can't use new signal/slot syntax here, MovingInterface is not a QObject connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); } documentReloaded(doc); } void PatchHighlighter::removeLineMarker( KTextEditor::MovingRange* range ) { auto* moving = qobject_cast(range->document()); if ( !moving ) return; auto* markIface = qobject_cast(range->document()); if( !markIface ) return; for (int line = range->start().line(); line <= range->end().line(); ++line) { markIface->removeMark(line, m_allmarks); } // Remove all ranges that are in the same line (the line markers) for (auto it = m_ranges.begin(); it != m_ranges.end();) { if (it.key() != range && range->overlaps(it.key()->toRange())) { delete it.key(); it = m_ranges.erase(it); } else { ++it; } } } void PatchHighlighter::addLineMarker( KTextEditor::MovingRange* range, Diff2::Difference* diff ) { auto* moving = qobject_cast(range->document()); if ( !moving ) return; auto* markIface = qobject_cast(range->document()); if( !markIface ) return; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); bool isOriginalState = diff->applied() == m_plugin->patch()->isAlreadyApplied(); if( isOriginalState ) { t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 0, 255, 255 ), 20 ) ) ); }else{ t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 255 ), 20 ) ) ); } range->setAttribute( t ); range->setZDepth( -500 ); KTextEditor::MarkInterface::MarkTypes mark; if( isOriginalState ) { mark = KTextEditor::MarkInterface::markType27; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType25; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType26; }else{ mark = KTextEditor::MarkInterface::markType24; if( isInsertion( diff ) ) mark = KTextEditor::MarkInterface::markType22; if( isRemoval( diff ) ) mark = KTextEditor::MarkInterface::markType23; } markIface->addMark( range->start().line(), mark ); Diff2::DifferenceStringList lines; if( diff->applied() ) lines = diff->destinationLines(); else lines = diff->sourceLines(); for( int a = 0; a < lines.size(); ++a ) { Diff2::DifferenceString* line = lines[a]; int currentPos = 0; QString string = line->string(); Diff2::MarkerList markers = line->markerList(); for( int b = 0; b < markers.size(); ++b ) { if( markers[b]->type() == Diff2::Marker::End ) { if( currentPos != 0 || markers[b]->offset() != static_cast( string.size() ) ) { KTextEditor::MovingRange * r2 = moving->newMovingRange( KTextEditor::Range( KTextEditor::Cursor( a + range->start().line(), currentPos ), KTextEditor::Cursor( a + range->start().line(), markers[b]->offset() ) ) ); m_ranges[r2] = nullptr; KTextEditor::Attribute::Ptr t( new KTextEditor::Attribute() ); t->setProperty( QTextFormat::BackgroundBrush, QBrush( ColorCache::self()->blendBackground( QColor( 255, 0, 0 ), 70 ) ) ); r2->setAttribute( t ); r2->setZDepth( -600 ); } } currentPos = markers[b]->offset(); } } } void PatchHighlighter::clear() { if( m_ranges.empty() ) return; auto* moving = qobject_cast(m_doc->textDocument()); if ( !moving ) return; auto* markIface = qobject_cast(m_doc->textDocument()); if( !markIface ) return; - for (auto it = markIface->marks().begin(), end = markIface->marks().end(); it != end; ++it) { - int line = it.key(); + const auto lines = markIface->marks().keys(); + for (int line : lines) { markIface->removeMark( line, m_allmarks ); } // Diff is taking care of its own objects (except removed ones) qDeleteAll( m_ranges.keys() ); m_ranges.clear(); } PatchHighlighter::~PatchHighlighter() { clear(); } IDocument* PatchHighlighter::doc() { return m_doc; } void PatchHighlighter::documentDestroyed() { qCDebug(PLUGIN_PATCHREVIEW) << "document destroyed"; m_ranges.clear(); } void PatchHighlighter::aboutToDeleteMovingInterfaceContent( KTextEditor::Document* ) { qCDebug(PLUGIN_PATCHREVIEW) << "about to delete"; clear(); } QList< KTextEditor::MovingRange* > PatchHighlighter::ranges() const { return m_ranges.keys(); }