Commit a7928b4b authored by Nikolai Kosjar's avatar Nikolai Kosjar

Clang: Integrate clang's fixits as refactoring actions

They are invokable by the usual means (Alt+Enter, editor's context menu
> Refactor) plus by the context menu of the editor's left margin for the
related line.

The fixit text comes directly from libclang and is thus not translated.
We modify the text slighty by stripping the diagnostic category prefix
("note:", "error:", ...) and capitalizing the first letter.

A follow-up change should properly indicate available refactorings with
a refactoring icon in the editor's left margin.

Task-number: QTCREATORBUG-14868
Change-Id: I86157c9f824d2a9dedf19087476d02ad1e6cc854
Reviewed-by: default avatarMarco Bubke <marco.bubke@theqtcompany.com>
parent de6d7f06
......@@ -27,6 +27,8 @@ SOURCES += \
clangdiagnosticmanager.cpp \
clangeditordocumentparser.cpp \
clangeditordocumentprocessor.cpp \
clangfixitoperation.cpp \
clangfixitoperationsextractor.cpp \
clangfunctionhintmodel.cpp \
clangmodelmanagersupport.cpp \
clangprojectsettings.cpp \
......@@ -67,6 +69,8 @@ HEADERS += \
clangdiagnosticmanager.h \
clangeditordocumentparser.h \
clangeditordocumentprocessor.h \
clangfixitoperation.h \
clangfixitoperationsextractor.h \
clangfunctionhintmodel.h \
clang_global.h \
clangmodelmanagersupport.h \
......
......@@ -140,6 +140,10 @@ QtcPlugin {
"clangdiagnosticfilter.h",
"clangdiagnosticmanager.cpp",
"clangdiagnosticmanager.h",
"clangfixitoperation.cpp",
"clangfixitoperation.h",
"clangfixitoperationsextractor.cpp",
"clangfixitoperationsextractor.h",
"clangmodelmanagersupport.cpp",
"clangmodelmanagersupport.h",
"clangcodemodelplugin.cpp",
......
......@@ -4,11 +4,12 @@ SOURCES += $$PWD/completionchunkstotextconverter.cpp \
$$PWD/activationsequenceprocessor.cpp \
$$PWD/activationsequencecontextprocessor.cpp \
$$PWD/clangcompletioncontextanalyzer.cpp \
$$PWD/clangdiagnosticfilter.cpp
$$PWD/clangdiagnosticfilter.cpp \
$$PWD/clangfixitoperation.cpp
HEADERS += $$PWD/completionchunkstotextconverter.h \
$$PWD/activationsequenceprocessor.h \
$$PWD/activationsequencecontextprocessor.h \
$$PWD/clangcompletioncontextanalyzer.h \
$$PWD/clangdiagnosticfilter.h
$$PWD/clangdiagnosticfilter.h \
$$PWD/clangfixitoperation.h
......@@ -61,6 +61,18 @@ filterDiagnostics(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
return filteredDiagnostics;
}
template <class Condition>
void
filterDiagnostics(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
const Condition &condition,
QVector<ClangBackEnd::DiagnosticContainer> &filteredDiagnostic)
{
std::copy_if(diagnostics.cbegin(),
diagnostics.cend(),
std::back_inserter(filteredDiagnostic),
condition);
}
} // anonymous namespace
namespace ClangCodeModel {
......@@ -88,6 +100,22 @@ void ClangDiagnosticFilter::filterDocumentRelatedErrors(
m_errorDiagnostics = filterDiagnostics(diagnostics, isLocalWarning);
}
void ClangDiagnosticFilter::filterFixits()
{
const auto hasFixIts = [] (const ClangBackEnd::DiagnosticContainer &diagnostic) {
return diagnostic.fixIts().size() > 0;
};
m_fixItdiagnostics.clear();
filterDiagnostics(m_warningDiagnostics, hasFixIts, m_fixItdiagnostics);
filterDiagnostics(m_errorDiagnostics, hasFixIts, m_fixItdiagnostics);
for (const auto &warningDiagnostic : m_warningDiagnostics)
filterDiagnostics(warningDiagnostic.children(), hasFixIts, m_fixItdiagnostics);
for (const auto &warningDiagnostic : m_errorDiagnostics)
filterDiagnostics(warningDiagnostic.children(), hasFixIts, m_fixItdiagnostics);
}
ClangDiagnosticFilter::ClangDiagnosticFilter(const QString &filePath)
: m_filePath(filePath)
{
......@@ -97,6 +125,7 @@ void ClangDiagnosticFilter::filter(const QVector<ClangBackEnd::DiagnosticContain
{
filterDocumentRelatedWarnings(diagnostics);
filterDocumentRelatedErrors(diagnostics);
filterFixits();
}
QVector<ClangBackEnd::DiagnosticContainer> ClangDiagnosticFilter::takeWarnings()
......@@ -115,6 +144,14 @@ QVector<ClangBackEnd::DiagnosticContainer> ClangDiagnosticFilter::takeErrors()
return diagnostics;
}
QVector<ClangBackEnd::DiagnosticContainer> ClangDiagnosticFilter::takeFixIts()
{
auto diagnostics = m_fixItdiagnostics;
m_fixItdiagnostics.clear();
return diagnostics;
}
} // namespace Internal
} // namespace ClangCodeModel
......@@ -47,15 +47,19 @@ public:
QVector<ClangBackEnd::DiagnosticContainer> takeWarnings();
QVector<ClangBackEnd::DiagnosticContainer> takeErrors();
QVector<ClangBackEnd::DiagnosticContainer> takeFixIts();
private:
void filterDocumentRelatedWarnings(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics);
void filterDocumentRelatedErrors(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics);
void filterFixits();
private:
const QString &m_filePath;
QVector<ClangBackEnd::DiagnosticContainer> m_warningDiagnostics;
QVector<ClangBackEnd::DiagnosticContainer> m_errorDiagnostics;
QVector<ClangBackEnd::DiagnosticContainer> m_fixItdiagnostics;
};
} // namespace Internal
......
......@@ -208,6 +208,12 @@ void ClangDiagnosticManager::processNewDiagnostics(
clearWarningsAndErrors();
}
const QVector<ClangBackEnd::DiagnosticContainer> &
ClangDiagnosticManager::diagnosticsWithFixIts() const
{
return m_fixItdiagnostics;
}
void ClangDiagnosticManager::addClangTextMarks(
const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics)
{
......@@ -245,6 +251,7 @@ void ClangDiagnosticManager::filterDiagnostics(
m_warningDiagnostics = filter.takeWarnings();
m_errorDiagnostics = filter.takeErrors();
m_fixItdiagnostics = filter.takeFixIts();
}
} // namespace Internal
......
......@@ -53,6 +53,7 @@ public:
void processNewDiagnostics(const QVector<ClangBackEnd::DiagnosticContainer> &allDiagnostics);
const QVector<ClangBackEnd::DiagnosticContainer> &diagnosticsWithFixIts() const;
QList<QTextEdit::ExtraSelection> takeExtraSelections();
private:
......@@ -68,6 +69,7 @@ private:
QVector<ClangBackEnd::DiagnosticContainer> m_warningDiagnostics;
QVector<ClangBackEnd::DiagnosticContainer> m_errorDiagnostics;
QVector<ClangBackEnd::DiagnosticContainer> m_fixItdiagnostics;
QList<QTextEdit::ExtraSelection> m_extraSelections;
std::vector<ClangTextMark> m_clangTextMarks;
};
......
......@@ -30,6 +30,8 @@
#include "clangeditordocumentprocessor.h"
#include "clangfixitoperation.h"
#include "clangfixitoperationsextractor.h"
#include "clangmodelmanagersupport.h"
#include "clangutils.h"
#include "cppcreatemarkers.h"
......@@ -42,6 +44,7 @@
#include <cpptools/cpptoolsplugin.h>
#include <cpptools/cppworkingcopy.h>
#include <texteditor/convenience.h>
#include <texteditor/fontsettings.h>
#include <texteditor/texteditor.h>
#include <texteditor/texteditorconstants.h>
......@@ -186,6 +189,24 @@ void ClangEditorDocumentProcessor::updateCodeWarnings(const QVector<ClangBackEnd
}
}
static int currentLine(const TextEditor::AssistInterface &assistInterface)
{
int line, column;
TextEditor::Convenience::convertPosition(assistInterface.textDocument(),
assistInterface.position(),
&line,
&column);
return line;
}
TextEditor::QuickFixOperations ClangEditorDocumentProcessor::extraRefactoringOperations(
const TextEditor::AssistInterface &assistInterface)
{
ClangFixItOperationsExtractor extractor(m_diagnosticManager.diagnosticsWithFixIts());
return extractor.extract(assistInterface.fileName(), currentLine(assistInterface));
}
ClangEditorDocumentProcessor *ClangEditorDocumentProcessor::get(const QString &filePath)
{
return qobject_cast<ClangEditorDocumentProcessor *>(BaseEditorDocumentProcessor::get(filePath));
......
......@@ -73,6 +73,9 @@ public:
void updateCodeWarnings(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
uint documentRevision);
TextEditor::QuickFixOperations
extraRefactoringOperations(const TextEditor::AssistInterface &assistInterface) override;
public:
static ClangEditorDocumentProcessor *get(const QString &filePath);
......
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "clangfixitoperation.h"
#include <texteditor/refactoringchanges.h>
namespace ClangCodeModel {
ClangFixItOperation::ClangFixItOperation(const Utf8String &filePath,
const Utf8String &fixItText,
const QVector<ClangBackEnd::FixItContainer> &fixItContainers)
: filePath(filePath),
fixItText(fixItText),
fixItContainers(fixItContainers)
{
}
int ClangFixItOperation::priority() const
{
return 10;
}
QString ClangCodeModel::ClangFixItOperation::description() const
{
return QStringLiteral("Apply Fix: ") + fixItText.toString();
}
void ClangFixItOperation::perform()
{
const TextEditor::RefactoringChanges refactoringChanges;
TextEditor::RefactoringFilePtr refactoringFile = refactoringChanges.file(filePath.toString());
refactoringFile->setChangeSet(changeSet());
refactoringFile->apply();
}
Utils::ChangeSet ClangFixItOperation::changeSet() const
{
Utils::ChangeSet changeSet;
for (const auto &fixItContainer : fixItContainers) {
const auto range = fixItContainer.range();
changeSet.replace(range.start().offset(),
range.end().offset(),
fixItContainer.text());
}
return changeSet;
}
} // namespace ClangCodeModel
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef CLANGCODEMODEL_CLANGFIXITOPERATION_H
#define CLANGCODEMODEL_CLANGFIXITOPERATION_H
#include <texteditor/quickfix.h>
#include <clangbackendipc/fixitcontainer.h>
#include <utils/changeset.h>
#include <QVector>
namespace ClangCodeModel {
class ClangFixItOperation : public TextEditor::QuickFixOperation
{
public:
ClangFixItOperation(const Utf8String &filePath,
const Utf8String &fixItText,
const QVector<ClangBackEnd::FixItContainer> &fixItContainers);
int priority() const override;
QString description() const override;
void perform() override;
Utils::ChangeSet changeSet() const;
private:
Utf8String filePath;
Utf8String fixItText;
QVector<ClangBackEnd::FixItContainer> fixItContainers;
};
} // namespace ClangCodeModel
#endif // CLANGCODEMODEL_CLANGFIXITOPERATION_H
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "clangfixitoperationsextractor.h"
#include "clangfixitoperation.h"
#include <clangbackendipc/sourcerangecontainer.h>
#include <utils/qtcassert.h>
#include <QDebug>
using ClangBackEnd::DiagnosticContainer;
namespace {
void capitalizeText(QString &text)
{
text[0] = text[0].toUpper();
}
void removeDiagnosticCategoryPrefix(QString &text)
{
// Prefixes are taken from $LLVM_SOURCE_DIR/tools/clang/lib/Frontend/TextDiagnostic.cpp,
// function TextDiagnostic::printDiagnosticLevel (llvm-3.6.2).
static const QStringList categoryPrefixes = {
QStringLiteral("note"),
QStringLiteral("remark"),
QStringLiteral("warning"),
QStringLiteral("error"),
QStringLiteral("fatal error")
};
foreach (const QString &prefix, categoryPrefixes) {
const QString fullPrefix = prefix + QStringLiteral(": ");
if (text.startsWith(fullPrefix)) {
text.remove(0, fullPrefix.length());
break;
}
}
}
QString tweakedDiagnosticText(const QString &diagnosticText)
{
// Examples:
// "note: use '==' to turn this assignment into an equality comparison"
// "error: expected ';' at end of declaration"
QString tweakedText = diagnosticText;
if (!tweakedText.isEmpty()) {
removeDiagnosticCategoryPrefix(tweakedText);
capitalizeText(tweakedText);
}
return tweakedText;
}
bool isDiagnosticFromFileAtLine(const DiagnosticContainer &diagnosticContainer,
const QString &filePath,
int line)
{
const auto location = diagnosticContainer.location();
return location.filePath().toString() == filePath
&& location.line() == uint(line);
}
} // anonymous namespace
namespace ClangCodeModel {
ClangFixItOperationsExtractor::ClangFixItOperationsExtractor(
const QVector<DiagnosticContainer> &diagnosticContainers)
: diagnosticContainers(diagnosticContainers)
{
}
TextEditor::QuickFixOperations
ClangFixItOperationsExtractor::extract(const QString &filePath, int line)
{
foreach (const DiagnosticContainer &diagnosticContainer, diagnosticContainers)
extractFromDiagnostic(diagnosticContainer, filePath, line);
return operations;
}
void ClangFixItOperationsExtractor::appendFixitOperationsFromDiagnostic(
const QString &filePath,
const DiagnosticContainer &diagnosticContainer)
{
const auto fixIts = diagnosticContainer.fixIts();
if (!fixIts.isEmpty()) {
const QString diagnosticText = tweakedDiagnosticText(diagnosticContainer.text().toString());
TextEditor::QuickFixOperation::Ptr operation(
new ClangFixItOperation(filePath,
diagnosticText,
fixIts));
operations.append(operation);
}
}
void ClangFixItOperationsExtractor::extractFromDiagnostic(
const DiagnosticContainer &diagnosticContainer,
const QString &filePath,
int line)
{
if (isDiagnosticFromFileAtLine(diagnosticContainer, filePath, line)) {
appendFixitOperationsFromDiagnostic(filePath, diagnosticContainer);
foreach (const auto &child, diagnosticContainer.children())
extractFromDiagnostic(child, filePath, line);
}
}
} // namespace ClangCodeModel
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef CLANGFIXITOPERATIONSEXTRACTOR_H
#define CLANGFIXITOPERATIONSEXTRACTOR_H
#include <texteditor/quickfix.h>
#include <clangbackendipc/diagnosticcontainer.h>
namespace ClangCodeModel {
class ClangFixItOperationsExtractor
{
public:
ClangFixItOperationsExtractor(const QVector<ClangBackEnd::DiagnosticContainer> &diagnosticContainers);
TextEditor::QuickFixOperations extract(const QString &filePath, int line);
private:
void extractFromDiagnostic(const ClangBackEnd::DiagnosticContainer &diagnosticContainer,
const QString &filePath,
int line);
void appendFixitOperationsFromDiagnostic(const QString &filePath,
const ClangBackEnd::DiagnosticContainer &diagnosticContainer);
private:
const QVector<ClangBackEnd::DiagnosticContainer> &diagnosticContainers;
TextEditor::QuickFixOperations operations;
};
} // namespace ClangCodeModel
#endif // CLANGFIXITOPERATIONSEXTRACTOR_H
......@@ -45,6 +45,8 @@
#include <utils/qtcassert.h>
#include <QCoreApplication>
#include <QMenu>
#include <QTextBlock>
using namespace ClangCodeModel;
using namespace ClangCodeModel::Internal;
......@@ -137,6 +139,15 @@ void ModelManagerSupportClang::connectTextDocumentToUnsavedFiles(TextEditor::Tex
Qt::UniqueConnection);
}
void ModelManagerSupportClang::connectToWidgetsMarkContextMenuRequested(QWidget *editorWidget)
{
const auto widget = qobject_cast<TextEditor::TextEditorWidget *>(editorWidget);
if (widget) {
connect(widget, &TextEditor::TextEditorWidget::markContextMenuRequested,
this, &ModelManagerSupportClang::onTextMarkContextMenuRequested);
}
}
void ModelManagerSupportClang::onEditorOpened(Core::IEditor *editor)
{
QTC_ASSERT(editor, return);
......@@ -145,10 +156,13 @@ void ModelManagerSupportClang::onEditorOpened(Core::IEditor *editor)
TextEditor::TextDocument *textDocument = qobject_cast<TextEditor::TextDocument *>(document);
if (textDocument && cppModelManager()->isCppEditor(editor)) {
if (cppModelManager()->isManagedByModelManagerSupport(textDocument, QLatin1String(Constants::CLANG_MODELMANAGERSUPPORT_ID)))
const QString clangSupportId = QLatin1String(Constants::CLANG_MODELMANAGERSUPPORT_ID);
if (cppModelManager()->isManagedByModelManagerSupport(textDocument, clangSupportId)) {
connectTextDocumentToTranslationUnit(textDocument);
else
connectToWidgetsMarkContextMenuRequested(editor->widget());
} else {
connectTextDocumentToUnsavedFiles(textDocument);
}
// TODO: Ensure that not fully loaded documents are updated?
}
......@@ -203,6 +217,50 @@ void ModelManagerSupportClang::onAbstractEditorSupportRemoved(const QString &fil
}
}
void addFixItsActionsToMenu(QMenu *menu, const TextEditor::QuickFixOperations &fixItOperations)
{
foreach (const auto &fixItOperation, fixItOperations) {
QAction *action = menu->addAction(fixItOperation->description());
QObject::connect(action, &QAction::triggered, [fixItOperation]() {
fixItOperation->perform();
});
}
}
static int lineToPosition(const QTextDocument *textDocument, int lineNumber)
{
QTC_ASSERT(textDocument, return 0);
const QTextBlock textBlock = textDocument->findBlockByLineNumber(lineNumber);
return textBlock.isValid() ? textBlock.position() - 1 : 0;
}
static TextEditor::AssistInterface createAssistInterface(TextEditor::TextEditorWidget *widget,
int lineNumber)
{
return TextEditor::AssistInterface(widget->document(),
lineToPosition(widget->document(), lineNumber),
widget->textDocument()->filePath().toString(),
TextEditor::IdleEditor);
}
void ModelManagerSupportClang::onTextMarkContextMenuRequested(TextEditor::TextEditorWidget *widget,
int lineNumber,
QMenu *menu)
{
QTC_ASSERT(widget, return);
QTC_ASSERT(lineNumber >= 1, return);
QTC_ASSERT(menu, return);
const auto filePath = widget->textDocument()->filePath().toString();
ClangEditorDocumentProcessor *processor = ClangEditorDocumentProcessor::get(filePath);
if (processor) {
const auto assistInterface = createAssistInterface(widget, lineNumber);
const auto fixItOperations = processor->extraRefactoringOperations(assistInterface);
addFixItsActionsToMenu(menu, fixItOperations);