LanguageClient: auto setup yaml and json ls

Change-Id: I8fff750594cfbd25a0401cd24068c89b86dcf5fc
Reviewed-by: Artem Sokolovskii <artem.sokolovskii@qt.io>
This commit is contained in:
David Schulz
2023-08-18 15:14:45 +02:00
parent 10d389f59f
commit b825d97e1e
3 changed files with 195 additions and 0 deletions

View File

@@ -530,6 +530,8 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor)
if (TextEditor::TextDocument *document = textEditor->textDocument()) { if (TextEditor::TextDocument *document = textEditor->textDocument()) {
if (Client *client = m_clientForDocument[document]) if (Client *client = m_clientForDocument[document])
client->activateEditor(editor); client->activateEditor(editor);
else
autoSetupLanguageServer(document);
} }
} }
} }

View File

@@ -12,11 +12,17 @@
#include <coreplugin/editormanager/documentmodel.h> #include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h> #include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
#include <texteditor/refactoringchanges.h> #include <texteditor/refactoringchanges.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
#include <utils/environment.h>
#include <utils/infobar.h>
#include <utils/process.h>
#include <utils/textutils.h> #include <utils/textutils.h>
#include <utils/treeviewcombobox.h> #include <utils/treeviewcombobox.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
@@ -25,6 +31,7 @@
#include <QFile> #include <QFile>
#include <QMenu> #include <QMenu>
#include <QTextDocument> #include <QTextDocument>
#include <QTimer>
#include <QToolBar> #include <QToolBar>
#include <QToolButton> #include <QToolButton>
@@ -391,4 +398,188 @@ bool applyDocumentChange(const Client *client, const DocumentChange &change)
return false; 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 } // namespace LanguageClient
#include "languageclientutils.moc"

View File

@@ -47,4 +47,6 @@ updateCodeActionRefactoringMarker(Client *client,
void updateEditorToolBar(Core::IEditor *editor); void updateEditorToolBar(Core::IEditor *editor);
const QIcon LANGUAGECLIENT_EXPORT symbolIcon(int type); const QIcon LANGUAGECLIENT_EXPORT symbolIcon(int type);
void autoSetupLanguageServer(TextEditor::TextDocument *document);
} // namespace LanguageClient } // namespace LanguageClient