Watch python extensions for changes on disk and update status bar

* We show a button to quickly reload changed extensions.
This commit is contained in:
baldurk
2026-08-13 17:46:41 +01:00
parent 22444aa44d
commit a70ee3a2eb
5 changed files with 105 additions and 5 deletions
+58
View File
@@ -220,6 +220,13 @@ CaptureContext::CaptureContext(PersistantConfig &cfg) : m_Config(cfg)
dir.mkpath(dir.absolutePath());
}
m_ExtensionWatcher = new QFileSystemWatcher({}, GetMainWindow()->Widget());
QObject::connect(m_ExtensionWatcher, &QFileSystemWatcher::directoryChanged,
[this](const QString &path) { ExtensionTouched(path); });
QObject::connect(m_ExtensionWatcher, &QFileSystemWatcher::fileChanged,
[this](const QString &path) { ExtensionTouched(path); });
rdcarray<ExtensionMetadata> exts = CaptureContext::GetInstalledExtensions();
for(const ExtensionMetadata &e : exts)
@@ -234,6 +241,8 @@ CaptureContext::CaptureContext(PersistantConfig &cfg) : m_Config(cfg)
CaptureContext::~CaptureContext()
{
delete m_ExtensionWatcher;
delete m_QtHelper;
RENDERDOC_UnregisterMemoryRegion(this);
delete m_Icon;
@@ -340,6 +349,8 @@ rdcarray<ExtensionMetadata> CaptureContext::GetInstalledExtensions()
ext.package = package;
ext.filePath = fileinfo.absolutePath();
ext.hasChanges = m_DirtyExtensions.contains(rdcstr(package));
if(json.contains(lit("name")))
{
ext.name = json[lit("name")].toString();
@@ -483,6 +494,12 @@ rdcstr CaptureContext::LoadExtension(rdcstr name)
if(ret.isEmpty())
{
m_ExtensionObjects[name].swap(m_PendingExtensionObjects);
m_DirtyExtensions.removeOne(name);
for(const ExtensionMetadata &e : GetInstalledExtensions())
if(e.package == name)
AddExtensionWatches(e.filePath);
}
else
{
@@ -2804,6 +2821,47 @@ void CaptureContext::setupDockWindow(QWidget *shad, bool hide)
shad->hide();
}
void CaptureContext::AddExtensionWatches(rdcstr filePath)
{
QDir dir(filePath);
// ignore directories with no python files
QStringList pyfiles = dir.entryList(QStringList() << lit("*.py"));
if(pyfiles.empty())
return;
// don't watch the directory itself. New files will not be usable and we will already catch
// renames.
// m_ExtensionWatcher->addPath(filePath);
for(QString pyfile : pyfiles)
m_ExtensionWatcher->addPath(dir.absoluteFilePath(pyfile));
for(QString subdir : dir.entryList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot))
AddExtensionWatches(dir.absoluteFilePath(subdir));
}
void CaptureContext::ExtensionTouched(const QString &extensionPath)
{
// find the root extension dir containing this path
for(const ExtensionMetadata &m : GetInstalledExtensions())
{
if(extensionPath.contains(m.filePath))
{
// remove all watched paths underneath
QStringList paths = m_ExtensionWatcher->directories();
paths.append(m_ExtensionWatcher->files());
for(QString path : paths)
{
if(path.contains(m.filePath))
m_ExtensionWatcher->removePath(path);
}
// mark it as 'dirty' / needing reload
m_DirtyExtensions.push_back(m.package);
}
}
}
void CaptureContext::RaiseDockWindow(QWidget *dockWindow)
{
ToolWindowManager::raiseToolWindow(dockWindow);
+5
View File
@@ -342,6 +342,9 @@ private:
bool SaveEdits();
void LoadEdits(const QString &data);
void AddExtensionWatches(rdcstr filePath);
void ExtensionTouched(const QString &path);
void CacheResources();
rdcstr GetResourceNameUnsuffixed(const ResourceDescription *desc) const;
@@ -428,6 +431,7 @@ private:
QList<QObject *> m_PendingExtensionObjects;
QMap<rdcstr, QList<QObject *>> m_ExtensionObjects;
rdcarray<rdcstr> m_DirtyExtensions;
QList<QPointer<RegisteredMenuItem>> m_RegisteredMenuItems;
@@ -436,6 +440,7 @@ private:
MiniQtHelper *m_QtHelper = NULL;
QFileSystemWatcher *m_Watcher = NULL;
QFileSystemWatcher *m_ExtensionWatcher = NULL;
// Windows
MainWindow *m_MainWindow = NULL;
+8
View File
@@ -357,6 +357,14 @@ struct ExtensionMetadata
:type: str
)");
rdcstr description;
DOCUMENT(R"(A flag indicating that the extension has changed on disk since the last time it was loaded.
This will always be false if the extension is unloaded.
:type: bool
)");
bool hasChanges = false;
};
DECLARE_REFLECTION_STRUCT(ExtensionMetadata);
+33 -5
View File
@@ -234,17 +234,32 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai
extensionStatus->setIcon(Icons::plugin());
extensionStatus->setVisible(false);
extensionReload = new RDToolButton(this);
extensionReload->setAutoRaise(true);
extensionReload->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui->statusBar->addWidget(extensionReload);
extensionReload->setIcon(Icons::update());
extensionReload->setText(tr("Reload changed extensions"));
extensionReload->setVisible(false);
QObject::connect(extensionStatus, &RDToolButton::clicked, this,
&MainWindow::on_action_Manage_Extensions_triggered);
QObject::connect(extensionReload, &RDToolButton::clicked, [this]() {
rdcarray<ExtensionMetadata> exts = m_Ctx.Extensions().GetInstalledExtensions();
for(const ExtensionMetadata &m : exts)
if(m.hasChanges)
m_Ctx.Extensions().LoadExtension(m.package);
});
QObject::connect(PythonContext::GetExtensionContext(), &PythonContext::extensionLoaded, this,
&MainWindow::PythonStatusUpdate);
QTimer *debuggerTimer = new QTimer(this);
QObject::connect(debuggerTimer, &QTimer::timeout, this, &MainWindow::PythonStatusUpdate);
QTimer *pyStatusTimer = new QTimer(this);
QObject::connect(pyStatusTimer, &QTimer::timeout, this, &MainWindow::PythonStatusUpdate);
debuggerTimer->setSingleShot(false);
debuggerTimer->setInterval(500);
debuggerTimer->start();
pyStatusTimer->setSingleShot(false);
pyStatusTimer->setInterval(500);
pyStatusTimer->start();
QObject::connect(&m_MessageTick, &QTimer::timeout, this, &MainWindow::messageCheck);
m_MessageTick.setSingleShot(false);
@@ -1348,6 +1363,19 @@ void MainWindow::PythonStatusUpdate()
extensionStatus->setVisible(true);
QString text = tr("%n extension(s) active", NULL, num);
bool reloadVisible = false;
for(const ExtensionMetadata &m : m_Ctx.Extensions().GetInstalledExtensions())
{
if(m.hasChanges)
{
text += tr(" (changed on disk)");
reloadVisible = true;
break;
}
}
extensionReload->setVisible(reloadVisible);
if(PythonContext::IsDebuggerConnected())
text += tr(": Python debugger connected");
extensionStatus->setText(text);
+1
View File
@@ -252,6 +252,7 @@ private:
RDLabel *statusIcon;
RDLabel *statusText;
RDToolButton *extensionStatus;
RDToolButton *extensionReload;
QProgressBar *statusProgress;
RDMenu *contextChooserMenu;
QToolButton *contextChooser;