forked from qt-creator/qt-creator
LanguageClient: auto setup yaml and json ls
Change-Id: I8fff750594cfbd25a0401cd24068c89b86dcf5fc Reviewed-by: Artem Sokolovskii <artem.sokolovskii@qt.io>
This commit is contained in:
@@ -530,6 +530,8 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor)
|
||||
if (TextEditor::TextDocument *document = textEditor->textDocument()) {
|
||||
if (Client *client = m_clientForDocument[document])
|
||||
client->activateEditor(editor);
|
||||
else
|
||||
autoSetupLanguageServer(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -47,4 +47,6 @@ updateCodeActionRefactoringMarker(Client *client,
|
||||
void updateEditorToolBar(Core::IEditor *editor);
|
||||
const QIcon LANGUAGECLIENT_EXPORT symbolIcon(int type);
|
||||
|
||||
void autoSetupLanguageServer(TextEditor::TextDocument *document);
|
||||
|
||||
} // namespace LanguageClient
|
||||
|
||||
Reference in New Issue
Block a user