forked from qt-creator/qt-creator
TextEditor: add generic cyclic suggestion
And use the added suggestion for copilot. Change-Id: I896fa4c74c7099e9c6df483d4f92617da910a733 Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
@@ -9,7 +9,6 @@ add_qtc_plugin(Copilot
|
|||||||
copilotplugin.cpp
|
copilotplugin.cpp
|
||||||
copilotprojectpanel.cpp copilotprojectpanel.h
|
copilotprojectpanel.cpp copilotprojectpanel.h
|
||||||
copilotsettings.cpp copilotsettings.h
|
copilotsettings.cpp copilotsettings.h
|
||||||
copilotsuggestion.cpp copilotsuggestion.h
|
|
||||||
requests/checkstatus.h
|
requests/checkstatus.h
|
||||||
requests/getcompletions.h
|
requests/getcompletions.h
|
||||||
requests/signinconfirm.h
|
requests/signinconfirm.h
|
||||||
|
@@ -23,8 +23,6 @@ QtcPlugin {
|
|||||||
"copilotprojectpanel.h",
|
"copilotprojectpanel.h",
|
||||||
"copilotsettings.cpp",
|
"copilotsettings.cpp",
|
||||||
"copilotsettings.h",
|
"copilotsettings.h",
|
||||||
"copilotsuggestion.cpp",
|
|
||||||
"copilotsuggestion.h",
|
|
||||||
"requests/checkstatus.h",
|
"requests/checkstatus.h",
|
||||||
"requests/getcompletions.h",
|
"requests/getcompletions.h",
|
||||||
"requests/signinconfirm.h",
|
"requests/signinconfirm.h",
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include "copilotclient.h"
|
#include "copilotclient.h"
|
||||||
#include "copilotsettings.h"
|
#include "copilotsettings.h"
|
||||||
#include "copilotsuggestion.h"
|
|
||||||
#include "copilottr.h"
|
#include "copilottr.h"
|
||||||
|
|
||||||
#include <languageclient/languageclientinterface.h>
|
#include <languageclient/languageclientinterface.h>
|
||||||
@@ -228,10 +227,19 @@ void CopilotClient::handleCompletions(const GetCompletionRequest::Response &resp
|
|||||||
if (delta > 0)
|
if (delta > 0)
|
||||||
completion.setText(completionText.chopped(delta));
|
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())
|
if (completions.isEmpty())
|
||||||
return;
|
return;
|
||||||
editor->insertSuggestion(
|
editor->insertSuggestion(
|
||||||
std::make_unique<CopilotSuggestion>(completions, editor->document()));
|
std::make_unique<TextEditor::CyclicSuggestion>(suggestions, editor->document()));
|
||||||
editor->addHoverHandler(&m_hoverHandler);
|
editor->addHoverHandler(&m_hoverHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
#include "copilothoverhandler.h"
|
#include "copilothoverhandler.h"
|
||||||
|
|
||||||
#include "copilotclient.h"
|
#include "copilotclient.h"
|
||||||
#include "copilotsuggestion.h"
|
|
||||||
#include "copilottr.h"
|
#include "copilottr.h"
|
||||||
|
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
@@ -28,21 +27,21 @@ namespace Copilot::Internal {
|
|||||||
class CopilotCompletionToolTip : public QToolBar
|
class CopilotCompletionToolTip : public QToolBar
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CopilotCompletionToolTip(QList<Completion> completions,
|
CopilotCompletionToolTip(QList<CyclicSuggestion::Data> suggestions,
|
||||||
int currentCompletion,
|
int currentSuggestion,
|
||||||
TextEditorWidget *editor)
|
TextEditorWidget *editor)
|
||||||
: m_numberLabel(new QLabel)
|
: m_numberLabel(new QLabel)
|
||||||
, m_completions(completions)
|
, m_suggestions(suggestions)
|
||||||
, m_currentCompletion(std::max(0, std::min<int>(currentCompletion, completions.size() - 1)))
|
, m_currentSuggestion(std::max(0, std::min<int>(currentSuggestion, suggestions.size() - 1)))
|
||||||
, m_editor(editor)
|
, m_editor(editor)
|
||||||
{
|
{
|
||||||
auto prev = addAction(Utils::Icons::PREV_TOOLBAR.icon(),
|
auto prev = addAction(Utils::Icons::PREV_TOOLBAR.icon(),
|
||||||
Tr::tr("Select Previous Copilot Suggestion"));
|
Tr::tr("Select Previous Copilot Suggestion"));
|
||||||
prev->setEnabled(m_completions.size() > 1);
|
prev->setEnabled(m_suggestions.size() > 1);
|
||||||
addWidget(m_numberLabel);
|
addWidget(m_numberLabel);
|
||||||
auto next = addAction(Utils::Icons::NEXT_TOOLBAR.icon(),
|
auto next = addAction(Utils::Icons::NEXT_TOOLBAR.icon(),
|
||||||
Tr::tr("Select Next Copilot Suggestion"));
|
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 apply = addAction(Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString()));
|
||||||
auto applyWord = addAction(
|
auto applyWord = addAction(
|
||||||
@@ -62,34 +61,33 @@ private:
|
|||||||
void updateLabels()
|
void updateLabels()
|
||||||
{
|
{
|
||||||
m_numberLabel->setText(Tr::tr("%1 of %2")
|
m_numberLabel->setText(Tr::tr("%1 of %2")
|
||||||
.arg(m_currentCompletion + 1)
|
.arg(m_currentSuggestion + 1)
|
||||||
.arg(m_completions.count()));
|
.arg(m_suggestions.count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectPrevious()
|
void selectPrevious()
|
||||||
{
|
{
|
||||||
--m_currentCompletion;
|
--m_currentSuggestion;
|
||||||
if (m_currentCompletion < 0)
|
if (m_currentSuggestion < 0)
|
||||||
m_currentCompletion = m_completions.size() - 1;
|
m_currentSuggestion = m_suggestions.size() - 1;
|
||||||
setCurrentCompletion();
|
setCurrentSuggestion();
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectNext()
|
void selectNext()
|
||||||
{
|
{
|
||||||
++m_currentCompletion;
|
++m_currentSuggestion;
|
||||||
if (m_currentCompletion >= m_completions.size())
|
if (m_currentSuggestion >= m_suggestions.size())
|
||||||
m_currentCompletion = 0;
|
m_currentSuggestion = 0;
|
||||||
setCurrentCompletion();
|
setCurrentSuggestion();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCurrentCompletion()
|
void setCurrentSuggestion()
|
||||||
{
|
{
|
||||||
updateLabels();
|
updateLabels();
|
||||||
if (TextSuggestion *suggestion = m_editor->currentSuggestion())
|
if (TextSuggestion *suggestion = m_editor->currentSuggestion())
|
||||||
suggestion->reset();
|
suggestion->reset();
|
||||||
m_editor->insertSuggestion(std::make_unique<CopilotSuggestion>(m_completions,
|
m_editor->insertSuggestion(std::make_unique<CyclicSuggestion>(
|
||||||
m_editor->document(),
|
m_suggestions, m_editor->document(), m_currentSuggestion));
|
||||||
m_currentCompletion));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply()
|
void apply()
|
||||||
@@ -120,8 +118,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
QLabel *m_numberLabel;
|
QLabel *m_numberLabel;
|
||||||
QList<Completion> m_completions;
|
QList<CyclicSuggestion::Data> m_suggestions;
|
||||||
int m_currentCompletion = 0;
|
int m_currentSuggestion = 0;
|
||||||
TextEditorWidget *m_editor;
|
TextEditorWidget *m_editor;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -136,13 +134,13 @@ void CopilotHoverHandler::identifyMatch(TextEditorWidget *editorWidget,
|
|||||||
QTextCursor cursor(editorWidget->document());
|
QTextCursor cursor(editorWidget->document());
|
||||||
cursor.setPosition(pos);
|
cursor.setPosition(pos);
|
||||||
m_block = cursor.block();
|
m_block = cursor.block();
|
||||||
auto *suggestion = dynamic_cast<CopilotSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
auto *suggestion = dynamic_cast<CyclicSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
||||||
|
|
||||||
if (!suggestion)
|
if (!suggestion)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const QList<Completion> completions = suggestion->completions();
|
const QList<CyclicSuggestion::Data> suggestions = suggestion->suggestions();
|
||||||
if (completions.isEmpty())
|
if (suggestions.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
cleanup.dismiss();
|
cleanup.dismiss();
|
||||||
@@ -152,13 +150,13 @@ void CopilotHoverHandler::identifyMatch(TextEditorWidget *editorWidget,
|
|||||||
void CopilotHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point)
|
void CopilotHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point)
|
||||||
{
|
{
|
||||||
Q_UNUSED(point)
|
Q_UNUSED(point)
|
||||||
auto *suggestion = dynamic_cast<CopilotSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
auto *suggestion = dynamic_cast<CyclicSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
||||||
|
|
||||||
if (!suggestion)
|
if (!suggestion)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto tooltipWidget = new CopilotCompletionToolTip(suggestion->completions(),
|
auto tooltipWidget = new CopilotCompletionToolTip(suggestion->suggestions(),
|
||||||
suggestion->currentCompletion(),
|
suggestion->currentSuggestion(),
|
||||||
editorWidget);
|
editorWidget);
|
||||||
|
|
||||||
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
|
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
#include "copiloticons.h"
|
#include "copiloticons.h"
|
||||||
#include "copilotprojectpanel.h"
|
#include "copilotprojectpanel.h"
|
||||||
#include "copilotsettings.h"
|
#include "copilotsettings.h"
|
||||||
#include "copilotsuggestion.h"
|
|
||||||
#include "copilottr.h"
|
#include "copilottr.h"
|
||||||
|
|
||||||
#include <coreplugin/actionmanager/actionmanager.h>
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
@@ -35,21 +34,20 @@ enum Direction { Previous, Next };
|
|||||||
static void cycleSuggestion(TextEditor::TextEditorWidget *editor, Direction direction)
|
static void cycleSuggestion(TextEditor::TextEditorWidget *editor, Direction direction)
|
||||||
{
|
{
|
||||||
QTextBlock block = editor->textCursor().block();
|
QTextBlock block = editor->textCursor().block();
|
||||||
if (auto suggestion = dynamic_cast<CopilotSuggestion *>(
|
if (auto suggestion = dynamic_cast<TextEditor::CyclicSuggestion *>(
|
||||||
TextEditor::TextDocumentLayout::suggestion(block))) {
|
TextEditor::TextDocumentLayout::suggestion(block))) {
|
||||||
int index = suggestion->currentCompletion();
|
int index = suggestion->currentSuggestion();
|
||||||
if (direction == Previous)
|
if (direction == Previous)
|
||||||
--index;
|
--index;
|
||||||
else
|
else
|
||||||
++index;
|
++index;
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
index = suggestion->completions().count() - 1;
|
index = suggestion->suggestions().count() - 1;
|
||||||
else if (index >= suggestion->completions().count())
|
else if (index >= suggestion->suggestions().count())
|
||||||
index = 0;
|
index = 0;
|
||||||
suggestion->reset();
|
suggestion->reset();
|
||||||
editor->insertSuggestion(std::make_unique<CopilotSuggestion>(suggestion->completions(),
|
editor->insertSuggestion(std::make_unique<TextEditor::CyclicSuggestion>(
|
||||||
editor->document(),
|
suggestion->suggestions(), editor->document(), index));
|
||||||
index));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 <texteditor/texteditor.h>
|
|
||||||
|
|
||||||
#include <utils/stringutils.h>
|
|
||||||
|
|
||||||
using namespace Utils;
|
|
||||||
using namespace TextEditor;
|
|
||||||
using namespace LanguageServerProtocol;
|
|
||||||
|
|
||||||
namespace Copilot::Internal {
|
|
||||||
|
|
||||||
CopilotSuggestion::CopilotSuggestion(const QList<Completion> &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<CopilotSuggestion>(
|
|
||||||
QList<Completion>{completion}, widget->document(), 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Copilot::Internal
|
|
||||||
|
|
@@ -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 <texteditor/textdocumentlayout.h>
|
|
||||||
#include <texteditor/texteditor.h>
|
|
||||||
|
|
||||||
namespace Copilot::Internal {
|
|
||||||
|
|
||||||
class CopilotSuggestion final : public TextEditor::TextSuggestion
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CopilotSuggestion(const QList<Completion> &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<Completion> &completions() const { return m_completions; }
|
|
||||||
int currentCompletion() const { return m_currentCompletion; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum Part {Word, Line};
|
|
||||||
bool applyPart(Part part, TextEditor::TextEditorWidget *widget);
|
|
||||||
|
|
||||||
QList<Completion> m_completions;
|
|
||||||
int m_currentCompletion = 0;
|
|
||||||
QTextCursor m_start;
|
|
||||||
};
|
|
||||||
} // namespace Copilot::Internal
|
|
@@ -119,8 +119,6 @@ public:
|
|||||||
|
|
||||||
virtual void reset() override { m_start.removeSelectedText(); }
|
virtual void reset() override { m_start.removeSelectedText(); }
|
||||||
|
|
||||||
virtual int position() override { return m_start.selectionEnd(); }
|
|
||||||
|
|
||||||
qsizetype size() const { return m_suggestions.size(); }
|
qsizetype size() const { return m_suggestions.size(); }
|
||||||
|
|
||||||
bool isEmpty() const { return m_suggestions.isEmpty(); }
|
bool isEmpty() const { return m_suggestions.isEmpty(); }
|
||||||
|
@@ -407,7 +407,7 @@ QAction *TextDocument::createDiffAgainstCurrentFileAction(
|
|||||||
void TextDocument::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
|
void TextDocument::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
|
||||||
{
|
{
|
||||||
QTextCursor cursor(&d->m_document);
|
QTextCursor cursor(&d->m_document);
|
||||||
cursor.setPosition(suggestion->position());
|
cursor.setPosition(suggestion->currentPosition());
|
||||||
const QTextBlock block = cursor.block();
|
const QTextBlock block = cursor.block();
|
||||||
TextDocumentLayout::userData(block)->insertSuggestion(std::move(suggestion));
|
TextDocumentLayout::userData(block)->insertSuggestion(std::move(suggestion));
|
||||||
TextDocumentLayout::updateSuggestionFormats(block, fontSettings());
|
TextDocumentLayout::updateSuggestionFormats(block, fontSettings());
|
||||||
|
@@ -1823,8 +1823,7 @@ void TextEditorWidgetPrivate::insertSuggestion(std::unique_ptr<TextSuggestion> &
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
auto cursor = q->textCursor();
|
auto cursor = q->textCursor();
|
||||||
suggestion->setCurrentPosition(cursor.position());
|
cursor.setPosition(suggestion->currentPosition());
|
||||||
cursor.setPosition(suggestion->position());
|
|
||||||
QTextOption option = suggestion->document()->defaultTextOption();
|
QTextOption option = suggestion->document()->defaultTextOption();
|
||||||
option.setTabStopDistance(charWidth() * m_document->tabSettings().m_tabSize);
|
option.setTabStopDistance(charWidth() * m_document->tabSettings().m_tabSize);
|
||||||
suggestion->document()->setDefaultTextOption(option);
|
suggestion->document()->setDefaultTextOption(option);
|
||||||
|
@@ -4,6 +4,12 @@
|
|||||||
#include "textsuggestion.h"
|
#include "textsuggestion.h"
|
||||||
|
|
||||||
#include "textdocumentlayout.h"
|
#include "textdocumentlayout.h"
|
||||||
|
#include "texteditor.h"
|
||||||
|
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/stringutils.h>
|
||||||
|
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
namespace TextEditor {
|
namespace TextEditor {
|
||||||
|
|
||||||
@@ -15,4 +21,81 @@ TextSuggestion::TextSuggestion()
|
|||||||
|
|
||||||
TextSuggestion::~TextSuggestion() = default;
|
TextSuggestion::~TextSuggestion() = default;
|
||||||
|
|
||||||
|
CyclicSuggestion::CyclicSuggestion(const QList<Data> &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<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
|
||||||
|
widget->insertSuggestion(
|
||||||
|
std::make_unique<CyclicSuggestion>(newSuggestion, widget->document(), 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace TextEditor
|
} // namespace TextEditor
|
||||||
|
@@ -5,6 +5,9 @@
|
|||||||
|
|
||||||
#include "texteditor_global.h"
|
#include "texteditor_global.h"
|
||||||
|
|
||||||
|
#include <utils/textutils.h>
|
||||||
|
|
||||||
|
#include <QTextCursor>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
|
||||||
namespace TextEditor {
|
namespace TextEditor {
|
||||||
@@ -22,7 +25,6 @@ public:
|
|||||||
virtual bool applyWord(TextEditorWidget *widget) = 0;
|
virtual bool applyWord(TextEditorWidget *widget) = 0;
|
||||||
virtual bool applyLine(TextEditorWidget *widget) = 0;
|
virtual bool applyLine(TextEditorWidget *widget) = 0;
|
||||||
virtual void reset() = 0;
|
virtual void reset() = 0;
|
||||||
virtual int position() = 0;
|
|
||||||
|
|
||||||
int currentPosition() const { return m_currentPosition; }
|
int currentPosition() const { return m_currentPosition; }
|
||||||
void setCurrentPosition(int position) { m_currentPosition = position; }
|
void setCurrentPosition(int position) { m_currentPosition = position; }
|
||||||
@@ -34,4 +36,35 @@ private:
|
|||||||
int m_currentPosition = -1;
|
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<Data> &suggestions, QTextDocument *sourceDocument, int currentCompletion = 0);
|
||||||
|
|
||||||
|
bool apply() override;
|
||||||
|
bool applyWord(TextEditorWidget *widget) override;
|
||||||
|
bool applyLine(TextEditorWidget *widget) override;
|
||||||
|
void reset() override;
|
||||||
|
|
||||||
|
QList<Data> suggestions() const { return m_suggestions; }
|
||||||
|
int currentSuggestion() const { return m_currentSuggestion; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum Part {Word, Line};
|
||||||
|
bool applyPart(Part part, TextEditor::TextEditorWidget *widget);
|
||||||
|
|
||||||
|
QList<Data> m_suggestions;
|
||||||
|
int m_currentSuggestion = 0;
|
||||||
|
QTextDocument *m_sourceDocument = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace TextEditor
|
} // namespace TextEditor
|
||||||
|
Reference in New Issue
Block a user