forked from quickshell/quickshell
		
	tooling: add automatic QMLLS support for new imports and singletons
This commit is contained in:
		
							parent
							
								
									4d8055f1cd
								
							
						
					
					
						commit
						986749cdb9
					
				
					 7 changed files with 283 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -38,6 +38,7 @@ qt_add_library(quickshell-core STATIC
 | 
			
		|||
	iconprovider.cpp
 | 
			
		||||
	scriptmodel.cpp
 | 
			
		||||
	colorquantizer.cpp
 | 
			
		||||
	toolsupport.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_qml_module(quickshell-core
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -135,6 +135,33 @@ QDir* QsPaths::instanceRunDir() {
 | 
			
		|||
	else return &this->mInstanceRunDir;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDir* QsPaths::shellVfsDir() {
 | 
			
		||||
	if (this->shellVfsState == DirState::Unknown) {
 | 
			
		||||
		if (auto* baseRunDir = this->baseRunDir()) {
 | 
			
		||||
			this->mShellVfsDir = QDir(baseRunDir->filePath("vfs"));
 | 
			
		||||
			this->mShellVfsDir = QDir(this->mShellVfsDir.filePath(this->shellId));
 | 
			
		||||
 | 
			
		||||
			qCDebug(logPaths) << "Initialized runtime vfs path:" << this->mShellVfsDir.path();
 | 
			
		||||
 | 
			
		||||
			if (!this->mShellVfsDir.mkpath(".")) {
 | 
			
		||||
				qCCritical(logPaths) << "Could not create runtime vfs directory at"
 | 
			
		||||
				                     << this->mShellVfsDir.path();
 | 
			
		||||
				this->shellVfsState = DirState::Failed;
 | 
			
		||||
			} else {
 | 
			
		||||
				this->shellVfsState = DirState::Ready;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			qCCritical(logPaths) << "Could not create shell runtime vfs path as it was not possible to "
 | 
			
		||||
			                        "create the base runtime path.";
 | 
			
		||||
 | 
			
		||||
			this->shellVfsState = DirState::Failed;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (this->shellVfsState == DirState::Failed) return nullptr;
 | 
			
		||||
	else return &this->mShellVfsDir;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QsPaths::linkRunDir() {
 | 
			
		||||
	if (auto* runDir = this->instanceRunDir()) {
 | 
			
		||||
		auto pidDir = QDir(this->baseRunDir()->filePath("by-pid"));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ public:
 | 
			
		|||
 | 
			
		||||
	QDir* baseRunDir();
 | 
			
		||||
	QDir* shellRunDir();
 | 
			
		||||
	QDir* shellVfsDir();
 | 
			
		||||
	QDir* instanceRunDir();
 | 
			
		||||
	void linkRunDir();
 | 
			
		||||
	void linkPathDir();
 | 
			
		||||
| 
						 | 
				
			
			@ -48,9 +49,11 @@ private:
 | 
			
		|||
	QString pathId;
 | 
			
		||||
	QDir mBaseRunDir;
 | 
			
		||||
	QDir mShellRunDir;
 | 
			
		||||
	QDir mShellVfsDir;
 | 
			
		||||
	QDir mInstanceRunDir;
 | 
			
		||||
	DirState baseRunState = DirState::Unknown;
 | 
			
		||||
	DirState shellRunState = DirState::Unknown;
 | 
			
		||||
	DirState shellVfsState = DirState::Unknown;
 | 
			
		||||
	DirState instanceRunState = DirState::Unknown;
 | 
			
		||||
 | 
			
		||||
	QDir mShellDataDir;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
 | 
			
		||||
#include <qdir.h>
 | 
			
		||||
#include <qfileinfo.h>
 | 
			
		||||
#include <qfilesystemwatcher.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmlcomponent.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -18,15 +19,26 @@
 | 
			
		|||
#include "instanceinfo.hpp"
 | 
			
		||||
#include "qmlglobal.hpp"
 | 
			
		||||
#include "scan.hpp"
 | 
			
		||||
#include "toolsupport.hpp"
 | 
			
		||||
 | 
			
		||||
RootWrapper::RootWrapper(QString rootPath, QString shellId)
 | 
			
		||||
    : QObject(nullptr)
 | 
			
		||||
    , rootPath(std::move(rootPath))
 | 
			
		||||
    , shellId(std::move(shellId))
 | 
			
		||||
    , originalWorkingDirectory(QDir::current().absolutePath()) {
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(QuickshellSettings::instance(), &QuickshellSettings::watchFilesChanged, this, &RootWrapper::onWatchFilesChanged);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    QuickshellSettings::instance(),
 | 
			
		||||
	    &QuickshellSettings::watchFilesChanged,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &RootWrapper::onWatchFilesChanged
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &this->configDirWatcher,
 | 
			
		||||
	    &QFileSystemWatcher::directoryChanged,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &RootWrapper::updateTooling
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	this->reloadGraph(true);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,6 +60,9 @@ void RootWrapper::reloadGraph(bool hard) {
 | 
			
		|||
	auto scanner = QmlScanner(rootPath);
 | 
			
		||||
	scanner.scanQmlFile(this->rootPath);
 | 
			
		||||
 | 
			
		||||
	qs::core::QmlToolingSupport::updateTooling(rootPath, scanner);
 | 
			
		||||
	this->configDirWatcher.addPath(rootPath.path());
 | 
			
		||||
 | 
			
		||||
	auto* generation = new EngineGeneration(rootPath, std::move(scanner));
 | 
			
		||||
	generation->wrapper = this;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -168,3 +183,9 @@ void RootWrapper::onWatchFilesChanged() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void RootWrapper::onWatchedFilesChanged() { this->reloadGraph(false); }
 | 
			
		||||
 | 
			
		||||
void RootWrapper::updateTooling() {
 | 
			
		||||
	if (!this->generation) return;
 | 
			
		||||
	auto configDir = QFileInfo(this->rootPath).dir();
 | 
			
		||||
	qs::core::QmlToolingSupport::updateTooling(configDir, this->generation->scanner);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qfilesystemwatcher.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmlengine.h>
 | 
			
		||||
#include <qtclasshelpermacros.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -22,10 +23,12 @@ private slots:
 | 
			
		|||
	void generationDestroyed();
 | 
			
		||||
	void onWatchFilesChanged();
 | 
			
		||||
	void onWatchedFilesChanged();
 | 
			
		||||
	void updateTooling();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QString rootPath;
 | 
			
		||||
	QString shellId;
 | 
			
		||||
	EngineGeneration* generation = nullptr;
 | 
			
		||||
	QString originalWorkingDirectory;
 | 
			
		||||
	QFileSystemWatcher configDirWatcher;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										205
									
								
								src/core/toolsupport.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								src/core/toolsupport.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,205 @@
 | 
			
		|||
#include "toolsupport.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qdebug.h>
 | 
			
		||||
#include <qdir.h>
 | 
			
		||||
#include <qfileinfo.h>
 | 
			
		||||
#include <qlist.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qtenvironmentvariables.h>
 | 
			
		||||
 | 
			
		||||
#include "logcat.hpp"
 | 
			
		||||
#include "paths.hpp"
 | 
			
		||||
#include "scan.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::core {
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
QS_LOGGING_CATEGORY(logTooling, "quickshell.tooling", QtWarningMsg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool QmlToolingSupport::updateTooling(const QDir& configRoot, QmlScanner& scanner) {
 | 
			
		||||
	auto* vfs = QsPaths::instance()->shellVfsDir();
 | 
			
		||||
 | 
			
		||||
	if (!vfs) {
 | 
			
		||||
		qCCritical(logTooling) << "Tooling dir could not be created";
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!QmlToolingSupport::updateQmllsConfig(configRoot, false)) {
 | 
			
		||||
		QDir(vfs->filePath("qs")).removeRecursively();
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QmlToolingSupport::updateToolingFs(scanner, configRoot, vfs->filePath("qs"));
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString QmlToolingSupport::getQmllsConfig() {
 | 
			
		||||
	static auto config = []() {
 | 
			
		||||
		QList<QString> importPaths;
 | 
			
		||||
 | 
			
		||||
		auto addPaths = [&](const QList<QString>& paths) {
 | 
			
		||||
			for (const auto& path: paths) {
 | 
			
		||||
				if (!importPaths.contains(path)) importPaths.append(path);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		addPaths(qEnvironmentVariable("QML_IMPORT_PATH").split(u':', Qt::SkipEmptyParts));
 | 
			
		||||
		addPaths(qEnvironmentVariable("QML2_IMPORT_PATH").split(u':', Qt::SkipEmptyParts));
 | 
			
		||||
 | 
			
		||||
		auto vfsPath = QsPaths::instance()->shellVfsDir()->path();
 | 
			
		||||
		auto importPathsStr = importPaths.join(u':');
 | 
			
		||||
 | 
			
		||||
		QString qmllsConfig;
 | 
			
		||||
		auto print = QDebug(&qmllsConfig).nospace();
 | 
			
		||||
		print << "[General]\nno-cmake-calls=true\nbuildDir=" << vfsPath
 | 
			
		||||
		      << "\nimportPaths=" << importPathsStr << '\n';
 | 
			
		||||
 | 
			
		||||
		return qmllsConfig;
 | 
			
		||||
	}();
 | 
			
		||||
 | 
			
		||||
	return config;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool QmlToolingSupport::updateQmllsConfig(const QDir& configRoot, bool create) {
 | 
			
		||||
	auto shellConfigPath = configRoot.filePath(".qmlls.ini");
 | 
			
		||||
	auto vfsConfigPath = QsPaths::instance()->shellVfsDir()->filePath(".qmlls.ini");
 | 
			
		||||
 | 
			
		||||
	auto shellFileInfo = QFileInfo(shellConfigPath);
 | 
			
		||||
	if (!create && !shellFileInfo.exists()) {
 | 
			
		||||
		if (QmlToolingSupport::toolingEnabled) {
 | 
			
		||||
			qInfo() << "QML tooling support disabled";
 | 
			
		||||
			QmlToolingSupport::toolingEnabled = false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		QFile::remove(vfsConfigPath);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto vfsFile = QFile(vfsConfigPath);
 | 
			
		||||
 | 
			
		||||
	if (!vfsFile.open(QFile::ReadWrite | QFile::Text)) {
 | 
			
		||||
		qCCritical(logTooling) << "Failed to create qmlls config in vfs";
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto config = QmlToolingSupport::getQmllsConfig();
 | 
			
		||||
 | 
			
		||||
	if (vfsFile.readAll() != config) {
 | 
			
		||||
		if (!vfsFile.resize(0) || !vfsFile.write(config.toUtf8())) {
 | 
			
		||||
			qCCritical(logTooling) << "Failed to write qmlls config in vfs";
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		qCDebug(logTooling) << "Wrote qmlls config in vfs";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!shellFileInfo.isSymLink() || shellFileInfo.symLinkTarget() != vfsConfigPath) {
 | 
			
		||||
		QFile::remove(shellConfigPath);
 | 
			
		||||
 | 
			
		||||
		if (!QFile::link(vfsConfigPath, shellConfigPath)) {
 | 
			
		||||
			qCCritical(logTooling) << "Failed to create qmlls config symlink";
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		qCDebug(logTooling) << "Created qmlls config symlink";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!QmlToolingSupport::toolingEnabled) {
 | 
			
		||||
		qInfo() << "QML tooling support enabled";
 | 
			
		||||
		QmlToolingSupport::toolingEnabled = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QmlToolingSupport::updateToolingFs(
 | 
			
		||||
    QmlScanner& scanner,
 | 
			
		||||
    const QDir& scanDir,
 | 
			
		||||
    const QDir& linkDir
 | 
			
		||||
) {
 | 
			
		||||
	QList<QString> files;
 | 
			
		||||
	QSet<QString> subdirs;
 | 
			
		||||
 | 
			
		||||
	auto scanPath = scanDir.path();
 | 
			
		||||
 | 
			
		||||
	linkDir.mkpath(".");
 | 
			
		||||
 | 
			
		||||
	for (auto& path: scanner.scannedFiles) {
 | 
			
		||||
		if (path.length() < scanPath.length() + 1 || !path.startsWith(scanPath)) continue;
 | 
			
		||||
		auto name = path.sliced(scanPath.length() + 1);
 | 
			
		||||
 | 
			
		||||
		if (name.contains('/')) {
 | 
			
		||||
			auto dirname = name.first(name.indexOf('/'));
 | 
			
		||||
			subdirs.insert(dirname);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto fileInfo = QFileInfo(path);
 | 
			
		||||
		if (!fileInfo.isFile()) continue;
 | 
			
		||||
 | 
			
		||||
		auto spath = linkDir.filePath(name);
 | 
			
		||||
		auto sFileInfo = QFileInfo(spath);
 | 
			
		||||
 | 
			
		||||
		if (!sFileInfo.isSymLink() || sFileInfo.symLinkTarget() != path) {
 | 
			
		||||
			QFile::remove(spath);
 | 
			
		||||
 | 
			
		||||
			if (QFile::link(path, spath)) {
 | 
			
		||||
				qCDebug(logTooling) << "Created symlink to" << path << "at" << spath;
 | 
			
		||||
				files.append(spath);
 | 
			
		||||
			} else {
 | 
			
		||||
				qCCritical(logTooling) << "Could not create symlink to" << path << "at" << spath;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			files.append(spath);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (auto [path, text]: scanner.fileIntercepts.asKeyValueRange()) {
 | 
			
		||||
		if (path.length() < scanPath.length() + 1 || !path.startsWith(scanPath)) continue;
 | 
			
		||||
		auto name = path.sliced(scanPath.length() + 1);
 | 
			
		||||
 | 
			
		||||
		if (name.contains('/')) {
 | 
			
		||||
			auto dirname = name.first(name.indexOf('/'));
 | 
			
		||||
			subdirs.insert(dirname);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto spath = linkDir.filePath(name);
 | 
			
		||||
		auto file = QFile(spath);
 | 
			
		||||
		if (!file.open(QFile::ReadWrite | QFile::Text)) {
 | 
			
		||||
			qCCritical(logTooling) << "Failed to open injected file" << spath;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (file.readAll() == text) {
 | 
			
		||||
			files.append(spath);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (file.resize(0) && file.write(text.toUtf8())) {
 | 
			
		||||
			files.append(spath);
 | 
			
		||||
			qCDebug(logTooling) << "Wrote injected file" << spath;
 | 
			
		||||
		} else {
 | 
			
		||||
			qCCritical(logTooling) << "Failed to write injected file" << spath;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (auto& name: linkDir.entryList(QDir::Files | QDir::System)) { // System = broken symlinks
 | 
			
		||||
		auto path = linkDir.filePath(name);
 | 
			
		||||
 | 
			
		||||
		if (!files.contains(path)) {
 | 
			
		||||
			if (QFile::remove(path)) qCDebug(logTooling) << "Removed old file at" << path;
 | 
			
		||||
			else qCWarning(logTooling) << "Failed to remove old file at" << path;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (const auto& subdir: subdirs) {
 | 
			
		||||
		QmlToolingSupport::updateToolingFs(scanner, scanDir.filePath(subdir), linkDir.filePath(subdir));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::core
 | 
			
		||||
							
								
								
									
										20
									
								
								src/core/toolsupport.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/core/toolsupport.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qdir.h>
 | 
			
		||||
 | 
			
		||||
#include "scan.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::core {
 | 
			
		||||
 | 
			
		||||
class QmlToolingSupport {
 | 
			
		||||
public:
 | 
			
		||||
	static bool updateTooling(const QDir& configRoot, QmlScanner& scanner);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static QString getQmllsConfig();
 | 
			
		||||
	static bool updateQmllsConfig(const QDir& configRoot, bool create);
 | 
			
		||||
	static void updateToolingFs(QmlScanner& scanner, const QDir& scanDir, const QDir& linkDir);
 | 
			
		||||
	static inline bool toolingEnabled = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::core
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue