diff options
author | David Schulz <[email protected]> | 2023-08-18 15:14:45 +0200 |
---|---|---|
committer | David Schulz <[email protected]> | 2024-01-03 14:04:34 +0000 |
commit | b825d97e1e7fe855c131071855313bb5f687a92c (patch) | |
tree | 8faf366608f8ccd9f23939b67df5a1d1422d8d55 /src/plugins/languageclient/languageclientutils.cpp | |
parent | 10d389f59f3deb161228546c0f594490c62d4ae7 (diff) |
LanguageClient: auto setup yaml and json ls
Change-Id: I8fff750594cfbd25a0401cd24068c89b86dcf5fc
Reviewed-by: Artem Sokolovskii <[email protected]>
Diffstat (limited to 'src/plugins/languageclient/languageclientutils.cpp')
-rw-r--r-- | src/plugins/languageclient/languageclientutils.cpp | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/src/plugins/languageclient/languageclientutils.cpp b/src/plugins/languageclient/languageclientutils.cpp index 8edf1666c8b..f13c66728a8 100644 --- a/src/plugins/languageclient/languageclientutils.cpp +++ b/src/plugins/languageclient/languageclientutils.cpp @@ -12,11 +12,17 @@ #include <coreplugin/editormanager/documentmodel.h> #include <coreplugin/icore.h> +#include <coreplugin/messagemanager.h> +#include <coreplugin/progressmanager/progressmanager.h> #include <texteditor/codeassist/textdocumentmanipulatorinterface.h> #include <texteditor/refactoringchanges.h> #include <texteditor/textdocument.h> #include <texteditor/texteditor.h> + +#include <utils/environment.h> +#include <utils/infobar.h> +#include <utils/process.h> #include <utils/textutils.h> #include <utils/treeviewcombobox.h> #include <utils/utilsicons.h> @@ -25,6 +31,7 @@ #include <QFile> #include <QMenu> #include <QTextDocument> +#include <QTimer> #include <QToolBar> #include <QToolButton> @@ -391,4 +398,188 @@ bool applyDocumentChange(const Client *client, const DocumentChange &change) return false; } +constexpr char installJsonLsInfoBarId[] = "LanguageClient::InstallJsonLs"; +constexpr char installYamlLsInfoBarId[] = "LanguageClient::InstallYamlLs"; + +const char npmInstallTaskId[] = "LanguageClient::npmInstallTask"; + +class NpmInstallTask : public QObject +{ + Q_OBJECT +public: + NpmInstallTask(const FilePath &npm, + const FilePath &workingDir, + const QString &package, + QObject *parent = nullptr) + : QObject(parent) + , m_package(package) + { + m_process.setCommand(CommandLine(npm, {"install", package})); + m_process.setWorkingDirectory(workingDir); + m_process.setTerminalMode(TerminalMode::Run); + connect(&m_process, &Process::done, this, &NpmInstallTask::handleDone); + connect(&m_killTimer, &QTimer::timeout, this, &NpmInstallTask::cancel); + connect(&m_watcher, &QFutureWatcher<void>::canceled, this, &NpmInstallTask::cancel); + m_watcher.setFuture(m_future.future()); + } + void run() + { + const QString taskTitle = Tr::tr("Install npm Package"); + Core::ProgressManager::addTask(m_future.future(), taskTitle, npmInstallTaskId); + + m_process.start(); + + Core::MessageManager::writeSilently( + Tr::tr("Running \"%1\" to install %2.") + .arg(m_process.commandLine().toUserOutput(), m_package)); + + m_killTimer.setSingleShot(true); + m_killTimer.start(5 /*minutes*/ * 60 * 1000); + } + +signals: + void finished(bool success); + +private: + void cancel() + { + m_process.stop(); + m_process.waitForFinished(); + Core::MessageManager::writeFlashing( + m_killTimer.isActive() + ? Tr::tr("The installation of \"%1\" was canceled by timeout.").arg(m_package) + : Tr::tr("The installation of \"%1\" was canceled by the user.") + .arg(m_package)); + } + void handleDone() + { + m_future.reportFinished(); + const bool success = m_process.result() == ProcessResult::FinishedWithSuccess; + if (!success) { + Core::MessageManager::writeFlashing(Tr::tr("Installing \"%1\" failed with exit code %2.") + .arg(m_package) + .arg(m_process.exitCode())); + } + emit finished(success); + } + + QString m_package; + Utils::Process m_process; + QFutureInterface<void> m_future; + QFutureWatcher<void> m_watcher; + QTimer m_killTimer; +}; + +void autoSetupLanguageServer(TextDocument *document) +{ + const QString mimeType = document->mimeType(); + if (mimeType == "application/x-yaml" || mimeType == "application/json") { + const bool isYaml = mimeType == "application/x-yaml"; + // check whether the user suppressed the info bar + const Id infoBarId = isYaml ? installYamlLsInfoBarId : installJsonLsInfoBarId; + + InfoBar *infoBar = document->infoBar(); + if (!infoBar->canInfoBeAdded(infoBarId)) + return; + + // check if it is already configured + const QList<BaseSettings *> settings = LanguageClientManager::currentSettings(); + for (BaseSettings *setting : settings) { + if (setting->isValid() && setting->m_languageFilter.isSupported(document)) + return; + } + + // check for npm + const FilePath npm = Environment::systemEnvironment().searchInPath("npm"); + if (!npm.isExecutableFile()) + return; + + const QString languageServer = isYaml ? QString("yaml-language-server") + : QString("vscode-json-languageserver"); + + FilePath lsExecutable; + + Process process; + process.setCommand(CommandLine(npm, {"list", "-g", languageServer})); + process.start(); + process.waitForFinished(); + if (process.exitCode() == 0) { + const FilePath lspath = FilePath::fromUserInput(process.stdOutLines().value(0)); + lsExecutable = lspath.pathAppended(languageServer); + if (HostOsInfo::isWindowsHost()) + lsExecutable = lsExecutable.stringAppended(".cmd"); + } + + const bool install = !lsExecutable.isExecutableFile(); + + const QString language = isYaml ? QString("YAML") : QString("JSON"); + + const QString message = install + ? Tr::tr("Install %1 language server via npm.").arg(language) + : Tr::tr("Setup %1 language server (%2).") + .arg(language) + .arg(lsExecutable.toUserOutput()); + InfoBarEntry info(infoBarId, message, InfoBarEntry::GlobalSuppression::Enabled); + info.addCustomButton(install ? Tr::tr("Install") : Tr::tr("Setup"), [=]() { + const QList<Core::IDocument *> &openedDocuments = Core::DocumentModel::openedDocuments(); + for (Core::IDocument *doc : openedDocuments) + doc->infoBar()->removeInfo(infoBarId); + + auto setupStdIOSettings = [=](const FilePath &executable){ + auto settings = new StdIOSettings(); + + settings->m_executable = executable; + settings->m_arguments = "--stdio"; + settings->m_name = Tr::tr("%1 Language Server").arg(language); + settings->m_languageFilter.mimeTypes = {mimeType}; + + LanguageClientSettings::addSettings(settings); + LanguageClientManager::applySettings(); + }; + + if (install) { + const FilePath lsPath = Core::ICore::userResourcePath(languageServer); + if (!lsPath.ensureWritableDir()) + return; + auto install = new NpmInstallTask(npm, + lsPath, + languageServer, + LanguageClientManager::instance()); + + auto handleInstall = [=](const bool success) { + if (success) { + Process process; + process.setCommand(CommandLine(npm, {"bin"})); + process.setWorkingDirectory(lsPath); + process.start(); + process.waitForFinished(); + const FilePath lspath = FilePath::fromUserInput( + process.stdOutLines().value(0)); + FilePath lsExecutable = lspath.pathAppended(languageServer); + if (HostOsInfo::isWindowsHost()) + lsExecutable = lsExecutable.stringAppended(".cmd"); + + if (lsExecutable.isExecutableFile()) + setupStdIOSettings(lsExecutable); + } + install->deleteLater(); + }; + + QObject::connect(install, + &NpmInstallTask::finished, + LanguageClientManager::instance(), + handleInstall); + + install->run(); + } else { + setupStdIOSettings(lsExecutable); + } + + }); + infoBar->addInfo(info); + } +} + } // namespace LanguageClient + +#include "languageclientutils.moc" |