From cb7fdc9b948025fb406573117595b7371c66d234 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 2 Jun 2021 15:06:55 +0100 Subject: [PATCH] Add auto-completion prompts for filter function parameters --- qrenderdoc/Code/Interface/QRDInterface.h | 30 ++++- qrenderdoc/Widgets/Extended/RDTextEdit.cpp | 14 +- qrenderdoc/Windows/EventBrowser.cpp | 142 +++++++++++++++++++-- qrenderdoc/Windows/EventBrowser.h | 3 +- 4 files changed, 173 insertions(+), 16 deletions(-) diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 024733fe6..fb3233db4 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -228,6 +228,26 @@ DOCUMENT(R"(The event browser window. :return: An empty string if the parse succeeded, otherwise any error messages to be displayed to the user, such as syntax or other errors. :rtype: str + +.. function:: AutoCompleteCallback(context, filter, params) + + Not a member function - the signature for any ``AutoCompleteCallback`` callbacks. + + Called when autocompletion is triggered inside a filter. The params passed are any previous + text inside the filter's parameter list up to where the cursor is. The callback should return a + list of identifiers used for auto-completion. + + The list does not have to be pre-filtered for matches to the :paramref:`params`, that is provided + to allow different autocompletion at different stages (e.g. if there are no parameters, you can + autocomplete a property, if a property is already present you can autocomplete valid values for + it) + + :param CaptureContext context: The current capture context. + :param str filter: The name of the filter function. + :param str params: The previous parameter text to the filter function. + :return: A list of strings giving identifiers to autocomplete, or an empty list of there are no + such identifiers to prompt. + :rtype: List[str] )"); struct IEventBrowser { @@ -237,6 +257,9 @@ struct IEventBrowser typedef std::function FilterParseCallback; + typedef std::function(ICaptureContext *, const rdcstr &, const rdcstr &)> + AutoCompleteCallback; + DOCUMENT(R"(Retrieves the PySide2 QWidget for this :class:`EventBrowser` if PySide2 is available, or otherwise returns a unique opaque pointer that can be passed back to any RenderDoc functions expecting a QWidget. @@ -289,12 +312,15 @@ expression. :param EventFilterCallback filter: The callback to call for each candidate event to perform filtering. :param FilterParseCallback parser: The callback to call when the parsing the parameters and checking - for any errors. + for any errors. This can be ``None`` if no pre-parsing is required. +:param AutoCompleteCallback completer: The callback to call when trying to provide autocomplete + suggestions. This can be ``None`` if no completion is desired/applicable. :return: Whether or not the registration was successful. :rtype: bool )"); virtual bool RegisterEventFilterFunction(const rdcstr &name, EventFilterCallback filter, - FilterParseCallback parser) = 0; + FilterParseCallback parser, + AutoCompleteCallback completer) = 0; DOCUMENT(R"(Unregisters an event browser filter function that was previously registered. diff --git a/qrenderdoc/Widgets/Extended/RDTextEdit.cpp b/qrenderdoc/Widgets/Extended/RDTextEdit.cpp index 68a51d00c..98bbd5425 100644 --- a/qrenderdoc/Widgets/Extended/RDTextEdit.cpp +++ b/qrenderdoc/Widgets/Extended/RDTextEdit.cpp @@ -232,7 +232,6 @@ void RDTextEdit::keyPressEvent(QKeyEvent *e) { QTextEdit::keyPressEvent(e); } - emit(keyPress(e)); // stop completing if the character just entered is not a word-compatible character if(completionInProgress() && e->text().length() > 0) @@ -240,11 +239,20 @@ void RDTextEdit::keyPressEvent(QKeyEvent *e) QChar c = e->text()[0]; if(c.isPrint() && !e->text()[0].isLetterOrNumber() && !m_WordCharacters.contains(e->text()[0])) { - m_Completer->popup()->hide(); - emit(completionEnd()); + if(c.isSpace() && m_Completer->completionPrefix().trimmed() == QString()) + { + // don't do anything if we have no prefix so far and the user enters whitespace + } + else + { + m_Completer->popup()->hide(); + emit(completionEnd()); + } } } + emit(keyPress(e)); + // update the completion if it's in progress, or we have our shortcut (and there's no selected // text) if((completionShortcut && !textCursor().hasSelection()) || completionInProgress()) diff --git a/qrenderdoc/Windows/EventBrowser.cpp b/qrenderdoc/Windows/EventBrowser.cpp index 7561f5713..3e88f70b5 100644 --- a/qrenderdoc/Windows/EventBrowser.cpp +++ b/qrenderdoc/Windows/EventBrowser.cpp @@ -1093,6 +1093,29 @@ struct EventFilter }; static QMap DrawFlagsLookup; +static QStringList DrawFlagsList; + +void CacheDrawFlagsLookup() +{ + if(DrawFlagsLookup.empty()) + { + for(uint32_t i = 0; i <= 31; i++) + { + DrawFlags flag = DrawFlags(1U << i); + + // bit of a hack, see if it's a valid flag by stringising and seeing if it contains + // DrawFlags( + QString str = ToQStr(flag); + if(str.contains(lit("DrawFlags("))) + continue; + + DrawFlagsList.push_back(str); + DrawFlagsLookup[str.toLower()] = flag; + } + + DrawFlagsList.sort(); + } +} bool EvaluateFilterSet(ICaptureContext &ctx, const rdcarray &filters, bool all, uint32_t eid, const SDChunk *chunk, const DrawcallDescription *draw, @@ -1187,11 +1210,13 @@ struct CustomFilterCallbacks { IEventBrowser::EventFilterCallback filter; IEventBrowser::FilterParseCallback parser; + IEventBrowser::AutoCompleteCallback completer; }; struct BuiltinFilterCallbacks { std::function makeFilter; + IEventBrowser::AutoCompleteCallback completer; }; struct EventFilterModel : public QSortFilterProxyModel @@ -1220,6 +1245,19 @@ public: MAKE_BUILTIN_FILTER(event); MAKE_BUILTIN_FILTER(draw); MAKE_BUILTIN_FILTER(dispatch); + + m_BuiltinFilters[lit("event")].completer = [this](ICaptureContext *ctx, QString name, + QString parameters) { + return filterCompleter_event(ctx, name, parameters); + }; + m_BuiltinFilters[lit("draw")].completer = [this](ICaptureContext *ctx, QString name, + QString parameters) { + return filterCompleter_draw(ctx, name, parameters); + }; + m_BuiltinFilters[lit("dispatch")].completer = [this](ICaptureContext *ctx, QString name, + QString parameters) { + return filterCompleter_dispatch(ctx, name, parameters); + }; } } void ResetCache() { m_VisibleCache.clear(); } @@ -1234,12 +1272,33 @@ public: return ret; } + QStringList GetCompletions(QString filter, QString params) + { + IEventBrowser::AutoCompleteCallback cb; + + if(m_BuiltinFilters.contains(filter)) + cb = m_BuiltinFilters[filter].completer; + else if(m_CustomFilters.contains(filter)) + cb = m_CustomFilters[filter].completer; + + rdcarray ret; + + if(cb) + ret = m_BuiltinFilters[filter].completer(&m_Ctx, filter, params); + + QStringList qret; + for(const rdcstr &s : ret) + qret << s; + return qret; + } + QStringList GetBuiltinFunctions() { return m_BuiltinFilters.keys(); } QStringList GetCustomFunctions() { return m_CustomFilters.keys(); } void SetEmptyRegionsVisible(bool visible) { m_EmptyRegionsVisible = visible; } static bool RegisterEventFilterFunction(const rdcstr &name, IEventBrowser::EventFilterCallback filter, - IEventBrowser::FilterParseCallback parser) + IEventBrowser::FilterParseCallback parser, + IEventBrowser::AutoCompleteCallback completer) { if(m_BuiltinFilters.contains(name)) { @@ -1255,7 +1314,7 @@ public: return false; } - m_CustomFilters[name] = {filter, parser}; + m_CustomFilters[name] = {filter, parser, completer}; return true; } @@ -1477,6 +1536,15 @@ private: }; } + rdcarray filterCompleter_event(ICaptureContext *ctx, const rdcstr &name, + const rdcstr ¶ms) + { + if(params.find_first_of(" \t") == -1) + return {"EID"}; + + return {}; + } + IEventBrowser::EventFilterCallback filterFunction_event(QString name, QString parameters, ParseError &errors) { @@ -1555,6 +1623,34 @@ private: } } + rdcarray filterCompleter_draw(ICaptureContext *ctx, const rdcstr &name, const rdcstr ¶ms) + { + if(params.find_first_of(" \t") == -1) + return { + "EID", "parent", "drawcallId", "numIndices", + // most aliases we don't autocomplete but this one we leave + "numVertices", "numInstances", "baseVertex", "indexOffset", "vertexOffset", + "instanceOffset", "dispatchX", "dispatchY", "dispatchZ", "dispatchSize", "duration", + "flags", + }; + + QList tokens = tokenise(params); + + if(tokens[0].text == lit("flags") && tokens.size() >= 2) + { + CacheDrawFlagsLookup(); + + rdcarray flags; + + for(QString s : DrawFlagsList) + flags.push_back(s); + + return flags; + } + + return {}; + } + IEventBrowser::EventFilterCallback filterFunction_draw(QString name, QString parameters, ParseError &errors) { @@ -1585,10 +1681,11 @@ private: static const NamedProp namedProps[] = { NAMED_PROP("eventid", draw->eventId), NAMED_PROP("eid", draw->eventId), NAMED_PROP("parent", draw->parent ? draw->parent->eventId : -12341234), - NAMED_PROP("drawcallid", draw->drawcallId), NAMED_PROP("numindices", draw->numIndices), - NAMED_PROP("numindexes", draw->numIndices), NAMED_PROP("numvertices", draw->numIndices), - NAMED_PROP("numvertexes", draw->numIndices), NAMED_PROP("indexcount", draw->numIndices), - NAMED_PROP("vertexcount", draw->numIndices), NAMED_PROP("numinstances", draw->numInstances), + NAMED_PROP("drawcallid", draw->drawcallId), NAMED_PROP("drawid", draw->drawcallId), + NAMED_PROP("numindices", draw->numIndices), NAMED_PROP("numindexes", draw->numIndices), + NAMED_PROP("numvertices", draw->numIndices), NAMED_PROP("numvertexes", draw->numIndices), + NAMED_PROP("indexcount", draw->numIndices), NAMED_PROP("vertexcount", draw->numIndices), + NAMED_PROP("numinstances", draw->numInstances), NAMED_PROP("instancecount", draw->numInstances), NAMED_PROP("basevertex", draw->baseVertex), NAMED_PROP("indexoffset", draw->indexOffset), NAMED_PROP("vertexoffset", draw->vertexOffset), NAMED_PROP("instanceoffset", draw->instanceOffset), @@ -1788,7 +1885,7 @@ private: } else if(tokens[0].text.toLower() == lit("flags")) { - if(tokens.size() < 3 || tokens[1].text != lit("&")) + if(tokens.size() < 3 || (tokens[1].text != lit("&") && tokens[1].text != lit("="))) { errors.position = tokens[0].position; errors.length = (tokens[1].position + tokens[1].length) - errors.position + 1; @@ -1853,6 +1950,17 @@ private: } } + rdcarray filterCompleter_dispatch(ICaptureContext *ctx, const rdcstr &name, + const rdcstr ¶ms) + { + if(params.find_first_of(" \t") == -1) + return { + "EID", "eventId", "parent", "drawcallId", "drawId", "x", "y", "z", "size", "duration", + }; + + return {}; + } + IEventBrowser::EventFilterCallback filterFunction_dispatch(QString name, QString parameters, ParseError &errors) { @@ -1878,6 +1986,9 @@ private: }; static const NamedProp namedProps[] = { + NAMED_PROP("eventid", draw->eventId), NAMED_PROP("eid", draw->eventId), + NAMED_PROP("parent", draw->parent ? draw->parent->eventId : -12341234), + NAMED_PROP("drawcallid", draw->drawcallId), NAMED_PROP("drawid", draw->drawcallId), NAMED_PROP("dispatchx", draw->dispatchDimension[0]), NAMED_PROP("dispatchy", draw->dispatchDimension[1]), NAMED_PROP("dispatchz", draw->dispatchDimension[2]), @@ -2712,6 +2823,15 @@ EventBrowser::EventBrowser(ICaptureContext &ctx, QWidget *parent) ui->filterExpression->setCompletionStrings(completions); } + else if(context.startsWith(QLatin1Char('$')) && context.contains(QLatin1Char('('))) + { + pos = context.indexOf(QLatin1Char('(')); + + QString filter = context.mid(1, pos - 1); + context.remove(0, pos + 1); + + ui->filterExpression->setCompletionStrings(m_FilterModel->GetCompletions(filter, context)); + } else { ui->filterExpression->setCompletionStrings({}); @@ -3007,7 +3127,8 @@ void EventBrowser::on_filterExpression_keyPress(QKeyEvent *e) filter_apply(); } - if(e->key() == Qt::Key_Dollar) + if(e->key() == Qt::Key_Dollar || e->key() == Qt::Key_Ampersand || e->key() == Qt::Key_Equal || + e->key() == Qt::Key_Bar || e->key() == Qt::Key_ParenLeft) { // force autocompletion for filter functions, as long as we're not inside a quoted string @@ -3625,9 +3746,10 @@ const DrawcallDescription *EventBrowser::GetDrawcallForEID(uint32_t eid) } bool EventBrowser::RegisterEventFilterFunction(const rdcstr &name, EventFilterCallback filter, - FilterParseCallback parser) + FilterParseCallback parser, + AutoCompleteCallback completer) { - return EventFilterModel::RegisterEventFilterFunction(name, filter, parser); + return EventFilterModel::RegisterEventFilterFunction(name, filter, parser, completer); } bool EventBrowser::UnregisterEventFilterFunction(const rdcstr &name) diff --git a/qrenderdoc/Windows/EventBrowser.h b/qrenderdoc/Windows/EventBrowser.h index 1e02114ee..c78b1ee1b 100644 --- a/qrenderdoc/Windows/EventBrowser.h +++ b/qrenderdoc/Windows/EventBrowser.h @@ -81,7 +81,8 @@ public: APIEvent GetAPIEventForEID(uint32_t eid) override; const DrawcallDescription *GetDrawcallForEID(uint32_t eid) override; bool RegisterEventFilterFunction(const rdcstr &name, EventFilterCallback filter, - FilterParseCallback parser) override; + FilterParseCallback parser, + AutoCompleteCallback completer) override; bool UnregisterEventFilterFunction(const rdcstr &name) override; void SetCurrentFilterText(const rdcstr &text) override;