diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index f1989cec6c7..010f948f8a9 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -46,4 +46,5 @@ add_qtc_plugin(CMakeProjectManager 3rdparty/cmake/cmListFileCache.cxx 3rdparty/cmake/cmListFileLexer.cxx 3rdparty/cmake/cmListFileCache.h + 3rdparty/rstparser/rstparser.cc 3rdparty/rstparser/rstparser.h ) diff --git a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp index 9b363faebe3..86f8823fdee 100644 --- a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp @@ -8,7 +8,6 @@ #include "cmakebuildsystem.h" #include "cmakefilecompletionassist.h" #include "cmakeindenter.h" -#include "cmakekitaspect.h" #include "cmakeprojectconstants.h" #include "3rdparty/cmake/cmListFileCache.h" @@ -53,13 +52,7 @@ public: CMakeEditor::CMakeEditor() { - CMakeTool *tool = nullptr; - if (auto bs = ProjectTree::currentBuildSystem()) - tool = CMakeKitAspect::cmakeTool(bs->target()->kit()); - if (!tool) - tool = CMakeToolManager::defaultCMakeTool(); - - if (tool) + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) m_keywords = tool->keywords(); } @@ -320,6 +313,7 @@ class CMakeHoverHandler : public TextEditor::BaseHoverHandler { mutable CMakeKeywords m_keywords; QString m_helpToolTip; + QString m_contextHelp; public: const CMakeKeywords &keywords() const; @@ -332,16 +326,9 @@ public: const CMakeKeywords &CMakeHoverHandler::keywords() const { - if (m_keywords.functions.isEmpty()) { - CMakeTool *tool = nullptr; - if (auto bs = ProjectTree::currentBuildSystem()) - tool = CMakeKitAspect::cmakeTool(bs->target()->kit()); - if (!tool) - tool = CMakeToolManager::defaultCMakeTool(); - - if (tool) + if (m_keywords.functions.isEmpty()) + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) m_keywords = tool->keywords(); - } return m_keywords; } @@ -357,24 +344,39 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget const QString word = Utils::Text::wordUnderCursor(cursor); FilePath helpFile; - for (const auto &map : {keywords().functions, - keywords().variables, - keywords().directoryProperties, - keywords().sourceProperties, - keywords().targetProperties, - keywords().testProperties, - keywords().properties, - keywords().includeStandardModules, - keywords().findModules, - keywords().policies}) { - if (map.contains(word)) { - helpFile = map.value(word); + QString helpCategory; + struct + { + const QMap ↦ + QString helpCategory; + } keywordsListMaps[] = {{keywords().functions, "command"}, + {keywords().variables, "variable"}, + {keywords().directoryProperties, "prop_dir"}, + {keywords().sourceProperties, "prop_sf"}, + {keywords().targetProperties, "prop_tgt"}, + {keywords().testProperties, "prop_test"}, + {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; } } m_helpToolTip.clear(); 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); } @@ -382,7 +384,7 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget void CMakeHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point) { 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 Utils::ToolTip::hide(); } diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp index adf223e9a9b..dbaf380d250 100644 --- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp +++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp @@ -5,8 +5,6 @@ #include "cmakebuildsystem.h" #include "cmakebuildtarget.h" -#include "cmakekitaspect.h" -#include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "cmaketool.h" #include "cmaketoolmanager.h" @@ -166,7 +164,7 @@ static QList generateList(const QMapsetText(it.key()); if (!it.value().isEmpty()) - item->setDetail(CMakeToolManager::readFirstParagraphs(it.value())); + item->setDetail(CMakeToolManager::toolTipForRstHelpFile(it.value())); item->setIcon(icon); list << item; } @@ -244,16 +242,8 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync() Project *project = nullptr; const FilePath &filePath = interface()->filePath(); if (!filePath.isEmpty() && filePath.isFile()) { - CMakeTool *cmake = nullptr; - project = static_cast(ProjectManager::projectForFile(filePath)); - if (project && project->activeTarget()) - cmake = CMakeKitAspect::cmakeTool(project->activeTarget()->kit()); - - if (!cmake) - cmake = CMakeToolManager::defaultCMakeTool(); - - if (cmake && cmake->isValid()) - keywords = cmake->keywords(); + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) + keywords = tool->keywords(); } QStringList buildTargets; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 96520f9127d..94dfa408e15 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -93,13 +93,15 @@ QtcPlugin { name: "3rdparty" cpp.includePaths: base.concat("3rdparty/cmake") - prefix: "3rdparty/cmake/" + prefix: "3rdparty/" files: [ - "cmListFileCache.cxx", - "cmListFileCache.h", - "cmListFileLexer.cxx", - "cmListFileLexer.h", - "cmStandardLexer.h", + "cmake/cmListFileCache.cxx", + "cmake/cmListFileCache.h", + "cmake/cmListFileLexer.cxx", + "cmake/cmListFileLexer.h", + "cmake/cmStandardLexer.h", + "rstparser/rstparser.cc", + "rstparser/rstparser.h" ] } } diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp index da2bcff6e05..65a4f55aa24 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp @@ -8,11 +8,17 @@ #include "cmakespecificsettings.h" #include "cmaketoolsettingsaccessor.h" +#include "3rdparty/rstparser/rstparser.h" + #include #include #include +#include +#include +#include +#include #include #include #include @@ -32,6 +38,137 @@ public: Internal::CMakeToolSettingsAccessor m_accessor; }; +class HtmlHandler : public rst::ContentHandler +{ +private: + std::stack 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; 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() { return findById(d->m_defaultCMake); @@ -167,21 +335,25 @@ void CMakeToolManager::updateDocumentation() Core::HelpManager::registerDocumentation(docs); } -QString CMakeToolManager::readFirstParagraphs(const FilePath &helpFile) +QString CMakeToolManager::toolTipForRstHelpFile(const FilePath &helpFile) { - static QMap map; + static QHash map; if (map.contains(helpFile)) return map.value(helpFile); auto content = helpFile.fileContents(1024).value_or(QByteArray()); - const QString firstParagraphs - = QString("```\n%1\n```").arg(QString::fromUtf8(content.left(content.lastIndexOf("\n")))); + content.replace("\r\n", "\n"); - map[helpFile] = firstParagraphs; - return firstParagraphs; + HtmlHandler handler; + rst::Parser parser(&handler); + parser.Parse(content.left(content.lastIndexOf('\n'))); + + const QString tooltip = handler.content(); + + map[helpFile] = tooltip; + return tooltip; } - QList CMakeToolManager::autoDetectCMakeForDevice(const FilePaths &searchPaths, const QString &detectionSource, QString *logMessage) diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h index 85dbcb53b9e..761ba22ad76 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h @@ -30,6 +30,8 @@ public: static bool registerCMakeTool(std::unique_ptr &&tool); static void deregisterCMakeTool(const Utils::Id &id); + static CMakeTool *defaultProjectOrDefaultCMakeTool(); + static CMakeTool *defaultCMakeTool(); static void setDefaultCMakeTool(const Utils::Id &id); static CMakeTool *findByCommand(const Utils::FilePath &command); @@ -40,7 +42,7 @@ public: static void updateDocumentation(); - static QString readFirstParagraphs(const Utils::FilePath &helpFile); + static QString toolTipForRstHelpFile(const Utils::FilePath &helpFile); public slots: QList autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths,