diff --git a/addons/gdbplugin/plugin_kategdb.cpp b/addons/gdbplugin/plugin_kategdb.cpp index a55433d32..3c69ff8d2 100644 --- a/addons/gdbplugin/plugin_kategdb.cpp +++ b/addons/gdbplugin/plugin_kategdb.cpp @@ -1,715 +1,715 @@ // // Description: Kate Plugin for GDB integration // // // Copyright (c) 2010 Ian Wakeling // Copyright (c) 2010-2014 Kåre Särs // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License version 2 as published by the Free Software Foundation. // // 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, write to // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, // Boston, MA 02110-1301, USA. #include "plugin_kategdb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KatePluginGDBFactory, "kategdbplugin.json", registerPlugin();) KatePluginGDB::KatePluginGDB(QObject *parent, const VariantList &) : KTextEditor::Plugin(parent) { // FIXME KF5 KGlobal::locale()->insertCatalog("kategdbplugin"); } KatePluginGDB::~KatePluginGDB() { } QObject *KatePluginGDB::createView(KTextEditor::MainWindow *mainWindow) { return new KatePluginGDBView(this, mainWindow); } KatePluginGDBView::KatePluginGDBView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_mainWin(mainWin) { m_lastExecUrl = QUrl(); m_lastExecLine = -1; m_lastExecFrame = 0; m_kateApplication = KTextEditor::Editor::instance()->application(); m_focusOnInput = true; m_activeThread = -1; KXMLGUIClient::setComponentName(QStringLiteral("kategdb"), i18n("Kate GDB")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = m_mainWin->createToolView(plugin, i18n("Debug View"), KTextEditor::MainWindow::Bottom, QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")), i18n("Debug View")); m_localsStackToolView = m_mainWin->createToolView(plugin, i18n("Locals and Stack"), KTextEditor::MainWindow::Right, QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png")), i18n("Locals and Stack")); m_tabWidget = new QTabWidget(m_toolView); // Output m_outputArea = new QTextEdit(); m_outputArea->setAcceptRichText(false); m_outputArea->setReadOnly(true); m_outputArea->setUndoRedoEnabled(false); // fixed wide font, like konsole m_outputArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); // alternate color scheme, like konsole KColorScheme schemeView(QPalette::Active, KColorScheme::View); m_outputArea->setTextBackgroundColor(schemeView.foreground().color()); m_outputArea->setTextColor(schemeView.background().color()); QPalette p = m_outputArea->palette(); p.setColor(QPalette::Base, schemeView.foreground().color()); m_outputArea->setPalette(p); // input m_inputArea = new KHistoryComboBox(true); connect(m_inputArea, static_cast(&KHistoryComboBox::returnPressed), this, &KatePluginGDBView::slotSendCommand); QHBoxLayout *inputLayout = new QHBoxLayout(); inputLayout->addWidget(m_inputArea, 10); inputLayout->setContentsMargins(0, 0, 0, 0); m_outputArea->setFocusProxy(m_inputArea); // take the focus from the outputArea m_gdbPage = new QWidget(); QVBoxLayout *layout = new QVBoxLayout(m_gdbPage); layout->addWidget(m_outputArea); layout->addLayout(inputLayout); layout->setStretch(0, 10); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); // stack page QWidget *stackContainer = new QWidget(); QVBoxLayout *stackLayout = new QVBoxLayout(stackContainer); m_threadCombo = new QComboBox(); m_stackTree = new QTreeWidget(); stackLayout->addWidget(m_threadCombo); stackLayout->addWidget(m_stackTree); stackLayout->setStretch(0, 10); stackLayout->setContentsMargins(0, 0, 0, 0); stackLayout->setSpacing(0); QStringList headers; headers << QStringLiteral(" ") << i18nc("Column label (frame number)", "Nr") << i18nc("Column label", "Frame"); m_stackTree->setHeaderLabels(headers); m_stackTree->setRootIsDecorated(false); m_stackTree->resizeColumnToContents(0); m_stackTree->resizeColumnToContents(1); m_stackTree->setAutoScroll(false); connect(m_stackTree, &QTreeWidget::itemActivated, this, &KatePluginGDBView::stackFrameSelected); connect(m_threadCombo, static_cast(&QComboBox::currentIndexChanged), this, &KatePluginGDBView::threadSelected); m_localsView = new LocalsView(); QSplitter *locStackSplitter = new QSplitter(m_localsStackToolView); locStackSplitter->addWidget(m_localsView); locStackSplitter->addWidget(stackContainer); locStackSplitter->setOrientation(Qt::Vertical); // config page m_configView = new ConfigView(nullptr, mainWin); m_ioView = new IOView(); connect(m_configView, &ConfigView::showIO, this, &KatePluginGDBView::showIO); m_tabWidget->addTab(m_gdbPage, i18nc("Tab label", "GDB Output")); m_tabWidget->addTab(m_configView, i18nc("Tab label", "Settings")); m_debugView = new DebugView(this); connect(m_debugView, &DebugView::readyForInput, this, &KatePluginGDBView::enableDebugActions); connect(m_debugView, &DebugView::outputText, this, &KatePluginGDBView::addOutputText); connect(m_debugView, &DebugView::outputError, this, &KatePluginGDBView::addErrorText); connect(m_debugView, &DebugView::debugLocationChanged, this, &KatePluginGDBView::slotGoTo); connect(m_debugView, &DebugView::breakPointSet, this, &KatePluginGDBView::slotBreakpointSet); connect(m_debugView, &DebugView::breakPointCleared, this, &KatePluginGDBView::slotBreakpointCleared); connect(m_debugView, &DebugView::clearBreakpointMarks, this, &KatePluginGDBView::clearMarks); connect(m_debugView, &DebugView::programEnded, this, &KatePluginGDBView::programEnded); connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::programEnded); connect(m_debugView, &DebugView::gdbEnded, this, &KatePluginGDBView::gdbEnded); connect(m_debugView, &DebugView::stackFrameInfo, this, &KatePluginGDBView::insertStackFrame); connect(m_debugView, &DebugView::stackFrameChanged, this, &KatePluginGDBView::stackFrameChanged); connect(m_debugView, &DebugView::infoLocal, m_localsView, &LocalsView::addLocal); connect(m_debugView, &DebugView::threadInfo, this, &KatePluginGDBView::insertThread); connect(m_localsView, &LocalsView::localsVisible, m_debugView, &DebugView::slotQueryLocals); // Actions m_configView->registerActions(actionCollection()); QAction *a = actionCollection()->addAction(QStringLiteral("debug")); a->setText(i18n("Start Debugging")); a->setIcon(QIcon(QStringLiteral(":/kategdb/22-actions-debug-kategdb.png"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotDebug); a = actionCollection()->addAction(QStringLiteral("kill")); a->setText(i18n("Kill / Stop Debugging")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotKill); a = actionCollection()->addAction(QStringLiteral("rerun")); a->setText(i18n("Restart Debugging")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRestart); a = actionCollection()->addAction(QStringLiteral("toggle_breakpoint")); a->setText(i18n("Toggle Breakpoint / Break")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotToggleBreakpoint); a = actionCollection()->addAction(QStringLiteral("step_in")); a->setText(i18n("Step In")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-into"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepInto); a = actionCollection()->addAction(QStringLiteral("step_over")); a->setText(i18n("Step Over")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-over"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOver); a = actionCollection()->addAction(QStringLiteral("step_out")); a->setText(i18n("Step Out")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-step-out"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotStepOut); a = actionCollection()->addAction(QStringLiteral("move_pc")); a->setText(i18nc("Move Program Counter (next execution)", "Move PC")); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotMovePC); a = actionCollection()->addAction(QStringLiteral("run_to_cursor")); a->setText(i18n("Run To Cursor")); a->setIcon(QIcon::fromTheme(QStringLiteral("debug-run-cursor"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotRunToCursor); a = actionCollection()->addAction(QStringLiteral("continue")); a->setText(i18n("Continue")); a->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); connect(a, &QAction::triggered, m_debugView, &DebugView::slotContinue); a = actionCollection()->addAction(QStringLiteral("print_value")); a->setText(i18n("Print Value")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); connect(a, &QAction::triggered, this, &KatePluginGDBView::slotValue); // popup context m_menu m_menu = new KActionMenu(i18n("Debug"), this); actionCollection()->addAction(QStringLiteral("popup_gdb"), m_menu); connect(m_menu->menu(), &QMenu::aboutToShow, this, &KatePluginGDBView::aboutToShowMenu); m_breakpoint = m_menu->menu()->addAction(i18n("popup_breakpoint"), this, &KatePluginGDBView::slotToggleBreakpoint); QAction *popupAction = m_menu->menu()->addAction(i18n("popup_run_to_cursor"), this, &KatePluginGDBView::slotRunToCursor); popupAction->setText(i18n("Run To Cursor")); popupAction = m_menu->menu()->addAction(QStringLiteral("move_pc"), this, &KatePluginGDBView::slotMovePC); popupAction->setText(i18nc("Move Program Counter (next execution)", "Move PC")); enableDebugActions(false); connect(m_mainWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginGDBView::handleEsc); m_toolView->installEventFilter(this); m_mainWin->guiFactory()->addClient(this); } KatePluginGDBView::~KatePluginGDBView() { m_mainWin->guiFactory()->removeClient(this); delete m_toolView; delete m_localsStackToolView; } void KatePluginGDBView::readSessionConfig(const KConfigGroup &config) { m_configView->readConfig(config); } void KatePluginGDBView::writeSessionConfig(KConfigGroup &config) { m_configView->writeConfig(config); } void KatePluginGDBView::slotDebug() { disconnect(m_ioView, &IOView::stdOutText, nullptr, nullptr); disconnect(m_ioView, &IOView::stdErrText, nullptr, nullptr); if (m_configView->showIOTab()) { connect(m_ioView, &IOView::stdOutText, m_ioView, &IOView::addStdOutText); connect(m_ioView, &IOView::stdErrText, m_ioView, &IOView::addStdErrText); } else { connect(m_ioView, &IOView::stdOutText, this, &KatePluginGDBView::addOutputText); connect(m_ioView, &IOView::stdErrText, this, &KatePluginGDBView::addErrorText); } QStringList ioFifos; ioFifos << m_ioView->stdinFifo(); ioFifos << m_ioView->stdoutFifo(); ioFifos << m_ioView->stderrFifo(); enableDebugActions(true); m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); m_localsView->clear(); m_debugView->runDebugger(m_configView->currentTarget(), ioFifos); } void KatePluginGDBView::slotRestart() { m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); m_localsView->clear(); m_debugView->slotReRun(); } void KatePluginGDBView::aboutToShowMenu() { if (!m_debugView->debuggerRunning() || m_debugView->debuggerBusy()) { m_breakpoint->setText(i18n("Insert breakpoint")); m_breakpoint->setDisabled(true); return; } m_breakpoint->setDisabled(false); KTextEditor::View *editView = m_mainWin->activeView(); QUrl url = editView->document()->url(); int line = editView->cursorPosition().line(); line++; // GDB uses 1 based line numbers, kate uses 0 based... if (m_debugView->hasBreakpoint(url, line)) { m_breakpoint->setText(i18n("Remove breakpoint")); } else { m_breakpoint->setText(i18n("Insert breakpoint")); } } void KatePluginGDBView::slotToggleBreakpoint() { if (!actionCollection()->action(QStringLiteral("continue"))->isEnabled()) { m_debugView->slotInterrupt(); } else { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); int line = editView->cursorPosition().line(); m_debugView->toggleBreakpoint(currURL, line + 1); } } void KatePluginGDBView::slotBreakpointSet(const QUrl &file, int line) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(file)); if (iface) { iface->setMarkDescription(KTextEditor::MarkInterface::BreakpointActive, i18n("Breakpoint")); iface->setMarkPixmap(KTextEditor::MarkInterface::BreakpointActive, QIcon::fromTheme(QStringLiteral("media-playback-pause")).pixmap(10, 10)); iface->addMark(line, KTextEditor::MarkInterface::BreakpointActive); } } void KatePluginGDBView::slotBreakpointCleared(const QUrl &file, int line) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(file)); if (iface) { iface->removeMark(line, KTextEditor::MarkInterface::BreakpointActive); } } void KatePluginGDBView::slotMovePC() { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); KTextEditor::Cursor cursor = editView->cursorPosition(); m_debugView->movePC(currURL, cursor.line() + 1); } void KatePluginGDBView::slotRunToCursor() { KTextEditor::View *editView = m_mainWin->activeView(); QUrl currURL = editView->document()->url(); KTextEditor::Cursor cursor = editView->cursorPosition(); // GDB starts lines from 1, kate returns lines starting from 0 (displaying 1) m_debugView->runToCursor(currURL, cursor.line() + 1); } void KatePluginGDBView::slotGoTo(const QUrl &url, int lineNum) { // skip not existing files if (!QFile::exists(url.toLocalFile())) { m_lastExecLine = -1; return; } m_lastExecUrl = url; m_lastExecLine = lineNum; KTextEditor::View *editView = m_mainWin->openUrl(m_lastExecUrl); editView->setCursorPosition(KTextEditor::Cursor(m_lastExecLine, 0)); m_mainWin->window()->raise(); m_mainWin->window()->setFocus(); } void KatePluginGDBView::enableDebugActions(bool enable) { actionCollection()->action(QStringLiteral("step_in"))->setEnabled(enable); actionCollection()->action(QStringLiteral("step_over"))->setEnabled(enable); actionCollection()->action(QStringLiteral("step_out"))->setEnabled(enable); actionCollection()->action(QStringLiteral("move_pc"))->setEnabled(enable); actionCollection()->action(QStringLiteral("run_to_cursor"))->setEnabled(enable); actionCollection()->action(QStringLiteral("popup_gdb"))->setEnabled(enable); actionCollection()->action(QStringLiteral("continue"))->setEnabled(enable); actionCollection()->action(QStringLiteral("print_value"))->setEnabled(enable); // "toggle breakpoint" doubles as interrupt while the program is running actionCollection()->action(QStringLiteral("toggle_breakpoint"))->setEnabled(m_debugView->debuggerRunning()); actionCollection()->action(QStringLiteral("kill"))->setEnabled(m_debugView->debuggerRunning()); actionCollection()->action(QStringLiteral("rerun"))->setEnabled(m_debugView->debuggerRunning()); m_inputArea->setEnabled(enable); m_threadCombo->setEnabled(enable); m_stackTree->setEnabled(enable); m_localsView->setEnabled(enable); if (enable) { m_inputArea->setFocusPolicy(Qt::WheelFocus); if (m_focusOnInput || m_configView->takeFocusAlways()) { m_inputArea->setFocus(); m_focusOnInput = false; } else { m_mainWin->activeView()->setFocus(); } } else { m_inputArea->setFocusPolicy(Qt::NoFocus); if (m_mainWin->activeView()) m_mainWin->activeView()->setFocus(); } m_ioView->enableInput(!enable && m_debugView->debuggerRunning()); if ((m_lastExecLine > -1)) { KTextEditor::MarkInterface *iface = qobject_cast(m_kateApplication->findUrl(m_lastExecUrl)); if (iface) { if (enable) { iface->setMarkDescription(KTextEditor::MarkInterface::Execution, i18n("Execution point")); iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10)); iface->addMark(m_lastExecLine, KTextEditor::MarkInterface::Execution); } else { iface->removeMark(m_lastExecLine, KTextEditor::MarkInterface::Execution); } } } } void KatePluginGDBView::programEnded() { // don't set the execution mark on exit m_lastExecLine = -1; m_stackTree->clear(); m_localsView->clear(); m_threadCombo->clear(); // Indicate the state change by showing the debug outputArea m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); } void KatePluginGDBView::gdbEnded() { m_outputArea->clear(); m_localsView->clear(); m_ioView->clearOutput(); clearMarks(); } void KatePluginGDBView::clearMarks() { KTextEditor::MarkInterface *iface; const auto documents = m_kateApplication->documents(); for (KTextEditor::Document *doc : documents) { iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if ((i.value()->type == KTextEditor::MarkInterface::Execution) || (i.value()->type == KTextEditor::MarkInterface::BreakpointActive)) { iface->removeMark(i.value()->line, i.value()->type); } } } } } void KatePluginGDBView::slotSendCommand() { QString cmd = m_inputArea->currentText(); if (cmd.isEmpty()) cmd = m_lastCommand; m_inputArea->addToHistory(cmd); m_inputArea->setCurrentItem(QString()); m_focusOnInput = true; m_lastCommand = cmd; m_debugView->issueCommand(cmd); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); } void KatePluginGDBView::insertStackFrame(QString const &level, QString const &info) { if (level.isEmpty() && info.isEmpty()) { m_stackTree->resizeColumnToContents(2); return; } if (level == QLatin1Char('0')) { m_stackTree->clear(); } QStringList columns; columns << QStringLiteral(" "); // icon place holder columns << level; int lastSpace = info.lastIndexOf(QLatin1Char(' ')); QString shortInfo = info.mid(lastSpace); columns << shortInfo; QTreeWidgetItem *item = new QTreeWidgetItem(columns); item->setToolTip(2, QStringLiteral("%1").arg(info)); m_stackTree->insertTopLevelItem(level.toInt(), item); } void KatePluginGDBView::stackFrameSelected() { m_debugView->issueCommand(QStringLiteral("(Q)f %1").arg(m_stackTree->currentIndex().row())); } void KatePluginGDBView::stackFrameChanged(int level) { QTreeWidgetItem *current = m_stackTree->topLevelItem(m_lastExecFrame); QTreeWidgetItem *next = m_stackTree->topLevelItem(level); if (current) current->setIcon(0, QIcon()); if (next) next->setIcon(0, QIcon::fromTheme(QStringLiteral("arrow-right"))); m_lastExecFrame = level; } void KatePluginGDBView::insertThread(int number, bool active) { if (number < 0) { m_threadCombo->clear(); m_activeThread = -1; return; } if (!active) { m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("")).pixmap(10, 10), i18n("Thread %1", number), number); } else { m_threadCombo->addItem(QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(10, 10), i18n("Thread %1", number), number); m_activeThread = m_threadCombo->count() - 1; } m_threadCombo->setCurrentIndex(m_activeThread); } void KatePluginGDBView::threadSelected(int thread) { m_debugView->issueCommand(QStringLiteral("thread %1").arg(m_threadCombo->itemData(thread).toInt())); } QString KatePluginGDBView::currentWord() { KTextEditor::View *kv = m_mainWin->activeView(); if (!kv) { - qDebug() << "no KTextEditor::View" << endl; + qDebug() << "no KTextEditor::View"; return QString(); } if (!kv->cursorPosition().isValid()) { - qDebug() << "cursor not valid!" << endl; + qDebug() << "cursor not valid!"; return QString(); } int line = kv->cursorPosition().line(); int col = kv->cursorPosition().column(); QString linestr = kv->document()->line(line); int startPos = qMax(qMin(col, linestr.length() - 1), 0); int lindex = linestr.length() - 1; int endPos = startPos; while (startPos >= 0 && (linestr[startPos].isLetterOrNumber() || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~') || ((startPos > 1) && (linestr[startPos] == QLatin1Char('.')) && !linestr[startPos - 1].isSpace()) || ((startPos > 2) && (linestr[startPos] == QLatin1Char('>')) && (linestr[startPos - 1] == QLatin1Char('-')) && !linestr[startPos - 2].isSpace()))) { if (linestr[startPos] == QLatin1Char('>')) { startPos--; } startPos--; } while (endPos < linestr.length() && (linestr[endPos].isLetterOrNumber() || linestr[endPos] == QLatin1Char('_') || ((endPos < lindex - 1) && (linestr[endPos] == QLatin1Char('.')) && !linestr[endPos + 1].isSpace()) || ((endPos < lindex - 2) && (linestr[endPos] == QLatin1Char('-')) && (linestr[endPos + 1] == QLatin1Char('>')) && !linestr[endPos + 2].isSpace()) || ((endPos > 1) && (linestr[endPos - 1] == QLatin1Char('-')) && (linestr[endPos] == QLatin1Char('>'))))) { if (linestr[endPos] == QLatin1Char('-')) { endPos++; } endPos++; } if (startPos == endPos) { - qDebug() << "no word found!" << endl; + qDebug() << "no word found!"; return QString(); } // qDebug() << linestr.mid(startPos+1, endPos-startPos-1); return linestr.mid(startPos + 1, endPos - startPos - 1); } void KatePluginGDBView::slotValue() { QString variable; KTextEditor::View *editView = m_mainWin->activeView(); if (editView && editView->selection() && editView->selectionRange().onSingleLine()) { variable = editView->selectionText(); } if (variable.isEmpty()) variable = currentWord(); if (variable.isEmpty()) return; QString cmd = QStringLiteral("print %1").arg(variable); m_debugView->issueCommand(cmd); m_inputArea->addToHistory(cmd); m_inputArea->setCurrentItem(QString()); m_mainWin->showToolView(m_toolView); m_tabWidget->setCurrentWidget(m_gdbPage); QScrollBar *sb = m_outputArea->verticalScrollBar(); sb->setValue(sb->maximum()); } void KatePluginGDBView::showIO(bool show) { if (show) { m_tabWidget->addTab(m_ioView, i18n("IO")); } else { m_tabWidget->removeTab(m_tabWidget->indexOf(m_ioView)); } } void KatePluginGDBView::addOutputText(QString const &text) { QScrollBar *scrollb = m_outputArea->verticalScrollBar(); if (!scrollb) return; bool atEnd = (scrollb->value() == scrollb->maximum()); QTextCursor cursor = m_outputArea->textCursor(); if (!cursor.atEnd()) cursor.movePosition(QTextCursor::End); cursor.insertText(text); if (atEnd) { scrollb->setValue(scrollb->maximum()); } } void KatePluginGDBView::addErrorText(QString const &text) { m_outputArea->setFontItalic(true); addOutputText(text); m_outputArea->setFontItalic(false); } bool KatePluginGDBView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_mainWin->hideToolView(m_toolView); event->accept(); return true; } } return QObject::eventFilter(obj, event); } void KatePluginGDBView::handleEsc(QEvent *e) { if (!m_mainWin) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_mainWin->hideToolView(m_toolView); } } } #include "plugin_kategdb.moc" diff --git a/addons/kate-ctags/kate_ctags_view.cpp b/addons/kate-ctags/kate_ctags_view.cpp index 95e8faed8..1812868af 100644 --- a/addons/kate-ctags/kate_ctags_view.cpp +++ b/addons/kate-ctags/kate_ctags_view.cpp @@ -1,614 +1,614 @@ /* Description : Kate CTags plugin * * Copyright (C) 2008-2011 by Kare Sars * * 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.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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, see . */ #include "kate_ctags_view.h" #include "kate_ctags_debug.h" #include "kate_ctags_plugin.h" #include #include #include #include #include #include #include #include #include #include #include /******************************************************************/ KateCTagsView::KateCTagsView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_proc(nullptr) { KXMLGUIClient::setComponentName(QStringLiteral("katectags"), i18n("Kate CTag")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mainWin->createToolView(plugin, QStringLiteral("kate_plugin_katectagsplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("CTags")); m_mWin = mainWin; QAction *back = actionCollection()->addAction(QStringLiteral("ctags_return_step")); back->setText(i18n("Jump back one step")); connect(back, &QAction::triggered, this, &KateCTagsView::stepBack); QAction *decl = actionCollection()->addAction(QStringLiteral("ctags_lookup_current_as_declaration")); decl->setText(i18n("Go to Declaration")); connect(decl, &QAction::triggered, this, &KateCTagsView::gotoDeclaration); QAction *defin = actionCollection()->addAction(QStringLiteral("ctags_lookup_current_as_definition")); defin->setText(i18n("Go to Definition")); connect(defin, &QAction::triggered, this, &KateCTagsView::gotoDefinition); QAction *lookup = actionCollection()->addAction(QStringLiteral("ctags_lookup_current")); lookup->setText(i18n("Lookup Current Text")); connect(lookup, &QAction::triggered, this, &KateCTagsView::lookupTag); QAction *updateDB = actionCollection()->addAction(QStringLiteral("ctags_update_global_db")); updateDB->setText(i18n("Configure ...")); connect(updateDB, &QAction::triggered, this, [this, plugin](bool) { if (m_mWin) { KateCTagsPlugin *p = static_cast(plugin); QDialog *confWin = new QDialog(m_mWin->window()); confWin->setAttribute(Qt::WA_DeleteOnClose); auto confPage = p->configPage(0, confWin); auto controls = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, confWin); connect(confWin, &QDialog::accepted, confPage, &KTextEditor::ConfigPage::apply); connect(controls, &QDialogButtonBox::accepted, confWin, &QDialog::accept); connect(controls, &QDialogButtonBox::rejected, confWin, &QDialog::reject); auto layout = new QVBoxLayout(confWin); layout->addWidget(confPage); layout->addWidget(controls); confWin->setLayout(layout); confWin->setWindowTitle(i18nc("@title:window", "Configure CTags Plugin")); confWin->setWindowIcon(confPage->icon()); confWin->show(); confWin->exec(); } }); // popup menu m_menu = new KActionMenu(i18n("CTags"), this); actionCollection()->addAction(QStringLiteral("popup_ctags"), m_menu); m_gotoDec = m_menu->menu()->addAction(i18n("Go to Declaration: %1", QString()), this, &KateCTagsView::gotoDeclaration); m_gotoDef = m_menu->menu()->addAction(i18n("Go to Definition: %1", QString()), this, &KateCTagsView::gotoDefinition); m_lookup = m_menu->menu()->addAction(i18n("Lookup: %1", QString()), this, &KateCTagsView::lookupTag); connect(m_menu->menu(), &QMenu::aboutToShow, this, &KateCTagsView::aboutToShow); QWidget *ctagsWidget = new QWidget(m_toolView.data()); m_ctagsUi.setupUi(ctagsWidget); m_ctagsUi.cmdEdit->setText(DEFAULT_CTAGS_CMD); m_ctagsUi.addButton->setToolTip(i18n("Add a directory to index.")); m_ctagsUi.addButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_ctagsUi.delButton->setToolTip(i18n("Remove a directory.")); m_ctagsUi.delButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_ctagsUi.updateButton->setToolTip(i18n("(Re-)generate the session specific CTags database.")); m_ctagsUi.updateButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.updateButton2->setToolTip(i18n("(Re-)generate the session specific CTags database.")); m_ctagsUi.updateButton2->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.resetCMD->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ctagsUi.tagsFile->setToolTip(i18n("Select new or existing database file.")); m_ctagsUi.tagsFile->setMode(KFile::File); connect(m_ctagsUi.resetCMD, &QToolButton::clicked, this, &KateCTagsView::resetCMD); connect(m_ctagsUi.addButton, &QPushButton::clicked, this, &KateCTagsView::addTagTarget); connect(m_ctagsUi.delButton, &QPushButton::clicked, this, &KateCTagsView::delTagTarget); connect(m_ctagsUi.updateButton, &QPushButton::clicked, this, &KateCTagsView::updateSessionDB); connect(m_ctagsUi.updateButton2, &QPushButton::clicked, this, &KateCTagsView::updateSessionDB); connect(&m_proc, static_cast(&QProcess::finished), this, &KateCTagsView::updateDone); connect(m_ctagsUi.inputEdit, &QLineEdit::textChanged, this, &KateCTagsView::startEditTmr); m_editTimer.setSingleShot(true); connect(&m_editTimer, &QTimer::timeout, this, &KateCTagsView::editLookUp); connect(m_ctagsUi.tagTreeWidget, &QTreeWidget::itemActivated, this, &KateCTagsView::tagHitClicked); connect(m_mWin, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KateCTagsView::handleEsc); m_toolView->layout()->addWidget(ctagsWidget); m_toolView->installEventFilter(this); m_mWin->guiFactory()->addClient(this); m_commonDB = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/katectags/common_db"); } /******************************************************************/ KateCTagsView::~KateCTagsView() { if (m_mWin && m_mWin->guiFactory()) { m_mWin->guiFactory()->removeClient(this); } if (m_toolView) { delete m_toolView; } } /******************************************************************/ void KateCTagsView::aboutToShow() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } if (Tags::hasTag(m_commonDB, currWord) || Tags::hasTag(m_ctagsUi.tagsFile->text(), currWord)) { QString squeezed = KStringHandler::csqueeze(currWord, 30); m_gotoDec->setText(i18n("Go to Declaration: %1", squeezed)); m_gotoDef->setText(i18n("Go to Definition: %1", squeezed)); m_lookup->setText(i18n("Lookup: %1", squeezed)); } } /******************************************************************/ void KateCTagsView::readSessionConfig(const KConfigGroup &cg) { m_ctagsUi.cmdEdit->setText(cg.readEntry("TagsGenCMD", DEFAULT_CTAGS_CMD)); int numEntries = cg.readEntry("SessionNumTargets", 0); QString nr; QString target; for (int i = 0; i < numEntries; i++) { nr = QStringLiteral("%1").arg(i, 3); target = cg.readEntry(QStringLiteral("SessionTarget_%1").arg(nr), QString()); if (!listContains(target)) { new QListWidgetItem(target, m_ctagsUi.targetList); } } QString sessionDB = cg.readEntry("SessionDatabase", QString()); m_ctagsUi.tagsFile->setText(sessionDB); } /******************************************************************/ void KateCTagsView::writeSessionConfig(KConfigGroup &cg) { cg.writeEntry("TagsGenCMD", m_ctagsUi.cmdEdit->text()); cg.writeEntry("SessionNumTargets", m_ctagsUi.targetList->count()); QString nr; for (int i = 0; i < m_ctagsUi.targetList->count(); i++) { nr = QStringLiteral("%1").arg(i, 3); cg.writeEntry(QStringLiteral("SessionTarget_%1").arg(nr), m_ctagsUi.targetList->item(i)->text()); } cg.writeEntry("SessionDatabase", m_ctagsUi.tagsFile->text()); cg.sync(); } /******************************************************************/ void KateCTagsView::stepBack() { if (m_jumpStack.isEmpty()) { return; } TagJump back; back = m_jumpStack.pop(); m_mWin->openUrl(back.url); m_mWin->activeView()->setCursorPosition(back.cursor); m_mWin->activeView()->setFocus(); } /******************************************************************/ void KateCTagsView::lookupTag() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } setNewLookupText(currWord); Tags::TagList list = Tags::getExactMatches(m_ctagsUi.tagsFile->text(), currWord); if (list.empty()) list = Tags::getExactMatches(m_commonDB, currWord); displayHits(list); // activate the hits tab m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); } /******************************************************************/ void KateCTagsView::editLookUp() { Tags::TagList list = Tags::getPartialMatches(m_ctagsUi.tagsFile->text(), m_ctagsUi.inputEdit->text()); if (list.empty()) list = Tags::getPartialMatches(m_commonDB, m_ctagsUi.inputEdit->text()); displayHits(list); } /******************************************************************/ void KateCTagsView::gotoDefinition() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } QStringList types; types << QStringLiteral("S") << QStringLiteral("d") << QStringLiteral("f") << QStringLiteral("t") << QStringLiteral("v"); gotoTagForTypes(currWord, types); } /******************************************************************/ void KateCTagsView::gotoDeclaration() { QString currWord = currentWord(); if (currWord.isEmpty()) { return; } QStringList types; types << QStringLiteral("L") << QStringLiteral("c") << QStringLiteral("e") << QStringLiteral("g") << QStringLiteral("m") << QStringLiteral("n") << QStringLiteral("p") << QStringLiteral("s") << QStringLiteral("u") << QStringLiteral("x"); gotoTagForTypes(currWord, types); } /******************************************************************/ void KateCTagsView::gotoTagForTypes(const QString &word, const QStringList &types) { Tags::TagList list = Tags::getMatches(m_ctagsUi.tagsFile->text(), word, false, types); if (list.empty()) list = Tags::getMatches(m_commonDB, word, false, types); // qCDebug(KTECTAGS) << "found" << list.count() << word << types; setNewLookupText(word); if (list.count() < 1) { m_ctagsUi.tagTreeWidget->clear(); new QTreeWidgetItem(m_ctagsUi.tagTreeWidget, QStringList(i18n("No hits found"))); m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); return; } displayHits(list); if (list.count() == 1) { Tags::TagEntry tag = list.first(); jumpToTag(tag.file, tag.pattern, word); } else { Tags::TagEntry tag = list.first(); jumpToTag(tag.file, tag.pattern, word); m_ctagsUi.tabWidget->setCurrentIndex(0); m_mWin->showToolView(m_toolView); } } /******************************************************************/ void KateCTagsView::setNewLookupText(const QString &newString) { m_ctagsUi.inputEdit->blockSignals(true); m_ctagsUi.inputEdit->setText(newString); m_ctagsUi.inputEdit->blockSignals(false); } /******************************************************************/ void KateCTagsView::displayHits(const Tags::TagList &list) { m_ctagsUi.tagTreeWidget->clear(); if (list.isEmpty()) { new QTreeWidgetItem(m_ctagsUi.tagTreeWidget, QStringList(i18n("No hits found"))); return; } m_ctagsUi.tagTreeWidget->setSortingEnabled(false); for (const auto &tag : list) { QTreeWidgetItem *item = new QTreeWidgetItem(m_ctagsUi.tagTreeWidget); item->setText(0, tag.tag); item->setText(1, tag.type); item->setText(2, tag.file); item->setData(0, Qt::UserRole, tag.pattern); QString pattern = tag.pattern; pattern.replace(QStringLiteral("\\/"), QStringLiteral("/")); pattern = pattern.mid(2, pattern.length() - 4); pattern = pattern.trimmed(); item->setData(0, Qt::ToolTipRole, pattern); item->setData(1, Qt::ToolTipRole, pattern); item->setData(2, Qt::ToolTipRole, pattern); } m_ctagsUi.tagTreeWidget->setSortingEnabled(true); } /******************************************************************/ void KateCTagsView::tagHitClicked(QTreeWidgetItem *item) { // get stuff const QString file = item->data(2, Qt::DisplayRole).toString(); const QString pattern = item->data(0, Qt::UserRole).toString(); const QString word = item->data(0, Qt::DisplayRole).toString(); jumpToTag(file, pattern, word); } /******************************************************************/ QString KateCTagsView::currentWord() { KTextEditor::View *kv = m_mWin->activeView(); if (!kv) { - qCDebug(KTECTAGS) << "no KTextEditor::View" << endl; + qCDebug(KTECTAGS) << "no KTextEditor::View"; return QString(); } if (kv->selection() && kv->selectionRange().onSingleLine()) { return kv->selectionText(); } if (!kv->cursorPosition().isValid()) { - qCDebug(KTECTAGS) << "cursor not valid!" << endl; + qCDebug(KTECTAGS) << "cursor not valid!"; return QString(); } int line = kv->cursorPosition().line(); int col = kv->cursorPosition().column(); bool includeColon = m_ctagsUi.cmdEdit->text().contains(QLatin1String("--extra=+q")); QString linestr = kv->document()->line(line); int startPos = qMax(qMin(col, linestr.length() - 1), 0); int endPos = startPos; while (startPos >= 0 && (linestr[startPos].isLetterOrNumber() || (linestr[startPos] == QLatin1Char(':') && includeColon) || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~'))) { startPos--; } while (endPos < linestr.length() && (linestr[endPos].isLetterOrNumber() || (linestr[endPos] == QLatin1Char(':') && includeColon) || linestr[endPos] == QLatin1Char('_'))) { endPos++; } if (startPos == endPos) { - qCDebug(KTECTAGS) << "no word found!" << endl; + qCDebug(KTECTAGS) << "no word found!"; return QString(); } linestr = linestr.mid(startPos + 1, endPos - startPos - 1); while (linestr.endsWith(QLatin1Char(':'))) { linestr.remove(linestr.size() - 1, 1); } while (linestr.startsWith(QLatin1Char(':'))) { linestr.remove(0, 1); } // qCDebug(KTECTAGS) << linestr; return linestr; } /******************************************************************/ void KateCTagsView::jumpToTag(const QString &file, const QString &pattern, const QString &word) { if (pattern.isEmpty()) return; // generate a regexp from the pattern // ctags interestingly escapes "/", but apparently nothing else. lets revert that QString unescaped = pattern; unescaped.replace(QStringLiteral("\\/"), QStringLiteral("/")); // most of the time, the ctags pattern has the form /^foo$/ // but this isn't true for some macro definitions // where the form is only /^foo/ // I have no idea if this is a ctags bug or not, but we have to deal with it QString reduced; QString escaped; QString re_string; if (unescaped.endsWith(QLatin1String("$/"))) { reduced = unescaped.mid(2, unescaped.length() - 4); escaped = QRegularExpression::escape(reduced); re_string = QStringLiteral("^%1$").arg(escaped); } else { reduced = unescaped.mid(2, unescaped.length() - 3); escaped = QRegularExpression::escape(reduced); re_string = QStringLiteral("^%1").arg(escaped); } QRegularExpression re(re_string); // save current location TagJump from; from.url = m_mWin->activeView()->document()->url(); from.cursor = m_mWin->activeView()->cursorPosition(); m_jumpStack.push(from); // open/activate the new file QFileInfo fInfo(file); // qCDebug(KTECTAGS) << pattern << file << fInfo.absoluteFilePath(); m_mWin->openUrl(QUrl::fromLocalFile(fInfo.absoluteFilePath())); // any view active? if (!m_mWin->activeView()) { return; } // look for the line QString linestr; int line; for (line = 0; line < m_mWin->activeView()->document()->lines(); line++) { linestr = m_mWin->activeView()->document()->line(line); if (linestr.indexOf(re) > -1) break; } // activate the line if (line != m_mWin->activeView()->document()->lines()) { // line found now look for the column int column = linestr.indexOf(word) + (word.length() / 2); m_mWin->activeView()->setCursorPosition(KTextEditor::Cursor(line, column)); } m_mWin->activeView()->setFocus(); } /******************************************************************/ void KateCTagsView::startEditTmr() { if (m_ctagsUi.inputEdit->text().size() > 3) { m_editTimer.start(500); } } /******************************************************************/ void KateCTagsView::updateSessionDB() { if (m_proc.state() != QProcess::NotRunning) { return; } QString targets; QString target; for (int i = 0; i < m_ctagsUi.targetList->count(); i++) { target = m_ctagsUi.targetList->item(i)->text(); if (target.endsWith(QLatin1Char('/')) || target.endsWith(QLatin1Char('\\'))) { target = target.left(target.size() - 1); } targets += target + QLatin1Char(' '); } QString pluginFolder = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/katectags"); QDir().mkpath(pluginFolder); if (m_ctagsUi.tagsFile->text().isEmpty()) { // FIXME we need a way to get the session name pluginFolder += QLatin1String("/session_db_"); pluginFolder += QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMdd_hhmmss")); m_ctagsUi.tagsFile->setText(pluginFolder); } if (targets.isEmpty()) { KMessageBox::error(nullptr, i18n("No folders or files to index")); QFile::remove(m_ctagsUi.tagsFile->text()); return; } QString command = QStringLiteral("%1 -f %2 %3").arg(m_ctagsUi.cmdEdit->text(), m_ctagsUi.tagsFile->text(), targets); m_proc.start(command); if (!m_proc.waitForStarted(500)) { KMessageBox::error(nullptr, i18n("Failed to run \"%1\". exitStatus = %2", command, m_proc.exitStatus())); return; } QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); m_ctagsUi.updateButton->setDisabled(true); m_ctagsUi.updateButton2->setDisabled(true); } /******************************************************************/ void KateCTagsView::updateDone(int exitCode, QProcess::ExitStatus status) { if (status == QProcess::CrashExit) { KMessageBox::error(m_toolView, i18n("The CTags executable crashed.")); } else if (exitCode != 0) { KMessageBox::error(m_toolView, i18n("The CTags program exited with code %1: %2", exitCode, QString::fromLocal8Bit(m_proc.readAllStandardError()))); } m_ctagsUi.updateButton->setDisabled(false); m_ctagsUi.updateButton2->setDisabled(false); QApplication::restoreOverrideCursor(); } /******************************************************************/ void KateCTagsView::addTagTarget() { QFileDialog dialog; dialog.setDirectory(QFileInfo(m_mWin->activeView()->document()->url().path()).path()); dialog.setFileMode(QFileDialog::Directory); // i18n("CTags Database Location")); if (dialog.exec() != QDialog::Accepted) { return; } QStringList urls = dialog.selectedFiles(); for (int i = 0; i < urls.size(); i++) { if (!listContains(urls[i])) { new QListWidgetItem(urls[i], m_ctagsUi.targetList); } } } /******************************************************************/ void KateCTagsView::delTagTarget() { delete m_ctagsUi.targetList->currentItem(); } /******************************************************************/ bool KateCTagsView::listContains(const QString &target) { for (int i = 0; i < m_ctagsUi.targetList->count(); i++) { if (m_ctagsUi.targetList->item(i)->text() == target) { return true; } } return false; } /******************************************************************/ bool KateCTagsView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_mWin->hideToolView(m_toolView); event->accept(); return true; } } return QObject::eventFilter(obj, event); } /******************************************************************/ void KateCTagsView::resetCMD() { m_ctagsUi.cmdEdit->setText(DEFAULT_CTAGS_CMD); } /******************************************************************/ void KateCTagsView::handleEsc(QEvent *e) { if (!m_mWin) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_mWin->hideToolView(m_toolView); } } } diff --git a/addons/katebuild-plugin/plugin_katebuild.cpp b/addons/katebuild-plugin/plugin_katebuild.cpp index bac42d67f..b59949d41 100644 --- a/addons/katebuild-plugin/plugin_katebuild.cpp +++ b/addons/katebuild-plugin/plugin_katebuild.cpp @@ -1,1260 +1,1260 @@ /* plugin_katebuild.c Kate Plugin ** ** Copyright (C) 2013 by Alexander Neundorf ** Copyright (C) 2006-2015 by Kåre Särs ** Copyright (C) 2011 by Ian Wakeling ** ** This code is mostly a modification of the GPL'ed Make plugin ** by Adriaan de Groot. */ /* ** 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 in a file called COPYING; if not, write to ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, ** MA 02110-1301, USA. */ #include "plugin_katebuild.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SelectTargetView.h" K_PLUGIN_FACTORY_WITH_JSON(KateBuildPluginFactory, "katebuildplugin.json", registerPlugin();) static const QString DefConfigCmd = QStringLiteral("cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr/local ../"); static const QString DefConfClean; static const QString DefTargetName = QStringLiteral("all"); static const QString DefBuildCmd = QStringLiteral("make"); static const QString DefCleanCmd = QStringLiteral("make clean"); static const QString NinjaPrefix = QStringLiteral("[ninja]"); static QIcon messageIcon(KateBuildView::ErrorCategory severity) { #define RETURN_CACHED_ICON(name) \ { \ static QIcon icon(QIcon::fromTheme(QStringLiteral(name))); \ return icon; \ } switch (severity) { case KateBuildView::CategoryError: RETURN_CACHED_ICON("dialog-error") case KateBuildView::CategoryWarning: RETURN_CACHED_ICON("dialog-warning") default: break; } return QIcon(); } struct ItemData { // ensure destruction, but not inadvertently so by a variant value copy QSharedPointer cursor; }; Q_DECLARE_METATYPE(ItemData) /******************************************************************/ KateBuildPlugin::KateBuildPlugin(QObject *parent, const VariantList &) : KTextEditor::Plugin(parent) { // KF5 FIXME KGlobal::locale()->insertCatalog("katebuild-plugin"); } /******************************************************************/ QObject *KateBuildPlugin::createView(KTextEditor::MainWindow *mainWindow) { return new KateBuildView(this, mainWindow); } /******************************************************************/ KateBuildView::KateBuildView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mw) : QObject(mw) , m_win(mw) , m_buildWidget(nullptr) , m_outputWidgetWidth(0) , m_proc(this) , m_stdOut() , m_stdErr() , m_buildCancelled(false) , m_displayModeBeforeBuild(1) // NOTE this will not allow spaces in file names. // e.g. from gcc: "main.cpp:14: error: cannot convert ‘std::string’ to ‘int’ in return" , m_filenameDetector(QStringLiteral("(([a-np-zA-Z]:[\\\\/])?[a-zA-Z0-9_\\.\\+\\-/\\\\]+\\.[a-zA-Z0-9]+):([0-9]+)(.*)")) // e.g. from icpc: "main.cpp(14): error: no suitable conversion function from "std::string" to "int" exists" , m_filenameDetectorIcpc(QStringLiteral("(([a-np-zA-Z]:[\\\\/])?[a-zA-Z0-9_\\.\\+\\-/\\\\]+\\.[a-zA-Z0-9]+)\\(([0-9]+)\\)(:.*)")) , m_filenameDetectorGccWorked(false) , m_newDirDetector(QStringLiteral("make\\[.+\\]: .+ `.*'")) { KXMLGUIClient::setComponentName(QStringLiteral("katebuild"), i18n("Kate Build Plugin")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mw->createToolView(plugin, QStringLiteral("kate_plugin_katebuildplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("application-x-ms-dos-executable")), i18n("Build Output")); QAction *a = actionCollection()->addAction(QStringLiteral("select_target")); a->setText(i18n("Select Target...")); a->setIcon(QIcon::fromTheme(QStringLiteral("select"))); connect(a, &QAction::triggered, this, &KateBuildView::slotSelectTarget); a = actionCollection()->addAction(QStringLiteral("build_default_target")); a->setText(i18n("Build Default Target")); connect(a, &QAction::triggered, this, &KateBuildView::slotBuildDefaultTarget); a = actionCollection()->addAction(QStringLiteral("build_previous_target")); a->setText(i18n("Build Previous Target")); connect(a, &QAction::triggered, this, &KateBuildView::slotBuildPreviousTarget); a = actionCollection()->addAction(QStringLiteral("stop")); a->setText(i18n("Stop")); a->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); connect(a, &QAction::triggered, this, &KateBuildView::slotStop); a = actionCollection()->addAction(QStringLiteral("goto_next")); a->setText(i18n("Next Error")); a->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); actionCollection()->setDefaultShortcut(a, Qt::SHIFT + Qt::ALT + Qt::Key_Right); connect(a, &QAction::triggered, this, &KateBuildView::slotNext); a = actionCollection()->addAction(QStringLiteral("goto_prev")); a->setText(i18n("Previous Error")); a->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); actionCollection()->setDefaultShortcut(a, Qt::SHIFT + Qt::ALT + Qt::Key_Left); connect(a, &QAction::triggered, this, &KateBuildView::slotPrev); m_showMarks = a = actionCollection()->addAction(QStringLiteral("show_marks")); a->setText(i18n("Show Marks")); a->setCheckable(true); connect(a, &QAction::triggered, this, &KateBuildView::slotDisplayOption); m_buildWidget = new QWidget(m_toolView); m_buildUi.setupUi(m_buildWidget); m_targetsUi = new TargetsUi(this, m_buildUi.u_tabWidget); m_buildUi.u_tabWidget->insertTab(0, m_targetsUi, i18nc("Tab label", "Target Settings")); m_buildUi.u_tabWidget->setCurrentWidget(m_targetsUi); m_buildWidget->installEventFilter(this); m_buildUi.buildAgainButton->setVisible(true); m_buildUi.cancelBuildButton->setVisible(true); m_buildUi.buildStatusLabel->setVisible(true); m_buildUi.buildAgainButton2->setVisible(false); m_buildUi.cancelBuildButton2->setVisible(false); m_buildUi.buildStatusLabel2->setVisible(false); m_buildUi.extraLineLayout->setAlignment(Qt::AlignRight); m_buildUi.cancelBuildButton->setEnabled(false); m_buildUi.cancelBuildButton2->setEnabled(false); connect(m_buildUi.errTreeWidget, &QTreeWidget::itemClicked, this, &KateBuildView::slotErrorSelected); m_buildUi.plainTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_buildUi.plainTextEdit->setReadOnly(true); slotDisplayMode(FullOutput); connect(m_buildUi.displayModeSlider, &QSlider::valueChanged, this, &KateBuildView::slotDisplayMode); connect(m_buildUi.buildAgainButton, &QPushButton::clicked, this, &KateBuildView::slotBuildPreviousTarget); connect(m_buildUi.cancelBuildButton, &QPushButton::clicked, this, &KateBuildView::slotStop); connect(m_buildUi.buildAgainButton2, &QPushButton::clicked, this, &KateBuildView::slotBuildPreviousTarget); connect(m_buildUi.cancelBuildButton2, &QPushButton::clicked, this, &KateBuildView::slotStop); connect(m_targetsUi->newTarget, &QToolButton::clicked, this, &KateBuildView::targetSetNew); connect(m_targetsUi->copyTarget, &QToolButton::clicked, this, &KateBuildView::targetOrSetCopy); connect(m_targetsUi->deleteTarget, &QToolButton::clicked, this, &KateBuildView::targetDelete); connect(m_targetsUi->addButton, &QToolButton::clicked, this, &KateBuildView::slotAddTargetClicked); connect(m_targetsUi->buildButton, &QToolButton::clicked, this, &KateBuildView::slotBuildActiveTarget); connect(m_targetsUi, &TargetsUi::enterPressed, this, &KateBuildView::slotBuildActiveTarget); m_proc.setOutputChannelMode(KProcess::SeparateChannels); connect(&m_proc, static_cast(&QProcess::finished), this, &KateBuildView::slotProcExited); connect(&m_proc, &KProcess::readyReadStandardError, this, &KateBuildView::slotReadReadyStdErr); connect(&m_proc, &KProcess::readyReadStandardOutput, this, &KateBuildView::slotReadReadyStdOut); connect(m_win, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KateBuildView::handleEsc); connect(m_win, &KTextEditor::MainWindow::viewChanged, this, &KateBuildView::slotViewChanged); m_toolView->installEventFilter(this); m_win->guiFactory()->addClient(this); // watch for project plugin view creation/deletion connect(m_win, &KTextEditor::MainWindow::pluginViewCreated, this, &KateBuildView::slotPluginViewCreated); connect(m_win, &KTextEditor::MainWindow::pluginViewDeleted, this, &KateBuildView::slotPluginViewDeleted); // Connect signals from project plugin to our slots m_projectPluginView = m_win->pluginView(QStringLiteral("kateprojectplugin")); slotPluginViewCreated(QStringLiteral("kateprojectplugin"), m_projectPluginView); } /******************************************************************/ KateBuildView::~KateBuildView() { m_win->guiFactory()->removeClient(this); delete m_toolView; } /******************************************************************/ void KateBuildView::readSessionConfig(const KConfigGroup &cg) { int numTargets = cg.readEntry(QStringLiteral("NumTargets"), 0); m_targetsUi->targetsModel.clear(); int tmpIndex; int tmpCmd; if (numTargets == 0) { // either the config is empty or uses the older format m_targetsUi->targetsModel.addTargetSet(i18n("Target Set"), QString()); m_targetsUi->targetsModel.addCommand(0, i18n("build"), cg.readEntry(QStringLiteral("Make Command"), DefBuildCmd)); m_targetsUi->targetsModel.addCommand(0, i18n("clean"), cg.readEntry(QStringLiteral("Clean Command"), DefCleanCmd)); m_targetsUi->targetsModel.addCommand(0, i18n("config"), DefConfigCmd); QString quickCmd = cg.readEntry(QStringLiteral("Quick Compile Command")); if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(0, i18n("quick"), quickCmd); } tmpIndex = 0; tmpCmd = 0; } else { for (int i = 0; i < numTargets; i++) { QStringList targetNames = cg.readEntry(QStringLiteral("%1 Target Names").arg(i), QStringList()); QString targetSetName = cg.readEntry(QStringLiteral("%1 Target").arg(i), QString()); QString buildDir = cg.readEntry(QStringLiteral("%1 BuildPath").arg(i), QString()); m_targetsUi->targetsModel.addTargetSet(targetSetName, buildDir); if (targetNames.isEmpty()) { QString quickCmd = cg.readEntry(QStringLiteral("%1 QuickCmd").arg(i)); m_targetsUi->targetsModel.addCommand(i, i18n("build"), cg.readEntry(QStringLiteral("%1 BuildCmd"), DefBuildCmd)); m_targetsUi->targetsModel.addCommand(i, i18n("clean"), cg.readEntry(QStringLiteral("%1 CleanCmd"), DefCleanCmd)); if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(i, i18n("quick"), quickCmd); } m_targetsUi->targetsModel.setDefaultCmd(i, i18n("build")); } else { for (int tn = 0; tn < targetNames.size(); ++tn) { const QString &targetName = targetNames.at(tn); m_targetsUi->targetsModel.addCommand(i, targetName, cg.readEntry(QStringLiteral("%1 BuildCmd %2").arg(i).arg(targetName), DefBuildCmd)); } QString defCmd = cg.readEntry(QStringLiteral("%1 Target Default").arg(i), QString()); m_targetsUi->targetsModel.setDefaultCmd(i, defCmd); } } tmpIndex = cg.readEntry(QStringLiteral("Active Target Index"), 0); tmpCmd = cg.readEntry(QStringLiteral("Active Target Command"), 0); } m_targetsUi->targetsView->expandAll(); m_targetsUi->targetsView->resizeColumnToContents(0); m_targetsUi->targetsView->collapseAll(); QModelIndex root = m_targetsUi->targetsModel.index(tmpIndex); QModelIndex cmdIndex = m_targetsUi->targetsModel.index(tmpCmd, 0, root); m_targetsUi->targetsView->setCurrentIndex(cmdIndex); auto showMarks = cg.readEntry(QStringLiteral("Show Marks"), false); m_showMarks->setChecked(showMarks); // Add project targets, if any slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::writeSessionConfig(KConfigGroup &cg) { // Don't save project targets, is not our area of accountability m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); QList targets = m_targetsUi->targetsModel.targetSets(); cg.writeEntry("NumTargets", targets.size()); for (int i = 0; i < targets.size(); i++) { cg.writeEntry(QStringLiteral("%1 Target").arg(i), targets[i].name); cg.writeEntry(QStringLiteral("%1 BuildPath").arg(i), targets[i].workDir); QStringList cmdNames; for (int j = 0; j < targets[i].commands.count(); j++) { const QString &cmdName = targets[i].commands[j].first; const QString &buildCmd = targets[i].commands[j].second; cmdNames << cmdName; cg.writeEntry(QStringLiteral("%1 BuildCmd %2").arg(i).arg(cmdName), buildCmd); } cg.writeEntry(QStringLiteral("%1 Target Names").arg(i), cmdNames); cg.writeEntry(QStringLiteral("%1 Target Default").arg(i), targets[i].defaultCmd); } int setRow = 0; int set = 0; QModelIndex ind = m_targetsUi->targetsView->currentIndex(); if (ind.internalId() == TargetModel::InvalidIndex) { set = ind.row(); } else { set = ind.internalId(); setRow = ind.row(); } if (setRow < 0) setRow = 0; cg.writeEntry(QStringLiteral("Active Target Index"), set); cg.writeEntry(QStringLiteral("Active Target Command"), setRow); cg.writeEntry(QStringLiteral("Show Marks"), m_showMarks->isChecked()); // Restore project targets, if any slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::slotNext() { const int itemCount = m_buildUi.errTreeWidget->topLevelItemCount(); if (itemCount == 0) { return; } QTreeWidgetItem *item = m_buildUi.errTreeWidget->currentItem(); if (item && item->isHidden()) item = nullptr; int i = (item == nullptr) ? -1 : m_buildUi.errTreeWidget->indexOfTopLevelItem(item); while (++i < itemCount) { item = m_buildUi.errTreeWidget->topLevelItem(i); // Search item which fit view settings and has desired data if (!item->text(1).isEmpty() && !item->isHidden() && item->data(1, Qt::UserRole).toInt()) { m_buildUi.errTreeWidget->setCurrentItem(item); m_buildUi.errTreeWidget->scrollToItem(item); slotErrorSelected(item); return; } } } /******************************************************************/ void KateBuildView::slotPrev() { const int itemCount = m_buildUi.errTreeWidget->topLevelItemCount(); if (itemCount == 0) { return; } QTreeWidgetItem *item = m_buildUi.errTreeWidget->currentItem(); if (item && item->isHidden()) item = nullptr; int i = (item == nullptr) ? itemCount : m_buildUi.errTreeWidget->indexOfTopLevelItem(item); while (--i >= 0) { item = m_buildUi.errTreeWidget->topLevelItem(i); // Search item which fit view settings and has desired data if (!item->text(1).isEmpty() && !item->isHidden() && item->data(1, Qt::UserRole).toInt()) { m_buildUi.errTreeWidget->setCurrentItem(item); m_buildUi.errTreeWidget->scrollToItem(item); slotErrorSelected(item); return; } } } /******************************************************************/ void KateBuildView::slotErrorSelected(QTreeWidgetItem *item) { // any view active? if (!m_win->activeView()) { return; } // Avoid garish highlighting of the selected line m_win->activeView()->setFocus(); // Search the item where the data we need is stored while (!item->data(1, Qt::UserRole).toInt()) { item = m_buildUi.errTreeWidget->itemAbove(item); if (!item) { return; } } // get stuff const QString filename = item->data(0, Qt::UserRole).toString(); if (filename.isEmpty()) { return; } int line = item->data(1, Qt::UserRole).toInt(); int column = item->data(2, Qt::UserRole).toInt(); // check with moving cursor auto data = item->data(0, DataRole).value(); if (data.cursor) { line = data.cursor->line(); column = data.cursor->column(); } // open file (if needed, otherwise, this will activate only the right view...) m_win->openUrl(QUrl::fromLocalFile(filename)); // do it ;) m_win->activeView()->setCursorPosition(KTextEditor::Cursor(line - 1, column - 1)); } /******************************************************************/ void KateBuildView::addError(const QString &filename, const QString &line, const QString &column, const QString &message) { ErrorCategory errorCategory = CategoryInfo; QTreeWidgetItem *item = new QTreeWidgetItem(m_buildUi.errTreeWidget); item->setBackground(1, Qt::gray); // The strings are twice in case kate is translated but not make. if (message.contains(QLatin1String("error")) || message.contains(i18nc("The same word as 'make' uses to mark an error.", "error")) || message.contains(QLatin1String("undefined reference")) || message.contains(i18nc("The same word as 'ld' uses to mark an ...", "undefined reference"))) { errorCategory = CategoryError; item->setForeground(1, Qt::red); m_numErrors++; item->setHidden(false); } if (message.contains(QLatin1String("warning")) || message.contains(i18nc("The same word as 'make' uses to mark a warning.", "warning"))) { errorCategory = CategoryWarning; item->setForeground(1, Qt::yellow); m_numWarnings++; item->setHidden(m_buildUi.displayModeSlider->value() > 2); } item->setTextAlignment(1, Qt::AlignRight); // visible text // remove path from visible file name QFileInfo file(filename); item->setText(0, file.fileName()); item->setText(1, line); item->setText(2, message.trimmed()); // used to read from when activating an item item->setData(0, Qt::UserRole, filename); item->setData(1, Qt::UserRole, line); item->setData(2, Qt::UserRole, column); if (errorCategory == CategoryInfo) { item->setHidden(m_buildUi.displayModeSlider->value() > 1); } item->setData(0, ErrorRole, errorCategory); // add tooltips in all columns // The enclosing ... enables word-wrap for long error messages item->setData(0, Qt::ToolTipRole, filename); item->setData(1, Qt::ToolTipRole, QStringLiteral("%1").arg(message)); item->setData(2, Qt::ToolTipRole, QStringLiteral("%1").arg(message)); } void KateBuildView::clearMarks() { for (auto &doc : m_markedDocs) { if (!doc) { continue; } KTextEditor::MarkInterface *iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); auto markType = KTextEditor::MarkInterface::Error | KTextEditor::MarkInterface::Warning; if (i.value()->type & markType) { iface->removeMark(i.value()->line, markType); } } } } m_markedDocs.clear(); } void KateBuildView::addMarks(KTextEditor::Document *doc, bool mark) { KTextEditor::MarkInterface *iface = qobject_cast(doc); KTextEditor::MovingInterface *miface = qobject_cast(doc); if (!iface || m_markedDocs.contains(doc)) return; QTreeWidgetItemIterator it(m_buildUi.errTreeWidget, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto filename = item->data(0, Qt::UserRole).toString(); auto url = QUrl::fromLocalFile(filename); if (url != doc->url()) continue; auto line = item->data(1, Qt::UserRole).toInt(); if (mark) { ErrorCategory category = static_cast(item->data(0, ErrorRole).toInt()); KTextEditor::MarkInterface::MarkTypes markType {}; switch (category) { case CategoryError: { markType = KTextEditor::MarkInterface::Error; iface->setMarkDescription(markType, i18n("Error")); break; } case CategoryWarning: { markType = KTextEditor::MarkInterface::Warning; iface->setMarkDescription(markType, i18n("Warning")); break; } default: break; } if (markType) { const int ps = 32; iface->setMarkPixmap(markType, messageIcon(category).pixmap(ps, ps)); iface->addMark(line - 1, markType); } m_markedDocs.insert(doc, doc); } // add moving cursor so link between message and location // is not broken by document changes if (miface) { auto data = item->data(0, DataRole).value(); if (!data.cursor) { auto column = item->data(2, Qt::UserRole).toInt(); data.cursor.reset(miface->newMovingCursor({line, column})); QVariant var; var.setValue(data); item->setData(0, DataRole, var); } } } // ensure cleanup if (miface) { auto conn = connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(slotInvalidateMoving(KTextEditor::Document *)), Qt::UniqueConnection); conn = connect(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(slotInvalidateMoving(KTextEditor::Document *)), Qt::UniqueConnection); } connect(doc, SIGNAL(markClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), this, SLOT(slotMarkClicked(KTextEditor::Document *, KTextEditor::Mark, bool &)), Qt::UniqueConnection); } void KateBuildView::slotInvalidateMoving(KTextEditor::Document *doc) { QTreeWidgetItemIterator it(m_buildUi.errTreeWidget, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto data = item->data(0, DataRole).value(); if (data.cursor && data.cursor->document() == doc) { item->setData(0, DataRole, 0); } } } void KateBuildView::slotMarkClicked(KTextEditor::Document *doc, KTextEditor::Mark mark, bool &handled) { auto tree = m_buildUi.errTreeWidget; QTreeWidgetItemIterator it(tree, QTreeWidgetItemIterator::All); while (*it) { QTreeWidgetItem *item = *it; ++it; auto filename = item->data(0, Qt::UserRole).toString(); auto line = item->data(1, Qt::UserRole).toInt(); // prefer moving cursor's opinion if so available auto data = item->data(0, DataRole).value(); if (data.cursor) { line = data.cursor->line(); } if (line - 1 == mark.line && QUrl::fromLocalFile(filename) == doc->url()) { tree->blockSignals(true); tree->setCurrentItem(item); tree->scrollToItem(item, QAbstractItemView::PositionAtCenter); tree->blockSignals(false); handled = true; break; } } } void KateBuildView::slotViewChanged() { KTextEditor::View *activeView = m_win->activeView(); auto doc = activeView ? activeView->document() : nullptr; if (doc) { addMarks(doc, m_showMarks->isChecked()); } } void KateBuildView::slotDisplayOption() { if (m_showMarks) { if (!m_showMarks->isChecked()) { clearMarks(); } else { slotViewChanged(); } } } /******************************************************************/ QUrl KateBuildView::docUrl() { KTextEditor::View *kv = m_win->activeView(); if (!kv) { - qDebug() << "no KTextEditor::View" << endl; + qDebug() << "no KTextEditor::View"; return QUrl(); } if (kv->document()->isModified()) kv->document()->save(); return kv->document()->url(); } /******************************************************************/ bool KateBuildView::checkLocal(const QUrl &dir) { if (dir.path().isEmpty()) { KMessageBox::sorry(nullptr, i18n("There is no file or directory specified for building.")); return false; } else if (!dir.isLocalFile()) { KMessageBox::sorry(nullptr, i18n("The file \"%1\" is not a local file. " "Non-local files cannot be compiled.", dir.path())); return false; } return true; } /******************************************************************/ void KateBuildView::clearBuildResults() { clearMarks(); m_buildUi.plainTextEdit->clear(); m_buildUi.errTreeWidget->clear(); m_stdOut.clear(); m_stdErr.clear(); m_numErrors = 0; m_numWarnings = 0; m_make_dir_stack.clear(); } /******************************************************************/ bool KateBuildView::startProcess(const QString &dir, const QString &command) { if (m_proc.state() != QProcess::NotRunning) { return false; } // clear previous runs clearBuildResults(); // activate the output tab m_buildUi.u_tabWidget->setCurrentIndex(1); m_displayModeBeforeBuild = m_buildUi.displayModeSlider->value(); m_buildUi.displayModeSlider->setValue(0); m_win->showToolView(m_toolView); // set working directory m_make_dir = dir; m_make_dir_stack.push(m_make_dir); if (!QFile::exists(m_make_dir)) { KMessageBox::error(nullptr, i18n("Cannot run command: %1\nWork path does not exist: %2", command, m_make_dir)); return false; } // ninja build tool sends all output to stdout, // so follow https://github.com/ninja-build/ninja/issues/1537 to separate ninja and compiler output auto env = QProcessEnvironment::systemEnvironment(); const auto nstatus = QStringLiteral("NINJA_STATUS"); auto curr = env.value(nstatus, QStringLiteral("[%f/%t] ")); // add marker to search on later on env.insert(nstatus, NinjaPrefix + curr); m_ninjaBuildDetected = false; m_proc.setProcessEnvironment(env); m_proc.setWorkingDirectory(m_make_dir); m_proc.setShellCommand(command); m_proc.start(); if (!m_proc.waitForStarted(500)) { KMessageBox::error(nullptr, i18n("Failed to run \"%1\". exitStatus = %2", command, m_proc.exitStatus())); return false; } m_buildUi.cancelBuildButton->setEnabled(true); m_buildUi.cancelBuildButton2->setEnabled(true); m_buildUi.buildAgainButton->setEnabled(false); m_buildUi.buildAgainButton2->setEnabled(false); QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); return true; } /******************************************************************/ bool KateBuildView::slotStop() { if (m_proc.state() != QProcess::NotRunning) { m_buildCancelled = true; QString msg = i18n("Building %1 cancelled", m_currentlyBuildingTarget); m_buildUi.buildStatusLabel->setText(msg); m_buildUi.buildStatusLabel2->setText(msg); m_proc.terminate(); return true; } return false; } /******************************************************************/ void KateBuildView::slotBuildActiveTarget() { if (!m_targetsUi->targetsView->currentIndex().isValid()) { slotSelectTarget(); } else { buildCurrentTarget(); } } /******************************************************************/ void KateBuildView::slotBuildPreviousTarget() { if (!m_previousIndex.isValid()) { slotSelectTarget(); } else { m_targetsUi->targetsView->setCurrentIndex(m_previousIndex); buildCurrentTarget(); } } /******************************************************************/ void KateBuildView::slotBuildDefaultTarget() { QModelIndex defaultTarget = m_targetsUi->targetsModel.defaultTarget(m_targetsUi->targetsView->currentIndex()); m_targetsUi->targetsView->setCurrentIndex(defaultTarget); buildCurrentTarget(); } /******************************************************************/ void KateBuildView::slotSelectTarget() { SelectTargetView *dialog = new SelectTargetView(&(m_targetsUi->targetsModel)); dialog->setCurrentIndex(m_targetsUi->targetsView->currentIndex()); int result = dialog->exec(); if (result == QDialog::Accepted) { m_targetsUi->targetsView->setCurrentIndex(dialog->currentIndex()); buildCurrentTarget(); } delete dialog; dialog = nullptr; } /******************************************************************/ bool KateBuildView::buildCurrentTarget() { if (m_proc.state() != QProcess::NotRunning) { displayBuildResult(i18n("Already building..."), KTextEditor::Message::Warning); return false; } QFileInfo docFInfo = docUrl().toLocalFile(); // docUrl() saves the current document QModelIndex ind = m_targetsUi->targetsView->currentIndex(); m_previousIndex = ind; if (!ind.isValid()) { KMessageBox::sorry(nullptr, i18n("No target available for building.")); return false; } QString buildCmd = m_targetsUi->targetsModel.command(ind); QString cmdName = m_targetsUi->targetsModel.cmdName(ind); QString workDir = m_targetsUi->targetsModel.workDir(ind); QString targetSet = m_targetsUi->targetsModel.targetName(ind); QString dir = workDir; if (workDir.isEmpty()) { dir = docFInfo.absolutePath(); if (dir.isEmpty()) { KMessageBox::sorry(nullptr, i18n("There is no local file or directory specified for building.")); return false; } } // a single target can serve to build lots of projects with similar directory layout if (m_projectPluginView) { QFileInfo baseDir = m_projectPluginView->property("projectBaseDir").toString(); dir.replace(QStringLiteral("%B"), baseDir.absoluteFilePath()); dir.replace(QStringLiteral("%b"), baseDir.baseName()); } // Check if the command contains the file name or directory if (buildCmd.contains(QLatin1String("%f")) || buildCmd.contains(QLatin1String("%d")) || buildCmd.contains(QLatin1String("%n"))) { if (docFInfo.absoluteFilePath().isEmpty()) { return false; } buildCmd.replace(QStringLiteral("%n"), docFInfo.baseName()); buildCmd.replace(QStringLiteral("%f"), docFInfo.absoluteFilePath()); buildCmd.replace(QStringLiteral("%d"), docFInfo.absolutePath()); } m_filenameDetectorGccWorked = false; m_currentlyBuildingTarget = QStringLiteral("%1: %2").arg(targetSet, cmdName); m_buildCancelled = false; QString msg = i18n("Building target %1 ...", m_currentlyBuildingTarget); m_buildUi.buildStatusLabel->setText(msg); m_buildUi.buildStatusLabel2->setText(msg); return startProcess(dir, buildCmd); } /******************************************************************/ void KateBuildView::displayBuildResult(const QString &msg, KTextEditor::Message::MessageType level) { KTextEditor::View *kv = m_win->activeView(); if (!kv) return; delete m_infoMessage; m_infoMessage = new KTextEditor::Message(xi18nc("@info", "Make Results:%1", msg), level); m_infoMessage->setWordWrap(true); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(5000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(kv); kv->document()->postMessage(m_infoMessage); } /******************************************************************/ void KateBuildView::slotProcExited(int exitCode, QProcess::ExitStatus) { QApplication::restoreOverrideCursor(); m_buildUi.cancelBuildButton->setEnabled(false); m_buildUi.cancelBuildButton2->setEnabled(false); m_buildUi.buildAgainButton->setEnabled(true); m_buildUi.buildAgainButton2->setEnabled(true); QString buildStatus = i18n("Building %1 completed.", m_currentlyBuildingTarget); // did we get any errors? if (m_numErrors || m_numWarnings || (exitCode != 0)) { m_buildUi.u_tabWidget->setCurrentIndex(1); if (m_buildUi.displayModeSlider->value() == 0) { m_buildUi.displayModeSlider->setValue(m_displayModeBeforeBuild > 0 ? m_displayModeBeforeBuild : 1); } m_buildUi.errTreeWidget->resizeColumnToContents(0); m_buildUi.errTreeWidget->resizeColumnToContents(1); m_buildUi.errTreeWidget->resizeColumnToContents(2); m_buildUi.errTreeWidget->horizontalScrollBar()->setValue(0); // m_buildUi.errTreeWidget->setSortingEnabled(true); m_win->showToolView(m_toolView); } if (m_numErrors || m_numWarnings) { QStringList msgs; if (m_numErrors) { msgs << i18np("Found one error.", "Found %1 errors.", m_numErrors); buildStatus = i18n("Building %1 had errors.", m_currentlyBuildingTarget); } else if (m_numWarnings) { msgs << i18np("Found one warning.", "Found %1 warnings.", m_numWarnings); buildStatus = i18n("Building %1 had warnings.", m_currentlyBuildingTarget); } displayBuildResult(msgs.join(QLatin1Char('\n')), m_numErrors ? KTextEditor::Message::Error : KTextEditor::Message::Warning); } else if (exitCode != 0) { displayBuildResult(i18n("Build failed."), KTextEditor::Message::Warning); } else { displayBuildResult(i18n("Build completed without problems."), KTextEditor::Message::Positive); } if (!m_buildCancelled) { m_buildUi.buildStatusLabel->setText(buildStatus); m_buildUi.buildStatusLabel2->setText(buildStatus); m_buildCancelled = false; // add marks slotViewChanged(); } } /******************************************************************/ void KateBuildView::slotReadReadyStdOut() { // read data from procs stdout and add // the text to the end of the output // FIXME This works for utf8 but not for all charsets QString l = QString::fromUtf8(m_proc.readAllStandardOutput()); l.remove(QLatin1Char('\r')); m_stdOut += l; // handle one line at a time do { const int end = m_stdOut.indexOf(QLatin1Char('\n')); if (end < 0) break; QString line = m_stdOut.mid(0, end); const bool ninjaOutput = line.startsWith(NinjaPrefix); m_ninjaBuildDetected |= ninjaOutput; if (ninjaOutput) { line = line.mid(NinjaPrefix.length()); } m_buildUi.plainTextEdit->appendPlainText(line); // qDebug() << line; if (m_newDirDetector.match(line).hasMatch()) { // qDebug() << "Enter/Exit dir found"; int open = line.indexOf(QLatin1Char('`')); int close = line.indexOf(QLatin1Char('\'')); QString newDir = line.mid(open + 1, close - open - 1); // qDebug () << "New dir = " << newDir; if ((m_make_dir_stack.size() > 1) && (m_make_dir_stack.top() == newDir)) { m_make_dir_stack.pop(); newDir = m_make_dir_stack.top(); } else { m_make_dir_stack.push(newDir); } m_make_dir = newDir; } else if (m_ninjaBuildDetected && !ninjaOutput) { processLine(line); } m_stdOut.remove(0, end + 1); } while (1); } /******************************************************************/ void KateBuildView::slotReadReadyStdErr() { // FIXME This works for utf8 but not for all charsets QString l = QString::fromUtf8(m_proc.readAllStandardError()); l.remove(QLatin1Char('\r')); m_stdErr += l; do { const int end = m_stdErr.indexOf(QLatin1Char('\n')); if (end < 0) break; const QString line = m_stdErr.mid(0, end); m_buildUi.plainTextEdit->appendPlainText(line); processLine(line); m_stdErr.remove(0, end + 1); } while (1); } /******************************************************************/ void KateBuildView::processLine(const QString &line) { // qDebug() << line ; // look for a filename QRegularExpressionMatch match = m_filenameDetector.match(line); if (match.hasMatch()) { m_filenameDetectorGccWorked = true; } else { if (!m_filenameDetectorGccWorked) { // let's see whether the icpc regexp works: // so for icpc users error detection will be a bit slower, // since always both regexps are checked. // But this should be the minority, for gcc and clang users // both regexes will only be checked until the first regex // matched the first time. match = m_filenameDetectorIcpc.match(line); } } if (!match.hasMatch()) { addError(QString(), QStringLiteral("0"), QString(), line); // kDebug() << "A filename was not found in the line "; return; } QString filename = match.captured(1); const QString line_n = match.captured(3); const QString msg = match.captured(4); // qDebug() << "File Name:"<targetsView->currentIndex(); if (current.parent().isValid()) { current = current.parent(); } QModelIndex index = m_targetsUi->targetsModel.addCommand(current.row(), DefTargetName, DefBuildCmd); m_targetsUi->targetsView->setCurrentIndex(index); } /******************************************************************/ void KateBuildView::targetSetNew() { int row = m_targetsUi->targetsModel.addTargetSet(i18n("Target Set"), QString()); QModelIndex buildIndex = m_targetsUi->targetsModel.addCommand(row, i18n("Build"), DefBuildCmd); m_targetsUi->targetsModel.addCommand(row, i18n("Clean"), DefCleanCmd); m_targetsUi->targetsModel.addCommand(row, i18n("Config"), DefConfigCmd); m_targetsUi->targetsModel.addCommand(row, i18n("ConfigClean"), DefConfClean); m_targetsUi->targetsView->setCurrentIndex(buildIndex); } /******************************************************************/ void KateBuildView::targetOrSetCopy() { QModelIndex newIndex = m_targetsUi->targetsModel.copyTargetOrSet(m_targetsUi->targetsView->currentIndex()); if (m_targetsUi->targetsModel.hasChildren(newIndex)) { m_targetsUi->targetsView->setCurrentIndex(newIndex.model()->index(0, 0, newIndex)); return; } m_targetsUi->targetsView->setCurrentIndex(newIndex); } /******************************************************************/ void KateBuildView::targetDelete() { QModelIndex current = m_targetsUi->targetsView->currentIndex(); m_targetsUi->targetsModel.deleteItem(current); if (m_targetsUi->targetsModel.rowCount() == 0) { targetSetNew(); } } /******************************************************************/ void KateBuildView::slotDisplayMode(int mode) { QTreeWidget *tree = m_buildUi.errTreeWidget; tree->setVisible(mode != 0); m_buildUi.plainTextEdit->setVisible(mode == 0); QString modeText; switch (mode) { case OnlyErrors: modeText = i18n("Only Errors"); break; case ErrorsAndWarnings: modeText = i18n("Errors and Warnings"); break; case ParsedOutput: modeText = i18n("Parsed Output"); break; case FullOutput: modeText = i18n("Full Output"); break; } m_buildUi.displayModeLabel->setText(modeText); if (mode < 1) { return; } const int itemCount = tree->topLevelItemCount(); for (int i = 0; i < itemCount; i++) { QTreeWidgetItem *item = tree->topLevelItem(i); const ErrorCategory errorCategory = static_cast(item->data(0, ErrorRole).toInt()); switch (errorCategory) { case CategoryInfo: item->setHidden(mode > 1); break; case CategoryWarning: item->setHidden(mode > 2); break; case CategoryError: item->setHidden(false); break; } } } /******************************************************************/ void KateBuildView::slotPluginViewCreated(const QString &name, QObject *pluginView) { // add view if (pluginView && name == QLatin1String("kateprojectplugin")) { m_projectPluginView = pluginView; slotAddProjectTarget(); connect(pluginView, SIGNAL(projectMapChanged()), this, SLOT(slotProjectMapChanged()), Qt::UniqueConnection); } } /******************************************************************/ void KateBuildView::slotPluginViewDeleted(const QString &name, QObject *) { // remove view if (name == QLatin1String("kateprojectplugin")) { m_projectPluginView = nullptr; m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); } } /******************************************************************/ void KateBuildView::slotProjectMapChanged() { // only do stuff with valid project if (!m_projectPluginView) { return; } m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); slotAddProjectTarget(); } /******************************************************************/ void KateBuildView::slotAddProjectTarget() { // only do stuff with valid project if (!m_projectPluginView) { return; } // query new project map QVariantMap projectMap = m_projectPluginView->property("projectMap").toMap(); // do we have a valid map for build settings? QVariantMap buildMap = projectMap.value(QStringLiteral("build")).toMap(); if (buildMap.isEmpty()) { return; } // Delete any old project plugin targets m_targetsUi->targetsModel.deleteTargetSet(i18n("Project Plugin Targets")); // handle build directory as relative to project file, if possible, see bug 413306 QString projectsBuildDir = buildMap.value(QStringLiteral("directory")).toString(); const QString projectsBaseDir = m_projectPluginView->property("projectBaseDir").toString(); if (!projectsBaseDir.isEmpty()) { projectsBuildDir = QDir(projectsBaseDir).absoluteFilePath(projectsBuildDir); } const int set = m_targetsUi->targetsModel.addTargetSet(i18n("Project Plugin Targets"), projectsBuildDir); const QVariantList targetsets = buildMap.value(QStringLiteral("targets")).toList(); for (const QVariant &targetVariant : targetsets) { QVariantMap targetMap = targetVariant.toMap(); QString tgtName = targetMap[QStringLiteral("name")].toString(); QString buildCmd = targetMap[QStringLiteral("build_cmd")].toString(); if (tgtName.isEmpty() || buildCmd.isEmpty()) { continue; } m_targetsUi->targetsModel.addCommand(set, tgtName, buildCmd); } QModelIndex ind = m_targetsUi->targetsModel.index(set); if (!ind.model()->index(0, 0, ind).data().isValid()) { QString buildCmd = buildMap.value(QStringLiteral("build")).toString(); QString cleanCmd = buildMap.value(QStringLiteral("clean")).toString(); QString quickCmd = buildMap.value(QStringLiteral("quick")).toString(); if (!buildCmd.isEmpty()) { // we have loaded an "old" project file (<= 4.12) m_targetsUi->targetsModel.addCommand(set, i18n("build"), buildCmd); } if (!cleanCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(set, i18n("clean"), cleanCmd); } if (!quickCmd.isEmpty()) { m_targetsUi->targetsModel.addCommand(set, i18n("quick"), quickCmd); } } } /******************************************************************/ bool KateBuildView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); if ((obj == m_toolView) && (ke->key() == Qt::Key_Escape)) { m_win->hideToolView(m_toolView); event->accept(); return true; } } if ((event->type() == QEvent::Resize) && (obj == m_buildWidget)) { if (m_buildUi.u_tabWidget->currentIndex() == 1) { if ((m_outputWidgetWidth == 0) && m_buildUi.buildAgainButton->isVisible()) { QSize msh = m_buildWidget->minimumSizeHint(); m_outputWidgetWidth = msh.width(); } } bool useVertLayout = (m_buildWidget->width() < m_outputWidgetWidth); m_buildUi.buildAgainButton->setVisible(!useVertLayout); m_buildUi.cancelBuildButton->setVisible(!useVertLayout); m_buildUi.buildStatusLabel->setVisible(!useVertLayout); m_buildUi.buildAgainButton2->setVisible(useVertLayout); m_buildUi.cancelBuildButton2->setVisible(useVertLayout); m_buildUi.buildStatusLabel2->setVisible(useVertLayout); } return QObject::eventFilter(obj, event); } /******************************************************************/ void KateBuildView::handleEsc(QEvent *e) { if (!m_win) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_toolView->isVisible()) { m_win->hideToolView(m_toolView); } } } #include "plugin_katebuild.moc" // kate: space-indent on; indent-width 4; replace-tabs on;