diff --git a/src/plugins/clangtools/CMakeLists.txt b/src/plugins/clangtools/CMakeLists.txt index 829b77c97a5..314edab2f3b 100644 --- a/src/plugins/clangtools/CMakeLists.txt +++ b/src/plugins/clangtools/CMakeLists.txt @@ -7,7 +7,7 @@ find_package(yaml-cpp QUIET MODULE) add_qtc_plugin(ClangTools CONDITION TARGET yaml-cpp DEPENDS ClangSupport yaml-cpp - PLUGIN_DEPENDS Core Debugger CppTools ${TST_COMPONENT} + PLUGIN_DEPENDS Core Debugger CppTools CppEditor ${TST_COMPONENT} PLUGIN_RECOMMENDS CppEditor INCLUDES ${CLANG_INCLUDE_DIRS} SOURCES @@ -33,6 +33,7 @@ add_qtc_plugin(ClangTools diagnosticconfigswidget.cpp diagnosticconfigswidget.h diagnosticmark.cpp diagnosticmark.h documentclangtoolrunner.cpp documentclangtoolrunner.h + documentquickfixfactory.cpp documentquickfixfactory.h executableinfo.cpp executableinfo.h filterdialog.cpp filterdialog.h filterdialog.ui runsettingswidget.cpp runsettingswidget.h runsettingswidget.ui diff --git a/src/plugins/clangtools/clangtools.pro b/src/plugins/clangtools/clangtools.pro index f4a85c24e64..0a40cf3f8fc 100644 --- a/src/plugins/clangtools/clangtools.pro +++ b/src/plugins/clangtools/clangtools.pro @@ -31,6 +31,7 @@ SOURCES += \ diagnosticconfigswidget.cpp \ diagnosticmark.cpp \ documentclangtoolrunner.cpp \ + documentquickfixfactory.cpp \ executableinfo.cpp \ filterdialog.cpp \ runsettingswidget.cpp \ @@ -59,6 +60,7 @@ HEADERS += \ diagnosticconfigswidget.h \ diagnosticmark.h \ documentclangtoolrunner.h \ + documentquickfixfactory.h \ executableinfo.h \ filterdialog.h \ runsettingswidget.h \ diff --git a/src/plugins/clangtools/clangtools.qbs b/src/plugins/clangtools/clangtools.qbs index 6e9d83b14db..2497c68635b 100644 --- a/src/plugins/clangtools/clangtools.qbs +++ b/src/plugins/clangtools/clangtools.qbs @@ -8,6 +8,7 @@ QtcPlugin { Depends { name: "Core" } Depends { name: "TextEditor" } Depends { name: "CppTools" } + Depends { name: "CppEditor" } Depends { name: "ProjectExplorer" } Depends { name: "QtSupport"; condition: qtc.testsEnabled } Depends { name: "QtcSsh" } @@ -70,6 +71,8 @@ QtcPlugin { "diagnosticmark.h", "documentclangtoolrunner.cpp", "documentclangtoolrunner.h", + "documentquickfixfactory.cpp", + "documentquickfixfactory.h", "executableinfo.cpp", "executableinfo.h", "filterdialog.cpp", diff --git a/src/plugins/clangtools/clangtools_dependencies.pri b/src/plugins/clangtools/clangtools_dependencies.pri index eaaaf295154..14d9f61b6c5 100644 --- a/src/plugins/clangtools/clangtools_dependencies.pri +++ b/src/plugins/clangtools/clangtools_dependencies.pri @@ -8,6 +8,7 @@ isEmpty(EXTERNAL_YAML_CPP_FOUND): QTC_LIB_DEPENDS += yaml-cpp QTC_PLUGIN_DEPENDS += \ debugger \ + cppeditor \ cpptools QTC_PLUGIN_RECOMMENDS += \ cppeditor diff --git a/src/plugins/clangtools/clangtoolsconstants.h b/src/plugins/clangtools/clangtoolsconstants.h index 7b8fe640391..bc44024758a 100644 --- a/src/plugins/clangtools/clangtoolsconstants.h +++ b/src/plugins/clangtools/clangtoolsconstants.h @@ -40,6 +40,10 @@ const char CLANGTIDYCLAZY_RUN_MODE[] = "ClangTidyClazy.RunMode"; const char CLANG_TIDY_EXECUTABLE_NAME[] = "clang-tidy"; const char CLAZY_STANDALONE_EXECUTABLE_NAME[] = "clazy-standalone"; +const char CLANG_TOOL_FIXIT_AVAILABLE_MARKER_ID[] = "ClangToolFixItAvailableMarker"; + +const char DIAGNOSTIC_MARK_ID[] = "ClangTool.DiagnosticMark"; + const char DIAG_CONFIG_TIDY_AND_CLAZY[] = "Builtin.DefaultTidyAndClazy"; } // Constants diff --git a/src/plugins/clangtools/clangtoolsplugin.cpp b/src/plugins/clangtools/clangtoolsplugin.cpp index 63f5df80ccc..dd6d25296a2 100644 --- a/src/plugins/clangtools/clangtoolsplugin.cpp +++ b/src/plugins/clangtools/clangtoolsplugin.cpp @@ -30,6 +30,7 @@ #include "clangtoolsprojectsettings.h" #include "clangtoolsprojectsettingswidget.h" #include "documentclangtoolrunner.h" +#include "documentquickfixfactory.h" #include "settingswidget.h" #ifdef WITH_TESTS @@ -83,9 +84,24 @@ ProjectPanelFactory *projectPanelFactory() class ClangToolsPluginPrivate { public: + ClangToolsPluginPrivate() + : quickFixFactory( + [this](const Utils::FilePath &filePath) { return runnerForFilePath(filePath); }) + {} + + DocumentClangToolRunner *runnerForFilePath(const Utils::FilePath &filePath) + { + for (DocumentClangToolRunner *runner : documentRunners) { + if (runner->filePath() == filePath) + return runner; + } + return nullptr; + } + ClangTool clangTool; ClangToolsOptionsPage optionsPage; QMap documentRunners; + DocumentQuickFixFactory quickFixFactory; }; ClangToolsPlugin::~ClangToolsPlugin() diff --git a/src/plugins/clangtools/diagnosticmark.cpp b/src/plugins/clangtools/diagnosticmark.cpp index 127c091f274..21ef7fd0bf9 100644 --- a/src/plugins/clangtools/diagnosticmark.cpp +++ b/src/plugins/clangtools/diagnosticmark.cpp @@ -25,6 +25,7 @@ #include "diagnosticmark.h" +#include "clangtoolsconstants.h" #include "clangtoolsutils.h" #include @@ -35,7 +36,7 @@ namespace Internal { DiagnosticMark::DiagnosticMark(const Diagnostic &diagnostic) : TextEditor::TextMark(Utils::FilePath::fromString(diagnostic.location.filePath), diagnostic.location.line, - Utils::Id("ClangTool.DiagnosticMark")) + Utils::Id(Constants::DIAGNOSTIC_MARK_ID)) , m_diagnostic(diagnostic) { if (diagnostic.type == "error" || diagnostic.type == "fatal") @@ -67,6 +68,11 @@ bool DiagnosticMark::enabled() const return m_enabled; } +Diagnostic DiagnosticMark::diagnostic() const +{ + return m_diagnostic; +} + } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/diagnosticmark.h b/src/plugins/clangtools/diagnosticmark.h index 38cbe281a2b..a0d40314305 100644 --- a/src/plugins/clangtools/diagnosticmark.h +++ b/src/plugins/clangtools/diagnosticmark.h @@ -41,6 +41,8 @@ public: void disable(); bool enabled() const; + Diagnostic diagnostic() const; + QString source; private: diff --git a/src/plugins/clangtools/documentclangtoolrunner.cpp b/src/plugins/clangtools/documentclangtoolrunner.cpp index 23c613c7be0..fadac9b8628 100644 --- a/src/plugins/clangtools/documentclangtoolrunner.cpp +++ b/src/plugins/clangtools/documentclangtoolrunner.cpp @@ -26,8 +26,10 @@ #include "documentclangtoolrunner.h" #include "clangfileinfo.h" +#include "clangfixitsrefactoringchanges.h" #include "clangtidyclazyrunner.h" #include "clangtoolruncontrol.h" +#include "clangtoolsconstants.h" #include "clangtoolsprojectsettings.h" #include "clangtoolsutils.h" #include "diagnosticmark.h" @@ -42,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -85,10 +88,32 @@ DocumentClangToolRunner::~DocumentClangToolRunner() qDeleteAll(m_marks); } +Utils::FilePath DocumentClangToolRunner::filePath() const +{ + return m_document->filePath(); +} + +Diagnostics DocumentClangToolRunner::diagnosticsAtLine(int lineNumber) const +{ + Diagnostics diagnostics; + if (auto textDocument = qobject_cast(m_document)) { + for (auto mark : textDocument->marksAt(lineNumber)) { + if (mark->category() == Constants::DIAGNOSTIC_MARK_ID) + diagnostics << static_cast(mark)->diagnostic(); + } + } + return diagnostics; +} + void DocumentClangToolRunner::scheduleRun() { for (DiagnosticMark *mark : m_marks) mark->disable(); + for (TextEditor::TextEditorWidget *editor : m_editorsWithMarkers) { + editor->setRefactorMarkers( + TextEditor::RefactorMarker::filterOutType(editor->refactorMarkers(), + Constants::CLANG_TOOL_FIXIT_AVAILABLE_MARKER_ID)); + } m_runTimer.start(); } @@ -233,19 +258,27 @@ void DocumentClangToolRunner::runNext() } } +static void updateLocation(Debugger::DiagnosticLocation &location) +{ + location.filePath = vfso().originalFilePath(Utils::FilePath::fromString(location.filePath)).toString(); +} + void DocumentClangToolRunner::onSuccess() { QString errorMessage; - Utils::FilePath mappedPath = vfso().filePath(m_document); + Utils::FilePath mappedPath = vfso().autoSavedFilePath(m_document); Diagnostics diagnostics = readExportedDiagnostics( Utils::FilePath::fromString(m_currentRunner->outputFilePath()), [&](const Utils::FilePath &path) { return path == mappedPath; }, &errorMessage); - if (mappedPath != m_document->filePath()) { - const QString originalPath = m_document->filePath().toString(); - for (Diagnostic &diag : diagnostics) - diag.location.filePath = originalPath; + for (Diagnostic &diag : diagnostics) { + updateLocation(diag.location); + for (ExplainingStep &explainingStep : diag.explainingSteps) { + updateLocation(explainingStep.location); + for (Debugger::DiagnosticLocation &rangeLocation : explainingStep.ranges) + updateLocation(rangeLocation); + } } // remove outdated marks of the current runner @@ -255,11 +288,41 @@ void DocumentClangToolRunner::onSuccess() m_marks = newMarks; qDeleteAll(toDelete); - m_marks << Utils::transform(diagnostics, [this](const Diagnostic &diagnostic) { + auto doc = qobject_cast(m_document); + + TextEditor::RefactorMarkers markers; + + for (const Diagnostic &diagnostic : diagnostics) { auto mark = new DiagnosticMark(diagnostic); mark->source = m_currentRunner->name(); - return mark; - }); + + if (doc && Utils::anyOf(diagnostic.explainingSteps, &ExplainingStep::isFixIt)) { + TextEditor::RefactorMarker marker; + marker.tooltip = diagnostic.description; + QTextCursor cursor(doc->document()); + cursor.setPosition(Utils::Text::positionInText(doc->document(), + diagnostic.location.line, + diagnostic.location.column)); + cursor.movePosition(QTextCursor::EndOfLine); + marker.cursor = cursor; + marker.type = Constants::CLANG_TOOL_FIXIT_AVAILABLE_MARKER_ID; + marker.callback = [marker](TextEditor::TextEditorWidget *editor) { + editor->setTextCursor(marker.cursor); + editor->invokeAssist(TextEditor::QuickFix); + }; + markers << marker; + } + + m_marks << mark; + } + + for (auto editor : TextEditor::BaseTextEditor::textEditorsForDocument(doc)) { + if (TextEditor::TextEditorWidget *widget = editor->editorWidget()) { + widget->setRefactorMarkers(markers + widget->refactorMarkers()); + m_editorsWithMarkers << widget; + } + } + runNext(); } diff --git a/src/plugins/clangtools/documentclangtoolrunner.h b/src/plugins/clangtools/documentclangtoolrunner.h index 4cf263c1aa3..e15ec336551 100644 --- a/src/plugins/clangtools/documentclangtoolrunner.h +++ b/src/plugins/clangtools/documentclangtoolrunner.h @@ -26,6 +26,7 @@ #pragma once #include "clangfileinfo.h" +#include "clangtoolsdiagnostic.h" #include #include @@ -35,6 +36,7 @@ namespace Core { class IDocument; } namespace CppTools { class ClangDiagnosticConfig; } +namespace TextEditor { class TextEditorWidget; } namespace ClangTools { @@ -50,6 +52,9 @@ public: DocumentClangToolRunner(Core::IDocument *doc); ~DocumentClangToolRunner(); + Utils::FilePath filePath() const; + Diagnostics diagnosticsAtLine(int lineNumber) const; + private: void scheduleRun(); void run(); @@ -62,6 +67,7 @@ private: void cancel(); + const CppTools::ClangDiagnosticConfig getDiagnosticConfig(ProjectExplorer::Project *project); template ClangToolRunner *createRunner(const CppTools::ClangDiagnosticConfig &config, @@ -75,6 +81,7 @@ private: QList m_marks; FileInfo m_fileInfo; QMetaObject::Connection m_projectSettingsUpdate; + QSet m_editorsWithMarkers; }; } // namespace Internal diff --git a/src/plugins/clangtools/documentquickfixfactory.cpp b/src/plugins/clangtools/documentquickfixfactory.cpp new file mode 100644 index 00000000000..66053de2495 --- /dev/null +++ b/src/plugins/clangtools/documentquickfixfactory.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "documentquickfixfactory.h" + +#include "clangfixitsrefactoringchanges.h" +#include "clangtoolsdiagnostic.h" +#include "documentclangtoolrunner.h" + +#include +#include + +namespace ClangTools { +namespace Internal { + +class ClangToolQuickFixOperation : public TextEditor::QuickFixOperation +{ +public: + explicit ClangToolQuickFixOperation(const Diagnostic &diagnostic) + : m_diagnostic(diagnostic) + {} + + QString description() const override { return m_diagnostic.description; } + void perform() override; + +private: + const Diagnostic m_diagnostic; +}; + +using Range = TextEditor::RefactoringFile::Range; +using DiagnosticRange = QPair; + +static Range toRange(const QTextDocument *doc, DiagnosticRange locations) +{ + Range range; + range.start = Utils::Text::positionInText(doc, locations.first.line, locations.first.column); + range.end = Utils::Text::positionInText(doc, locations.second.line, locations.second.column); + return range; +} + +void ClangToolQuickFixOperation::perform() +{ + TextEditor::RefactoringChanges changes; + QMap refactoringFiles; + + for (const ExplainingStep &step : m_diagnostic.explainingSteps) { + if (!step.isFixIt) + continue; + TextEditor::RefactoringFilePtr &refactoringFile = refactoringFiles[step.location.filePath]; + if (refactoringFile.isNull()) + refactoringFile = changes.file(step.location.filePath); + Utils::ChangeSet changeSet = refactoringFile->changeSet(); + Range range = toRange(refactoringFile->document(), {step.ranges.first(), step.ranges.last()}); + changeSet.replace(range, step.message); + refactoringFile->setChangeSet(changeSet); + } + + for (const TextEditor::RefactoringFilePtr &refactoringFile : qAsConst(refactoringFiles)) + refactoringFile->apply(); +} + +DocumentQuickFixFactory::DocumentQuickFixFactory(DocumentQuickFixFactory::RunnerCollector runnerCollector) + : m_runnerCollector(runnerCollector) +{} + +void DocumentQuickFixFactory::match(const CppEditor::Internal::CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + QTC_ASSERT(m_runnerCollector, return ); + if (DocumentClangToolRunner *runner = m_runnerCollector(interface.filePath())) { + const QTextBlock &block = interface.textDocument()->findBlock(interface.position()); + if (!block.isValid()) + return; + + const int lineNumber = block.blockNumber() + 1; + + for (Diagnostic diagnostic : runner->diagnosticsAtLine(lineNumber)) { + if (diagnostic.hasFixits) + result << new ClangToolQuickFixOperation(diagnostic); + } + } +} + +} // namespace Internal +} // namespace ClangTools diff --git a/src/plugins/clangtools/documentquickfixfactory.h b/src/plugins/clangtools/documentquickfixfactory.h new file mode 100644 index 00000000000..df34c7e7ccb --- /dev/null +++ b/src/plugins/clangtools/documentquickfixfactory.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace ClangTools { +namespace Internal { + +class DocumentClangToolRunner; + +class DocumentQuickFixFactory : public CppEditor::CppQuickFixFactory +{ +public: + using RunnerCollector = std::function; + + DocumentQuickFixFactory(RunnerCollector runnerCollector); + void match(const CppEditor::Internal::CppQuickFixInterface &interface, + QuickFixOperations &result) override; + +private: + RunnerCollector m_runnerCollector; +}; + +} // namespace Internal +} // namespace ClangTools diff --git a/src/plugins/clangtools/virtualfilesystemoverlay.cpp b/src/plugins/clangtools/virtualfilesystemoverlay.cpp index a0f9f2725cd..e230ee41866 100644 --- a/src/plugins/clangtools/virtualfilesystemoverlay.cpp +++ b/src/plugins/clangtools/virtualfilesystemoverlay.cpp @@ -26,6 +26,7 @@ #include "virtualfilesystemoverlay.h" #include +#include #include #include @@ -80,6 +81,9 @@ void VirtualFileSystemOverlay::update() qCDebug(LOG) << error; } m_saved = newSaved; + m_mapping.clear(); + for (auto it = m_saved.constBegin(), end = m_saved.constEnd(); it != end; ++it) + m_mapping[it.value().path] = it.key()->filePath(); auto toContent = [this](Core::IDocument *document) { QJsonObject content; @@ -112,7 +116,7 @@ void VirtualFileSystemOverlay::update() Utils::FilePath VirtualFileSystemOverlay::overlayFilePath() { return m_overlayFilePath; } -Utils::FilePath VirtualFileSystemOverlay::filePath(Core::IDocument *doc) +Utils::FilePath VirtualFileSystemOverlay::autoSavedFilePath(Core::IDocument *doc) { auto it = m_saved.find(doc); if (it != m_saved.end()) @@ -120,5 +124,10 @@ Utils::FilePath VirtualFileSystemOverlay::filePath(Core::IDocument *doc) return doc->filePath(); } +Utils::FilePath VirtualFileSystemOverlay::originalFilePath(const Utils::FilePath &file) +{ + return m_mapping.value(file, file); +} + } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/virtualfilesystemoverlay.h b/src/plugins/clangtools/virtualfilesystemoverlay.h index 9cc16c8f2c2..768b6366bc8 100644 --- a/src/plugins/clangtools/virtualfilesystemoverlay.h +++ b/src/plugins/clangtools/virtualfilesystemoverlay.h @@ -43,7 +43,8 @@ public: void update(); Utils::FilePath overlayFilePath(); - Utils::FilePath filePath(Core::IDocument *doc); + Utils::FilePath autoSavedFilePath(Core::IDocument *doc); + Utils::FilePath originalFilePath(const Utils::FilePath &file); private: Utils::TemporaryDirectory m_root; @@ -55,6 +56,7 @@ private: }; QMap m_saved; + QMap m_mapping; }; } // namespace Internal