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