CMakePM: Integrate RSTParser into hover help / code completion

Change-Id: I1618be1aff83e8164c53040bb2c7230bcc1ec8ee
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Cristian Adam
2023-09-25 23:08:38 +02:00
parent 94d7c76d67
commit 695952f84b
6 changed files with 227 additions and 58 deletions

View File

@@ -46,4 +46,5 @@ add_qtc_plugin(CMakeProjectManager
3rdparty/cmake/cmListFileCache.cxx 3rdparty/cmake/cmListFileCache.cxx
3rdparty/cmake/cmListFileLexer.cxx 3rdparty/cmake/cmListFileLexer.cxx
3rdparty/cmake/cmListFileCache.h 3rdparty/cmake/cmListFileCache.h
3rdparty/rstparser/rstparser.cc 3rdparty/rstparser/rstparser.h
) )

View File

@@ -8,7 +8,6 @@
#include "cmakebuildsystem.h" #include "cmakebuildsystem.h"
#include "cmakefilecompletionassist.h" #include "cmakefilecompletionassist.h"
#include "cmakeindenter.h" #include "cmakeindenter.h"
#include "cmakekitaspect.h"
#include "cmakeprojectconstants.h" #include "cmakeprojectconstants.h"
#include "3rdparty/cmake/cmListFileCache.h" #include "3rdparty/cmake/cmListFileCache.h"
@@ -53,13 +52,7 @@ public:
CMakeEditor::CMakeEditor() CMakeEditor::CMakeEditor()
{ {
CMakeTool *tool = nullptr; if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool())
if (auto bs = ProjectTree::currentBuildSystem())
tool = CMakeKitAspect::cmakeTool(bs->target()->kit());
if (!tool)
tool = CMakeToolManager::defaultCMakeTool();
if (tool)
m_keywords = tool->keywords(); m_keywords = tool->keywords();
} }
@@ -320,6 +313,7 @@ class CMakeHoverHandler : public TextEditor::BaseHoverHandler
{ {
mutable CMakeKeywords m_keywords; mutable CMakeKeywords m_keywords;
QString m_helpToolTip; QString m_helpToolTip;
QString m_contextHelp;
public: public:
const CMakeKeywords &keywords() const; const CMakeKeywords &keywords() const;
@@ -332,16 +326,9 @@ public:
const CMakeKeywords &CMakeHoverHandler::keywords() const const CMakeKeywords &CMakeHoverHandler::keywords() const
{ {
if (m_keywords.functions.isEmpty()) { if (m_keywords.functions.isEmpty())
CMakeTool *tool = nullptr; if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool())
if (auto bs = ProjectTree::currentBuildSystem())
tool = CMakeKitAspect::cmakeTool(bs->target()->kit());
if (!tool)
tool = CMakeToolManager::defaultCMakeTool();
if (tool)
m_keywords = tool->keywords(); m_keywords = tool->keywords();
}
return m_keywords; return m_keywords;
} }
@@ -357,24 +344,39 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget
const QString word = Utils::Text::wordUnderCursor(cursor); const QString word = Utils::Text::wordUnderCursor(cursor);
FilePath helpFile; FilePath helpFile;
for (const auto &map : {keywords().functions, QString helpCategory;
keywords().variables, struct
keywords().directoryProperties, {
keywords().sourceProperties, const QMap<QString, Utils::FilePath> &map;
keywords().targetProperties, QString helpCategory;
keywords().testProperties, } keywordsListMaps[] = {{keywords().functions, "command"},
keywords().properties, {keywords().variables, "variable"},
keywords().includeStandardModules, {keywords().directoryProperties, "prop_dir"},
keywords().findModules, {keywords().sourceProperties, "prop_sf"},
keywords().policies}) { {keywords().targetProperties, "prop_tgt"},
if (map.contains(word)) { {keywords().testProperties, "prop_test"},
helpFile = map.value(word); {keywords().properties, "prop_gbl"},
{keywords().includeStandardModules, "module"},
{keywords().findModules, "module"},
{keywords().policies, "policy"}};
for (const auto &pair : keywordsListMaps) {
if (pair.map.contains(word)) {
helpFile = pair.map.value(word);
helpCategory = pair.helpCategory;
break; break;
} }
} }
m_helpToolTip.clear(); m_helpToolTip.clear();
if (!helpFile.isEmpty()) if (!helpFile.isEmpty())
m_helpToolTip = CMakeToolManager::readFirstParagraphs(helpFile); m_helpToolTip = CMakeToolManager::toolTipForRstHelpFile(helpFile);
if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool())
m_contextHelp = QString("%1/%2/%3")
.arg(tool->documentationUrl(tool->version(),
tool->qchFilePath().isEmpty()),
helpCategory,
word);
setPriority(m_helpToolTip.isEmpty() ? Priority_Tooltip : Priority_None); setPriority(m_helpToolTip.isEmpty() ? Priority_Tooltip : Priority_None);
} }
@@ -382,7 +384,7 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget
void CMakeHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point) void CMakeHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point)
{ {
if (!m_helpToolTip.isEmpty()) if (!m_helpToolTip.isEmpty())
Utils::ToolTip::show(point, m_helpToolTip, Qt::MarkdownText, editorWidget); Utils::ToolTip::show(point, m_helpToolTip, Qt::MarkdownText, editorWidget, m_contextHelp);
else else
Utils::ToolTip::hide(); Utils::ToolTip::hide();
} }

View File

@@ -5,8 +5,6 @@
#include "cmakebuildsystem.h" #include "cmakebuildsystem.h"
#include "cmakebuildtarget.h" #include "cmakebuildtarget.h"
#include "cmakekitaspect.h"
#include "cmakeproject.h"
#include "cmakeprojectconstants.h" #include "cmakeprojectconstants.h"
#include "cmaketool.h" #include "cmaketool.h"
#include "cmaketoolmanager.h" #include "cmaketoolmanager.h"
@@ -166,7 +164,7 @@ static QList<AssistProposalItemInterface *> generateList(const QMap<QString, Fil
MarkDownAssitProposalItem *item = new MarkDownAssitProposalItem(); MarkDownAssitProposalItem *item = new MarkDownAssitProposalItem();
item->setText(it.key()); item->setText(it.key());
if (!it.value().isEmpty()) if (!it.value().isEmpty())
item->setDetail(CMakeToolManager::readFirstParagraphs(it.value())); item->setDetail(CMakeToolManager::toolTipForRstHelpFile(it.value()));
item->setIcon(icon); item->setIcon(icon);
list << item; list << item;
} }
@@ -244,16 +242,8 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync()
Project *project = nullptr; Project *project = nullptr;
const FilePath &filePath = interface()->filePath(); const FilePath &filePath = interface()->filePath();
if (!filePath.isEmpty() && filePath.isFile()) { if (!filePath.isEmpty() && filePath.isFile()) {
CMakeTool *cmake = nullptr; if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool())
project = static_cast<CMakeProject *>(ProjectManager::projectForFile(filePath)); keywords = tool->keywords();
if (project && project->activeTarget())
cmake = CMakeKitAspect::cmakeTool(project->activeTarget()->kit());
if (!cmake)
cmake = CMakeToolManager::defaultCMakeTool();
if (cmake && cmake->isValid())
keywords = cmake->keywords();
} }
QStringList buildTargets; QStringList buildTargets;

View File

@@ -93,13 +93,15 @@ QtcPlugin {
name: "3rdparty" name: "3rdparty"
cpp.includePaths: base.concat("3rdparty/cmake") cpp.includePaths: base.concat("3rdparty/cmake")
prefix: "3rdparty/cmake/" prefix: "3rdparty/"
files: [ files: [
"cmListFileCache.cxx", "cmake/cmListFileCache.cxx",
"cmListFileCache.h", "cmake/cmListFileCache.h",
"cmListFileLexer.cxx", "cmake/cmListFileLexer.cxx",
"cmListFileLexer.h", "cmake/cmListFileLexer.h",
"cmStandardLexer.h", "cmake/cmStandardLexer.h",
"rstparser/rstparser.cc",
"rstparser/rstparser.h"
] ]
} }
} }

View File

@@ -8,11 +8,17 @@
#include "cmakespecificsettings.h" #include "cmakespecificsettings.h"
#include "cmaketoolsettingsaccessor.h" #include "cmaketoolsettingsaccessor.h"
#include "3rdparty/rstparser/rstparser.h"
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
#include <coreplugin/helpmanager.h> #include <coreplugin/helpmanager.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/target.h>
#include <stack>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/pointeralgorithm.h> #include <utils/pointeralgorithm.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -32,6 +38,137 @@ public:
Internal::CMakeToolSettingsAccessor m_accessor; Internal::CMakeToolSettingsAccessor m_accessor;
}; };
class HtmlHandler : public rst::ContentHandler
{
private:
std::stack<QString> m_tags;
QStringList m_p;
QStringList m_h3;
QStringList m_cmake_code;
QString m_last_directive_type;
QString m_last_directive_class;
void StartBlock(rst::BlockType type) final
{
QString tag;
switch (type) {
case rst::REFERENCE_LINK:
// not used, HandleReferenceLink is used instead
break;
case rst::H1:
tag = "h1";
break;
case rst::H2:
tag = "h2";
break;
case rst::H3:
tag = "h3";
break;
case rst::H4:
tag = "h4";
break;
case rst::H5:
tag = "h5";
break;
case rst::CODE:
tag = "code";
break;
case rst::PARAGRAPH:
tag = "p";
break;
case rst::LINE_BLOCK:
tag = "pre";
break;
case rst::BLOCK_QUOTE:
if (m_last_directive_type == "code-block" && m_last_directive_class == "cmake")
tag = "cmake-code";
else
tag = "blockquote";
break;
case rst::BULLET_LIST:
tag = "ul";
break;
case rst::LIST_ITEM:
tag = "li";
break;
case rst::LITERAL_BLOCK:
tag = "pre";
break;
}
if (tag == "p")
m_p.push_back(QString());
if (tag == "h3")
m_h3.push_back(QString());
if (tag == "cmake-code")
m_cmake_code.push_back(QString());
if (tag == "code" && m_tags.top() == "p")
m_p.last().append("`");
m_tags.push(tag);
}
void EndBlock() final
{
// Add a new "p" collector for any `code` markup that comes afterwads
// since we are insterested only in the first paragraph.
if (m_tags.top() == "p")
m_p.push_back(QString());
if (m_tags.top() == "code" && !m_p.isEmpty()) {
m_tags.pop();
if (m_tags.size() > 0 && m_tags.top() == "p")
m_p.last().append("`");
} else {
m_tags.pop();
}
}
void HandleText(const char *text, std::size_t size) final
{
if (m_last_directive_type.endsWith("replace"))
return;
QString str = QString::fromUtf8(text, size);
if (m_tags.top() == "h3")
m_h3.last().append(str);
if (m_tags.top() == "p")
m_p.last().append(str);
if (m_tags.top() == "cmake-code")
m_cmake_code.last().append(str);
if (m_tags.top() == "code" && !m_p.isEmpty())
m_p.last().append(str);
}
void HandleDirective(const std::string &type, const std::string &name) final
{
m_last_directive_type = QString::fromStdString(type);
m_last_directive_class = QString::fromStdString(name);
}
void HandleReferenceLink(const std::string &type, const std::string &text) final
{
Q_UNUSED(type)
if (!m_p.isEmpty())
m_p.last().append(QString::fromStdString(text));
}
public:
QString content() const
{
const QString title = m_h3.isEmpty() ? QString() : m_h3.first();
const QString description = m_p.isEmpty() ? QString() : m_p.first();
const QString cmakeCode = m_cmake_code.isEmpty() ? QString() : m_cmake_code.first();
return QString("### %1\n\n%2\n\n````\n%3\n````").arg(title, description, cmakeCode);
}
};
static CMakeToolManagerPrivate *d = nullptr; static CMakeToolManagerPrivate *d = nullptr;
CMakeToolManager *CMakeToolManager::m_instance = nullptr; CMakeToolManager *CMakeToolManager::m_instance = nullptr;
@@ -108,6 +245,37 @@ void CMakeToolManager::deregisterCMakeTool(const Id &id)
} }
} }
CMakeTool *CMakeToolManager::defaultProjectOrDefaultCMakeTool()
{
static CMakeTool *tool = nullptr;
auto updateTool = [&] {
tool = nullptr;
if (auto bs = ProjectExplorer::ProjectTree::currentBuildSystem())
tool = CMakeKitAspect::cmakeTool(bs->target()->kit());
if (!tool)
tool = CMakeToolManager::defaultCMakeTool();
};
if (!tool)
updateTool();
QObject::connect(CMakeToolManager::instance(),
&CMakeToolManager::cmakeUpdated,
CMakeToolManager::instance(),
[&]() { updateTool(); });
QObject::connect(CMakeToolManager::instance(),
&CMakeToolManager::cmakeRemoved,
CMakeToolManager::instance(),
[&]() { updateTool(); });
QObject::connect(CMakeToolManager::instance(),
&CMakeToolManager::defaultCMakeChanged,
CMakeToolManager::instance(),
[&]() { updateTool(); });
return tool;
}
CMakeTool *CMakeToolManager::defaultCMakeTool() CMakeTool *CMakeToolManager::defaultCMakeTool()
{ {
return findById(d->m_defaultCMake); return findById(d->m_defaultCMake);
@@ -167,21 +335,25 @@ void CMakeToolManager::updateDocumentation()
Core::HelpManager::registerDocumentation(docs); Core::HelpManager::registerDocumentation(docs);
} }
QString CMakeToolManager::readFirstParagraphs(const FilePath &helpFile) QString CMakeToolManager::toolTipForRstHelpFile(const FilePath &helpFile)
{ {
static QMap<FilePath, QString> map; static QHash<FilePath, QString> map;
if (map.contains(helpFile)) if (map.contains(helpFile))
return map.value(helpFile); return map.value(helpFile);
auto content = helpFile.fileContents(1024).value_or(QByteArray()); auto content = helpFile.fileContents(1024).value_or(QByteArray());
const QString firstParagraphs content.replace("\r\n", "\n");
= QString("```\n%1\n```").arg(QString::fromUtf8(content.left(content.lastIndexOf("\n"))));
map[helpFile] = firstParagraphs; HtmlHandler handler;
return firstParagraphs; rst::Parser parser(&handler);
parser.Parse(content.left(content.lastIndexOf('\n')));
const QString tooltip = handler.content();
map[helpFile] = tooltip;
return tooltip;
} }
QList<Id> CMakeToolManager::autoDetectCMakeForDevice(const FilePaths &searchPaths, QList<Id> CMakeToolManager::autoDetectCMakeForDevice(const FilePaths &searchPaths,
const QString &detectionSource, const QString &detectionSource,
QString *logMessage) QString *logMessage)

View File

@@ -30,6 +30,8 @@ public:
static bool registerCMakeTool(std::unique_ptr<CMakeTool> &&tool); static bool registerCMakeTool(std::unique_ptr<CMakeTool> &&tool);
static void deregisterCMakeTool(const Utils::Id &id); static void deregisterCMakeTool(const Utils::Id &id);
static CMakeTool *defaultProjectOrDefaultCMakeTool();
static CMakeTool *defaultCMakeTool(); static CMakeTool *defaultCMakeTool();
static void setDefaultCMakeTool(const Utils::Id &id); static void setDefaultCMakeTool(const Utils::Id &id);
static CMakeTool *findByCommand(const Utils::FilePath &command); static CMakeTool *findByCommand(const Utils::FilePath &command);
@@ -40,7 +42,7 @@ public:
static void updateDocumentation(); static void updateDocumentation();
static QString readFirstParagraphs(const Utils::FilePath &helpFile); static QString toolTipForRstHelpFile(const Utils::FilePath &helpFile);
public slots: public slots:
QList<Utils::Id> autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths, QList<Utils::Id> autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths,