diff options
Diffstat (limited to 'plugins/haskell/ghcmod.cpp')
-rw-r--r-- | plugins/haskell/ghcmod.cpp | 81 |
1 files changed, 76 insertions, 5 deletions
diff --git a/plugins/haskell/ghcmod.cpp b/plugins/haskell/ghcmod.cpp index 4eabc1a..1d5595c 100644 --- a/plugins/haskell/ghcmod.cpp +++ b/plugins/haskell/ghcmod.cpp @@ -25,17 +25,24 @@ #include "ghcmod.h" +#include <coreplugin/editormanager/documentmodel.h> +#include <coreplugin/idocument.h> +#include <utils/algorithm.h> #include <utils/environment.h> +#include <utils/qtcassert.h> #include <QFileInfo> #include <QFutureWatcher> #include <QLoggingCategory> +#include <QMetaObject> #include <QMutexLocker> #include <QProcess> #include <QRegularExpression> #include <QTime> #include <QTimer> +#include <functional> + Q_LOGGING_CATEGORY(ghcModLog, "qtc.haskell.ghcmod") Q_LOGGING_CATEGORY(asyncGhcModLog, "qtc.haskell.ghcmod.async") @@ -65,6 +72,15 @@ FileName GhcMod::basePath() const return m_path; } +void GhcMod::setFileMap(const QHash<FileName, FileName> &fileMap) +{ + if (fileMap != m_fileMap) { + log("setting new file map"); + m_fileMap = fileMap; + shutdown(); + } +} + static QString toUnicode(QByteArray data) { // clean zero bytes which let QString think that the string ends there @@ -82,6 +98,15 @@ Utils::optional<QString> GhcMod::typeInfo(const FileName &filePath, int line, in return parseTypeInfo(runTypeInfo(filePath, line, col)); } +static QStringList fileMapArgs(const QHash<FileName, FileName> &map) +{ + QStringList result; + const auto end = map.cend(); + for (auto it = map.cbegin(); it != end; ++it) + result << "--map-file" << it.key().toString() + "=" + it.value().toString(); + return result; +} + bool GhcMod::ensureStarted() { m_mutex.lock(); @@ -99,7 +124,9 @@ bool GhcMod::ensureStarted() m_process.reset(new QProcess); m_process->setWorkingDirectory(m_path.toString()); m_process->setEnvironment(env.toStringList()); - m_process->start(stackExecutable.toString(), {"exec", "ghc-mod", "--", "legacy-interactive"}); + m_process->start(stackExecutable.toString(), + QStringList{"exec", "ghc-mod", "--"} + fileMapArgs(m_fileMap) + << "legacy-interactive"); if (!m_process->waitForStarted(kTimeoutMS)) { log("failed to start"); return false; @@ -223,12 +250,20 @@ void GhcMod::setStackExecutable(const FileName &filePath) m_stackExecutable = filePath; } +static QList<Core::IDocument *> getOpenDocuments(const FileName &path) +{ + return Utils::filtered(Core::DocumentModel::openedDocuments(), [path] (Core::IDocument *doc) { + return path.isEmpty() || doc->filePath().isChildOf(path); + }); +} + AsyncGhcMod::AsyncGhcMod(const FileName &path) - : m_ghcmod(path) + : m_ghcmod(path), + m_fileCache("haskell", std::bind(getOpenDocuments, path)) { qCDebug(asyncGhcModLog) << "starting thread for" << m_ghcmod.basePath().toString(); - moveToThread(&m_thread); m_thread.start(); + m_threadTarget.moveToThread(&m_thread); } AsyncGhcMod::~AsyncGhcMod() @@ -255,6 +290,7 @@ QFuture<Result> createFuture(AsyncGhcMod::Operation op, auto fi = new QFutureInterface<Result>; fi->reportStarted(); + // propagate inner events to outside future auto opWatcher = new QFutureWatcher<Utils::optional<QByteArray>>(); QObject::connect(opWatcher, &QFutureWatcherBase::canceled, [fi] { fi->cancel(); }); QObject::connect(opWatcher, &QFutureWatcherBase::finished, opWatcher, &QObject::deleteLater); @@ -268,6 +304,7 @@ QFuture<Result> createFuture(AsyncGhcMod::Operation op, }); opWatcher->setFuture(op.fi.future()); + // propagate cancel from outer future to inner future auto fiWatcher = new QFutureWatcher<Result>(); QObject::connect(fiWatcher, &QFutureWatcherBase::canceled, [op] { op.fi.cancel(); }); QObject::connect(fiWatcher, &QFutureWatcherBase::finished, fiWatcher, &QObject::deleteLater); @@ -276,27 +313,59 @@ QFuture<Result> createFuture(AsyncGhcMod::Operation op, return fi->future(); } +/*! + Asynchronously looks up the \a symbol in the context of \a filePath. + + Returns a QFuture handle for the asynchronous operation. You may not block the main event loop + while waiting for it to finish - doing so will result in a deadlock. +*/ QFuture<Utils::optional<SymbolInfo>> AsyncGhcMod::findSymbol(const FileName &filePath, const QString &symbol) { QMutexLocker lock(&m_mutex); Operation op([this, filePath, symbol] { return m_ghcmod.runFindSymbol(filePath, symbol); }); m_queue.append(op); - QTimer::singleShot(0, this, [this] { reduceQueue(); }); + QTimer::singleShot(0, &m_threadTarget, [this] { reduceQueue(); }); return createFuture<Utils::optional<SymbolInfo>>(op, &GhcMod::parseFindSymbol); } +/*! + Asynchronously looks up the type at \a line and \a col in \a filePath. + + Returns a QFuture handle for the asynchronous operation. You may not block the main event loop + while waiting for it to finish - doing so will result in a deadlock. +*/ QFuture<Utils::optional<QString>> AsyncGhcMod::typeInfo(const FileName &filePath, int line, int col) { QMutexLocker lock(&m_mutex); Operation op([this, filePath, line, col] { return m_ghcmod.runTypeInfo(filePath, line, col); }); m_queue.append(op); - QTimer::singleShot(0, this, [this] { reduceQueue(); }); + QTimer::singleShot(0, &m_threadTarget, [this] { reduceQueue(); }); return createFuture<Utils::optional<QString>>(op, &GhcMod::parseTypeInfo); } +/*! + Synchronously runs an update of the cache of modified files. + This must be run on the main thread. + + \internal +*/ +void AsyncGhcMod::updateCache() +{ + m_fileCache.update(); +} + +/*! + Takes operations from the queue and executes them, until the queue is empty. + This must be run within the internal thread whenever an operation is added to the queue. + Canceled operations are not executed, but removed from the queue. + Before each operation, the cache of modified files is updated on the main thread. + + \internal +*/ void AsyncGhcMod::reduceQueue() { + QTC_ASSERT(QThread::currentThread() != thread(), return); const auto takeFirst = [this]() { Operation op; m_mutex.lock(); @@ -309,6 +378,8 @@ void AsyncGhcMod::reduceQueue() Operation op; while ((op = takeFirst()).op) { if (!op.fi.isCanceled()) { + QMetaObject::invokeMethod(this, "updateCache", Qt::BlockingQueuedConnection); + m_ghcmod.setFileMap(m_fileCache.fileMap()); Utils::optional<QByteArray> result = op.op(); op.fi.reportResult(result); } |