diff --git a/src/plugins/copilot/CMakeLists.txt b/src/plugins/copilot/CMakeLists.txt index 04b80abc3f3..8feed9d17d5 100644 --- a/src/plugins/copilot/CMakeLists.txt +++ b/src/plugins/copilot/CMakeLists.txt @@ -9,7 +9,6 @@ add_qtc_plugin(Copilot copilotplugin.cpp copilotprojectpanel.cpp copilotprojectpanel.h copilotsettings.cpp copilotsettings.h - copilotsuggestion.cpp copilotsuggestion.h requests/checkstatus.h requests/getcompletions.h requests/signinconfirm.h diff --git a/src/plugins/copilot/copilot.qbs b/src/plugins/copilot/copilot.qbs index 680c29dd14d..b02829f79cf 100644 --- a/src/plugins/copilot/copilot.qbs +++ b/src/plugins/copilot/copilot.qbs @@ -23,8 +23,6 @@ QtcPlugin { "copilotprojectpanel.h", "copilotsettings.cpp", "copilotsettings.h", - "copilotsuggestion.cpp", - "copilotsuggestion.h", "requests/checkstatus.h", "requests/getcompletions.h", "requests/signinconfirm.h", diff --git a/src/plugins/copilot/copilotclient.cpp b/src/plugins/copilot/copilotclient.cpp index 3c47c0e66a6..66463093574 100644 --- a/src/plugins/copilot/copilotclient.cpp +++ b/src/plugins/copilot/copilotclient.cpp @@ -3,7 +3,6 @@ #include "copilotclient.h" #include "copilotsettings.h" -#include "copilotsuggestion.h" #include "copilottr.h" #include @@ -228,10 +227,19 @@ void CopilotClient::handleCompletions(const GetCompletionRequest::Response &resp if (delta > 0) completion.setText(completionText.chopped(delta)); } + auto suggestions = Utils::transform(completions, [](const Completion &c){ + auto toTextPos = [](const LanguageServerProtocol::Position pos){ + return Text::Position{pos.line() + 1, pos.character()}; + }; + + Text::Range range{toTextPos(c.range().start()), toTextPos(c.range().end())}; + Text::Position pos{toTextPos(c.position())}; + return CyclicSuggestion::Data{range, pos, c.text()}; + }); if (completions.isEmpty()) return; editor->insertSuggestion( - std::make_unique(completions, editor->document())); + std::make_unique(suggestions, editor->document())); editor->addHoverHandler(&m_hoverHandler); } } diff --git a/src/plugins/copilot/copilothoverhandler.cpp b/src/plugins/copilot/copilothoverhandler.cpp index 789650e67c2..c1fd7b22367 100644 --- a/src/plugins/copilot/copilothoverhandler.cpp +++ b/src/plugins/copilot/copilothoverhandler.cpp @@ -4,7 +4,6 @@ #include "copilothoverhandler.h" #include "copilotclient.h" -#include "copilotsuggestion.h" #include "copilottr.h" #include @@ -28,21 +27,21 @@ namespace Copilot::Internal { class CopilotCompletionToolTip : public QToolBar { public: - CopilotCompletionToolTip(QList completions, - int currentCompletion, + CopilotCompletionToolTip(QList suggestions, + int currentSuggestion, TextEditorWidget *editor) : m_numberLabel(new QLabel) - , m_completions(completions) - , m_currentCompletion(std::max(0, std::min(currentCompletion, completions.size() - 1))) + , m_suggestions(suggestions) + , m_currentSuggestion(std::max(0, std::min(currentSuggestion, suggestions.size() - 1))) , m_editor(editor) { auto prev = addAction(Utils::Icons::PREV_TOOLBAR.icon(), Tr::tr("Select Previous Copilot Suggestion")); - prev->setEnabled(m_completions.size() > 1); + 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_completions.size() > 1); + next->setEnabled(m_suggestions.size() > 1); auto apply = addAction(Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString())); auto applyWord = addAction( @@ -62,34 +61,33 @@ private: void updateLabels() { m_numberLabel->setText(Tr::tr("%1 of %2") - .arg(m_currentCompletion + 1) - .arg(m_completions.count())); + .arg(m_currentSuggestion + 1) + .arg(m_suggestions.count())); } void selectPrevious() { - --m_currentCompletion; - if (m_currentCompletion < 0) - m_currentCompletion = m_completions.size() - 1; - setCurrentCompletion(); + --m_currentSuggestion; + if (m_currentSuggestion < 0) + m_currentSuggestion = m_suggestions.size() - 1; + setCurrentSuggestion(); } void selectNext() { - ++m_currentCompletion; - if (m_currentCompletion >= m_completions.size()) - m_currentCompletion = 0; - setCurrentCompletion(); + ++m_currentSuggestion; + if (m_currentSuggestion >= m_suggestions.size()) + m_currentSuggestion = 0; + setCurrentSuggestion(); } - void setCurrentCompletion() + void setCurrentSuggestion() { updateLabels(); if (TextSuggestion *suggestion = m_editor->currentSuggestion()) suggestion->reset(); - m_editor->insertSuggestion(std::make_unique(m_completions, - m_editor->document(), - m_currentCompletion)); + m_editor->insertSuggestion(std::make_unique( + m_suggestions, m_editor->document(), m_currentSuggestion)); } void apply() @@ -120,8 +118,8 @@ private: } QLabel *m_numberLabel; - QList m_completions; - int m_currentCompletion = 0; + QList m_suggestions; + int m_currentSuggestion = 0; TextEditorWidget *m_editor; }; @@ -136,13 +134,13 @@ void CopilotHoverHandler::identifyMatch(TextEditorWidget *editorWidget, QTextCursor cursor(editorWidget->document()); cursor.setPosition(pos); m_block = cursor.block(); - auto *suggestion = dynamic_cast(TextDocumentLayout::suggestion(m_block)); + auto *suggestion = dynamic_cast(TextDocumentLayout::suggestion(m_block)); if (!suggestion) return; - const QList completions = suggestion->completions(); - if (completions.isEmpty()) + const QList suggestions = suggestion->suggestions(); + if (suggestions.isEmpty()) return; cleanup.dismiss(); @@ -152,13 +150,13 @@ void CopilotHoverHandler::identifyMatch(TextEditorWidget *editorWidget, void CopilotHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point) { Q_UNUSED(point) - auto *suggestion = dynamic_cast(TextDocumentLayout::suggestion(m_block)); + auto *suggestion = dynamic_cast(TextDocumentLayout::suggestion(m_block)); if (!suggestion) return; - auto tooltipWidget = new CopilotCompletionToolTip(suggestion->completions(), - suggestion->currentCompletion(), + auto tooltipWidget = new CopilotCompletionToolTip(suggestion->suggestions(), + suggestion->currentSuggestion(), editorWidget); const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor()); diff --git a/src/plugins/copilot/copilotplugin.cpp b/src/plugins/copilot/copilotplugin.cpp index 9e7baeab165..8a9ac9a9adf 100644 --- a/src/plugins/copilot/copilotplugin.cpp +++ b/src/plugins/copilot/copilotplugin.cpp @@ -6,7 +6,6 @@ #include "copiloticons.h" #include "copilotprojectpanel.h" #include "copilotsettings.h" -#include "copilotsuggestion.h" #include "copilottr.h" #include @@ -35,21 +34,20 @@ enum Direction { Previous, Next }; static void cycleSuggestion(TextEditor::TextEditorWidget *editor, Direction direction) { QTextBlock block = editor->textCursor().block(); - if (auto suggestion = dynamic_cast( + if (auto suggestion = dynamic_cast( TextEditor::TextDocumentLayout::suggestion(block))) { - int index = suggestion->currentCompletion(); + int index = suggestion->currentSuggestion(); if (direction == Previous) --index; else ++index; if (index < 0) - index = suggestion->completions().count() - 1; - else if (index >= suggestion->completions().count()) + index = suggestion->suggestions().count() - 1; + else if (index >= suggestion->suggestions().count()) index = 0; suggestion->reset(); - editor->insertSuggestion(std::make_unique(suggestion->completions(), - editor->document(), - index)); + editor->insertSuggestion(std::make_unique( + suggestion->suggestions(), editor->document(), index)); } } diff --git a/src/plugins/copilot/copilotsuggestion.cpp b/src/plugins/copilot/copilotsuggestion.cpp deleted file mode 100644 index a935a9f8b9c..00000000000 --- a/src/plugins/copilot/copilotsuggestion.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "copilotsuggestion.h" - -#include - -#include - -using namespace Utils; -using namespace TextEditor; -using namespace LanguageServerProtocol; - -namespace Copilot::Internal { - -CopilotSuggestion::CopilotSuggestion(const QList &completions, - QTextDocument *origin, - int currentCompletion) - : m_completions(completions) - , m_currentCompletion(currentCompletion) -{ - const Completion completion = completions.value(currentCompletion); - const Position start = completion.range().start(); - const Position end = completion.range().end(); - QString text = start.toTextCursor(origin).block().text(); - int length = text.length() - start.character(); - if (start.line() == end.line()) - length = end.character() - start.character(); - text.replace(start.character(), length, completion.text()); - document()->setPlainText(text); - m_start = completion.position().toTextCursor(origin); - m_start.setKeepPositionOnInsert(true); -} - -bool CopilotSuggestion::apply() -{ - reset(); - const Completion completion = m_completions.value(m_currentCompletion); - QTextCursor cursor = completion.range().toSelection(m_start.document()); - cursor.insertText(completion.text()); - return true; -} - -bool CopilotSuggestion::applyWord(TextEditorWidget *widget) -{ - return applyPart(Word, widget); -} - -bool CopilotSuggestion::applyLine(TextEditor::TextEditorWidget *widget) -{ - return applyPart(Line, widget); -} - -void CopilotSuggestion::reset() -{ - m_start.removeSelectedText(); -} - -int CopilotSuggestion::position() -{ - return m_start.selectionEnd(); -} - -bool CopilotSuggestion::applyPart(Part part, TextEditor::TextEditorWidget *widget) -{ - Completion completion = m_completions.value(m_currentCompletion); - const Range range = completion.range(); - const QTextCursor cursor = 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()); - int next = part == Word ? endOfNextWord(text, startPos) : text.indexOf('\n', startPos); - - if (next == -1) - return apply(); - - if (part == Line) - ++next; - QString subText = text.mid(startPos, next - startPos); - if (subText.isEmpty()) - return false; - - currentCursor.insertText(subText); - if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) { - const QString newCompletionText = text.mid(startPos + seperatorPos + 1); - if (!newCompletionText.isEmpty()) { - completion.setText(newCompletionText); - const Position newStart(range.start().line() + subText.count('\n'), 0); - int nextSeperatorPos = newCompletionText.indexOf('\n'); - if (nextSeperatorPos == -1) - nextSeperatorPos = newCompletionText.size(); - const Position newEnd(newStart.line(), nextSeperatorPos); - completion.setRange(Range(newStart, newEnd)); - completion.setPosition(newStart); - widget->insertSuggestion(std::make_unique( - QList{completion}, widget->document(), 0)); - } - } - return false; -} - -} // namespace Copilot::Internal - diff --git a/src/plugins/copilot/copilotsuggestion.h b/src/plugins/copilot/copilotsuggestion.h deleted file mode 100644 index 4684bef12fb..00000000000 --- a/src/plugins/copilot/copilotsuggestion.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once - -#include "requests/getcompletions.h" - -#include -#include - -namespace Copilot::Internal { - -class CopilotSuggestion final : public TextEditor::TextSuggestion -{ -public: - CopilotSuggestion(const QList &completions, - QTextDocument *origin, - int currentCompletion = 0); - - bool apply() final; - bool applyWord(TextEditor::TextEditorWidget *widget) final; - bool applyLine(TextEditor::TextEditorWidget *widget) final; - void reset() final; - int position() final; - - const QList &completions() const { return m_completions; } - int currentCompletion() const { return m_currentCompletion; } - -private: - enum Part {Word, Line}; - bool applyPart(Part part, TextEditor::TextEditorWidget *widget); - - QList m_completions; - int m_currentCompletion = 0; - QTextCursor m_start; -}; -} // namespace Copilot::Internal diff --git a/src/plugins/lua/bindings/texteditor.cpp b/src/plugins/lua/bindings/texteditor.cpp index ae901bb6b21..f1661aa6fed 100644 --- a/src/plugins/lua/bindings/texteditor.cpp +++ b/src/plugins/lua/bindings/texteditor.cpp @@ -119,8 +119,6 @@ public: virtual void reset() override { m_start.removeSelectedText(); } - virtual int position() override { return m_start.selectionEnd(); } - qsizetype size() const { return m_suggestions.size(); } bool isEmpty() const { return m_suggestions.isEmpty(); } diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp index 79c0d3ff8ab..c8c088006cf 100644 --- a/src/plugins/texteditor/textdocument.cpp +++ b/src/plugins/texteditor/textdocument.cpp @@ -407,7 +407,7 @@ QAction *TextDocument::createDiffAgainstCurrentFileAction( void TextDocument::insertSuggestion(std::unique_ptr &&suggestion) { QTextCursor cursor(&d->m_document); - cursor.setPosition(suggestion->position()); + cursor.setPosition(suggestion->currentPosition()); const QTextBlock block = cursor.block(); TextDocumentLayout::userData(block)->insertSuggestion(std::move(suggestion)); TextDocumentLayout::updateSuggestionFormats(block, fontSettings()); diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 292a3950cbb..c7145f60ef0 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -1823,8 +1823,7 @@ void TextEditorWidgetPrivate::insertSuggestion(std::unique_ptr & return; auto cursor = q->textCursor(); - suggestion->setCurrentPosition(cursor.position()); - cursor.setPosition(suggestion->position()); + cursor.setPosition(suggestion->currentPosition()); QTextOption option = suggestion->document()->defaultTextOption(); option.setTabStopDistance(charWidth() * m_document->tabSettings().m_tabSize); suggestion->document()->setDefaultTextOption(option); diff --git a/src/plugins/texteditor/textsuggestion.cpp b/src/plugins/texteditor/textsuggestion.cpp index 78c58a6bacc..7b6c93a559f 100644 --- a/src/plugins/texteditor/textsuggestion.cpp +++ b/src/plugins/texteditor/textsuggestion.cpp @@ -4,6 +4,12 @@ #include "textsuggestion.h" #include "textdocumentlayout.h" +#include "texteditor.h" + +#include +#include + +using namespace Utils; namespace TextEditor { @@ -15,4 +21,81 @@ TextSuggestion::TextSuggestion() TextSuggestion::~TextSuggestion() = default; +CyclicSuggestion::CyclicSuggestion(const QList &suggestions, QTextDocument *sourceDocument, int currentSuggestion) + : m_suggestions(suggestions) + , m_currentSuggestion(currentSuggestion) + , m_sourceDocument(sourceDocument) +{ + if (QTC_GUARD(!suggestions.isEmpty())) { + QTC_ASSERT( + m_currentSuggestion >= 0 && m_currentSuggestion < suggestions.size(), + m_currentSuggestion = 0); + Data current = suggestions.at(m_currentSuggestion); + document()->setPlainText(current.text); + setCurrentPosition(current.position.toPositionInDocument(sourceDocument)); + } +} + +bool CyclicSuggestion::apply() +{ + const Data &suggestion = m_suggestions.value(m_currentSuggestion); + QTextCursor c = suggestion.range.begin.toTextCursor(m_sourceDocument); + c.setPosition(currentPosition(), QTextCursor::KeepAnchor); + c.insertText(suggestion.text); + return true; +} + +bool CyclicSuggestion::applyWord(TextEditorWidget *widget) +{ + return applyPart(Word, widget); +} + +bool CyclicSuggestion::applyLine(TextEditorWidget *widget) +{ + return applyPart(Line, widget); +} + +void CyclicSuggestion::reset() +{ + const Data &suggestion = m_suggestions.value(m_currentSuggestion); + QTextCursor c = suggestion.position.toTextCursor(m_sourceDocument); + c.setPosition(currentPosition(), QTextCursor::KeepAnchor); + c.removeSelectedText(); +} + +bool CyclicSuggestion::applyPart(Part part, TextEditorWidget *widget) +{ + const Data suggestion = m_suggestions.value(m_currentSuggestion); + const Text::Range range = suggestion.range; + const QTextCursor cursor = range.toTextCursor(m_sourceDocument); + QTextCursor currentCursor = widget->textCursor(); + const QString text = suggestion.text; + const int startPos = currentCursor.positionInBlock() - cursor.positionInBlock() + + (cursor.selectionEnd() - cursor.selectionStart()); + int next = part == Word ? endOfNextWord(text, startPos) : text.indexOf('\n', startPos); + + if (next == -1) + return apply(); + + if (part == Line) + ++next; + QString subText = text.mid(startPos, next - startPos); + if (subText.isEmpty()) + return false; + + currentCursor.insertText(subText); + if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) { + const QString newCompletionText = text.mid(startPos + seperatorPos + 1); + if (!newCompletionText.isEmpty()) { + const Text::Position newStart{int(range.begin.line + subText.count('\n')), 0}; + const Text::Position newEnd{newStart.line, int(subText.length() - seperatorPos - 1)}; + const Text::Range newRange{newStart, newEnd}; + const QList newSuggestion{{newRange, newEnd, newCompletionText}}; + widget->insertSuggestion( + std::make_unique(newSuggestion, widget->document(), 0)); + } + } + return false; +} + } // namespace TextEditor diff --git a/src/plugins/texteditor/textsuggestion.h b/src/plugins/texteditor/textsuggestion.h index 5774fb41072..acff9eac1fe 100644 --- a/src/plugins/texteditor/textsuggestion.h +++ b/src/plugins/texteditor/textsuggestion.h @@ -5,6 +5,9 @@ #include "texteditor_global.h" +#include + +#include #include namespace TextEditor { @@ -22,7 +25,6 @@ public: virtual bool applyWord(TextEditorWidget *widget) = 0; virtual bool applyLine(TextEditorWidget *widget) = 0; virtual void reset() = 0; - virtual int position() = 0; int currentPosition() const { return m_currentPosition; } void setCurrentPosition(int position) { m_currentPosition = position; } @@ -34,4 +36,35 @@ private: int m_currentPosition = -1; }; +class TEXTEDITOR_EXPORT CyclicSuggestion : public TextSuggestion +{ +public: + class TEXTEDITOR_EXPORT Data + { + public: + Utils::Text::Range range; + Utils::Text::Position position; + QString text; + }; + + CyclicSuggestion( + const QList &suggestions, QTextDocument *sourceDocument, int currentCompletion = 0); + + bool apply() override; + bool applyWord(TextEditorWidget *widget) override; + bool applyLine(TextEditorWidget *widget) override; + void reset() override; + + QList suggestions() const { return m_suggestions; } + int currentSuggestion() const { return m_currentSuggestion; } + +private: + enum Part {Word, Line}; + bool applyPart(Part part, TextEditor::TextEditorWidget *widget); + + QList m_suggestions; + int m_currentSuggestion = 0; + QTextDocument *m_sourceDocument = nullptr; +}; + } // namespace TextEditor