Copilot: Add insert next word action

Fixes: QTCREATORBUG-28959
Change-Id: Ied53ad5676133e2eb71988ecfcce90c5ad77e3c3
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-04-06 08:24:22 +02:00
parent a8a9bf1713
commit 45c2e3fe58
7 changed files with 91 additions and 9 deletions

View File

@@ -546,4 +546,26 @@ QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QStrin
return splitAtFirst(view, ch); return splitAtFirst(view, ch);
} }
QTCREATOR_UTILS_EXPORT int endOfNextWord(const QString &string, int position)
{
QTC_ASSERT(string.size() > position, return -1);
static const QString wordSeparators = QStringLiteral(" \t\n\r()[]{}<>");
const auto predicate = [](const QChar &c) { return wordSeparators.contains(c); };
auto it = string.begin() + position;
if (predicate(*it))
it = std::find_if_not(it, string.end(), predicate);
if (it == string.end())
return -1;
it = std::find_if(it, string.end(), predicate);
if (it == string.end())
return -1;
return std::distance(string.begin(), it);
}
} // namespace Utils } // namespace Utils

View File

@@ -119,4 +119,6 @@ QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QStrin
QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QStringView &stringView, QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QStringView &stringView,
QChar ch); QChar ch);
QTCREATOR_UTILS_EXPORT int endOfNextWord(const QString &string, int position = 0);
} // namespace Utils } // namespace Utils

View File

@@ -43,11 +43,14 @@ public:
Tr::tr("Select Next Copilot Suggestion")); Tr::tr("Select Next Copilot Suggestion"));
next->setEnabled(m_completions.size() > 1); next->setEnabled(m_completions.size() > 1);
auto apply = addAction(Tr::tr("Apply (Tab)")); 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()));
connect(prev, &QAction::triggered, this, &CopilotCompletionToolTip::selectPrevious); connect(prev, &QAction::triggered, this, &CopilotCompletionToolTip::selectPrevious);
connect(next, &QAction::triggered, this, &CopilotCompletionToolTip::selectNext); connect(next, &QAction::triggered, this, &CopilotCompletionToolTip::selectNext);
connect(apply, &QAction::triggered, this, &CopilotCompletionToolTip::apply); connect(apply, &QAction::triggered, this, &CopilotCompletionToolTip::apply);
connect(applyWord, &QAction::triggered, this, &CopilotCompletionToolTip::applyWord);
updateLabels(); updateLabels();
} }
@@ -88,8 +91,19 @@ private:
void apply() void apply()
{ {
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
suggestion->apply(); if (!suggestion->apply())
return;
}
ToolTip::hide();
}
void applyWord()
{
if (TextSuggestion *suggestion = m_editor->currentSuggestion()) {
if (!suggestion->applyWord(m_editor))
return;
}
ToolTip::hide(); ToolTip::hide();
} }

View File

@@ -3,6 +3,13 @@
#include "copilotsuggestion.h" #include "copilotsuggestion.h"
#include <texteditor/texteditor.h>
#include <utils/stringutils.h>
using namespace Utils;
using namespace TextEditor;
namespace Copilot::Internal { namespace Copilot::Internal {
CopilotSuggestion::CopilotSuggestion(const QList<Completion> &completions, CopilotSuggestion::CopilotSuggestion(const QList<Completion> &completions,
@@ -27,6 +34,29 @@ bool CopilotSuggestion::apply()
return true; return true;
} }
bool CopilotSuggestion::applyWord(TextEditorWidget *widget)
{
const Completion completion = m_completions.value(m_currentCompletion);
const QTextCursor cursor = completion.range().toSelection(m_start.document());
QTextCursor currentCursor = widget->textCursor();
const QString text = completion.text();
const int startPos = currentCursor.positionInBlock() - cursor.positionInBlock()
+ (cursor.selectionEnd() - cursor.selectionStart());
const int next = endOfNextWord(text, startPos);
if (next == -1)
return apply();
// TODO: Allow adding more than one line
QString subText = text.mid(startPos, next - startPos);
subText = subText.left(subText.indexOf('\n'));
if (subText.isEmpty())
return false;
currentCursor.insertText(subText);
return false;
}
void CopilotSuggestion::reset() void CopilotSuggestion::reset()
{ {
m_start.removeSelectedText(); m_start.removeSelectedText();

View File

@@ -5,6 +5,7 @@
#include "requests/getcompletions.h" #include "requests/getcompletions.h"
#include <texteditor/textdocumentlayout.h> #include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditor.h>
namespace Copilot::Internal { namespace Copilot::Internal {
@@ -16,6 +17,7 @@ public:
int currentCompletion = 0); int currentCompletion = 0);
bool apply() final; bool apply() final;
bool applyWord(TextEditor::TextEditorWidget *widget) final;
void reset() final; void reset() final;
int position() final; int position() final;

View File

@@ -47,7 +47,10 @@ class TEXTEDITOR_EXPORT TextSuggestion
public: public:
TextSuggestion(); TextSuggestion();
virtual ~TextSuggestion(); virtual ~TextSuggestion();
// Returns true if the suggestion was applied completely, false if it was only partially applied.
virtual bool apply() = 0; virtual bool apply() = 0;
// Returns true if the suggestion was applied completely, false if it was only partially applied.
virtual bool applyWord(TextEditorWidget *widget) = 0;
virtual void reset() = 0; virtual void reset() = 0;
virtual int position() = 0; virtual int position() = 0;

View File

@@ -2577,6 +2577,21 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
const bool inOverwriteMode = overwriteMode(); const bool inOverwriteMode = overwriteMode();
const bool hasMultipleCursors = cursor.hasMultipleCursors(); const bool hasMultipleCursors = cursor.hasMultipleCursors();
if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(d->m_suggestionBlock)) {
if (e->matches(QKeySequence::MoveToNextWord)) {
e->accept();
if (suggestion->applyWord(this))
d->clearCurrentSuggestion();
return;
} else if (e->modifiers() == Qt::NoModifier
&& (e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab)) {
e->accept();
if (suggestion->apply())
d->clearCurrentSuggestion();
return;
}
}
if (!ro if (!ro
&& (e == QKeySequence::InsertParagraphSeparator && (e == QKeySequence::InsertParagraphSeparator
|| (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))) { || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))) {
@@ -2695,12 +2710,6 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
return; return;
} }
QTextCursor cursor = textCursor(); QTextCursor cursor = textCursor();
if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(d->m_suggestionBlock)) {
suggestion->apply();
d->clearCurrentSuggestion();
e->accept();
return;
}
if (d->m_skipAutoCompletedText && e->key() == Qt::Key_Tab) { if (d->m_skipAutoCompletedText && e->key() == Qt::Key_Tab) {
bool skippedAutoCompletedText = false; bool skippedAutoCompletedText = false;
while (!d->m_autoCompleteHighlightPos.isEmpty() while (!d->m_autoCompleteHighlightPos.isEmpty()