ClangTools: Add QuickFixes to the editor

Change-Id: I9862231f0aa8e8274e8529e57e80eac5ececded9
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
David Schulz
2020-08-25 06:18:26 +02:00
parent b55a313b3d
commit ef10f3b937
14 changed files with 284 additions and 12 deletions

View File

@@ -7,7 +7,7 @@ find_package(yaml-cpp QUIET MODULE)
add_qtc_plugin(ClangTools add_qtc_plugin(ClangTools
CONDITION TARGET yaml-cpp CONDITION TARGET yaml-cpp
DEPENDS ClangSupport yaml-cpp DEPENDS ClangSupport yaml-cpp
PLUGIN_DEPENDS Core Debugger CppTools ${TST_COMPONENT} PLUGIN_DEPENDS Core Debugger CppTools CppEditor ${TST_COMPONENT}
PLUGIN_RECOMMENDS CppEditor PLUGIN_RECOMMENDS CppEditor
INCLUDES ${CLANG_INCLUDE_DIRS} INCLUDES ${CLANG_INCLUDE_DIRS}
SOURCES SOURCES
@@ -33,6 +33,7 @@ add_qtc_plugin(ClangTools
diagnosticconfigswidget.cpp diagnosticconfigswidget.h diagnosticconfigswidget.cpp diagnosticconfigswidget.h
diagnosticmark.cpp diagnosticmark.h diagnosticmark.cpp diagnosticmark.h
documentclangtoolrunner.cpp documentclangtoolrunner.h documentclangtoolrunner.cpp documentclangtoolrunner.h
documentquickfixfactory.cpp documentquickfixfactory.h
executableinfo.cpp executableinfo.h executableinfo.cpp executableinfo.h
filterdialog.cpp filterdialog.h filterdialog.ui filterdialog.cpp filterdialog.h filterdialog.ui
runsettingswidget.cpp runsettingswidget.h runsettingswidget.ui runsettingswidget.cpp runsettingswidget.h runsettingswidget.ui

View File

@@ -31,6 +31,7 @@ SOURCES += \
diagnosticconfigswidget.cpp \ diagnosticconfigswidget.cpp \
diagnosticmark.cpp \ diagnosticmark.cpp \
documentclangtoolrunner.cpp \ documentclangtoolrunner.cpp \
documentquickfixfactory.cpp \
executableinfo.cpp \ executableinfo.cpp \
filterdialog.cpp \ filterdialog.cpp \
runsettingswidget.cpp \ runsettingswidget.cpp \
@@ -59,6 +60,7 @@ HEADERS += \
diagnosticconfigswidget.h \ diagnosticconfigswidget.h \
diagnosticmark.h \ diagnosticmark.h \
documentclangtoolrunner.h \ documentclangtoolrunner.h \
documentquickfixfactory.h \
executableinfo.h \ executableinfo.h \
filterdialog.h \ filterdialog.h \
runsettingswidget.h \ runsettingswidget.h \

View File

@@ -8,6 +8,7 @@ QtcPlugin {
Depends { name: "Core" } Depends { name: "Core" }
Depends { name: "TextEditor" } Depends { name: "TextEditor" }
Depends { name: "CppTools" } Depends { name: "CppTools" }
Depends { name: "CppEditor" }
Depends { name: "ProjectExplorer" } Depends { name: "ProjectExplorer" }
Depends { name: "QtSupport"; condition: qtc.testsEnabled } Depends { name: "QtSupport"; condition: qtc.testsEnabled }
Depends { name: "QtcSsh" } Depends { name: "QtcSsh" }
@@ -70,6 +71,8 @@ QtcPlugin {
"diagnosticmark.h", "diagnosticmark.h",
"documentclangtoolrunner.cpp", "documentclangtoolrunner.cpp",
"documentclangtoolrunner.h", "documentclangtoolrunner.h",
"documentquickfixfactory.cpp",
"documentquickfixfactory.h",
"executableinfo.cpp", "executableinfo.cpp",
"executableinfo.h", "executableinfo.h",
"filterdialog.cpp", "filterdialog.cpp",

View File

@@ -8,6 +8,7 @@ isEmpty(EXTERNAL_YAML_CPP_FOUND): QTC_LIB_DEPENDS += yaml-cpp
QTC_PLUGIN_DEPENDS += \ QTC_PLUGIN_DEPENDS += \
debugger \ debugger \
cppeditor \
cpptools cpptools
QTC_PLUGIN_RECOMMENDS += \ QTC_PLUGIN_RECOMMENDS += \
cppeditor cppeditor

View File

@@ -40,6 +40,10 @@ const char CLANGTIDYCLAZY_RUN_MODE[] = "ClangTidyClazy.RunMode";
const char CLANG_TIDY_EXECUTABLE_NAME[] = "clang-tidy"; const char CLANG_TIDY_EXECUTABLE_NAME[] = "clang-tidy";
const char CLAZY_STANDALONE_EXECUTABLE_NAME[] = "clazy-standalone"; 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"; const char DIAG_CONFIG_TIDY_AND_CLAZY[] = "Builtin.DefaultTidyAndClazy";
} // Constants } // Constants

View File

@@ -30,6 +30,7 @@
#include "clangtoolsprojectsettings.h" #include "clangtoolsprojectsettings.h"
#include "clangtoolsprojectsettingswidget.h" #include "clangtoolsprojectsettingswidget.h"
#include "documentclangtoolrunner.h" #include "documentclangtoolrunner.h"
#include "documentquickfixfactory.h"
#include "settingswidget.h" #include "settingswidget.h"
#ifdef WITH_TESTS #ifdef WITH_TESTS
@@ -83,9 +84,24 @@ ProjectPanelFactory *projectPanelFactory()
class ClangToolsPluginPrivate class ClangToolsPluginPrivate
{ {
public: 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; ClangTool clangTool;
ClangToolsOptionsPage optionsPage; ClangToolsOptionsPage optionsPage;
QMap<Core::IDocument *, DocumentClangToolRunner *> documentRunners; QMap<Core::IDocument *, DocumentClangToolRunner *> documentRunners;
DocumentQuickFixFactory quickFixFactory;
}; };
ClangToolsPlugin::~ClangToolsPlugin() ClangToolsPlugin::~ClangToolsPlugin()

View File

@@ -25,6 +25,7 @@
#include "diagnosticmark.h" #include "diagnosticmark.h"
#include "clangtoolsconstants.h"
#include "clangtoolsutils.h" #include "clangtoolsutils.h"
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
@@ -35,7 +36,7 @@ namespace Internal {
DiagnosticMark::DiagnosticMark(const Diagnostic &diagnostic) DiagnosticMark::DiagnosticMark(const Diagnostic &diagnostic)
: TextEditor::TextMark(Utils::FilePath::fromString(diagnostic.location.filePath), : TextEditor::TextMark(Utils::FilePath::fromString(diagnostic.location.filePath),
diagnostic.location.line, diagnostic.location.line,
Utils::Id("ClangTool.DiagnosticMark")) Utils::Id(Constants::DIAGNOSTIC_MARK_ID))
, m_diagnostic(diagnostic) , m_diagnostic(diagnostic)
{ {
if (diagnostic.type == "error" || diagnostic.type == "fatal") if (diagnostic.type == "error" || diagnostic.type == "fatal")
@@ -67,6 +68,11 @@ bool DiagnosticMark::enabled() const
return m_enabled; return m_enabled;
} }
Diagnostic DiagnosticMark::diagnostic() const
{
return m_diagnostic;
}
} // namespace Internal } // namespace Internal
} // namespace ClangTools } // namespace ClangTools

View File

@@ -41,6 +41,8 @@ public:
void disable(); void disable();
bool enabled() const; bool enabled() const;
Diagnostic diagnostic() const;
QString source; QString source;
private: private:

View File

@@ -26,8 +26,10 @@
#include "documentclangtoolrunner.h" #include "documentclangtoolrunner.h"
#include "clangfileinfo.h" #include "clangfileinfo.h"
#include "clangfixitsrefactoringchanges.h"
#include "clangtidyclazyrunner.h" #include "clangtidyclazyrunner.h"
#include "clangtoolruncontrol.h" #include "clangtoolruncontrol.h"
#include "clangtoolsconstants.h"
#include "clangtoolsprojectsettings.h" #include "clangtoolsprojectsettings.h"
#include "clangtoolsutils.h" #include "clangtoolsutils.h"
#include "diagnosticmark.h" #include "diagnosticmark.h"
@@ -42,6 +44,7 @@
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <texteditor/textmark.h> #include <texteditor/textmark.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
@@ -85,10 +88,32 @@ DocumentClangToolRunner::~DocumentClangToolRunner()
qDeleteAll(m_marks); 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<TextEditor::TextDocument *>(m_document)) {
for (auto mark : textDocument->marksAt(lineNumber)) {
if (mark->category() == Constants::DIAGNOSTIC_MARK_ID)
diagnostics << static_cast<DiagnosticMark *>(mark)->diagnostic();
}
}
return diagnostics;
}
void DocumentClangToolRunner::scheduleRun() void DocumentClangToolRunner::scheduleRun()
{ {
for (DiagnosticMark *mark : m_marks) for (DiagnosticMark *mark : m_marks)
mark->disable(); 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(); 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() void DocumentClangToolRunner::onSuccess()
{ {
QString errorMessage; QString errorMessage;
Utils::FilePath mappedPath = vfso().filePath(m_document); Utils::FilePath mappedPath = vfso().autoSavedFilePath(m_document);
Diagnostics diagnostics = readExportedDiagnostics( Diagnostics diagnostics = readExportedDiagnostics(
Utils::FilePath::fromString(m_currentRunner->outputFilePath()), Utils::FilePath::fromString(m_currentRunner->outputFilePath()),
[&](const Utils::FilePath &path) { return path == mappedPath; }, [&](const Utils::FilePath &path) { return path == mappedPath; },
&errorMessage); &errorMessage);
if (mappedPath != m_document->filePath()) { for (Diagnostic &diag : diagnostics) {
const QString originalPath = m_document->filePath().toString(); updateLocation(diag.location);
for (Diagnostic &diag : diagnostics) for (ExplainingStep &explainingStep : diag.explainingSteps) {
diag.location.filePath = originalPath; updateLocation(explainingStep.location);
for (Debugger::DiagnosticLocation &rangeLocation : explainingStep.ranges)
updateLocation(rangeLocation);
}
} }
// remove outdated marks of the current runner // remove outdated marks of the current runner
@@ -255,11 +288,41 @@ void DocumentClangToolRunner::onSuccess()
m_marks = newMarks; m_marks = newMarks;
qDeleteAll(toDelete); qDeleteAll(toDelete);
m_marks << Utils::transform(diagnostics, [this](const Diagnostic &diagnostic) { auto doc = qobject_cast<TextEditor::TextDocument *>(m_document);
TextEditor::RefactorMarkers markers;
for (const Diagnostic &diagnostic : diagnostics) {
auto mark = new DiagnosticMark(diagnostic); auto mark = new DiagnosticMark(diagnostic);
mark->source = m_currentRunner->name(); 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(); runNext();
} }

View File

@@ -26,6 +26,7 @@
#pragma once #pragma once
#include "clangfileinfo.h" #include "clangfileinfo.h"
#include "clangtoolsdiagnostic.h"
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/temporarydirectory.h> #include <utils/temporarydirectory.h>
@@ -35,6 +36,7 @@
namespace Core { class IDocument; } namespace Core { class IDocument; }
namespace CppTools { class ClangDiagnosticConfig; } namespace CppTools { class ClangDiagnosticConfig; }
namespace TextEditor { class TextEditorWidget; }
namespace ClangTools { namespace ClangTools {
@@ -50,6 +52,9 @@ public:
DocumentClangToolRunner(Core::IDocument *doc); DocumentClangToolRunner(Core::IDocument *doc);
~DocumentClangToolRunner(); ~DocumentClangToolRunner();
Utils::FilePath filePath() const;
Diagnostics diagnosticsAtLine(int lineNumber) const;
private: private:
void scheduleRun(); void scheduleRun();
void run(); void run();
@@ -62,6 +67,7 @@ private:
void cancel(); void cancel();
const CppTools::ClangDiagnosticConfig getDiagnosticConfig(ProjectExplorer::Project *project); const CppTools::ClangDiagnosticConfig getDiagnosticConfig(ProjectExplorer::Project *project);
template<class T> template<class T>
ClangToolRunner *createRunner(const CppTools::ClangDiagnosticConfig &config, ClangToolRunner *createRunner(const CppTools::ClangDiagnosticConfig &config,
@@ -75,6 +81,7 @@ private:
QList<DiagnosticMark *> m_marks; QList<DiagnosticMark *> m_marks;
FileInfo m_fileInfo; FileInfo m_fileInfo;
QMetaObject::Connection m_projectSettingsUpdate; QMetaObject::Connection m_projectSettingsUpdate;
QSet<TextEditor::TextEditorWidget *> m_editorsWithMarkers;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -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 <texteditor/refactoringchanges.h>
#include <utils/qtcassert.h>
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<Debugger::DiagnosticLocation, Debugger::DiagnosticLocation>;
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<QString, TextEditor::RefactoringFilePtr> 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

View File

@@ -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 <cppeditor/cppquickfix.h>
namespace ClangTools {
namespace Internal {
class DocumentClangToolRunner;
class DocumentQuickFixFactory : public CppEditor::CppQuickFixFactory
{
public:
using RunnerCollector = std::function<DocumentClangToolRunner *(const Utils::FilePath &)>;
DocumentQuickFixFactory(RunnerCollector runnerCollector);
void match(const CppEditor::Internal::CppQuickFixInterface &interface,
QuickFixOperations &result) override;
private:
RunnerCollector m_runnerCollector;
};
} // namespace Internal
} // namespace ClangTools

View File

@@ -26,6 +26,7 @@
#include "virtualfilesystemoverlay.h" #include "virtualfilesystemoverlay.h"
#include <coreplugin/documentmanager.h> #include <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/documentmodel.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <QJsonArray> #include <QJsonArray>
@@ -80,6 +81,9 @@ void VirtualFileSystemOverlay::update()
qCDebug(LOG) << error; qCDebug(LOG) << error;
} }
m_saved = newSaved; 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) { auto toContent = [this](Core::IDocument *document) {
QJsonObject content; QJsonObject content;
@@ -112,7 +116,7 @@ void VirtualFileSystemOverlay::update()
Utils::FilePath VirtualFileSystemOverlay::overlayFilePath() { return m_overlayFilePath; } 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); auto it = m_saved.find(doc);
if (it != m_saved.end()) if (it != m_saved.end())
@@ -120,5 +124,10 @@ Utils::FilePath VirtualFileSystemOverlay::filePath(Core::IDocument *doc)
return doc->filePath(); return doc->filePath();
} }
Utils::FilePath VirtualFileSystemOverlay::originalFilePath(const Utils::FilePath &file)
{
return m_mapping.value(file, file);
}
} // namespace Internal } // namespace Internal
} // namespace ClangTools } // namespace ClangTools

View File

@@ -43,7 +43,8 @@ public:
void update(); void update();
Utils::FilePath overlayFilePath(); Utils::FilePath overlayFilePath();
Utils::FilePath filePath(Core::IDocument *doc); Utils::FilePath autoSavedFilePath(Core::IDocument *doc);
Utils::FilePath originalFilePath(const Utils::FilePath &file);
private: private:
Utils::TemporaryDirectory m_root; Utils::TemporaryDirectory m_root;
@@ -55,6 +56,7 @@ private:
}; };
QMap<Core::IDocument *, AutoSavedPath> m_saved; QMap<Core::IDocument *, AutoSavedPath> m_saved;
QMap<Utils::FilePath, Utils::FilePath> m_mapping;
}; };
} // namespace Internal } // namespace Internal