aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/languageclient/languageclientutils.cpp
diff options
context:
space:
mode:
authorDavid Schulz <[email protected]>2023-08-18 15:14:45 +0200
committerDavid Schulz <[email protected]>2024-01-03 14:04:34 +0000
commitb825d97e1e7fe855c131071855313bb5f687a92c (patch)
tree8faf366608f8ccd9f23939b67df5a1d1422d8d55 /src/plugins/languageclient/languageclientutils.cpp
parent10d389f59f3deb161228546c0f594490c62d4ae7 (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.cpp191
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"