184 const QCommandLineOption &qmlImportPathOption,
185 const QCommandLineOption &environmentOption,
186 const QCommandLineOption &qmlImportNoDefault)
188 QStringList importPaths;
189 if (parser.isSet(qmlImportPathOption)) {
190 const QStringList pathsFromOption =
191 QQmlToolingUtils::getAndWarnForInvalidDirsFromOption(parser, qmlImportPathOption);
192 qInfo().nospace().noquote() <<
"Using import directories passed by -I: \""
193 << pathsFromOption.join(u"\", \""_s) <<
"\".";
194 importPaths << pathsFromOption;
196 if (parser.isSet(environmentOption)) {
197 if (
const QStringList dirsFromEnv =
198 QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QML_IMPORT_PATH"_s);
199 !dirsFromEnv.isEmpty()) {
200 qInfo().nospace().noquote()
201 <<
"Using import directories passed from environment variable "
202 "\"QML_IMPORT_PATH\": \""
203 << dirsFromEnv.join(u"\", \""_s) <<
"\".";
204 importPaths << dirsFromEnv;
207 if (
const QStringList dirsFromEnv2 =
208 QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QML2_IMPORT_PATH"_s);
209 !dirsFromEnv2.isEmpty()) {
210 qInfo().nospace().noquote()
211 <<
"Using import directories passed from the deprecated environment variable "
212 "\"QML2_IMPORT_PATH\": \""
213 << dirsFromEnv2.join(u"\", \""_s) <<
"\".";
214 importPaths << dirsFromEnv2;
219 if (!parser.isSet(qmlImportNoDefault))
220 importPaths << QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
305 int err = _setmode(_fileno(stdout), _O_BINARY);
307 perror(
"Cannot set mode for stdout");
308 err = _setmode(_fileno(stdin), _O_BINARY);
310 perror(
"Cannot set mode for stdin");
313 QHashSeed::setDeterministicGlobalSeed();
314 QCoreApplication app(argv, argc);
316 QCommandLineParser parser;
317 QQmlToolingSharedSettings settings(
"qmlls"_L1);
318 parser.setApplicationDescription(
"QML languageserver"_L1);
320 parser.addHelpOption();
321 QCommandLineOption waitOption(QStringList() <<
"w"_L1
323 "Waits the given number of seconds before startup"_L1,
325 parser.addOption(waitOption);
327 QCommandLineOption verboseOption(
328 QStringList() <<
"v"_L1
330 "Outputs extra information on the operations being performed"_L1);
331 parser.addOption(verboseOption);
333 QCommandLineOption logFileOption(QStringList() <<
"l"_L1
335 "Writes logging to the given file"_L1,
"logFile"_L1);
336 parser.addOption(logFileOption);
338 QString buildDir = QStringLiteral(u"buildDir");
339 QCommandLineOption buildDirOption(QStringList() <<
"b"_L1
341 "Adds a build dir to look up for qml information"_L1,
343 parser.addOption(buildDirOption);
344 settings.addOption(buildDir);
346 QString qmlImportPath = QStringLiteral(u"importPaths");
347 QCommandLineOption qmlImportPathOption(QStringList() <<
"I"_L1,
348 "Look for QML modules in the specified directory"_L1,
350 parser.addOption(qmlImportPathOption);
351 settings.addOption(qmlImportPath);
353 QCommandLineOption environmentOption(
354 QStringList() <<
"E"_L1,
355 "Use the QML_IMPORT_PATH environment variable to look for QML Modules"_L1);
356 parser.addOption(environmentOption);
358 QCommandLineOption writeDefaultsOption(
359 QStringList() <<
"write-defaults"_L1,
360 "Writes defaults settings to .qmlls.ini and exits (Warning: This "
361 "will overwrite any existing settings and comments!)"_L1);
362 parser.addOption(writeDefaultsOption);
364 QCommandLineOption ignoreSettings(QStringList() <<
"ignore-settings"_L1,
365 "Ignores all settings files and only takes "
366 "command line options into consideration"_L1);
367 parser.addOption(ignoreSettings);
369 QCommandLineOption noCMakeCallsOption(
370 QStringList() <<
"no-cmake-calls"_L1,
371 "Disables automatic CMake rebuilds and C++ file watching."_L1);
372 parser.addOption(noCMakeCallsOption);
373 settings.addOption(
"no-cmake-calls"_L1,
"false"_L1);
375 QCommandLineOption cmakeJobsOption(QStringList() <<
"cmake-jobs"_L1 <<
"j"_L1,
376 "Number of CMake jobs for automatic CMake rebuilds. "_L1,
378 parser.addOption(cmakeJobsOption);
379 settings.addOption(
"CMakeJobs"_L1,
"1"_L1);
381 QCommandLineOption docDir({ {
"d"_L1,
"p"_L1,
"doc-dir"_L1 },
382 "Documentation path to use for the documentation hints feature"_L1,
385 parser.addOption(docDir);
386 settings.addOption(
"docDir"_L1);
388 QCommandLineOption qmlImportNoDefault(QStringList() <<
"bare"_L1,
389 "Do not include default import directories. "
390 "This may be used to run qmlls on a Boot2Qt project."_L1);
391 parser.addOption(qmlImportNoDefault);
392 const QString qmlImportNoDefaultSetting =
"DisableDefaultImports"_L1;
393 settings.addOption(qmlImportNoDefaultSetting,
false);
395 QCommandLineOption inputFile(QStringList() <<
"inputFile"_L1,
396 "Read from file instead of stdin"_L1,
"fileName"_L1);
397 inputFile.setFlags(QCommandLineOption::HiddenFromHelp);
398 parser.addOption(inputFile);
401 QCommandLineOption versionOption(
"version"_L1,
"Displays version information."_L1);
402 parser.addOption(versionOption);
406 if (parser.isSet(versionOption)) {
407 parser.showVersion();
411 if (parser.isSet(writeDefaultsOption)) {
412 return settings.writeDefaults() ? 0 : 1;
415 if (parser.isSet(logFileOption)) {
416 QString fileName = parser.value(logFileOption);
417 qInfo() <<
"will log to" << fileName;
418 logFile =
new QFile(fileName);
419 logFileLock =
new QMutex;
420 if (!logFile->open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) {
421 qWarning(
"Failed to open file %s: %s", qPrintable(logFile->fileName()),
422 qPrintable(logFile->errorString()));
424 qInstallMessageHandler([](QtMsgType t,
const QMessageLogContext &,
const QString &msg) {
426 logFile->write(QString::number(
int(t)).toUtf8());
433 if (parser.isSet(waitOption)) {
434 int waitSeconds = parser.value(waitOption).toInt();
436 qDebug() <<
"waiting";
437 QThread::sleep(waitSeconds);
438 qDebug() <<
"starting";
441 QQmlLanguageServer qmlServer(
442 [&writeMutex](
const QByteArray &data) {
443 QMutexLocker l(&writeMutex);
444 std::cout.write(data.constData(), data.size());
447 (parser.isSet(ignoreSettings) ?
nullptr : &settings));
449 if (parser.isSet(verboseOption)) {
450 QLoggingCategory::setFilterRules(
"qt.languageserver*.debug=true\n"_L1);
451 qmlServer.codeModelManager()->setVerbose(
true);
453 if (parser.isSet(docDir))
454 qmlServer.codeModelManager()->setDocumentationRootPath(
455 QString::fromUtf8(parser.value(docDir).toUtf8()));
457 if (parser.isSet(buildDirOption)) {
458 const QStringList dirs =
459 QQmlToolingUtils::getAndWarnForInvalidDirsFromOption(parser, buildDirOption);
461 qInfo().nospace().noquote()
462 <<
"Using build directories passed by -b: \"" << dirs.join(u"\", \""_s) <<
"\".";
464 qmlServer.codeModelManager()->setBuildPathsForRootUrl(QByteArray(), dirs);
465 }
else if (QStringList dirsFromEnv =
466 QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QMLLS_BUILD_DIRS"_s);
467 !dirsFromEnv.isEmpty()) {
471 qInfo().nospace().noquote() <<
"Using build directories passed from environment variable "
472 "\"QMLLS_BUILD_DIRS\": \""
473 << dirsFromEnv.join(u"\", \""_s) <<
"\".";
474 qmlServer.codeModelManager()->setBuildPathsForRootUrl(QByteArray(), dirsFromEnv);
476 qInfo() <<
"Using the build directories found in the .qmlls.ini file. Your build folder "
477 "might not be found if no .qmlls.ini files are present in the root source "
481 qmlServer.codeModelManager()->setImportPaths(
482 collectImportPaths(parser, qmlImportPathOption, environmentOption, qmlImportNoDefault));
484 if (prepareCMakeFeature(parser, noCMakeCallsOption)) {
485 qmlServer.codeModelManager()->setCMakeJobs(cmakeJobs(parser, cmakeJobsOption));
486 qmlServer.codeModelManager()->tryEnableCMakeCalls();
488 qmlServer.codeModelManager()->disableCMakeCalls();
491 auto r = parser.isSet(inputFile) && parser.value(inputFile) !=
"-"_L1
492 ? std::unique_ptr<AbstractReader>(std::make_unique<FileReader>(parser.value(inputFile)))
493 : std::unique_ptr<AbstractReader>(std::make_unique<StdinReader>());
494 QThread workerThread;
495 r->moveToThread(&workerThread);
496 QObject::connect(r.get(), &AbstractReader::receivedData, qmlServer.server(),
497 &QLanguageServer::receiveData);
498 QObject::connect(qmlServer.server(), &QLanguageServer::readNextMessage, r.get(),
499 &AbstractReader::readNextMessage);
500 auto exit = [&app, &workerThread]() {
503 QTimer::singleShot(100, &app, []() {
504 QCoreApplication::processEvents();
505 QCoreApplication::exit();
508 QObject::connect(r.get(), &StdinReader::eof, &app, exit);
509 QObject::connect(qmlServer.server(), &QLanguageServer::exit, &workerThread, exit);
511 emit r->readNextMessage();
512 workerThread.start();
516 return qmlServer.returnValue();