LanguageClient: generate issue pane entries for diagnostics

The clang code model already generated issue pane entries for received
diagnostics. Move the tracking of these issue pane entries to the
generic language client support and also generate entries for other
language servers.

Fixes: QTCREATORBUG-30549
Change-Id: I29f2ffbf199c3fdc5ca59a670f5033c833b96a49
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
David Schulz
2024-03-22 12:40:25 +01:00
parent b27be7e2bf
commit 6e6d25d117
8 changed files with 126 additions and 84 deletions

View File

@@ -356,7 +356,6 @@ public:
QHash<TextDocument *, HighlightingData> highlightingData; QHash<TextDocument *, HighlightingData> highlightingData;
QHash<Utils::FilePath, CppEditor::BaseEditorDocumentParser::Configuration> parserConfigs; QHash<Utils::FilePath, CppEditor::BaseEditorDocumentParser::Configuration> parserConfigs;
QHash<Utils::FilePath, Tasks> issuePaneEntries;
QHash<Utils::FilePath, int> openedExtraFiles; QHash<Utils::FilePath, int> openedExtraFiles;
VersionedDataCache<const TextDocument *, ClangdAstNode> astCache; VersionedDataCache<const TextDocument *, ClangdAstNode> astCache;
@@ -690,30 +689,14 @@ const LanguageClient::Client::CustomInspectorTabs ClangdClient::createCustomInsp
class ClangdDiagnosticManager : public LanguageClient::DiagnosticManager class ClangdDiagnosticManager : public LanguageClient::DiagnosticManager
{ {
using LanguageClient::DiagnosticManager::DiagnosticManager; public:
ClangdDiagnosticManager(LanguageClient::Client *client)
ClangdClient *getClient() const { return qobject_cast<ClangdClient *>(client()); } : LanguageClient::DiagnosticManager(client)
bool isCurrentDocument(const Utils::FilePath &filePath) const
{ {
const IDocument * const doc = EditorManager::currentDocument(); setTaskCategory(Constants::TASK_CATEGORY_DIAGNOSTICS);
return doc && doc->filePath() == filePath; setForceCreateTasks(false);
}
void showDiagnostics(const Utils::FilePath &filePath, int version) override
{
getClient()->clearTasks(filePath);
DiagnosticManager::showDiagnostics(filePath, version);
if (isCurrentDocument(filePath))
getClient()->switchIssuePaneEntries(filePath);
}
void hideDiagnostics(const Utils::FilePath &filePath) override
{
DiagnosticManager::hideDiagnostics(filePath);
if (isCurrentDocument(filePath))
TaskHub::clearTasks(Constants::TASK_CATEGORY_DIAGNOSTICS);
} }
private:
QList<Diagnostic> filteredDiagnostics(const QList<Diagnostic> &diagnostics) const override QList<Diagnostic> filteredDiagnostics(const QList<Diagnostic> &diagnostics) const override
{ {
@@ -729,7 +712,18 @@ class ClangdDiagnosticManager : public LanguageClient::DiagnosticManager
const Diagnostic &diagnostic, const Diagnostic &diagnostic,
bool isProjectFile) const override bool isProjectFile) const override
{ {
return new ClangdTextMark(doc, diagnostic, isProjectFile, getClient()); return new ClangdTextMark(
doc, diagnostic, isProjectFile, qobject_cast<ClangdClient *>(client()));
}
QString taskText(const Diagnostic &diagnostic) const override
{
QString text = diagnostic.message();
auto splitIndex = text.indexOf("\n\n");
if (splitIndex >= 0)
text.truncate(splitIndex);
return diagnosticCategoryPrefixRemoved(text);
} }
}; };
@@ -938,24 +932,6 @@ void ClangdClient::updateParserConfig(const Utils::FilePath &filePath,
emit configChanged(); emit configChanged();
} }
void ClangdClient::switchIssuePaneEntries(const FilePath &filePath)
{
TaskHub::clearTasks(Constants::TASK_CATEGORY_DIAGNOSTICS);
const Tasks tasks = d->issuePaneEntries.value(filePath);
for (const Task &t : tasks)
TaskHub::addTask(t);
}
void ClangdClient::addTask(const ProjectExplorer::Task &task)
{
d->issuePaneEntries[task.file] << task;
}
void ClangdClient::clearTasks(const Utils::FilePath &filePath)
{
d->issuePaneEntries[filePath].clear();
}
std::optional<bool> ClangdClient::hasVirtualFunctionAt(TextDocument *doc, int revision, std::optional<bool> ClangdClient::hasVirtualFunctionAt(TextDocument *doc, int revision,
const Range &range) const Range &range)
{ {

View File

@@ -97,9 +97,6 @@ public:
void updateParserConfig(const Utils::FilePath &filePath, void updateParserConfig(const Utils::FilePath &filePath,
const CppEditor::BaseEditorDocumentParser::Configuration &config); const CppEditor::BaseEditorDocumentParser::Configuration &config);
void switchIssuePaneEntries(const Utils::FilePath &filePath);
void addTask(const ProjectExplorer::Task &task);
void clearTasks(const Utils::FilePath &filePath);
std::optional<bool> hasVirtualFunctionAt(TextEditor::TextDocument *doc, int revision, std::optional<bool> hasVirtualFunctionAt(TextEditor::TextDocument *doc, int revision,
const LanguageServerProtocol::Range &range); const LanguageServerProtocol::Range &range);

View File

@@ -457,10 +457,8 @@ void ClangModelManagerSupport::onCurrentEditorChanged(IEditor *editor)
const FilePath filePath = editor->document()->filePath(); const FilePath filePath = editor->document()->filePath();
if (auto processor = ClangEditorDocumentProcessor::get(filePath)) { if (auto processor = ClangEditorDocumentProcessor::get(filePath)) {
processor->semanticRehighlight(); processor->semanticRehighlight();
if (const auto client = clientForFile(filePath)) { if (const auto client = clientForFile(filePath))
client->updateParserConfig(filePath, processor->parserConfig()); client->updateParserConfig(filePath, processor->parserConfig());
client->switchIssuePaneEntries(filePath);
}
} }
} }

View File

@@ -16,8 +16,6 @@
#include <cppeditor/cppeditorconstants.h> #include <cppeditor/cppeditorconstants.h>
#include <cppeditor/cpptoolsreuse.h> #include <cppeditor/cpptoolsreuse.h>
#include <projectexplorer/task.h>
#include <utils/fadingindicator.h> #include <utils/fadingindicator.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
@@ -204,34 +202,6 @@ ClangDiagnostic convertDiagnostic(const ClangdDiagnostic &src,
return target; return target;
} }
Task createTask(const ClangDiagnostic &diagnostic)
{
Task::TaskType taskType = Task::TaskType::Unknown;
QIcon icon;
switch (diagnostic.severity) {
case ClangDiagnostic::Severity::Fatal:
case ClangDiagnostic::Severity::Error:
taskType = Task::TaskType::Error;
icon = ::Utils::Icons::CODEMODEL_ERROR.icon();
break;
case ClangDiagnostic::Severity::Warning:
taskType = Task::TaskType::Warning;
icon = ::Utils::Icons::CODEMODEL_WARNING.icon();
break;
default:
break;
}
return Task(taskType,
diagnosticCategoryPrefixRemoved(diagnostic.text),
diagnostic.location.targetFilePath,
diagnostic.location.targetLine,
Constants::TASK_CATEGORY_DIAGNOSTICS,
icon,
Task::NoOptions);
}
} // anonymous namespace } // anonymous namespace
ClangdTextMark::ClangdTextMark(TextEditor::TextDocument *doc, ClangdTextMark::ClangdTextMark(TextEditor::TextDocument *doc,
@@ -258,7 +228,6 @@ ClangdTextMark::ClangdTextMark(TextEditor::TextDocument *doc,
setLineAnnotation(diagnostic.message()); setLineAnnotation(diagnostic.message());
setColor(isError ? Theme::CodeModel_Error_TextMarkColor setColor(isError ? Theme::CodeModel_Error_TextMarkColor
: Theme::CodeModel_Warning_TextMarkColor); : Theme::CodeModel_Warning_TextMarkColor);
client->addTask(createTask(m_diagnostic));
} }
setActionsProvider([diag = m_diagnostic] { setActionsProvider([diag = m_diagnostic] {

View File

@@ -4,11 +4,13 @@
#include "diagnosticmanager.h" #include "diagnosticmanager.h"
#include "client.h" #include "client.h"
#include "languageclientmanager.h"
#include "languageclienttr.h" #include "languageclienttr.h"
#include <coreplugin/editormanager/documentmodel.h> #include <coreplugin/editormanager/documentmodel.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/taskhub.h>
#include <texteditor/fontsettings.h> #include <texteditor/fontsettings.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
@@ -23,8 +25,9 @@
#include <QAction> #include <QAction>
using namespace LanguageServerProtocol; using namespace LanguageServerProtocol;
using namespace Utils; using namespace ProjectExplorer;
using namespace TextEditor; using namespace TextEditor;
using namespace Utils;
namespace LanguageClient { namespace LanguageClient {
@@ -67,15 +70,35 @@ public:
: m_client(client) : m_client(client)
{} {}
QMap<Utils::FilePath, VersionedDiagnostics> m_diagnostics; void showTasks(TextDocument *doc) {
QMap<Utils::FilePath, Marks> m_marks; if (!doc || m_client != LanguageClientManager::clientForDocument(doc))
return;
TaskHub::clearTasks(m_taskCategory);
const Tasks tasks = m_issuePaneEntries.value(doc->filePath());
for (const Task &t : tasks)
TaskHub::addTask(t);
}
QMap<FilePath, VersionedDiagnostics> m_diagnostics;
QMap<FilePath, Marks> m_marks;
Client *m_client; Client *m_client;
Utils::Id m_extraSelectionsId = TextEditorWidget::CodeWarningsSelection; QHash<FilePath, Tasks> m_issuePaneEntries;
Id m_extraSelectionsId = TextEditorWidget::CodeWarningsSelection;
bool m_forceCreateTasks = true;
Id m_taskCategory = Constants::TASK_CATEGORY_DIAGNOSTICS;
}; };
DiagnosticManager::DiagnosticManager(Client *client) DiagnosticManager::DiagnosticManager(Client *client)
: d(std::make_unique<DiagnosticManagerPrivate>(client)) : d(std::make_unique<DiagnosticManagerPrivate>(client))
{ {
auto updateCurrentEditor = [this](Core::IEditor *editor) {
if (editor)
d->showTasks(qobject_cast<TextDocument *>(editor->document()));
};
connect(Core::EditorManager::instance(),
&Core::EditorManager::currentEditorChanged,
this,
updateCurrentEditor);
} }
DiagnosticManager::~DiagnosticManager() DiagnosticManager::~DiagnosticManager()
@@ -94,10 +117,13 @@ void DiagnosticManager::setDiagnostics(const FilePath &filePath,
void DiagnosticManager::hideDiagnostics(const Utils::FilePath &filePath) void DiagnosticManager::hideDiagnostics(const Utils::FilePath &filePath)
{ {
if (auto doc = TextDocument::textDocumentForFilePath(filePath)) { if (auto doc = TextDocument::textDocumentForFilePath(filePath)) {
if (doc == TextDocument::currentTextDocument())
TaskHub::clearTasks(d->m_taskCategory);
for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc)) for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc))
editor->editorWidget()->setExtraSelections(d->m_extraSelectionsId, {}); editor->editorWidget()->setExtraSelections(d->m_extraSelectionsId, {});
} }
d->m_marks.remove(filePath); d->m_marks.remove(filePath);
d->m_issuePaneEntries.remove(filePath);
} }
QList<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic> &diagnostics) const QList<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic> &diagnostics) const
@@ -118,6 +144,7 @@ void DiagnosticManager::disableDiagnostics(TextEditor::TextDocument *document)
void DiagnosticManager::showDiagnostics(const FilePath &filePath, int version) void DiagnosticManager::showDiagnostics(const FilePath &filePath, int version)
{ {
d->m_issuePaneEntries.remove(filePath);
if (TextDocument *doc = TextDocument::textDocumentForFilePath(filePath)) { if (TextDocument *doc = TextDocument::textDocumentForFilePath(filePath)) {
QList<QTextEdit::ExtraSelection> extraSelections; QList<QTextEdit::ExtraSelection> extraSelections;
const VersionedDiagnostics &versionedDiagnostics = d->m_diagnostics.value(filePath); const VersionedDiagnostics &versionedDiagnostics = d->m_diagnostics.value(filePath);
@@ -132,6 +159,8 @@ void DiagnosticManager::showDiagnostics(const FilePath &filePath, int version)
extraSelections << selection; extraSelections << selection;
if (TextEditor::TextMark *mark = createTextMark(doc, diagnostic, isProjectFile)) if (TextEditor::TextMark *mark = createTextMark(doc, diagnostic, isProjectFile))
marks.marks.append(mark); marks.marks.append(mark);
if (std::optional<Task> task = createTask(doc, diagnostic, isProjectFile))
d->m_issuePaneEntries[filePath].append(*task);
} }
if (!marks.marks.isEmpty()) if (!marks.marks.isEmpty())
emit textMarkCreated(filePath); emit textMarkCreated(filePath);
@@ -139,6 +168,9 @@ void DiagnosticManager::showDiagnostics(const FilePath &filePath, int version)
for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc)) for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc))
editor->editorWidget()->setExtraSelections(d->m_extraSelectionsId, extraSelections); editor->editorWidget()->setExtraSelections(d->m_extraSelectionsId, extraSelections);
if (doc == TextDocument::currentTextDocument())
d->showTasks(doc);
} }
} }
@@ -166,6 +198,56 @@ TextEditor::TextMark *DiagnosticManager::createTextMark(TextDocument *doc,
return mark; return mark;
} }
std::optional<Task> DiagnosticManager::createTask(
TextDocument *doc,
const LanguageServerProtocol::Diagnostic &diagnostic,
bool isProjectFile) const
{
if (!isProjectFile && !d->m_forceCreateTasks)
return {};
Task::TaskType taskType = Task::TaskType::Unknown;
QIcon icon;
if (const std::optional<DiagnosticSeverity> severity = diagnostic.severity()) {
switch (*severity) {
case DiagnosticSeverity::Error:
taskType = Task::TaskType::Error;
icon = Icons::CODEMODEL_ERROR.icon();
break;
case DiagnosticSeverity::Warning:
taskType = Task::TaskType::Warning;
icon = Icons::CODEMODEL_WARNING.icon();
break;
default:
break;
}
}
return Task(taskType,
taskText(diagnostic),
doc->filePath(),
diagnostic.range().start().line(),
d->m_taskCategory,
icon,
Task::NoOptions);
}
QString DiagnosticManager::taskText(const LanguageServerProtocol::Diagnostic &diagnostic) const
{
return diagnostic.message();
}
void DiagnosticManager::setTaskCategory(const Utils::Id &taskCategory)
{
d->m_taskCategory = taskCategory;
}
void DiagnosticManager::setForceCreateTasks(bool forceCreateTasks)
{
d->m_forceCreateTasks = forceCreateTasks;
}
QTextEdit::ExtraSelection DiagnosticManager::createDiagnosticSelection( QTextEdit::ExtraSelection DiagnosticManager::createDiagnosticSelection(
const LanguageServerProtocol::Diagnostic &diagnostic, QTextDocument *textDocument) const const LanguageServerProtocol::Diagnostic &diagnostic, QTextDocument *textDocument) const
{ {

View File

@@ -9,7 +9,6 @@
#include <utils/id.h> #include <utils/id.h>
#include <QMap>
#include <QTextEdit> #include <QTextEdit>
#include <functional> #include <functional>
@@ -19,6 +18,8 @@ class TextDocument;
class TextMark; class TextMark;
} }
namespace ProjectExplorer { class Task; }
namespace LanguageClient { namespace LanguageClient {
class Client; class Client;
@@ -58,6 +59,17 @@ protected:
virtual TextEditor::TextMark *createTextMark(TextEditor::TextDocument *doc, virtual TextEditor::TextMark *createTextMark(TextEditor::TextDocument *doc,
const LanguageServerProtocol::Diagnostic &diagnostic, const LanguageServerProtocol::Diagnostic &diagnostic,
bool isProjectFile) const; bool isProjectFile) const;
virtual std::optional<ProjectExplorer::Task> createTask(
TextEditor::TextDocument *doc,
const LanguageServerProtocol::Diagnostic &diagnostic,
bool isProjectFile) const;
virtual QString taskText(const LanguageServerProtocol::Diagnostic &diagnostic) const;
void setTaskCategory(const Utils::Id &taskCategory);
// enables task creations for diagnostics outside of the clients project (default: on)
void setForceCreateTasks(bool forceCreateTasks);
virtual QTextEdit::ExtraSelection createDiagnosticSelection( virtual QTextEdit::ExtraSelection createDiagnosticSelection(
const LanguageServerProtocol::Diagnostic &diagnostic, QTextDocument *textDocument) const; const LanguageServerProtocol::Diagnostic &diagnostic, QTextDocument *textDocument) const;

View File

@@ -41,6 +41,7 @@ const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DESCRIPTION[]
"Locates functions and methods in the language server workspace."); "Locates functions and methods in the language server workspace.");
const char CALL_HIERARCHY_FACTORY_ID[] = "LanguageClient.CallHierarchy"; const char CALL_HIERARCHY_FACTORY_ID[] = "LanguageClient.CallHierarchy";
const char TASK_CATEGORY_DIAGNOSTICS[] = "LanguageClient.DiagnosticTask";
} // namespace Constants } // namespace Constants
} // namespace LanguageClient } // namespace LanguageClient

View File

@@ -14,6 +14,8 @@
#include <extensionsystem/iplugin.h> #include <extensionsystem/iplugin.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
#include <projectexplorer/taskhub.h>
#include <QAction> #include <QAction>
#include <QMenu> #include <QMenu>
@@ -59,6 +61,11 @@ void LanguageClientPlugin::initialize()
inspectAction.setText(Tr::tr("Inspect Language Clients...")); inspectAction.setText(Tr::tr("Inspect Language Clients..."));
inspectAction.addToContainer(Core::Constants::M_TOOLS_DEBUG); inspectAction.addToContainer(Core::Constants::M_TOOLS_DEBUG);
inspectAction.addOnTriggered(this, &LanguageClientManager::showInspector); inspectAction.addOnTriggered(this, &LanguageClientManager::showInspector);
ProjectExplorer::TaskHub::addCategory(
{Constants::TASK_CATEGORY_DIAGNOSTICS,
Tr::tr("Language Server Diagnostics"),
Tr::tr("Issues provided by the Language Server in the current document.")});
} }
void LanguageClientPlugin::extensionsInitialized() void LanguageClientPlugin::extensionsInitialized()