Copilot: move CopilotHoverHandler to TextEditor

And rename it to SuggestionHoverHandler in order to reuse it in the lua
text editor interface.

Change-Id: I8be9a0c2b22c20fc35adf020f8483aee573ab826
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
David Schulz
2024-09-05 10:54:50 +02:00
parent c8f7ca63ca
commit 6547d1e776
9 changed files with 162 additions and 214 deletions

View File

@@ -5,7 +5,6 @@ add_qtc_plugin(Copilot
copilot.qrc copilot.qrc
copilotclient.cpp copilotclient.h copilotclient.cpp copilotclient.h
copilotconstants.h copilotconstants.h
copilothoverhandler.cpp copilothoverhandler.h
copilotplugin.cpp copilotplugin.cpp
copilotprojectpanel.cpp copilotprojectpanel.h copilotprojectpanel.cpp copilotprojectpanel.h
copilotsettings.cpp copilotsettings.h copilotsettings.cpp copilotsettings.h

View File

@@ -16,8 +16,6 @@ QtcPlugin {
"copilotclient.cpp", "copilotclient.cpp",
"copilotclient.h", "copilotclient.h",
"copilotconstants.h", "copilotconstants.h",
"copilothoverhandler.cpp",
"copilothoverhandler.h",
"copilotplugin.cpp", "copilotplugin.cpp",
"copilotprojectpanel.cpp", "copilotprojectpanel.cpp",
"copilotprojectpanel.h", "copilotprojectpanel.h",

View File

@@ -97,13 +97,7 @@ CopilotClient::CopilotClient(const FilePath &nodePath, const FilePath &distPath)
openDoc(doc); openDoc(doc);
} }
CopilotClient::~CopilotClient() CopilotClient::~CopilotClient() = default;
{
for (IEditor *editor : DocumentModel::editorsForOpenedDocuments()) {
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor))
textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler);
}
}
void CopilotClient::openDocument(TextDocument *document) void CopilotClient::openDocument(TextDocument *document)
{ {
@@ -240,7 +234,6 @@ void CopilotClient::handleCompletions(const GetCompletionRequest::Response &resp
return; return;
editor->insertSuggestion( editor->insertSuggestion(
std::make_unique<TextEditor::CyclicSuggestion>(suggestions, editor->document())); std::make_unique<TextEditor::CyclicSuggestion>(suggestions, editor->document()));
editor->addHoverHandler(&m_hoverHandler);
} }
} }

View File

@@ -3,7 +3,6 @@
#pragma once #pragma once
#include "copilothoverhandler.h"
#include "requests/checkstatus.h" #include "requests/checkstatus.h"
#include "requests/getcompletions.h" #include "requests/getcompletions.h"
#include "requests/seteditorinfo.h" #include "requests/seteditorinfo.h"
@@ -63,7 +62,6 @@ private:
QTimer *timer = nullptr; QTimer *timer = nullptr;
}; };
QHash<TextEditor::TextEditorWidget *, ScheduleData> m_scheduledRequests; QHash<TextEditor::TextEditorWidget *, ScheduleData> m_scheduledRequests;
CopilotHoverHandler m_hoverHandler;
bool m_isAskingForPassword{false}; bool m_isAskingForPassword{false};
}; };

View File

@@ -1,169 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "copilothoverhandler.h"
#include "copilotclient.h"
#include "copilottr.h"
#include <texteditor/textdocument.h>
#include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditor.h>
#include <utils/tooltip/tooltip.h>
#include <utils/utilsicons.h>
#include <QPushButton>
#include <QScopeGuard>
#include <QToolBar>
#include <QToolButton>
using namespace TextEditor;
using namespace LanguageServerProtocol;
using namespace Utils;
namespace Copilot::Internal {
class CopilotCompletionToolTip : public QToolBar
{
public:
CopilotCompletionToolTip(QList<CyclicSuggestion::Data> suggestions,
int currentSuggestion,
TextEditorWidget *editor)
: m_numberLabel(new QLabel)
, m_suggestions(suggestions)
, m_currentSuggestion(std::max(0, std::min<int>(currentSuggestion, suggestions.size() - 1)))
, m_editor(editor)
{
auto prev = addAction(Utils::Icons::PREV_TOOLBAR.icon(),
Tr::tr("Select Previous Copilot Suggestion"));
prev->setEnabled(m_suggestions.size() > 1);
addWidget(m_numberLabel);
auto next = addAction(Utils::Icons::NEXT_TOOLBAR.icon(),
Tr::tr("Select Next Copilot Suggestion"));
next->setEnabled(m_suggestions.size() > 1);
auto apply = addAction(Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString()));
auto applyWord = addAction(
Tr::tr("Apply Word (%1)").arg(QKeySequence(QKeySequence::MoveToNextWord).toString()));
auto applyLine = addAction(Tr::tr("Apply Line"));
connect(prev, &QAction::triggered, this, &CopilotCompletionToolTip::selectPrevious);
connect(next, &QAction::triggered, this, &CopilotCompletionToolTip::selectNext);
connect(apply, &QAction::triggered, this, &CopilotCompletionToolTip::apply);
connect(applyWord, &QAction::triggered, this, &CopilotCompletionToolTip::applyWord);
connect(applyLine, &QAction::triggered, this, &CopilotCompletionToolTip::applyLine);
updateLabels();
}
private:
void updateLabels()
{
m_numberLabel->setText(Tr::tr("%1 of %2")
.arg(m_currentSuggestion + 1)
.arg(m_suggestions.count()));
}
void selectPrevious()
{
--m_currentSuggestion;
if (m_currentSuggestion < 0)
m_currentSuggestion = m_suggestions.size() - 1;
setCurrentSuggestion();
}
void selectNext()
{
++m_currentSuggestion;
if (m_currentSuggestion >= m_suggestions.size())
m_currentSuggestion = 0;
setCurrentSuggestion();
}
void setCurrentSuggestion()
{
updateLabels();
if (TextSuggestion *suggestion = m_editor->currentSuggestion())
suggestion->reset();
m_editor->insertSuggestion(std::make_unique<CyclicSuggestion>(
m_suggestions, m_editor->document(), m_currentSuggestion));
}
void apply()
{
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
if (!suggestion->apply())
return;
}
ToolTip::hide();
}
void applyWord()
{
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
if (!suggestion->applyWord(m_editor))
return;
}
ToolTip::hide();
}
void applyLine()
{
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
if (!suggestion->applyLine(m_editor))
return;
}
ToolTip::hide();
}
QLabel *m_numberLabel;
QList<CyclicSuggestion::Data> m_suggestions;
int m_currentSuggestion = 0;
TextEditorWidget *m_editor;
};
void CopilotHoverHandler::identifyMatch(TextEditorWidget *editorWidget,
int pos,
ReportPriority report)
{
QScopeGuard cleanup([&] { report(Priority_None); });
if (!editorWidget->suggestionVisible())
return;
QTextCursor cursor(editorWidget->document());
cursor.setPosition(pos);
m_block = cursor.block();
auto *suggestion = dynamic_cast<CyclicSuggestion *>(TextDocumentLayout::suggestion(m_block));
if (!suggestion)
return;
const QList<CyclicSuggestion::Data> suggestions = suggestion->suggestions();
if (suggestions.isEmpty())
return;
cleanup.dismiss();
report(Priority_Suggestion);
}
void CopilotHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point)
{
Q_UNUSED(point)
auto *suggestion = dynamic_cast<CyclicSuggestion *>(TextDocumentLayout::suggestion(m_block));
if (!suggestion)
return;
auto tooltipWidget = new CopilotCompletionToolTip(suggestion->suggestions(),
suggestion->currentSuggestion(),
editorWidget);
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft())
- Utils::ToolTip::offsetFromPosition();
pos.ry() -= tooltipWidget->sizeHint().height();
ToolTip::show(pos, tooltipWidget, editorWidget);
}
} // namespace Copilot::Internal

View File

@@ -1,32 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "requests/getcompletions.h"
#include <texteditor/basehoverhandler.h>
#include <QTextBlock>
#pragma once
namespace TextEditor { class TextSuggestion; }
namespace Copilot::Internal {
class CopilotClient;
class CopilotHoverHandler final : public TextEditor::BaseHoverHandler
{
public:
CopilotHoverHandler() = default;
protected:
void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
int pos,
ReportPriority report) final;
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) final;
private:
QTextBlock m_block;
};
} // namespace Copilot::Internal

View File

@@ -10218,6 +10218,7 @@ TextEditorFactory::TextEditorFactory()
: d(new TextEditorFactoryPrivate(this)) : d(new TextEditorFactoryPrivate(this))
{ {
setEditorCreator([]() { return new BaseTextEditor; }); setEditorCreator([]() { return new BaseTextEditor; });
addHoverHandler(new SuggestionHoverHandler);
} }
TextEditorFactory::~TextEditorFactory() TextEditorFactory::~TextEditorFactory()

View File

@@ -5,9 +5,13 @@
#include "textdocumentlayout.h" #include "textdocumentlayout.h"
#include "texteditor.h" #include "texteditor.h"
#include "texteditortr.h"
#include <QToolBar>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
#include <utils/tooltip/tooltip.h>
#include <utils/utilsicons.h>
using namespace Utils; using namespace Utils;
@@ -98,4 +102,142 @@ bool CyclicSuggestion::applyPart(Part part, TextEditorWidget *widget)
return false; return false;
} }
class SuggestionToolTip : public QToolBar
{
public:
SuggestionToolTip(
QList<CyclicSuggestion::Data> suggestions, int currentSuggestion, TextEditorWidget *editor)
: m_numberLabel(new QLabel)
, m_suggestions(suggestions)
, m_currentSuggestion(std::max(0, std::min<int>(currentSuggestion, suggestions.size() - 1)))
, m_editor(editor)
{
auto prev = addAction(
Utils::Icons::PREV_TOOLBAR.icon(), Tr::tr("Select Previous Copilot Suggestion"));
prev->setEnabled(m_suggestions.size() > 1);
addWidget(m_numberLabel);
auto next
= addAction(Utils::Icons::NEXT_TOOLBAR.icon(), Tr::tr("Select Next Copilot Suggestion"));
next->setEnabled(m_suggestions.size() > 1);
auto apply = addAction(Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString()));
auto applyWord = addAction(
Tr::tr("Apply Word (%1)").arg(QKeySequence(QKeySequence::MoveToNextWord).toString()));
auto applyLine = addAction(Tr::tr("Apply Line"));
connect(prev, &QAction::triggered, this, &SuggestionToolTip::selectPrevious);
connect(next, &QAction::triggered, this, &SuggestionToolTip::selectNext);
connect(apply, &QAction::triggered, this, &SuggestionToolTip::apply);
connect(applyWord, &QAction::triggered, this, &SuggestionToolTip::applyWord);
connect(applyLine, &QAction::triggered, this, &SuggestionToolTip::applyLine);
updateLabels();
}
private:
void updateLabels()
{
m_numberLabel->setText(
Tr::tr("%1 of %2").arg(m_currentSuggestion + 1).arg(m_suggestions.count()));
}
void selectPrevious()
{
--m_currentSuggestion;
if (m_currentSuggestion < 0)
m_currentSuggestion = m_suggestions.size() - 1;
setCurrentSuggestion();
}
void selectNext()
{
++m_currentSuggestion;
if (m_currentSuggestion >= m_suggestions.size())
m_currentSuggestion = 0;
setCurrentSuggestion();
}
void setCurrentSuggestion()
{
updateLabels();
if (TextSuggestion *suggestion = m_editor->currentSuggestion())
suggestion->reset();
m_editor->insertSuggestion(std::make_unique<CyclicSuggestion>(
m_suggestions, m_editor->document(), m_currentSuggestion));
}
void apply()
{
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
if (!suggestion->apply())
return;
}
ToolTip::hide();
}
void applyWord()
{
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
if (!suggestion->applyWord(m_editor))
return;
}
ToolTip::hide();
}
void applyLine()
{
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
if (!suggestion->applyLine(m_editor))
return;
}
ToolTip::hide();
}
QLabel *m_numberLabel;
QList<CyclicSuggestion::Data> m_suggestions;
int m_currentSuggestion = 0;
TextEditorWidget *m_editor;
};
void SuggestionHoverHandler::identifyMatch(
TextEditorWidget *editorWidget, int pos, ReportPriority report)
{
QScopeGuard cleanup([&] { report(Priority_None); });
if (!editorWidget->suggestionVisible())
return;
QTextCursor cursor(editorWidget->document());
cursor.setPosition(pos);
m_block = cursor.block();
auto *suggestion = dynamic_cast<CyclicSuggestion *>(TextDocumentLayout::suggestion(m_block));
if (!suggestion)
return;
const QList<CyclicSuggestion::Data> suggestions = suggestion->suggestions();
if (suggestions.isEmpty())
return;
cleanup.dismiss();
report(Priority_Suggestion);
}
void SuggestionHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point)
{
Q_UNUSED(point)
auto *suggestion = dynamic_cast<CyclicSuggestion *>(TextDocumentLayout::suggestion(m_block));
if (!suggestion)
return;
auto tooltipWidget = new SuggestionToolTip(
suggestion->suggestions(), suggestion->currentSuggestion(), editorWidget);
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft())
- Utils::ToolTip::offsetFromPosition();
pos.ry() -= tooltipWidget->sizeHint().height();
ToolTip::show(pos, tooltipWidget, editorWidget);
}
} // namespace TextEditor } // namespace TextEditor

View File

@@ -5,8 +5,11 @@
#include "texteditor_global.h" #include "texteditor_global.h"
#include "basehoverhandler.h"
#include <utils/textutils.h> #include <utils/textutils.h>
#include <QTextBlock>
#include <QTextCursor> #include <QTextCursor>
#include <QTextDocument> #include <QTextDocument>
@@ -67,4 +70,19 @@ private:
QTextDocument *m_sourceDocument = nullptr; QTextDocument *m_sourceDocument = nullptr;
}; };
class SuggestionHoverHandler final : public BaseHoverHandler
{
public:
SuggestionHoverHandler() = default;
protected:
void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
int pos,
ReportPriority report) final;
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) final;
private:
QTextBlock m_block;
};
} // namespace TextEditor } // namespace TextEditor