Commit 9a4284d6 authored by Nikolai Kosjar's avatar Nikolai Kosjar Committed by Alessandro Portale

Clang: Use CppHoverHandler for diagnostic tooltips

We used to call QTextCharFormat::setToolTip from the ExtraSelection to
install the diagnostic tooltip. Since this allows to set only text
tooltips and we would like to introduce a custom tooltip widget for
diagnostics, make use of CppHoverHandler, which is more flexible.

Change-Id: Ia1b2c3c50810596ce4a3a025002e6e4efd8789db
Reviewed-by: default avatarAlessandro Portale <alessandro.portale@theqtcompany.com>
parent 593ed52c
......@@ -53,6 +53,7 @@ HEADERS += \
clangfixitoperationsextractor.h \
clangfunctionhintmodel.h \
clanghighlightingmarksreporter.h \
clangisdiagnosticrelatedtolocation.h \
clangmodelmanagersupport.h \
clangpreprocessorassistproposalitem.h \
clangtextmark.h \
......
......@@ -77,6 +77,7 @@ QtcPlugin {
"clangfunctionhintmodel.h",
"clanghighlightingmarksreporter.cpp",
"clanghighlightingmarksreporter.h",
"clangisdiagnosticrelatedtolocation.h",
"clangmodelmanagersupport.cpp",
"clangmodelmanagersupport.h",
"clangpreprocessorassistproposalitem.cpp",
......
......@@ -16,4 +16,5 @@ HEADERS += \
$$PWD/clangcompletioncontextanalyzer.h \
$$PWD/clangdiagnosticfilter.h \
$$PWD/clangfixitoperation.h \
$$PWD/clanghighlightingmarksreporter.h
$$PWD/clanghighlightingmarksreporter.h \
$$PWD/clangisdiagnosticrelatedtolocation.h
......@@ -25,7 +25,9 @@
#include "clangdiagnosticfilter.h"
#include "clangdiagnosticmanager.h"
#include "clangisdiagnosticrelatedtolocation.h"
#include <texteditor/convenience.h>
#include <texteditor/fontsettings.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditorsettings.h>
......@@ -38,14 +40,12 @@
namespace {
QTextEdit::ExtraSelection createExtraSelections(const QTextCharFormat &mainformat,
const QTextCursor &cursor,
const QString &diagnosticText)
const QTextCursor &cursor)
{
QTextEdit::ExtraSelection extraSelection;
extraSelection.format = mainformat;
extraSelection.cursor = cursor;
extraSelection.format.setToolTip(diagnosticText);
return extraSelection;
}
......@@ -61,7 +61,6 @@ int positionInText(QTextDocument *textDocument,
void addRangeSelections(const ClangBackEnd::DiagnosticContainer &diagnostic,
QTextDocument *textDocument,
const QTextCharFormat &contextFormat,
const QString &diagnosticText,
QList<QTextEdit::ExtraSelection> &extraSelections)
{
for (auto &&range : diagnostic.ranges()) {
......@@ -69,7 +68,7 @@ void addRangeSelections(const ClangBackEnd::DiagnosticContainer &diagnostic,
cursor.setPosition(positionInText(textDocument, range.start()));
cursor.setPosition(positionInText(textDocument, range.end()), QTextCursor::KeepAnchor);
auto extraSelection = createExtraSelections(contextFormat, cursor, diagnosticText);
auto extraSelection = createExtraSelections(contextFormat, cursor);
extraSelections.push_back(std::move(extraSelection));
}
......@@ -90,37 +89,6 @@ QTextCursor createSelectionCursor(QTextDocument *textDocument,
return cursor;
}
bool isHelpfulChildDiagnostic(const ClangBackEnd::DiagnosticContainer &parentDiagnostic,
const ClangBackEnd::DiagnosticContainer &childDiagnostic)
{
auto parentLocation = parentDiagnostic.location();
auto childLocation = childDiagnostic.location();
return parentLocation == childLocation;
}
QString diagnosticText(const ClangBackEnd::DiagnosticContainer &diagnostic)
{
QString text = diagnostic.category().toString()
+ QStringLiteral("\n\n")
+ diagnostic.text().toString();
#ifdef QT_DEBUG
if (!diagnostic.disableOption().isEmpty()) {
text += QStringLiteral(" (disable with ")
+ diagnostic.disableOption().toString()
+ QStringLiteral(")");
}
#endif
for (auto &&childDiagnostic : diagnostic.children()) {
if (isHelpfulChildDiagnostic(diagnostic, childDiagnostic))
text += QStringLiteral("\n ") + childDiagnostic.text().toString();
}
return text;
}
void addSelections(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
QTextDocument *textDocument,
const QTextCharFormat &mainFormat,
......@@ -129,11 +97,9 @@ void addSelections(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics
{
for (auto &&diagnostic : diagnostics) {
auto cursor = createSelectionCursor(textDocument, diagnostic.location());
auto extraSelection = createExtraSelections(mainFormat, cursor);
auto text = diagnosticText(diagnostic);
auto extraSelection = createExtraSelections(mainFormat, cursor, text);
addRangeSelections(diagnostic, textDocument, contextFormat, text, extraSelections);
addRangeSelections(diagnostic, textDocument, contextFormat, extraSelections);
extraSelections.push_back(std::move(extraSelection));
}
......@@ -164,6 +130,69 @@ void addErrorSelections(const QVector<ClangBackEnd::DiagnosticContainer> &diagno
addSelections(diagnostics, textDocument, errorFormat, errorContextFormat, extraSelections);
}
ClangBackEnd::SourceLocationContainer toSourceLocation(QTextDocument *textDocument, int position)
{
int line, column;
if (TextEditor::Convenience::convertPosition(textDocument, position, &line, &column))
return ClangBackEnd::SourceLocationContainer(Utf8String(), line, column);
return ClangBackEnd::SourceLocationContainer();
}
ClangBackEnd::SourceRangeContainer toSourceRange(const QTextCursor &cursor)
{
using namespace ClangBackEnd;
QTextDocument *textDocument = cursor.document();
return SourceRangeContainer(toSourceLocation(textDocument, cursor.anchor()),
toSourceLocation(textDocument, cursor.position()));
}
bool isDiagnosticAtLocation(const ClangBackEnd::DiagnosticContainer &diagnostic,
uint line,
uint column,
QTextDocument *textDocument)
{
using namespace ClangCodeModel::Internal;
const ClangBackEnd::SourceLocationContainer &location = diagnostic.location();
const QTextCursor cursor = createSelectionCursor(textDocument, location);
const ClangBackEnd::SourceRangeContainer cursorRange = toSourceRange(cursor);
return isDiagnosticRelatedToLocation(diagnostic, {cursorRange}, line, column);
}
QVector<ClangBackEnd::DiagnosticContainer>
filteredDiagnosticsAtLocation(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
uint line,
uint column,
QTextDocument *textDocument)
{
QVector<ClangBackEnd::DiagnosticContainer> filteredDiagnostics;
foreach (const auto &diagnostic, diagnostics) {
if (isDiagnosticAtLocation(diagnostic, line, column, textDocument))
filteredDiagnostics.append(diagnostic);
}
return filteredDiagnostics;
}
bool editorDocumentProcessorHasDiagnosticAt(
const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
uint line,
uint column,
QTextDocument *textDocument)
{
foreach (const auto &diagnostic, diagnostics) {
if (isDiagnosticAtLocation(diagnostic, line, column, textDocument))
return true;
}
return false;
}
} // anonymous
namespace ClangCodeModel {
......@@ -192,6 +221,26 @@ QList<QTextEdit::ExtraSelection> ClangDiagnosticManager::takeExtraSelections()
return extraSelections;
}
bool ClangDiagnosticManager::hasDiagnosticsAt(uint line, uint column) const
{
QTextDocument *textDocument = m_textDocument->document();
return editorDocumentProcessorHasDiagnosticAt(m_errorDiagnostics, line, column, textDocument)
|| editorDocumentProcessorHasDiagnosticAt(m_warningDiagnostics, line, column, textDocument);
}
QVector<ClangBackEnd::DiagnosticContainer>
ClangDiagnosticManager::diagnosticsAt(uint line, uint column) const
{
QTextDocument *textDocument = m_textDocument->document();
QVector<ClangBackEnd::DiagnosticContainer> diagnostics;
diagnostics += filteredDiagnosticsAtLocation(m_errorDiagnostics, line, column, textDocument);
diagnostics += filteredDiagnosticsAtLocation(m_warningDiagnostics, line, column, textDocument);
return diagnostics;
}
void ClangDiagnosticManager::clearDiagnosticsWithFixIts()
{
m_fixItdiagnostics.clear();
......@@ -213,8 +262,6 @@ void ClangDiagnosticManager::processNewDiagnostics(
generateTextMarks();
generateEditorSelections();
clearWarningsAndErrors();
}
const QVector<ClangBackEnd::DiagnosticContainer> &
......@@ -241,12 +288,6 @@ void ClangDiagnosticManager::addClangTextMarks(
}
}
void ClangDiagnosticManager::clearWarningsAndErrors()
{
m_warningDiagnostics.clear();
m_errorDiagnostics.clear();
}
QString ClangDiagnosticManager::filePath() const
{
return m_textDocument->filePath().toString();
......
......@@ -51,6 +51,9 @@ public:
const QVector<ClangBackEnd::DiagnosticContainer> &diagnosticsWithFixIts() const;
QList<QTextEdit::ExtraSelection> takeExtraSelections();
bool hasDiagnosticsAt(uint line, uint column) const;
QVector<ClangBackEnd::DiagnosticContainer> diagnosticsAt(uint line, uint column) const;
void clearDiagnosticsWithFixIts();
private:
......@@ -59,7 +62,6 @@ private:
void generateEditorSelections();
void generateTextMarks();
void addClangTextMarks(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics);
void clearWarningsAndErrors();
private:
TextEditor::TextDocument *m_textDocument;
......
......@@ -50,6 +50,7 @@
#include <cplusplus/CppDocument.h>
#include <utils/qtcassert.h>
#include <utils/tooltip/tooltip.h>
#include <utils/QtConcurrentTools>
#include <QTextBlock>
......@@ -233,6 +234,73 @@ TextEditor::QuickFixOperations ClangEditorDocumentProcessor::extraRefactoringOpe
return extractor.extract(assistInterface.fileName(), currentLine(assistInterface));
}
bool ClangEditorDocumentProcessor::hasDiagnosticsAt(uint line, uint column) const
{
return m_diagnosticManager.hasDiagnosticsAt(line, column);
}
namespace {
bool isHelpfulChildDiagnostic(const ClangBackEnd::DiagnosticContainer &parentDiagnostic,
const ClangBackEnd::DiagnosticContainer &childDiagnostic)
{
auto parentLocation = parentDiagnostic.location();
auto childLocation = childDiagnostic.location();
return parentLocation == childLocation;
}
QString diagnosticText(const ClangBackEnd::DiagnosticContainer &diagnostic)
{
QString text = diagnostic.category().toString()
+ QStringLiteral("\n\n")
+ diagnostic.text().toString();
#ifdef QT_DEBUG
if (!diagnostic.disableOption().isEmpty()) {
text += QStringLiteral(" (disable with ")
+ diagnostic.disableOption().toString()
+ QStringLiteral(")");
}
#endif
for (auto &&childDiagnostic : diagnostic.children()) {
if (isHelpfulChildDiagnostic(diagnostic, childDiagnostic))
text += QStringLiteral("\n ") + childDiagnostic.text().toString();
}
return text;
}
QString generateTooltipText(const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics)
{
QString text;
foreach (const ClangBackEnd::DiagnosticContainer &diagnostic, diagnostics) {
if (text.isEmpty())
text += diagnosticText(diagnostic);
else
text += QStringLiteral("\n\n\n") + diagnosticText(diagnostic);
}
return text;
}
} // anonymous namespace
void ClangEditorDocumentProcessor::showDiagnosticTooltip(const QPoint &point,
QWidget *parent,
uint line,
uint column) const
{
const QVector<ClangBackEnd::DiagnosticContainer> diagnostics
= m_diagnosticManager.diagnosticsAt(line, column);
const QString tooltipText = generateTooltipText(diagnostics);
::Utils::ToolTip::show(point, tooltipText, parent);
}
ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const
{
return fileContainerWithArguments(m_projectPart.data());
......
......@@ -76,6 +76,12 @@ public:
TextEditor::QuickFixOperations
extraRefactoringOperations(const TextEditor::AssistInterface &assistInterface) override;
bool hasDiagnosticsAt(uint line, uint column) const override;
void showDiagnosticTooltip(const QPoint &point,
QWidget *parent,
uint line,
uint column) const override;
ClangBackEnd::FileContainer fileContainerWithArguments() const;
void clearDiagnosticsWithFixIts();
......
/****************************************************************************
**
** Copyright (C) 2016 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 <clangbackendipc/diagnosticcontainer.h>
namespace ClangCodeModel {
namespace Internal {
bool isWithinRange(const ClangBackEnd::SourceRangeContainer &range,
uint line,
uint column)
{
const ClangBackEnd::SourceLocationContainer startLocation = range.start();
const ClangBackEnd::SourceLocationContainer endLocation = range.end();
return startLocation.line() <= line
&& startLocation.column() <= column
&& line <= endLocation.line()
&& column <= endLocation.column();
}
bool isWithinOneRange(const QVector<ClangBackEnd::SourceRangeContainer> &ranges,
uint line,
uint column)
{
foreach (const ClangBackEnd::SourceRangeContainer &range, ranges) {
if (isWithinRange(range, line, column))
return true;
}
return false;
}
bool isDiagnosticRelatedToLocation(const ClangBackEnd::DiagnosticContainer &diagnostic,
const QVector<ClangBackEnd::SourceRangeContainer> &additionalRanges,
uint line,
uint column)
{
const ClangBackEnd::SourceLocationContainer location = diagnostic.location();
if (location.line() == line && location.column() == column)
return true;
if (isWithinOneRange(additionalRanges, line, column))
return true;
if (isWithinOneRange(diagnostic.ranges(), line, column))
return true;
return false;
}
} // namespace Internal
} // namespace ClangCodeModel
......@@ -29,6 +29,10 @@
#include "cppelementevaluator.h"
#include <coreplugin/helpmanager.h>
#include <cpptools/baseeditordocumentprocessor.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/editordocumenthandle.h>
#include <texteditor/convenience.h>
#include <texteditor/texteditor.h>
#include <utils/qtcassert.h>
......@@ -39,12 +43,55 @@
using namespace Core;
using namespace TextEditor;
namespace {
CppTools::BaseEditorDocumentProcessor *editorDocumentProcessor(TextEditorWidget *editorWidget)
{
const QString filePath = editorWidget->textDocument()->filePath().toString();
auto cppModelManager = CppTools::CppModelManager::instance();
CppTools::CppEditorDocumentHandle *editorHandle = cppModelManager->cppEditorDocument(filePath);
if (editorHandle)
return editorHandle->processor();
return 0;
}
bool editorDocumentProcessorHasDiagnosticAt(TextEditorWidget *editorWidget, int pos)
{
if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) {
int line, column;
if (Convenience::convertPosition(editorWidget->document(), pos, &line, &column))
return processor->hasDiagnosticsAt(line, column);
}
return false;
}
void processWithEditorDocumentProcessor(TextEditorWidget *editorWidget,
const QPoint &point,
int position)
{
if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) {
int line, column;
if (Convenience::convertPosition(editorWidget->document(), position, &line, &column))
processor->showDiagnosticTooltip(point, editorWidget, line, column);
}
}
} // anonymous namespace
namespace CppEditor {
namespace Internal {
void CppHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos)
{
if (!editorWidget->extraSelectionTooltip(pos).isEmpty()) {
m_positionForEditorDocumentProcessor = -1;
if (editorDocumentProcessorHasDiagnosticAt(editorWidget, pos)) {
setIsDiagnosticTooltip(true);
m_positionForEditorDocumentProcessor = pos;
} else if (!editorWidget->extraSelectionTooltip(pos).isEmpty()) {
setToolTip(editorWidget->extraSelectionTooltip(pos));
} else {
QTextCursor tc(editorWidget->document());
......@@ -82,6 +129,9 @@ void CppHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos)
void CppHoverHandler::decorateToolTip()
{
if (m_positionForEditorDocumentProcessor != -1)
return;
if (Qt::mightBeRichText(toolTip()))
setToolTip(toolTip().toHtmlEscaped());
......@@ -114,5 +164,14 @@ void CppHoverHandler::decorateToolTip()
}
}
void CppHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget,
const QPoint &point)
{
if (m_positionForEditorDocumentProcessor != -1)
processWithEditorDocumentProcessor(editorWidget, point, m_positionForEditorDocumentProcessor);
else
BaseHoverHandler::operateTooltip(editorWidget, point);
}
} // namespace Internal
} // namespace CppEditor
......@@ -36,6 +36,10 @@ class CppHoverHandler : public TextEditor::BaseHoverHandler
private:
void identifyMatch(TextEditor::TextEditorWidget *editorWidget, int pos) override;
void decorateToolTip() override;
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) override;
private:
int m_positionForEditorDocumentProcessor = -1;
};
} // namespace Internal
......
......@@ -58,6 +58,15 @@ BaseEditorDocumentProcessor::extraRefactoringOperations(const TextEditor::Assist
return TextEditor::QuickFixOperations();
}
bool BaseEditorDocumentProcessor::hasDiagnosticsAt(uint, uint) const
{
return false;
}
void BaseEditorDocumentProcessor::showDiagnosticTooltip(const QPoint &, QWidget *, uint, uint) const
{
}
void BaseEditorDocumentProcessor::runParser(QFutureInterface<void> &future,
BaseEditorDocumentParser::Ptr parser,
const WorkingCopy workingCopy)
......
......@@ -65,6 +65,12 @@ public:
virtual TextEditor::QuickFixOperations
extraRefactoringOperations(const TextEditor::AssistInterface &assistInterface);
virtual bool hasDiagnosticsAt(uint line, uint column) const;
virtual void showDiagnosticTooltip(const QPoint &point,
QWidget *parent,
uint line,
uint column) const;
signals:
// Signal interface to implement
void codeWarningsUpdated(unsigned revision,
......
/****************************************************************************
**
** Copyright (C) 2016 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 <clangisdiagnosticrelatedtolocation.h>
#include <diagnosticcontainer.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include "gtest-qt-printing.h"
using ClangBackEnd::DiagnosticContainer;
using ClangBackEnd::SourceLocationContainer;
using ClangBackEnd::SourceRangeContainer;
using ClangCodeModel::Internal::isDiagnosticRelatedToLocation;
namespace {
SourceRangeContainer createRange(uint startLine,
uint startColumn,
uint endLine,
uint endColumn)
{
const SourceLocationContainer startLocation(Utf8String(), startLine, startColumn);
const SourceLocationContainer endLocation(Utf8String(), endLine, endColumn);
return SourceRangeContainer(startLocation, endLocation);
}
DiagnosticContainer createDiagnosticWithLocation(uint line, uint column)
{
const SourceLocationContainer location(Utf8String(), line, column);
return DiagnosticContainer(Utf8String(),
Utf8String(),
std::pair<Utf8String, Utf8String>(),
ClangBackEnd::DiagnosticSeverity::Error,
location,
QVector<SourceRangeContainer>(),
QVector<ClangBackEnd::FixItContainer>(),
QVector<DiagnosticContainer>());
}
DiagnosticContainer createDiagnosticWithRange(uint startLine,
uint startColumn,
uint endLine,
uint endColumn)
{
const SourceRangeContainer range = createRange(startLine, startColumn, endLine, endColumn);
return DiagnosticContainer(Utf8String(),
Utf8String(),
std::pair<Utf8String, Utf8String>(),
ClangBackEnd::DiagnosticSeverity::Error,
SourceLocationContainer(),
{range},
QVector<ClangBackEnd::FixItContainer>(),
QVector<DiagnosticContainer>());
}
class ClangIsDiagnosticRelatedToLocation : public ::testing::Test
{
protected:
const QVector<SourceRangeContainer> emptyRanges;
};
TEST_F(ClangIsDiagnosticRelatedToLocation, MatchLocation)
{
const DiagnosticContainer diagnostic = createDiagnosticWithLocation(5, 5);
ASSERT_TRUE(isDiagnosticRelatedToLocation(diagnostic, emptyRanges, 5, 5));
}
TEST_F(ClangIsDiagnosticRelatedToLocation, DoNotMatchLocation)
{
const DiagnosticContainer diagnostic = createDiagnosticWithLocation(5, 5);
ASSERT_FALSE(isDiagnosticRelatedToLocation(diagnostic, emptyRanges, 5, 4));
}
TEST_F(ClangIsDiagnosticRelatedToLocation, MatchStartRange)
{
const DiagnosticContainer diagnostic = createDiagnosticWithRange(5, 5, 5, 10);
ASSERT_TRUE(isDiagnosticRelatedToLocation(diagnostic, emptyRanges, 5, 5));
}
TEST_F(ClangIsDiagnosticRelatedToLocation, MatchWithinRange)
{
const DiagnosticContainer diagnostic = createDiagnosticWithRange(5, 5, 5, 10);
ASSERT_TRUE(isDiagnosticRelatedToLocation(diagnostic, emptyRanges, 5, 7));
}
TEST_F(ClangIsDiagnosticRelatedToLocation, MatchEndRange)
{
const DiagnosticContainer diagnostic = createDiagnosticWithRange(5, 5, 5, 10);
ASSERT_TRUE(isDiagnosticRelatedToLocation(diagnostic, emptyRanges, 5, 10));
}
TEST_F(ClangIsDiagnosticRelatedToLocation, DoNoMatchBeforeRangeStart)
{
const DiagnosticContainer diagnostic = createDiagnosticWithRange(5, 5, 5, 10);