diff --git a/src/plugins/texteditor/basehoverhandler.cpp b/src/plugins/texteditor/basehoverhandler.cpp index 56ad0c0ddb8..025d27b057b 100644 --- a/src/plugins/texteditor/basehoverhandler.cpp +++ b/src/plugins/texteditor/basehoverhandler.cpp @@ -26,6 +26,7 @@ #include "basehoverhandler.h" #include "texteditor.h" +#include #include namespace TextEditor { @@ -33,6 +34,11 @@ namespace TextEditor { BaseHoverHandler::~BaseHoverHandler() {} +bool BaseHoverHandler::isAsyncHandler() const +{ + return m_isAsyncHandler; +} + void BaseHoverHandler::showToolTip(TextEditorWidget *widget, const QPoint &point, bool decorate) { if (decorate) @@ -40,13 +46,18 @@ void BaseHoverHandler::showToolTip(TextEditorWidget *widget, const QPoint &point operateTooltip(widget, point); } -int BaseHoverHandler::checkToolTip(TextEditorWidget *widget, int pos) +void BaseHoverHandler::checkPriority(TextEditorWidget *widget, + int pos, + ReportPriority report) { widget->setContextHelpId(QString()); - process(widget, pos); + process(widget, pos, report); +} - return priority(); +void BaseHoverHandler::cancelAsyncCheck() +{ + QTC_CHECK(false && "BaseHoverHandler: Implement cancelCheck() in derived class!"); } int BaseHoverHandler::priority() const @@ -73,7 +84,7 @@ QString BaseHoverHandler::contextHelpId(TextEditorWidget *widget, int pos) // If the tooltip is visible and there is a help match, this match is used to update // the help id. Otherwise, let the identification process happen. if (!Utils::ToolTip::isVisible() || !lastHelpItemIdentified().isValid()) - process(widget, pos); + process(widget, pos, ReportPriority()); // TODO if (lastHelpItemIdentified().isValid()) return lastHelpItemIdentified().helpId(); @@ -100,13 +111,23 @@ const HelpItem &BaseHoverHandler::lastHelpItemIdentified() const return m_lastHelpItemIdentified; } -void BaseHoverHandler::process(TextEditorWidget *widget, int pos) +void BaseHoverHandler::process(TextEditorWidget *widget, int pos, ReportPriority report) { m_toolTip.clear(); m_priority = -1; m_lastHelpItemIdentified = HelpItem(); - identifyMatch(widget, pos); + if (m_isAsyncHandler) { + identifyMatchAsync(widget, pos, report); + } else { + identifyMatch(widget, pos); + report(priority()); + } +} + +void BaseHoverHandler::setIsAsyncHandler(bool isAsyncHandler) +{ + m_isAsyncHandler = isAsyncHandler; } void BaseHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos) @@ -116,6 +137,11 @@ void BaseHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos) setToolTip(tooltip); } +void BaseHoverHandler::identifyMatchAsync(TextEditorWidget *, int, BaseHoverHandler::ReportPriority) +{ + QTC_CHECK(false && "BaseHoverHandler: Implement identifyMatchAsync() in derived class!"); +} + void BaseHoverHandler::decorateToolTip() { if (Qt::mightBeRichText(toolTip())) diff --git a/src/plugins/texteditor/basehoverhandler.h b/src/plugins/texteditor/basehoverhandler.h index 44dfc61e922..cdd8735264b 100644 --- a/src/plugins/texteditor/basehoverhandler.h +++ b/src/plugins/texteditor/basehoverhandler.h @@ -28,6 +28,8 @@ #include "texteditor_global.h" #include "helpitem.h" +#include + QT_BEGIN_NAMESPACE class QPoint; QT_END_NAMESPACE @@ -41,9 +43,15 @@ class TEXTEDITOR_EXPORT BaseHoverHandler public: virtual ~BaseHoverHandler(); + bool isAsyncHandler() const; + void setIsAsyncHandler(bool isAsyncHandler); + QString contextHelpId(TextEditorWidget *widget, int pos); - int checkToolTip(TextEditorWidget *widget, int pos); + using ReportPriority = std::function; + void checkPriority(TextEditorWidget *widget, int pos, ReportPriority report); + virtual void cancelAsyncCheck(); + void showToolTip(TextEditorWidget *widget, const QPoint &point, bool decorate = true); protected: @@ -63,11 +71,14 @@ protected: const HelpItem &lastHelpItemIdentified() const; virtual void identifyMatch(TextEditorWidget *editorWidget, int pos); + virtual void identifyMatchAsync(TextEditorWidget *editorWidget, int pos, ReportPriority report); virtual void decorateToolTip(); virtual void operateTooltip(TextEditorWidget *editorWidget, const QPoint &point); private: - void process(TextEditorWidget *widget, int pos); + void process(TextEditorWidget *widget, int pos, ReportPriority report); + + bool m_isAsyncHandler = false; QString m_toolTip; HelpItem m_lastHelpItemIdentified; diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index bc9a2b2f7f9..0e64e0c61ff 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -248,6 +248,119 @@ public: TextEditorFactoryPrivate *m_origin; }; +class HoverHandlerRunner +{ +public: + HoverHandlerRunner(TextEditorWidget *widget, QList &handlers) + : m_widget(widget) + , m_handlers(handlers) + { + } + + void startChecking(const QTextCursor &textCursor, const QPoint &point) + { + if (m_handlers.empty()) + return; + + // Does the last handler still applies? + const int documentRevision = textCursor.document()->revision(); + const int position = Convenience::wordStartCursor(textCursor).position(); + if (m_lastHandlerInfo.applies(documentRevision, position)) { + m_lastHandlerInfo.handler->showToolTip(m_widget, point, /*decorate=*/ false); + return; + } + + // Cancel currently running checks + for (BaseHoverHandler *handler : m_handlers) { + if (handler->isAsyncHandler()) + handler->cancelAsyncCheck(); + } + + // Update invocation data + m_documentRevision = documentRevision; + m_position = position; + m_point = point; + + // Re-initialize process data + m_currentHandlerIndex = 0; + m_bestHandler = nullptr; + m_highestHandlerPriority = -1; + + // Start checking + checkNext(); + } + + void checkNext() + { + QTC_ASSERT(m_currentHandlerIndex < m_handlers.size(), return); + BaseHoverHandler *currentHandler = m_handlers[m_currentHandlerIndex]; + + currentHandler->checkPriority(m_widget, m_position, [this](int priority) { + onHandlerFinished(m_documentRevision, m_position, priority); + }); + } + + void onHandlerFinished(int documentRevision, int position, int priority) + { + QTC_ASSERT(m_currentHandlerIndex < m_handlers.size(), return); + QTC_ASSERT(documentRevision == m_documentRevision, return); + QTC_ASSERT(position == m_position, return); + + BaseHoverHandler *currentHandler = m_handlers[m_currentHandlerIndex]; + if (priority > m_highestHandlerPriority) { + m_highestHandlerPriority = priority; + m_bestHandler = currentHandler; + } + + // There are more, check next + ++m_currentHandlerIndex; + if (m_currentHandlerIndex < m_handlers.size()) { + checkNext(); + return; + } + + // All were queried, run the best + if (m_bestHandler) { + m_lastHandlerInfo = LastHandlerInfo(m_bestHandler, m_documentRevision, m_position); + m_bestHandler->showToolTip(m_widget, m_point); + } + } + +private: + TextEditorWidget *m_widget = nullptr; + const QList &m_handlers; + + struct LastHandlerInfo { + LastHandlerInfo() = default; + LastHandlerInfo(BaseHoverHandler *handler, int documentRevision, int cursorPosition) + : handler(handler) + , documentRevision(documentRevision) + , cursorPosition(cursorPosition) + {} + + bool applies(int documentRevision, int cursorPosition) const + { + return handler + && documentRevision == this->documentRevision + && cursorPosition == this->cursorPosition; + } + + BaseHoverHandler *handler = nullptr; + int documentRevision = -1; + int cursorPosition = -1; + } m_lastHandlerInfo; + + // invocation data + QPoint m_point; + int m_position = -1; + int m_documentRevision = -1; + + // processing data + int m_currentHandlerIndex = -1; + int m_highestHandlerPriority = -1; + BaseHoverHandler *m_bestHandler = nullptr; +}; + class TextEditorWidgetPrivate : public QObject { public: @@ -469,26 +582,8 @@ public: CodeAssistant m_codeAssistant; bool m_assistRelevantContentAdded = false; - struct LastHoverHandlerInfo { - LastHoverHandlerInfo() = default; - LastHoverHandlerInfo(BaseHoverHandler *handler, int documentRevision, int cursorPosition) - : handler(handler) - , documentRevision(documentRevision) - , cursorPosition(cursorPosition) - {} - - bool applies(int documentRevision, int cursorPosition) const - { - return handler - && documentRevision == this->documentRevision - && cursorPosition == this->cursorPosition; - } - - BaseHoverHandler *handler = nullptr; - int documentRevision = -1; - int cursorPosition = -1; - } m_lastHoverHandlerInfo; QList m_hoverHandlers; // Not owned + HoverHandlerRunner m_hoverHandlerRunner; QPointer m_navigationAnimation; @@ -535,6 +630,7 @@ TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent) m_requestMarkEnabled(true), m_lineSeparatorsAllowed(false), m_maybeFakeTooltipEvent(false), + m_hoverHandlerRunner(parent, m_hoverHandlers), m_clipboardAssistProvider(new ClipboardAssistProvider), m_autoCompleter(new AutoCompleter) { @@ -3181,30 +3277,7 @@ void TextEditorWidgetPrivate::processTooltipRequest(const QTextCursor &c) return; } - // Does the last handler still applies? - const int documentRevision = m_document->document()->revision(); - const int cursorPosition = Convenience::wordStartCursor(c).position(); - if (m_lastHoverHandlerInfo.applies(documentRevision, cursorPosition)) { - m_lastHoverHandlerInfo.handler->showToolTip(q, toolTipPoint, /*decorate=*/ false); - return; - } - - // Determine best handler - int highestPriority = -1; - BaseHoverHandler *highest = 0; - foreach (BaseHoverHandler *handler, m_hoverHandlers) { - int priority = handler->checkToolTip(q, c.position()); - if (priority > highestPriority) { - highestPriority = priority; - highest = handler; - } - } - - // Let the best handler show the tooltip - if (highest) { - m_lastHoverHandlerInfo = LastHoverHandlerInfo{highest, documentRevision, cursorPosition}; - highest->showToolTip(q, toolTipPoint); - } + m_hoverHandlerRunner.startChecking(c, toolTipPoint); } bool TextEditorWidgetPrivate::processAnnotaionTooltipRequest(const QTextBlock &block,