diff --git a/src/plugins/nim/editor/nimcompletionassistprovider.cpp b/src/plugins/nim/editor/nimcompletionassistprovider.cpp new file mode 100644 index 00000000000..96080fba7d9 --- /dev/null +++ b/src/plugins/nim/editor/nimcompletionassistprovider.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 "nimcompletionassistprovider.h" +#include "suggest/nimsuggestcache.h" +#include "suggest/nimsuggest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace ProjectExplorer; +using namespace TextEditor; + +namespace Nim { + +bool isIdentifierChar(QChar c) +{ + return c.isLetterOrNumber() || c == QLatin1Char('_'); +} + +bool isActivationChar(QChar c) +{ + static const QSet chars {QLatin1Char('.'), QLatin1Char('(')}; + return chars.contains(c); +} + +class NimCompletionAssistProcessor : public QObject, public TextEditor::IAssistProcessor +{ + Q_OBJECT + +public: + TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) final + { + QTC_ASSERT(this->thread() == qApp->thread(), return nullptr); + + if (interface->reason() == IdleEditor && !acceptsIdleEditor(interface)) + return nullptr; + + Suggest::NimSuggest *suggest = nimSuggestInstance(interface); + QTC_ASSERT(suggest, return nullptr); + + if (suggest->executablePath().isEmpty() || suggest->projectFile().isEmpty()) + return nullptr; + + if (!suggest->isReady()) { + m_interface = interface; + QObject::connect(suggest, &Suggest::NimSuggest::readyChanged, + this, &NimCompletionAssistProcessor::onNimSuggestReady); + } else { + doPerform(interface, suggest); + } + + m_running = true; + return nullptr; + } + + bool running() + { + return m_running; + } + +private: + void onNimSuggestReady(bool ready) + { + auto suggest = dynamic_cast(sender()); + QTC_ASSERT(suggest, return); + QTC_ASSERT(m_interface, return); + + if (!ready || !suggest) { + m_running = false; + setAsyncProposalAvailable(nullptr); + } else { + doPerform(m_interface, suggest); + } + } + + void doPerform(const TextEditor::AssistInterface *interface, + Suggest::NimSuggest *suggest) + { + int pos = findCompletionPos(interface); + + std::unique_ptr dirtyFile = writeDirtyFile(interface); + QTC_ASSERT(dirtyFile, return); + + std::shared_ptr request = sendRequest(interface, + suggest, + dirtyFile->fileName(), + pos); + QTC_ASSERT(request, return); + connect(request.get(), &Suggest::SugRequest::finished, this, + &NimCompletionAssistProcessor::onRequestFinished); + + m_pos = pos; + m_dirtyFile = std::move(dirtyFile); + m_request = std::move(request); + } + + void onRequestFinished() + { + auto items = Utils::transform(m_request->lines(), &createProposal); + setAsyncProposalAvailable(new GenericProposal(m_pos, items)); + m_running = false; + m_dirtyFile.reset(); + m_request.reset(); + } + + static bool acceptsIdleEditor(const AssistInterface *interface) + { + int pos = interface->position(); + QChar c = interface->textDocument()->characterAt(pos - 1); + return isIdentifierChar(c) || isActivationChar(c); + } + + static int findCompletionPos(const AssistInterface *interface) + { + int pos = interface->position(); + while (isIdentifierChar(interface->textDocument()->characterAt(pos - 1))) + pos--; + return pos; + } + + static Suggest::NimSuggest *nimSuggestInstance(const AssistInterface *interface) + { + auto filename = Utils::FileName::fromString(interface->fileName()); + return Nim::Suggest::NimSuggestCache::instance().get(filename); + } + + static std::shared_ptr sendRequest(const AssistInterface *interface, + Suggest::NimSuggest *suggest, + QString dirtyFile, + int pos) + { + int line = 0, column = 0; + Utils::Text::convertPosition(interface->textDocument(), pos, &line, &column); + QTC_ASSERT(column >= 1, return nullptr); + auto filename = Utils::FileName::fromString(interface->fileName()); + return suggest->sug(filename.toString(), line, column - 1, dirtyFile); + } + + static std::unique_ptr writeDirtyFile(const TextEditor::AssistInterface *interface) + { + auto result = std::make_unique("qtcnim.XXXXXX.nim"); + QTC_ASSERT(result->open(), return nullptr); + QTextStream stream(result.get()); + stream << interface->textDocument()->toPlainText(); + result->close(); + return result; + } + + static AssistProposalItemInterface *createProposal(const Nim::Suggest::Line &line) + { + auto item = new AssistProposalItem(); + item->setIcon(Utils::CodeModelIcon::iconForType(symbolIcon(line.symbol_kind))); + item->setText(line.data.back()); + item->setDetail(line.symbol_type); + item->setOrder(symbolOrder(line.symbol_kind)); + return item; + } + + static int symbolOrder(Suggest::Line::SymbolKind kind) + { + switch (kind) { + case Suggest::Line::SymbolKind::skField: + return 2; + case Suggest::Line::SymbolKind::skVar: + case Suggest::Line::SymbolKind::skLet: + case Suggest::Line::SymbolKind::skEnumField: + case Suggest::Line::SymbolKind::skResult: + case Suggest::Line::SymbolKind::skForVar: + case Suggest::Line::SymbolKind::skParam: + case Suggest::Line::SymbolKind::skLabel: + case Suggest::Line::SymbolKind::skGenericParam: + return 1; + default: + return 0; + } + } + + static Utils::CodeModelIcon::Type symbolIcon(Suggest::Line::SymbolKind kind) + { + switch (kind) { + case Suggest::Line::SymbolKind::skField: + return Utils::CodeModelIcon::Property; + case Suggest::Line::SymbolKind::skVar: + case Suggest::Line::SymbolKind::skLet: + case Suggest::Line::SymbolKind::skEnumField: + case Suggest::Line::SymbolKind::skResult: + case Suggest::Line::SymbolKind::skForVar: + case Suggest::Line::SymbolKind::skParam: + case Suggest::Line::SymbolKind::skLabel: + case Suggest::Line::SymbolKind::skGenericParam: + return Utils::CodeModelIcon::VarPublic; + case Suggest::Line::SymbolKind::skProc: + case Suggest::Line::SymbolKind::skFunc: + case Suggest::Line::SymbolKind::skMethod: + case Suggest::Line::SymbolKind::skIterator: + case Suggest::Line::SymbolKind::skConverter: + return Utils::CodeModelIcon::FuncPublic; + case Suggest::Line::SymbolKind::skType: + return Utils::CodeModelIcon::Struct; + case Suggest::Line::SymbolKind::skMacro: + case Suggest::Line::SymbolKind::skTemplate: + return Utils::CodeModelIcon::Macro; + case Suggest::Line::SymbolKind::skModule: + case Suggest::Line::SymbolKind::skPackage: + return Utils::CodeModelIcon::Namespace; + case Suggest::Line::SymbolKind::skUnknown: + case Suggest::Line::SymbolKind::skConditional: + case Suggest::Line::SymbolKind::skDynLib: + case Suggest::Line::SymbolKind::skTemp: + case Suggest::Line::SymbolKind::skConst: + case Suggest::Line::SymbolKind::skStub: + case Suggest::Line::SymbolKind::skAlias: + default: + return Utils::CodeModelIcon::Type::Unknown; + } + } + + bool m_running = false; + int m_pos = -1; + std::weak_ptr m_suggest; + std::shared_ptr m_request; + std::unique_ptr m_dirtyFile; + const TextEditor::AssistInterface *m_interface = nullptr; +}; + + +TextEditor::IAssistProcessor *NimCompletionAssistProvider::createProcessor() const +{ + return new NimCompletionAssistProcessor(); +} + +int NimCompletionAssistProvider::activationCharSequenceLength() const +{ + return 1; +} + +bool NimCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const +{ + return sequence.size() && isActivationChar(sequence.front()); +} + +IAssistProvider::RunType NimCompletionAssistProvider::runType() const +{ + return RunType::Asynchronous; +} + +} + +#include "nimcompletionassistprovider.moc" diff --git a/src/plugins/nim/editor/nimcompletionassistprovider.h b/src/plugins/nim/editor/nimcompletionassistprovider.h new file mode 100644 index 00000000000..77c5077b789 --- /dev/null +++ b/src/plugins/nim/editor/nimcompletionassistprovider.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 + +namespace Nim { + +class NimCompletionAssistProvider : public TextEditor::CompletionAssistProvider +{ + Q_OBJECT + +public: + TextEditor::IAssistProcessor *createProcessor() const final; + int activationCharSequenceLength() const final; + bool isActivationCharSequence(const QString &sequence) const final; + RunType runType() const final; +}; + +} diff --git a/src/plugins/nim/editor/nimeditorfactory.cpp b/src/plugins/nim/editor/nimeditorfactory.cpp index 4a2cb4d59b7..91a57adcb2d 100644 --- a/src/plugins/nim/editor/nimeditorfactory.cpp +++ b/src/plugins/nim/editor/nimeditorfactory.cpp @@ -26,6 +26,7 @@ #include "nimeditorfactory.h" #include "nimindenter.h" #include "nimhighlighter.h" +#include "nimcompletionassistprovider.h" #include "../nimconstants.h" #include "../nimplugin.h" @@ -65,6 +66,7 @@ NimEditorFactory::NimEditorFactory() setSyntaxHighlighterCreator([]() { return new NimHighlighter; }); + setCompletionAssistProvider(new NimCompletionAssistProvider()); setCommentDefinition(CommentDefinition::HashStyle); setParenthesesMatchingEnabled(true); setCodeFoldingSupported(true); diff --git a/src/plugins/nim/nim.pro b/src/plugins/nim/nim.pro index 1247d4b5586..e2e4fb18c1b 100644 --- a/src/plugins/nim/nim.pro +++ b/src/plugins/nim/nim.pro @@ -11,6 +11,7 @@ INCLUDEPATH += $$PWD HEADERS += \ nimplugin.h \ nimconstants.h \ + editor/nimcompletionassistprovider.h \ editor/nimhighlighter.h \ editor/nimindenter.h \ tools/nimlexer.h \ @@ -29,11 +30,20 @@ HEADERS += \ settings/nimcodestylepreferencesfactory.h \ settings/nimsettings.h \ settings/nimcodestylepreferenceswidget.h \ + settings/nimtoolssettingspage.h \ project/nimtoolchain.h \ - project/nimtoolchainfactory.h + project/nimtoolchainfactory.h \ + suggest/client.h \ + suggest/clientrequests.h \ + suggest/nimsuggest.h \ + suggest/nimsuggestcache.h \ + suggest/server.h \ + suggest/sexprlexer.h \ + suggest/sexprparser.h SOURCES += \ nimplugin.cpp \ + editor/nimcompletionassistprovider.cpp \ editor/nimhighlighter.cpp \ editor/nimindenter.cpp \ tools/nimlexer.cpp \ @@ -51,10 +61,17 @@ SOURCES += \ settings/nimcodestylepreferencesfactory.cpp \ settings/nimsettings.cpp \ settings/nimcodestylepreferenceswidget.cpp \ + settings/nimtoolssettingspage.cpp \ project/nimtoolchain.cpp \ - project/nimtoolchainfactory.cpp + project/nimtoolchainfactory.cpp \ + suggest/client.cpp \ + suggest/clientrequests.cpp \ + suggest/nimsuggest.cpp \ + suggest/nimsuggestcache.cpp \ + suggest/server.cpp FORMS += \ project/nimcompilerbuildstepconfigwidget.ui \ project/nimcompilercleanstepconfigwidget.ui \ - settings/nimcodestylepreferenceswidget.ui + settings/nimcodestylepreferenceswidget.ui \ + settings/nimtoolssettingswidget.ui diff --git a/src/plugins/nim/nim.qbs b/src/plugins/nim/nim.qbs index b3f5a62fae9..b009e4d88ff 100644 --- a/src/plugins/nim/nim.qbs +++ b/src/plugins/nim/nim.qbs @@ -27,7 +27,8 @@ QtcPlugin { files: [ "nimeditorfactory.h", "nimeditorfactory.cpp", "nimhighlighter.h", "nimhighlighter.cpp", - "nimindenter.h", "nimindenter.cpp" + "nimindenter.h", "nimindenter.cpp", + "nimcompletionassistprovider.h", "nimcompletionassistprovider.cpp" ] } @@ -57,6 +58,7 @@ QtcPlugin { "nimcodestylepreferenceswidget.h", "nimcodestylepreferenceswidget.cpp", "nimcodestylepreferenceswidget.ui", "nimcodestylesettingspage.h", "nimcodestylesettingspage.cpp", "nimsettings.h", "nimsettings.cpp", + "nimtoolssettingspage.h", "nimtoolssettingspage.cpp", "nimtoolssettingswidget.ui" ] } @@ -68,4 +70,18 @@ QtcPlugin { "sourcecodestream.h" ] } + + Group { + name: "Suggest" + prefix: "suggest/" + files: [ + "client.h", "client.cpp", + "clientrequests.h", "clientrequests.cpp", + "nimsuggest.h", "nimsuggest.cpp", + "nimsuggestcache.h", "nimsuggestcache.cpp", + "server.h", "server.cpp", + "sexprlexer.h", + "sexprparser.h", + ] + } } diff --git a/src/plugins/nim/nimconstants.h b/src/plugins/nim/nimconstants.h index a4ac8527abf..d37cdcedb73 100644 --- a/src/plugins/nim/nimconstants.h +++ b/src/plugins/nim/nimconstants.h @@ -77,6 +77,12 @@ const char C_NIMCODESTYLESETTINGSPAGE_ID[] = "Nim.NimCodeStyleSettings"; const char C_NIMCODESTYLESETTINGSPAGE_DISPLAY[] = QT_TRANSLATE_NOOP("NimCodeStyleSettingsPage", "Code Style"); const char C_NIMCODESTYLESETTINGSPAGE_CATEGORY[] = "Z.Nim"; const char C_NIMCODESTYLESETTINGSPAGE_CATEGORY_DISPLAY[] = QT_TRANSLATE_NOOP("NimCodeStyleSettingsPage", "Nim"); + +const char C_NIMTOOLSSETTINGSPAGE_ID[] = "Nim.NimToolsSettings"; +const char C_NIMTOOLSSETTINGSPAGE_DISPLAY[] = QT_TRANSLATE_NOOP("NimToolsSettingsPage", "Tools"); +const char C_NIMTOOLSSETTINGSPAGE_CATEGORY[] = "Z.Nim"; +const char C_NIMTOOLSSETTINGSPAGE_CATEGORY_DISPLAY[] = QT_TRANSLATE_NOOP("NimToolsSettingsPage", "Nim"); + const char C_NIMLANGUAGE_NAME[] = QT_TRANSLATE_NOOP("NimCodeStylePreferencesFactory", "Nim"); const char C_NIMGLOBALCODESTYLE_ID[] = "NimGlobal"; const QString C_NIMSNIPPETSGROUP_ID = QStringLiteral("Nim.NimSnippetsGroup"); @@ -103,5 +109,9 @@ const char C_NIM_SCRIPT_MIMETYPE[] = "text/x-nim-script"; const char C_NIM_MIME_ICON[] = "text-x-nim"; const char C_NIM_PROJECT_MIMETYPE[] = "text/x-nim-project"; +const char C_NIM_SETTINGS_GROUP[] = "Nim"; +const char C_NIM_SETTINGS_NIMSUGGEST_GROUP[] = "NimSuggest"; +const char C_NIM_SETTINGS_COMMAND[] = "Command"; + } } diff --git a/src/plugins/nim/nimplugin.cpp b/src/plugins/nim/nimplugin.cpp index 248d5e99e01..3afcb19b5fa 100644 --- a/src/plugins/nim/nimplugin.cpp +++ b/src/plugins/nim/nimplugin.cpp @@ -36,7 +36,9 @@ #include "project/nimtoolchainfactory.h" #include "settings/nimcodestylepreferencesfactory.h" #include "settings/nimcodestylesettingspage.h" +#include "settings/nimtoolssettingspage.h" #include "settings/nimsettings.h" +#include "suggest/nimsuggestcache.h" #include #include @@ -51,6 +53,15 @@ namespace Nim { class NimPluginPrivate { public: + NimPluginPrivate() + : toolsSettingsPage(&settings) + { + Suggest::NimSuggestCache::instance().setExecutablePath(settings.nimSuggestPath()); + QObject::connect(&settings, &NimSettings::nimSuggestPathChanged, + &Suggest::NimSuggestCache::instance(), + &Suggest::NimSuggestCache::setExecutablePath); + } + NimSettings settings; NimEditorFactory editorFactory; NimBuildConfigurationFactory buildConfigFactory; @@ -58,6 +69,7 @@ public: NimCompilerBuildStepFactory buildStepFactory; NimCompilerCleanStepFactory cleanStepFactory; NimCodeStyleSettingsPage codeStyleSettingsPage; + NimToolsSettingsPage toolsSettingsPage; NimCodeStylePreferencesFactory codeStylePreferencesPage; NimToolChainFactory toolChainFactory; }; @@ -89,7 +101,8 @@ void NimPlugin::extensionsInitialized() { // Add MIME overlay icons (these icons displayed at Project dock panel) const QIcon icon = Utils::Icon({{":/nim/images/settingscategory_nim.png", - Utils::Theme::PanelTextColorDark}}, Utils::Icon::Tint).icon(); + Utils::Theme::PanelTextColorDark + }}, Utils::Icon::Tint).icon(); if (!icon.isNull()) { Core::FileIconProvider::registerIconOverlayForMimeType(icon, Constants::C_NIM_MIMETYPE); Core::FileIconProvider::registerIconOverlayForMimeType(icon, Constants::C_NIM_SCRIPT_MIMETYPE); diff --git a/src/plugins/nim/project/nimproject.cpp b/src/plugins/nim/project/nimproject.cpp index da0c98e81bc..2af19e4d492 100644 --- a/src/plugins/nim/project/nimproject.cpp +++ b/src/plugins/nim/project/nimproject.cpp @@ -47,6 +47,9 @@ #include #include +#include +#include + #include #include @@ -68,7 +71,8 @@ NimProject::NimProject(const FileName &fileName) : Project(Constants::C_NIM_MIME connect(&m_projectScanTimer, &QTimer::timeout, this, &NimProject::collectProjectFiles); connect(this, &Project::settingsLoaded, this, &NimProject::collectProjectFiles); - connect(&m_futureWatcher, &QFutureWatcher>::finished, this, &NimProject::updateProject); + connect(&m_futureWatcher, &QFutureWatcher>::finished, this, + &NimProject::updateProject); } void NimProject::scheduleProjectScan() @@ -86,7 +90,9 @@ void NimProject::scheduleProjectScan() bool NimProject::addFiles(const QStringList &filePaths) { - m_excludedFiles = Utils::filtered(m_excludedFiles, [&](const QString &f) { return !filePaths.contains(f); }); + m_excludedFiles = Utils::filtered(m_excludedFiles, [&](const QString & f) { + return !filePaths.contains(f); + }); scheduleProjectScan(); return true; } @@ -112,8 +118,9 @@ void NimProject::collectProjectFiles() m_lastProjectScan.start(); QTC_ASSERT(!m_futureWatcher.future().isRunning(), return); FileName prjDir = projectDirectory(); - QFuture> future = Utils::runAsync([prjDir, excluded = m_excludedFiles] { - return FileNode::scanForFiles(prjDir, [excluded](const FileName &fn) -> FileNode * { + QFuture> future = Utils::runAsync([prjDir, + excluded = m_excludedFiles] { + return FileNode::scanForFiles(prjDir, [excluded](const FileName & fn) -> FileNode * { const QString fileName = fn.fileName(); if (excluded.contains(fn.toString()) || fileName.endsWith(".nimproject", HostOsInfo::fileNameCaseSensitivity()) @@ -132,7 +139,9 @@ void NimProject::updateProject() auto newRoot = std::make_unique(*this, projectDirectory()); - for (FileNode *node : m_futureWatcher.future().result()) + QList files = m_futureWatcher.future().result(); + + for (FileNode *node : files) newRoot->addNestedNode(std::unique_ptr(node)); newRoot->setDisplayName(displayName()); @@ -144,7 +153,8 @@ void NimProject::updateProject() QList NimProject::projectIssues(const Kit *k) const { QList result = Project::projectIssues(k); - auto tc = dynamic_cast(ToolChainKitInformation::toolChain(k, Constants::C_NIMLANGUAGE_ID)); + auto tc = dynamic_cast(ToolChainKitInformation::toolChain(k, + Constants::C_NIMLANGUAGE_ID)); if (!tc) { result.append(createProjectTask(Task::TaskType::Error, tr("No Nim compiler set."))); return result; @@ -157,7 +167,9 @@ QList NimProject::projectIssues(const Kit *k) const FileNameList NimProject::nimFiles() const { - return files([](const ProjectExplorer::Node *n) { return AllFiles(n) && n->filePath().endsWith(".nim"); }); + return files([](const ProjectExplorer::Node *n) { + return AllFiles(n) && n->filePath().endsWith(".nim"); + }); } QVariantMap NimProject::toMap() const diff --git a/src/plugins/nim/project/nimproject.h b/src/plugins/nim/project/nimproject.h index 49190059213..5670fbe533c 100644 --- a/src/plugins/nim/project/nimproject.h +++ b/src/plugins/nim/project/nimproject.h @@ -57,7 +57,6 @@ private: void collectProjectFiles(); void updateProject(); - QStringList m_files; QStringList m_excludedFiles; QFutureWatcher> m_futureWatcher; QElapsedTimer m_lastProjectScan; diff --git a/src/plugins/nim/settings/nimsettings.cpp b/src/plugins/nim/settings/nimsettings.cpp index cd43df48490..57622225627 100644 --- a/src/plugins/nim/settings/nimsettings.cpp +++ b/src/plugins/nim/settings/nimsettings.cpp @@ -39,10 +39,50 @@ using namespace TextEditor; namespace Nim { -SimpleCodeStylePreferences *m_globalCodeStyle = nullptr; +static SimpleCodeStylePreferences *m_globalCodeStyle = nullptr; NimSettings::NimSettings(QObject *parent) : QObject(parent) +{ + InitializeCodeStyleSettings(); + InitializeNimSuggestSettings(); +} + +NimSettings::~NimSettings() +{ + TerminateCodeStyleSettings(); +} + +QString NimSettings::nimSuggestPath() const +{ + return m_nimSuggestPath; +} + +void NimSettings::setNimSuggestPath(const QString &path) +{ + if (m_nimSuggestPath == path) + return; + m_nimSuggestPath = path; + emit nimSuggestPathChanged(path); +} + +void NimSettings::save() +{ + QSettings *s = Core::ICore::settings(); + s->beginGroup(Constants::C_NIM_SETTINGS_GROUP); + s->beginGroup(Constants::C_NIM_SETTINGS_NIMSUGGEST_GROUP); + s->setValue(QString::fromStdString(Constants::C_NIM_SETTINGS_COMMAND), nimSuggestPath()); + s->endGroup(); + s->endGroup(); + s->sync(); +} + +SimpleCodeStylePreferences *NimSettings::globalCodeStyle() +{ + return m_globalCodeStyle; +} + +void NimSettings::InitializeCodeStyleSettings() { // code style factory auto factory = new NimCodeStylePreferencesFactory(); @@ -63,12 +103,14 @@ NimSettings::NimSettings(QObject *parent) nimCodeStyle->setId("nim"); nimCodeStyle->setDisplayName(tr("Nim")); nimCodeStyle->setReadOnly(true); + TabSettings nimTabSettings; nimTabSettings.m_tabPolicy = TabSettings::SpacesOnlyTabPolicy; nimTabSettings.m_tabSize = 2; nimTabSettings.m_indentSize = 2; nimTabSettings.m_continuationAlignBehavior = TabSettings::ContinuationAlignWithIndent; nimCodeStyle->setTabSettings(nimTabSettings); + pool->addCodeStyle(nimCodeStyle); m_globalCodeStyle->setCurrentDelegate(nimCodeStyle); @@ -79,11 +121,24 @@ NimSettings::NimSettings(QObject *parent) QSettings *s = Core::ICore::settings(); m_globalCodeStyle->fromSettings(QLatin1String(Nim::Constants::C_NIMLANGUAGE_ID), s); - TextEditorSettings::registerMimeTypeForLanguageId(Nim::Constants::C_NIM_MIMETYPE, Nim::Constants::C_NIMLANGUAGE_ID); - TextEditorSettings::registerMimeTypeForLanguageId(Nim::Constants::C_NIM_SCRIPT_MIMETYPE, Nim::Constants::C_NIMLANGUAGE_ID); + TextEditorSettings::registerMimeTypeForLanguageId(Nim::Constants::C_NIM_MIMETYPE, + Nim::Constants::C_NIMLANGUAGE_ID); + TextEditorSettings::registerMimeTypeForLanguageId(Nim::Constants::C_NIM_SCRIPT_MIMETYPE, + Nim::Constants::C_NIMLANGUAGE_ID); } -NimSettings::~NimSettings() +void NimSettings::InitializeNimSuggestSettings() +{ + QSettings *s = Core::ICore::settings(); + s->beginGroup(Constants::C_NIM_SETTINGS_GROUP); + s->beginGroup(Constants::C_NIM_SETTINGS_NIMSUGGEST_GROUP); + setNimSuggestPath(s->value(QString::fromStdString(Constants::C_NIM_SETTINGS_COMMAND), + QString()).toString()); + s->endGroup(); + s->endGroup(); +} + +void NimSettings::TerminateCodeStyleSettings() { TextEditorSettings::unregisterCodeStyle(Nim::Constants::C_NIMLANGUAGE_ID); TextEditorSettings::unregisterCodeStylePool(Nim::Constants::C_NIMLANGUAGE_ID); @@ -93,9 +148,4 @@ NimSettings::~NimSettings() m_globalCodeStyle = nullptr; } -SimpleCodeStylePreferences *NimSettings::globalCodeStyle() -{ - return m_globalCodeStyle; -} - } // namespace Nim diff --git a/src/plugins/nim/settings/nimsettings.h b/src/plugins/nim/settings/nimsettings.h index cbc809d9191..3e648d97c9a 100644 --- a/src/plugins/nim/settings/nimsettings.h +++ b/src/plugins/nim/settings/nimsettings.h @@ -39,7 +39,24 @@ public: NimSettings(QObject *parent = nullptr); ~NimSettings() override; + QString nimSuggestPath() const; + void setNimSuggestPath(const QString &path); + + void save(); + static TextEditor::SimpleCodeStylePreferences *globalCodeStyle(); + +signals: + void nimSuggestPathChanged(QString path); + +private: + void InitializeCodeStyleSettings(); + void TerminateCodeStyleSettings(); + + void InitializeNimSuggestSettings(); + void TerminateNimSuggestSettings(); + + QString m_nimSuggestPath; }; } // namespace Nim diff --git a/src/plugins/nim/settings/nimtoolssettingspage.cpp b/src/plugins/nim/settings/nimtoolssettingspage.cpp new file mode 100644 index 00000000000..b658917395f --- /dev/null +++ b/src/plugins/nim/settings/nimtoolssettingspage.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 "nimtoolssettingspage.h" +#include "nimconstants.h" +#include "nimsettings.h" +#include "ui_nimtoolssettingswidget.h" + +#include + +namespace Nim { + +NimToolsSettingsWidget::NimToolsSettingsWidget(NimSettings *settings, QWidget *parent) + : QWidget(parent) + , ui(new Ui::NimToolsSettingsWidget) + , m_settings(settings) +{ + ui->setupUi(this); + ui->pathWidget->setExpectedKind(Utils::PathChooser::ExistingCommand); +} + +NimToolsSettingsWidget::~NimToolsSettingsWidget() +{ + delete ui; +} + +QString NimToolsSettingsWidget::command() const +{ + return ui->pathWidget->path(); +} + +void NimToolsSettingsWidget::setCommand(const QString &filename) +{ + ui->pathWidget->setPath(filename); +} + +NimToolsSettingsPage::NimToolsSettingsPage(NimSettings *settings, QWidget *parent) + : Core::IOptionsPage(parent) + , m_settings(settings) +{ + setId(Nim::Constants::C_NIMTOOLSSETTINGSPAGE_ID); + setDisplayName(tr(Nim::Constants::C_NIMTOOLSSETTINGSPAGE_DISPLAY)); + setCategory(Nim::Constants::C_NIMTOOLSSETTINGSPAGE_CATEGORY); + setDisplayCategory(tr("Nim")); + setCategoryIcon(Utils::Icon({{":/nim/images/settingscategory_nim.png", + Utils::Theme::PanelTextColorDark + }}, Utils::Icon::Tint)); +} + +NimToolsSettingsPage::~NimToolsSettingsPage() = default; + +QWidget *NimToolsSettingsPage::widget() +{ + if (!m_widget) + m_widget.reset(new NimToolsSettingsWidget(m_settings)); + m_widget->setCommand(m_settings->nimSuggestPath()); + return m_widget.get(); +} + +void NimToolsSettingsPage::apply() +{ + m_settings->setNimSuggestPath(m_widget->command()); + m_settings->save(); +} + +void NimToolsSettingsPage::finish() +{ + m_widget.reset(); +} + +} diff --git a/src/plugins/nim/settings/nimtoolssettingspage.h b/src/plugins/nim/settings/nimtoolssettingspage.h new file mode 100644 index 00000000000..4a19ed00aa3 --- /dev/null +++ b/src/plugins/nim/settings/nimtoolssettingspage.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 +#include + +#include + +#include + +namespace Nim { + +class NimSettings; + +namespace Ui { class NimToolsSettingsWidget; } + +class NimToolsSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit NimToolsSettingsWidget(NimSettings *settings, QWidget *parent = nullptr); + + ~NimToolsSettingsWidget(); + + QString command() const; + void setCommand(const QString &filename); + +private: + Ui::NimToolsSettingsWidget *ui; + NimSettings *m_settings = nullptr; +}; + +class NimToolsSettingsPage : public Core::IOptionsPage +{ + Q_OBJECT + +public: + NimToolsSettingsPage(NimSettings *settings, QWidget *parent = nullptr); + + ~NimToolsSettingsPage(); + + QWidget *widget() final; + void apply() final; + void finish() final; + +private: + std::unique_ptr m_widget; + NimSettings *m_settings = nullptr; +}; + +} diff --git a/src/plugins/nim/settings/nimtoolssettingswidget.ui b/src/plugins/nim/settings/nimtoolssettingswidget.ui new file mode 100644 index 00000000000..7322c6a8eca --- /dev/null +++ b/src/plugins/nim/settings/nimtoolssettingswidget.ui @@ -0,0 +1,69 @@ + + + Nim::NimToolsSettingsWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Nimsuggest + + + + + + + + Path + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Utils::PathChooser + QWidget +
utils/pathchooser.h
+ 1 + + editingFinished() + browsingFinished() + +
+
+ + +
diff --git a/src/plugins/nim/suggest/client.cpp b/src/plugins/nim/suggest/client.cpp new file mode 100644 index 00000000000..f4312ea597d --- /dev/null +++ b/src/plugins/nim/suggest/client.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 "client.h" + +#include "sexprparser.h" + +namespace Nim { +namespace Suggest { + +NimSuggestClient::NimSuggestClient(QObject *parent) : QObject(parent) +{ + connect(&m_socket, &QTcpSocket::readyRead, this, &NimSuggestClient::onReadyRead); + connect(&m_socket, &QTcpSocket::connected, this, &NimSuggestClient::connected); + connect(&m_socket, &QTcpSocket::disconnected, this, &NimSuggestClient::disconnected); +} + +bool NimSuggestClient::connectToServer(quint16 port) +{ + m_port = port; + m_socket.connectToHost("localhost", m_port); + return true; +} + +bool NimSuggestClient::disconnectFromServer() +{ + m_socket.disconnectFromHost(); + clear(); + return true; +} + +std::shared_ptr NimSuggestClient::sug(const QString &nimFile, + int line, int column, + const QString &dirtyFile) +{ + if (!m_socket.isOpen()) + return nullptr; + + auto result = std::make_shared(m_lastMessageId++); + m_requests.emplace(result->id(), result); + + QByteArray body = QString(R"((call %1 sug ("%2" %3 %4 "%5"))\n)") + .arg(result->id()) + .arg(nimFile) + .arg(line).arg(column) + .arg(dirtyFile) + .toUtf8(); + + QByteArray length = QString::number(body.size(), 16).rightJustified(6, '0').toUtf8(); + QByteArray message = length + body; + + m_socket.write(message); + m_socket.waitForBytesWritten(); + return result; +} + +void NimSuggestClient::clear() +{ + for (const auto &pair : m_requests) { + if (auto req = pair.second.lock()) { + emit req->finished(); + } + } + m_lines.clear(); + m_readBuffer.clear(); + m_requests.clear(); + m_lastMessageId = 0; +} + +void NimSuggestClient::onDisconnectedFromServer() +{ + clear(); +} + +void NimSuggestClient::onReadyRead() +{ + std::array buffer; + + for (;;) { + qint64 num_read = m_socket.read(buffer.data(), buffer.size()); + m_readBuffer.insert(m_readBuffer.end(), buffer.begin(), buffer.begin() + num_read); + if (num_read <= 0) { + break; + } + } + + while (m_readBuffer.size() >= 6) { + const size_t payload_size = QByteArray::fromRawData(m_readBuffer.data(), 6).toUInt(nullptr, 16); + if (payload_size <= m_readBuffer.size() - 6) { + parsePayload(m_readBuffer.data() + 6, payload_size); + m_readBuffer.erase(m_readBuffer.begin(), m_readBuffer.begin() + 6u + payload_size); + } else { + break; + } + } +} + +void NimSuggestClient::parsePayload(const char *payload, std::size_t size) +{ + SExprParser::Node root_ast; + SExprParser parser(payload, size); + if (!parser.parse(root_ast)) + return; + + if (root_ast.nodes.at(0).value != "return") + return; + + const quint64 uid = std::stoull(root_ast.nodes.at(1).value, nullptr, 0); + + auto it = m_requests.find(uid); + if (it == m_requests.end()) + return; + + auto req = std::dynamic_pointer_cast((*it).second.lock()); + if (!req) + return; + + std::vector suggestions; + suggestions.reserve(root_ast.nodes.at(2).nodes.size()); + for (const SExprParser::Node &row : root_ast.nodes.at(2).nodes) { + Line line; + if (!Line::fromString(line.line_type, row.nodes.at(0).value)) + continue; + if (!Line::fromString(line.symbol_kind, row.nodes.at(1).value)) + continue; + line.data.reserve(row.nodes.at(2).nodes.size()); + for (const SExprParser::Node &field : row.nodes.at(2).nodes) + line.data.push_back(QString::fromStdString(field.value)); + line.abs_path = QString::fromStdString(row.nodes.at(3).value); + line.symbol_type = QString::fromStdString(row.nodes.at(4).value); + line.row = std::stoi(row.nodes.at(5).value); + line.column = std::stoi(row.nodes.at(6).value); + line.doc = QString::fromStdString(row.nodes.at(7).value); + suggestions.push_back(std::move(line)); + } + req->setFinished(std::move(suggestions)); + + m_requests.erase(it); +} + +} // namespace Suggest +} // namespace Nim diff --git a/src/plugins/nim/suggest/client.h b/src/plugins/nim/suggest/client.h new file mode 100644 index 00000000000..7aeaa826d93 --- /dev/null +++ b/src/plugins/nim/suggest/client.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 +#include +#include + +#include +#include +#include + +#include "clientrequests.h" + +namespace Nim { +namespace Suggest { + +class NimSuggestClient : public QObject +{ + Q_OBJECT + +public: + NimSuggestClient(QObject *parent = nullptr); + + bool connectToServer(quint16 port); + + bool disconnectFromServer(); + + std::shared_ptr sug(const QString &nimFile, + int line, int column, + const QString &dirtyFile); + +signals: + void connected(); + void disconnected(); + +private: + void clear(); + void onDisconnectedFromServer(); + void onReadyRead(); + void parsePayload(const char *payload, std::size_t size); + + QTcpSocket m_socket; + quint16 m_port; + std::unordered_map> m_requests; + std::vector m_lines; + std::vector m_readBuffer; + quint64 m_lastMessageId = 0; +}; + +} // namespace Suggest +} // namespace Nim diff --git a/src/plugins/nim/suggest/clientrequests.cpp b/src/plugins/nim/suggest/clientrequests.cpp new file mode 100644 index 00000000000..c14d5f2bd22 --- /dev/null +++ b/src/plugins/nim/suggest/clientrequests.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 "clientrequests.h" + +#include +#include + +namespace Nim { +namespace Suggest { + +bool Line::fromString(Line::LineType &type, const std::string &str) +{ + static const auto metaobject = QMetaEnum::fromType(); + bool result = false; + type = static_cast(metaobject.keyToValue(str.c_str(), &result)); + return result; +} + +bool Line::fromString(Line::SymbolKind &type, const std::string &str) +{ + static const auto metaobject = QMetaEnum::fromType(); + bool result = false; + type = static_cast(metaobject.keyToValue(str.c_str(), &result)); + return result; +} + +BaseNimSuggestClientRequest::BaseNimSuggestClientRequest(quint64 id) + : m_id(id) +{} + +quint64 BaseNimSuggestClientRequest::id() const +{ + return m_id; +} + +} // namespace Suggest +} // namespace Nim + +QDebug operator<<(QDebug debug, const Nim::Suggest::Line &c) +{ + QDebugStateSaver saver(debug); + debug.space() << c.line_type << c.symbol_kind << c.symbol_type << c.data << c.row << c.column << + c.abs_path; + return debug; +} diff --git a/src/plugins/nim/suggest/clientrequests.h b/src/plugins/nim/suggest/clientrequests.h new file mode 100644 index 00000000000..fccddb6b3d5 --- /dev/null +++ b/src/plugins/nim/suggest/clientrequests.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 + +namespace Nim { +namespace Suggest { + +class Line +{ + Q_GADGET +public: + enum class LineType : int { + sug, + con, + def, + use, + dus, + chk, + mod, + highlight, + outline, + known + }; + + Q_ENUM(LineType) + + enum class SymbolKind : int { + skUnknown, + skConditional, + skDynLib, + skParam, + skGenericParam, + skTemp, + skModule, + skType, + skVar, + skLet, + skConst, + skResult, + skProc, + skFunc, + skMethod, + skIterator, + skConverter, + skMacro, + skTemplate, + skField, + skEnumField, + skForVar, + skLabel, + skStub, + skPackage, + skAlias + }; + + Q_ENUM(SymbolKind) + + static bool fromString(LineType &type, const std::string &str); + static bool fromString(SymbolKind &type, const std::string &str); + + LineType line_type; + SymbolKind symbol_kind; + QString abs_path; + QString symbol_type; + std::vector data; + int row; + int column; + QString doc; +}; + +class BaseNimSuggestClientRequest : public QObject +{ + Q_OBJECT + +public: + BaseNimSuggestClientRequest(quint64 id); + + quint64 id() const; + +signals: + void finished(); + +private: + const quint64 m_id; +}; + +class SugRequest : public BaseNimSuggestClientRequest +{ +public: + using BaseNimSuggestClientRequest::BaseNimSuggestClientRequest; + + std::vector &lines() + { + return m_lines; + } + + const std::vector &lines() const + { + return m_lines; + } + +private: + friend class NimSuggestClient; + void setFinished(std::vector lines) + { + m_lines = std::move(lines); + emit finished(); + } + + std::vector m_lines; +}; + +} // namespace Suggest +} // namespace Nim + +QDebug operator<<(QDebug debug, const Nim::Suggest::Line &c); diff --git a/src/plugins/nim/suggest/nimsuggest.cpp b/src/plugins/nim/suggest/nimsuggest.cpp new file mode 100644 index 00000000000..8e59e37c90d --- /dev/null +++ b/src/plugins/nim/suggest/nimsuggest.cpp @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 "nimsuggest.h" + +namespace Nim { +namespace Suggest { + +NimSuggest::NimSuggest(QObject *parent) + : QObject(parent) +{ + connect(&m_server, &NimSuggestServer::started, this, &NimSuggest::onServerStarted); + connect(&m_server, &NimSuggestServer::crashed, this, &NimSuggest::onServerCrashed); + connect(&m_server, &NimSuggestServer::finished, this, &NimSuggest::onServerFinished); + + connect(&m_client, &NimSuggestClient::disconnected, this, &NimSuggest::onClientDisconnected); + connect(&m_client, &NimSuggestClient::connected, this, &NimSuggest::onClientConnected); +} + +QString NimSuggest::projectFile() const +{ + return m_projectFile; +} + +void NimSuggest::setProjectFile(const QString &file) +{ + if (m_projectFile == file) + return; + + auto old = m_projectFile; + m_projectFile = file; + emit projectFileChanged(file); + + restart(); +} + +QString NimSuggest::executablePath() const +{ + return m_executablePath; +} + +void NimSuggest::setExecutablePath(const QString &path) +{ + if (m_executablePath == path) + return; + + auto old = m_executablePath; + m_executablePath = path; + emit executablePathChanged(path); + + restart(); +} + +bool NimSuggest::isReady() const +{ + return m_ready; +} + +std::shared_ptr NimSuggest::sug(const QString &filename, int line, int column, + const QString &dirtyFilename) +{ + return m_ready ? m_client.sug(filename, line, column, dirtyFilename) : nullptr; +} + +void NimSuggest::restart() +{ + disconnectClient(); + setClientReady(false); + + stopServer(); + setServerReady(false); + + startServer(); +} + +void NimSuggest::setReady(bool ready) +{ + if (m_ready == ready) + return; + m_ready = ready; + emit readyChanged(ready); +} + +void NimSuggest::setServerReady(bool ready) +{ + if (m_serverReady == ready) + return; + m_serverReady = ready; + setReady(m_clientReady && m_serverReady); +} + +void NimSuggest::setClientReady(bool ready) +{ + if (m_clientReady == ready) + return; + m_clientReady = ready; + setReady(m_clientReady && m_serverReady); +} + +void NimSuggest::connectClient() +{ + m_client.connectToServer(m_server.port()); +} + +void NimSuggest::disconnectClient() +{ + m_client.disconnectFromServer(); +} + +void NimSuggest::stopServer() +{ + m_server.kill(); +} + +void NimSuggest::startServer() +{ + if (!m_projectFile.isEmpty() && !m_executablePath.isEmpty()) { + m_server.start(m_executablePath, m_projectFile); + } +} + +void NimSuggest::onServerStarted() +{ + setServerReady(true); + connectClient(); +} + +void NimSuggest::onServerCrashed() +{ + setServerReady(false); + disconnectClient(); + restart(); +} + +void NimSuggest::onServerFinished() +{ + onServerCrashed(); +} + +void NimSuggest::onClientConnected() +{ + setClientReady(true); +} + +void NimSuggest::onClientDisconnected() +{ + setClientReady(false); + if (isServerReady()) + connectClient(); +} + +} // namespace Suggest +} // namespace Nim diff --git a/src/plugins/nim/suggest/nimsuggest.h b/src/plugins/nim/suggest/nimsuggest.h new file mode 100644 index 00000000000..981c63b6282 --- /dev/null +++ b/src/plugins/nim/suggest/nimsuggest.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 "client.h" +#include "server.h" + +namespace Nim { +namespace Suggest { + +class NimSuggest : public QObject +{ + Q_OBJECT + +public: + NimSuggest(QObject *parent = nullptr); + + QString projectFile() const; + void setProjectFile(const QString &file); + + QString executablePath() const; + void setExecutablePath(const QString &path); + + bool isReady() const; + + std::shared_ptr sug(const QString &filename, int line, int column, + const QString &dirtyFilename); + +signals: + void readyChanged(bool ready); + void projectFileChanged(const QString &projectFile); + void executablePathChanged(const QString &executablePath); + +private: + void restart(); + + void setReady(bool ready); + + bool isServerReady() const { return m_serverReady; } + void setServerReady(bool ready); + + bool isClientReady() const { return m_clientReady; } + void setClientReady(bool ready); + + void connectClient(); + void disconnectClient(); + + void stopServer(); + void startServer(); + + void onServerStarted(); + void onServerCrashed(); + void onServerFinished(); + + void onClientConnected(); + void onClientDisconnected(); + + bool m_ready = false; + bool m_clientReady = false; + bool m_serverReady = false; + QString m_projectFile; + QString m_executablePath; + NimSuggestServer m_server; + NimSuggestClient m_client; +}; + +} // namespace Suggest +} // namespace Nim diff --git a/src/plugins/nim/suggest/nimsuggestcache.cpp b/src/plugins/nim/suggest/nimsuggestcache.cpp new file mode 100644 index 00000000000..69538836088 --- /dev/null +++ b/src/plugins/nim/suggest/nimsuggestcache.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 "nimsuggestcache.h" + +#include "nimconstants.h" +#include "nimsuggest.h" + +#include +#include + +namespace Nim { +namespace Suggest { + +NimSuggestCache &NimSuggestCache::instance() +{ + static NimSuggestCache instance; + return instance; +} + +NimSuggestCache::~NimSuggestCache() = default; + +NimSuggest *NimSuggestCache::get(const Utils::FileName &filename) +{ + auto it = m_nimSuggestInstances.find(filename); + if (it == m_nimSuggestInstances.end()) { + auto instance = std::make_unique(this); + instance->setProjectFile(filename.toString()); + instance->setExecutablePath(m_executablePath); + it = m_nimSuggestInstances.emplace(filename, std::move(instance)).first; + } + return it->second.get(); +} + +NimSuggestCache::NimSuggestCache() +{ + Core::EditorManager *editorManager = Core::EditorManager::instance(); + connect(editorManager, &Core::EditorManager::editorOpened, + this, &NimSuggestCache::onEditorOpened); + connect(editorManager, &Core::EditorManager::editorAboutToClose, + this, &NimSuggestCache::onEditorClosed); +} + +QString NimSuggestCache::executablePath() const +{ + return m_executablePath; +} + +void NimSuggestCache::setExecutablePath(const QString &path) +{ + if (m_executablePath == path) + return; + + m_executablePath = path; + + for (const auto &pair : m_nimSuggestInstances) { + pair.second->setExecutablePath(path); + } +} + +void Nim::Suggest::NimSuggestCache::onEditorOpened(Core::IEditor *editor) +{ + if (editor->document()->mimeType() == Constants::C_NIM_MIMETYPE) { + get(editor->document()->filePath()); + } +} + +void Nim::Suggest::NimSuggestCache::onEditorClosed(Core::IEditor *editor) +{ + auto it = m_nimSuggestInstances.find(editor->document()->filePath()); + if (it != m_nimSuggestInstances.end()) + m_nimSuggestInstances.erase(it); +} + +} +} diff --git a/src/plugins/nim/suggest/nimsuggestcache.h b/src/plugins/nim/suggest/nimsuggestcache.h new file mode 100644 index 00000000000..c7b632e82e1 --- /dev/null +++ b/src/plugins/nim/suggest/nimsuggestcache.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 + +#include + +#include + +namespace Core { class IEditor; } + +namespace Nim { +namespace Suggest { + +class NimSuggest; + +class NimSuggestCache : public QObject +{ + Q_OBJECT + +public: + static NimSuggestCache &instance(); + + NimSuggest *get(const Utils::FileName &filename); + + QString executablePath() const; + void setExecutablePath(const QString &path); + +private: + NimSuggestCache(); + ~NimSuggestCache(); + + void onEditorOpened(Core::IEditor *editor); + void onEditorClosed(Core::IEditor *editor); + + std::unordered_map> m_nimSuggestInstances; + + QString m_executablePath; +}; + +} // namespace Suggest +} // namespace Nim diff --git a/src/plugins/nim/suggest/server.cpp b/src/plugins/nim/suggest/server.cpp new file mode 100644 index 00000000000..862513f1088 --- /dev/null +++ b/src/plugins/nim/suggest/server.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 "server.h" + +namespace Nim { +namespace Suggest { + +NimSuggestServer::NimSuggestServer(QObject *parent) : QObject(parent) +{ + connect(&m_process, static_cast + (&QProcess::finished), this, &NimSuggestServer::onFinished); + connect(&m_process, &QProcess::started, this, &NimSuggestServer::onStarted); + connect(&m_process, &QProcess::readyReadStandardOutput, this, + &NimSuggestServer::onStandardOutputAvailable); +} + +NimSuggestServer::~NimSuggestServer() +{ + kill(); +} + +QString NimSuggestServer::executablePath() const +{ + return m_executablePath; +} + +bool NimSuggestServer::start(const QString &executablePath, + const QString &projectFilePath) +{ + if (!QFile::exists(executablePath)) { + qWarning() << "NimSuggest executable path" << executablePath << "does not exist"; + return false; + } + + if (!QFile::exists(projectFilePath)) { + qWarning() << "Project file" << projectFilePath << "doesn't exist"; + return false; + } + + m_port = 0; + m_executablePath = executablePath; + m_projectFilePath = projectFilePath; + m_process.start(executablePath, {"--epc", m_projectFilePath}); + return true; +} + +void NimSuggestServer::kill() +{ + disconnect(&m_process, static_cast + (&QProcess::finished), this, &NimSuggestServer::onFinished); + m_process.kill(); + m_process.waitForFinished(); + clearState(); +} + +quint16 NimSuggestServer::port() const +{ + return m_port; +} + +QString NimSuggestServer::projectFilePath() const +{ + return m_projectFilePath; +} + +void NimSuggestServer::onStarted() +{ + m_started = true; +} + +void NimSuggestServer::onStandardOutputAvailable() +{ + if (m_started && !m_portAvailable) { + auto output = QString::fromUtf8(m_process.readAllStandardOutput()); + m_port = static_cast(output.toUInt()); + m_portAvailable = true; + emit started(); + } else { + qDebug() << m_process.readAllStandardOutput(); + } +} + +void NimSuggestServer::onFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + clearState(); + + Q_UNUSED(exitCode); + if (exitStatus == QProcess::ExitStatus::CrashExit) + emit crashed(); + else + emit finished(); +} + +void NimSuggestServer::clearState() +{ + m_started = false; + m_portAvailable = false; + m_port = 0; +} + +} // namespace Suggest +} // namespace Nim diff --git a/src/plugins/nim/suggest/server.h b/src/plugins/nim/suggest/server.h new file mode 100644 index 00000000000..5fa6d4c1bc2 --- /dev/null +++ b/src/plugins/nim/suggest/server.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 +#include +#include +#include + +namespace Nim { +namespace Suggest { + +class NimSuggestServer : public QObject +{ + Q_OBJECT + +public: + NimSuggestServer(QObject *parent = nullptr); + + ~NimSuggestServer(); + + bool start(const QString &executablePath, const QString &projectFilePath); + + void kill(); + + quint16 port() const; + + QString executablePath() const; + + QString projectFilePath() const; + +signals: + void started(); + + void finished(); + + void crashed(); + +private: + void onStarted(); + + void onStandardOutputAvailable(); + + void onFinished(int exitCode, QProcess::ExitStatus exitStatus); + + void clearState(); + + bool m_started = false; + bool m_portAvailable = false; + QProcess m_process; + quint16 m_port = 0; + QString m_projectFilePath; + QString m_executablePath; +}; + +} // namespace Suggest +} // namespace Nim diff --git a/src/plugins/nim/suggest/sexprlexer.h b/src/plugins/nim/suggest/sexprlexer.h new file mode 100644 index 00000000000..a30ca833a51 --- /dev/null +++ b/src/plugins/nim/suggest/sexprlexer.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 +#include +#include +#include + +namespace Nim { + +struct SExprLexer { + enum Result { + Finished, + TokenAvailable, + Error + }; + + enum TokenType : std::size_t { + STRING, + NUMBER, + IDENTIFIER, + OPEN_BRACE, + CLOSE_BRACE, + }; + + struct Token { + TokenType type; + std::size_t start; + std::size_t end; + }; + + SExprLexer(const char *data, std::size_t length) + : m_data(data) + , m_dataLength(length) + , m_pos(0) + {} + + SExprLexer(const char *data) + : SExprLexer(data, ::strlen(data)) + {} + + SExprLexer(const std::string &data) + : SExprLexer(data.c_str(), data.length()) + {} + + const char *data() const + { + return m_data; + } + + std::size_t dataLength() const + { + return m_dataLength; + } + + static size_t tokenLength(Token &token) + { + return token.end - token.start + 1; + } + + std::string tokenValue(Token &token) const + { + return std::string(m_data + token.start, tokenLength(token)); + } + + Result next(Token &token) + { + while (m_pos < m_dataLength) { + if (m_data[m_pos] == '(') { + token = Token{OPEN_BRACE, m_pos, m_pos + 1}; + m_pos++; + return TokenAvailable; + } else if (m_data[m_pos] == ')') { + token = Token{CLOSE_BRACE, m_pos, m_pos + 1}; + m_pos++; + return TokenAvailable; + } else if (m_data[m_pos] == '"') { + Token token_tmp {STRING, m_pos, m_pos}; + char previous = '"'; + m_pos++; + while (true) { + if (m_pos >= m_dataLength) + return Error; + if (m_data[m_pos] == '"' && previous != '\\') + break; + previous = m_data[m_pos]; + m_pos++; + } + token_tmp.end = m_pos; + token = std::move(token_tmp); + m_pos++; + return TokenAvailable; + } else if (std::isdigit(m_data[m_pos])) { + bool decimal_separator_found = false; + token = {NUMBER, m_pos, m_pos}; + m_pos++; + while (true) { + if (m_pos >= m_dataLength) + break; + if (m_data[m_pos] == ',' || m_data[m_pos] == '.') { + if (decimal_separator_found) + return Error; + decimal_separator_found = true; + } else if (!std::isdigit(m_data[m_pos])) + break; + m_pos++; + } + token.end = m_pos - 1; + return TokenAvailable; + } else if (!std::isspace(m_data[m_pos])) { + token = {IDENTIFIER, m_pos, m_pos}; + m_pos++; + while (true) { + if (m_pos >= m_dataLength) + break; + if (std::isspace(m_data[m_pos]) || m_data[m_pos] == '(' || m_data[m_pos] == ')') + break; + m_pos++; + } + token.end = m_pos - 1; + return TokenAvailable; + } else { + m_pos++; + } + } + return Finished; + } + + static std::vector parse(const std::string &data) + { + return parse(data.data(), data.size()); + } + + static std::vector parse(const char *data) + { + return parse(data, ::strlen(data)); + } + + static std::vector parse(const char *data, std::size_t data_size) + { + std::vector result; + SExprLexer lexer(data, data_size); + Token token {}; + while (lexer.next(token)) { + result.push_back(token); + token = {}; + } + return result; + } + +private: + const char *m_data = nullptr; + std::size_t m_dataLength = 0; + std::size_t m_pos = 0; +}; + +} // namespace Nim diff --git a/src/plugins/nim/suggest/sexprparser.h b/src/plugins/nim/suggest/sexprparser.h new file mode 100644 index 00000000000..ca92b567add --- /dev/null +++ b/src/plugins/nim/suggest/sexprparser.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) Filippo Cucchetto +** 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 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 +#include +#include +#include + +#include "sexprlexer.h" + +namespace Nim { + +struct SExprParser { + enum NodeType : uint8_t { + ATOM_STRING = 1, + ATOM_NUMBER = 2, + ATOM_IDENTIFIER = 4, + LIST = 8 + }; + + struct Node { + NodeType kind; + std::size_t from; + std::size_t end; + std::vector nodes; + std::string value; + }; + + SExprParser(const std::string &str) + : SExprParser(str.data(), str.length()) + {} + + SExprParser(const char *data) + : SExprParser(data, ::strlen(data)) + {} + + SExprParser(const char *data, std::size_t length) + : m_lexer(SExprLexer(data, length)) + {} + + bool parse(Node &node) + { + SExprLexer::Token token; + if (m_lexer.next(token) != SExprLexer::Result::TokenAvailable + || token.type != SExprLexer::OPEN_BRACE) + return false; + node = Node {LIST, token.start, token.start, {}, {}}; + return parseList(node); + } + +private: + bool parseList(Node &node) + { + while (true) { + SExprLexer::Token token; + if (m_lexer.next(token) != SExprLexer::Result::TokenAvailable) + return false; + + switch (token.type) { + case SExprLexer::STRING: { + std::string value = m_lexer.tokenValue(token); + value.pop_back(); + value.erase(value.begin()); + node.nodes.emplace_back(Node{ATOM_STRING, token.start, token.end, {}, std::move(value)}); + break; + } + case SExprLexer::NUMBER: { + node.nodes.emplace_back(Node{ATOM_NUMBER, token.start, token.end, {}, m_lexer.tokenValue(token)}); + break; + } + case SExprLexer::IDENTIFIER: { + node.nodes.emplace_back(Node{ATOM_IDENTIFIER, token.start, token.end, {}, m_lexer.tokenValue(token)}); + break; + } + case SExprLexer::OPEN_BRACE: { + Node list {LIST, token.start, token.start, {}, {}}; + if (!parseList(list)) { + return false; + } + node.nodes.emplace_back(std::move(list)); + break; + } + case SExprLexer::CLOSE_BRACE: { + node.end = token.end; + return true; + } + } + } + } + + SExprLexer m_lexer; +}; + +} // namespace Nim