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 (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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user