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
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

View File

@@ -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 \

View File

@@ -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",

View File

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

View File

@@ -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

View File

@@ -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<Core::IDocument *, DocumentClangToolRunner *> documentRunners;
DocumentQuickFixFactory quickFixFactory;
};
ClangToolsPlugin::~ClangToolsPlugin()

View File

@@ -25,6 +25,7 @@
#include "diagnosticmark.h"
#include "clangtoolsconstants.h"
#include "clangtoolsutils.h"
#include <utils/utilsicons.h>
@@ -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

View File

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

View File

@@ -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 <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <texteditor/textmark.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
@@ -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<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()
{
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<TextEditor::TextDocument *>(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();
}

View File

@@ -26,6 +26,7 @@
#pragma once
#include "clangfileinfo.h"
#include "clangtoolsdiagnostic.h"
#include <utils/fileutils.h>
#include <utils/temporarydirectory.h>
@@ -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<class T>
ClangToolRunner *createRunner(const CppTools::ClangDiagnosticConfig &config,
@@ -75,6 +81,7 @@ private:
QList<DiagnosticMark *> m_marks;
FileInfo m_fileInfo;
QMetaObject::Connection m_projectSettingsUpdate;
QSet<TextEditor::TextEditorWidget *> m_editorsWithMarkers;
};
} // 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 <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/documentmodel.h>
#include <texteditor/textdocument.h>
#include <QJsonArray>
@@ -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

View File

@@ -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<Core::IDocument *, AutoSavedPath> m_saved;
QMap<Utils::FilePath, Utils::FilePath> m_mapping;
};
} // namespace Internal