TextEditor: introduce text suggestion interface

And also a copilot suggestion implementing that interface that allows
reverting all changes done to a suggestion as well as applying it.

Change-Id: I236c1fc5e5844d19ac606672af54e273e9c42e1c
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
David Schulz
2023-03-27 07:28:48 +02:00
parent 1ee46580ac
commit 8a1e34f084
10 changed files with 190 additions and 147 deletions

View File

@@ -311,6 +311,16 @@ bool Range::overlaps(const Range &range) const
return !isLeftOf(range) && !range.isLeftOf(*this); return !isLeftOf(range) && !range.isLeftOf(*this);
} }
QTextCursor Range::toSelection(QTextDocument *doc) const
{
QTC_ASSERT(doc, return {});
if (!isValid())
return {};
QTextCursor cursor = start().toTextCursor(doc);
cursor.setPosition(end().toPositionInDocument(doc), QTextCursor::KeepAnchor);
return cursor;
}
QString expressionForGlob(QString globPattern) QString expressionForGlob(QString globPattern)
{ {
const QString anySubDir("qtc_anysubdir_id"); const QString anySubDir("qtc_anysubdir_id");

View File

@@ -117,6 +117,8 @@ public:
bool isLeftOf(const Range &other) const bool isLeftOf(const Range &other) const
{ return isEmpty() || other.isEmpty() ? end() < other.start() : end() <= other.start(); } { return isEmpty() || other.isEmpty() ? end() < other.start() : end() <= other.start(); }
QTextCursor toSelection(QTextDocument *doc) const;
bool isValid() const override bool isValid() const override
{ return JsonObject::contains(startKey) && JsonObject::contains(endKey); } { return JsonObject::contains(startKey) && JsonObject::contains(endKey); }
}; };

View File

@@ -87,6 +87,13 @@ public:
return QJsonValue(); return QJsonValue();
} }
QList<T> toListOrEmpty() const
{
if (std::holds_alternative<QList<T>>(*this))
return std::get<QList<T>>(*this);
return {};
}
QList<T> toList() const QList<T> toList() const
{ {
QTC_ASSERT(std::holds_alternative<QList<T>>(*this), return {}); QTC_ASSERT(std::holds_alternative<QList<T>>(*this), return {});

View File

@@ -11,6 +11,7 @@
#include <utils/filepath.h> #include <utils/filepath.h>
#include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
#include <languageserverprotocol/lsptypes.h> #include <languageserverprotocol/lsptypes.h>
@@ -128,6 +129,39 @@ void CopilotClient::requestCompletions(TextEditorWidget *editor)
sendMessage(request); sendMessage(request);
} }
class CopilotSuggestion final : public TextEditor::TextSuggestion
{
public:
CopilotSuggestion(const Completion &completion, QTextDocument *origin)
: m_completion(completion)
{
document()->setPlainText(completion.text());
m_start = completion.position().toTextCursor(origin);
m_start.setKeepPositionOnInsert(true);
setCurrentPosition(m_start.position());
}
bool apply() final
{
reset();
QTextCursor cursor = m_completion.range().toSelection(m_start.document());
cursor.insertText(m_completion.text());
return true;
}
void reset() final
{
m_start.removeSelectedText();
}
int position() final
{
return m_start.position();
}
private:
Completion m_completion;
QTextCursor m_start;
};
void CopilotClient::handleCompletions(const GetCompletionRequest::Response &response, void CopilotClient::handleCompletions(const GetCompletionRequest::Response &response,
TextEditorWidget *editor) TextEditorWidget *editor)
{ {
@@ -142,19 +176,15 @@ void CopilotClient::handleCompletions(const GetCompletionRequest::Response &resp
if (cursors.hasMultipleCursors()) if (cursors.hasMultipleCursors())
return; return;
const QTextCursor cursor = cursors.mainCursor();
if (cursors.hasSelection() || cursors.mainCursor().position() != requestPosition) if (cursors.hasSelection() || cursors.mainCursor().position() != requestPosition)
return; return;
if (const std::optional<GetCompletionResponse> result = response.result()) { if (const std::optional<GetCompletionResponse> result = response.result()) {
LanguageClientArray<Completion> completions = result->completions(); QList<Completion> completions = result->completions().toListOrEmpty();
if (completions.isNull() || completions.toList().isEmpty()) if (completions.isEmpty())
return; return;
editor->insertSuggestion(
const Completion firstCompletion = completions.toList().first(); std::make_unique<CopilotSuggestion>(completions.first(), editor->document()));
const QString content = firstCompletion.text().mid(firstCompletion.position().character());
editor->insertSuggestion(content);
} }
} }

View File

@@ -373,16 +373,13 @@ QAction *TextDocument::createDiffAgainstCurrentFileAction(
return diffAction; return diffAction;
} }
void TextDocument::insertSuggestion(const QString &text, const QTextCursor &cursor) void TextDocument::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
{ {
QTextCursor cursor(&d->m_document);
cursor.setPosition(suggestion->position());
const QTextBlock block = cursor.block(); const QTextBlock block = cursor.block();
const QString blockText = block.text(); TextDocumentLayout::userData(block)->insertSuggestion(std::move(suggestion));
QString replacement = blockText.left(cursor.positionInBlock()) + text; TextDocumentLayout::updateSuggestionFormats(block, fontSettings());
if (!text.contains('\n'))
replacement.append(blockText.mid(cursor.positionInBlock()));
TextDocumentLayout::userData(block)->setReplacement(replacement);
TextDocumentLayout::userData(block)->setReplacementPosition(cursor.positionInBlock());
TextDocumentLayout::updateReplacementFormats(block, fontSettings());
updateLayout(); updateLayout();
} }
@@ -434,7 +431,7 @@ void TextDocument::applyFontSettings()
d->m_fontSettingsNeedsApply = false; d->m_fontSettingsNeedsApply = false;
QTextBlock block = document()->firstBlock(); QTextBlock block = document()->firstBlock();
while (block.isValid()) { while (block.isValid()) {
TextDocumentLayout::updateReplacementFormats(block, fontSettings()); TextDocumentLayout::updateSuggestionFormats(block, fontSettings());
block = block.next(); block = block.next();
} }
updateLayout(); updateLayout();

View File

@@ -37,6 +37,7 @@ class SyntaxHighlighter;
class TabSettings; class TabSettings;
class TextDocumentPrivate; class TextDocumentPrivate;
class TextMark; class TextMark;
class TextSuggestion;
class TypingSettings; class TypingSettings;
using TextMarks = QList<TextMark *>; using TextMarks = QList<TextMark *>;
@@ -145,6 +146,7 @@ public:
const std::function<Utils::FilePath()> &filePath); const std::function<Utils::FilePath()> &filePath);
void insertSuggestion(const QString &text, const QTextCursor &cursor); void insertSuggestion(const QString &text, const QTextCursor &cursor);
void insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion);
#ifdef WITH_TESTS #ifdef WITH_TESTS
void setSilentReload(); void setSilentReload();

View File

@@ -345,22 +345,19 @@ void TextBlockUserData::setCodeFormatterData(CodeFormatterData *data)
m_codeFormatterData = data; m_codeFormatterData = data;
} }
void TextBlockUserData::setReplacement(const QString &replacement) void TextBlockUserData::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
{ {
m_replacement.reset(new QTextDocument(replacement)); m_suggestion = std::move(suggestion);
m_replacement->setDocumentLayout(new TextDocumentLayout(m_replacement.get()));
m_replacement->setDocumentMargin(0);
} }
void TextBlockUserData::setReplacementPosition(int replacementPosition) TextSuggestion *TextBlockUserData::suggestion() const
{ {
m_replacementPosition = replacementPosition; return m_suggestion.get();
} }
void TextBlockUserData::clearReplacement() void TextBlockUserData::clearSuggestion()
{ {
m_replacement.reset(); m_suggestion.release();
m_replacementPosition = -1;
} }
void TextBlockUserData::addMark(TextMark *mark) void TextBlockUserData::addMark(TextMark *mark)
@@ -536,21 +533,29 @@ QByteArray TextDocumentLayout::expectedRawStringSuffix(const QTextBlock &block)
return {}; return {};
} }
void TextDocumentLayout::updateReplacementFormats(const QTextBlock &block, TextSuggestion *TextDocumentLayout::suggestion(const QTextBlock &block)
{
if (TextBlockUserData *userData = textUserData(block))
return userData->suggestion();
return nullptr;
}
void TextDocumentLayout::updateSuggestionFormats(const QTextBlock &block,
const FontSettings &fontSettings) const FontSettings &fontSettings)
{ {
if (QTextDocument *replacement = replacementDocument(block)) { if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
QTextDocument *suggestionDoc = suggestion->document();
const QTextCharFormat replacementFormat = fontSettings.toTextCharFormat( const QTextCharFormat replacementFormat = fontSettings.toTextCharFormat(
TextStyles{C_TEXT, {C_DISABLED_CODE}}); TextStyles{C_TEXT, {C_DISABLED_CODE}});
QList<QTextLayout::FormatRange> formats = block.layout()->formats(); QList<QTextLayout::FormatRange> formats = block.layout()->formats();
QTextCursor cursor(replacement); QTextCursor cursor(suggestionDoc);
cursor.select(QTextCursor::Document); cursor.select(QTextCursor::Document);
cursor.setCharFormat(fontSettings.toTextCharFormat(C_TEXT)); cursor.setCharFormat(fontSettings.toTextCharFormat(C_TEXT));
const int position = replacementPosition(block); const int position = suggestion->currentPosition() - block.position();
cursor.setPosition(position); cursor.setPosition(position);
const QString trailingText = block.text().mid(position); const QString trailingText = block.text().mid(position);
if (!trailingText.isEmpty()) { if (!trailingText.isEmpty()) {
const int trailingIndex = replacement->firstBlock().text().indexOf(trailingText, const int trailingIndex = suggestionDoc->firstBlock().text().indexOf(trailingText,
position); position);
if (trailingIndex >= 0) { if (trailingIndex >= 0) {
cursor.setPosition(trailingIndex, QTextCursor::KeepAnchor); cursor.setPosition(trailingIndex, QTextCursor::KeepAnchor);
@@ -581,45 +586,22 @@ void TextDocumentLayout::updateReplacementFormats(const QTextBlock &block,
} }
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.setCharFormat(replacementFormat); cursor.setCharFormat(replacementFormat);
replacement->firstBlock().layout()->setFormats(formats); suggestionDoc->firstBlock().layout()->setFormats(formats);
} }
} }
QString TextDocumentLayout::replacement(const QTextBlock &block) bool TextDocumentLayout::updateSuggestion(const QTextBlock &block,
int position,
const FontSettings &fontSettings)
{ {
if (QTextDocument *replacement = replacementDocument(block)) { if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
QTextCursor cursor(replacement); auto positionInBlock = position - block.position();
const int position = replacementPosition(block); const QString start = block.text().left(positionInBlock);
cursor.setPosition(position); const QString end = block.text().mid(positionInBlock);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); const QString replacement = suggestion->document()->firstBlock().text();
return cursor.selectedText(); if (replacement.startsWith(start) && replacement.indexOf(end, start.size()) >= 0) {
} suggestion->setCurrentPosition(position);
return {}; TextDocumentLayout::updateSuggestionFormats(block, fontSettings);
}
QTextDocument *TextDocumentLayout::replacementDocument(const QTextBlock &block)
{
TextBlockUserData *userData = textUserData(block);
return userData ? userData->replacement() : nullptr;
}
int TextDocumentLayout::replacementPosition(const QTextBlock &block)
{
TextBlockUserData *userData = textUserData(block);
return userData ? userData->replacementPosition() : -1;
}
bool TextDocumentLayout::updateReplacement(const QTextBlock &block,
int position,
const FontSettings &fontSettings)
{
if (QTextDocument *replacementDocument = TextDocumentLayout::replacementDocument(block)) {
const QString start = block.text().left(position);
const QString end = block.text().mid(position);
const QString replacement = replacementDocument->firstBlock().text();
if (replacement.startsWith(start) && replacement.endsWith(end)) {
userData(block)->setReplacementPosition(position);
TextDocumentLayout::updateReplacementFormats(block, fontSettings);
return true; return true;
} }
} }
@@ -756,11 +738,11 @@ static QRectF replacementBoundingRect(const QTextDocument *replacement)
QRectF TextDocumentLayout::blockBoundingRect(const QTextBlock &block) const QRectF TextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
{ {
if (QTextDocument *replacement = replacementDocument(block)) { if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
// since multiple code paths expects that we have a valid block layout after requesting the // since multiple code paths expects that we have a valid block layout after requesting the
// block bounding rect explicitly create that layout here // block bounding rect explicitly create that layout here
ensureBlockLayout(block); ensureBlockLayout(block);
return replacementBoundingRect(replacement); return replacementBoundingRect(suggestion->document());
} }
QRectF boundingRect = QPlainTextDocumentLayout::blockBoundingRect(block); QRectF boundingRect = QPlainTextDocumentLayout::blockBoundingRect(block);
@@ -851,4 +833,10 @@ void insertSorted(Parentheses &list, const Parenthesis &elem)
list.insert(it, elem); list.insert(it, elem);
} }
TextSuggestion::TextSuggestion()
{
m_replacementDocument.setDocumentLayout(new TextDocumentLayout(&m_replacementDocument));
m_replacementDocument.setDocumentMargin(0);
}
} // namespace TextEditor } // namespace TextEditor

View File

@@ -42,6 +42,24 @@ public:
virtual ~CodeFormatterData(); virtual ~CodeFormatterData();
}; };
class TEXTEDITOR_EXPORT TextSuggestion
{
public:
TextSuggestion();
virtual bool apply() = 0;
virtual void reset() = 0;
virtual int position() = 0;
int currentPosition() const { return m_currentPosition; }
void setCurrentPosition(int position) { m_currentPosition = position; }
QTextDocument *document() { return &m_replacementDocument; }
private:
QTextDocument m_replacementDocument;
int m_currentPosition = -1;
};
class TEXTEDITOR_EXPORT TextBlockUserData : public QTextBlockUserData class TEXTEDITOR_EXPORT TextBlockUserData : public QTextBlockUserData
{ {
public: public:
@@ -126,11 +144,9 @@ public:
QByteArray expectedRawStringSuffix() { return m_expectedRawStringSuffix; } QByteArray expectedRawStringSuffix() { return m_expectedRawStringSuffix; }
void setExpectedRawStringSuffix(const QByteArray &suffix) { m_expectedRawStringSuffix = suffix; } void setExpectedRawStringSuffix(const QByteArray &suffix) { m_expectedRawStringSuffix = suffix; }
void setReplacement(const QString &replacement); void insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion);
void setReplacementPosition(int replacementPosition); TextSuggestion *suggestion() const;
void clearReplacement(); void clearSuggestion();
QTextDocument *replacement() const { return m_replacement.get(); }
int replacementPosition() const { return m_replacementPosition; }
private: private:
TextMarks m_marks; TextMarks m_marks;
@@ -146,7 +162,7 @@ private:
KSyntaxHighlighting::State m_syntaxState; KSyntaxHighlighting::State m_syntaxState;
QByteArray m_expectedRawStringSuffix; // A bit C++-specific, but let's be pragmatic. QByteArray m_expectedRawStringSuffix; // A bit C++-specific, but let's be pragmatic.
std::unique_ptr<QTextDocument> m_replacement; std::unique_ptr<QTextDocument> m_replacement;
int m_replacementPosition = -1; std::unique_ptr<TextSuggestion> m_suggestion;
}; };
@@ -180,14 +196,12 @@ public:
static void setFolded(const QTextBlock &block, bool folded); static void setFolded(const QTextBlock &block, bool folded);
static void setExpectedRawStringSuffix(const QTextBlock &block, const QByteArray &suffix); static void setExpectedRawStringSuffix(const QTextBlock &block, const QByteArray &suffix);
static QByteArray expectedRawStringSuffix(const QTextBlock &block); static QByteArray expectedRawStringSuffix(const QTextBlock &block);
static void updateReplacementFormats(const QTextBlock &block, static TextSuggestion *suggestion(const QTextBlock &block);
static void updateSuggestionFormats(const QTextBlock &block,
const FontSettings &fontSettings); const FontSettings &fontSettings);
static QString replacement(const QTextBlock &block); static bool updateSuggestion(const QTextBlock &block,
static QTextDocument *replacementDocument(const QTextBlock &block); int position,
static int replacementPosition(const QTextBlock &block); const FontSettings &fontSettings);
static bool updateReplacement(const QTextBlock &block,
int position,
const FontSettings &fontSettings);
class TEXTEDITOR_EXPORT FoldValidator class TEXTEDITOR_EXPORT FoldValidator
{ {

View File

@@ -820,7 +820,7 @@ public:
QList<int> m_visualIndentCache; QList<int> m_visualIndentCache;
int m_visualIndentOffset = 0; int m_visualIndentOffset = 0;
void insertSuggestion(const QString &suggestion); void insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion);
void updateSuggestion(); void updateSuggestion();
void clearCurrentSuggestion(); void clearCurrentSuggestion();
QTextBlock m_suggestionBlock; QTextBlock m_suggestionBlock;
@@ -1651,12 +1651,13 @@ void TextEditorWidgetPrivate::handleMoveBlockSelection(QTextCursor::MoveOperatio
q->setMultiTextCursor(MultiTextCursor(cursors)); q->setMultiTextCursor(MultiTextCursor(cursors));
} }
void TextEditorWidgetPrivate::insertSuggestion(const QString &suggestion) void TextEditorWidgetPrivate::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
{ {
clearCurrentSuggestion(); clearCurrentSuggestion();
auto cursor = q->textCursor(); auto cursor = q->textCursor();
cursor.setPosition(suggestion->position());
m_suggestionBlock = cursor.block(); m_suggestionBlock = cursor.block();
m_document->insertSuggestion(suggestion, cursor); m_document->insertSuggestion(std::move(suggestion));
} }
void TextEditorWidgetPrivate::updateSuggestion() void TextEditorWidgetPrivate::updateSuggestion()
@@ -1666,10 +1667,9 @@ void TextEditorWidgetPrivate::updateSuggestion()
if (m_cursors.mainCursor().block() != m_suggestionBlock) { if (m_cursors.mainCursor().block() != m_suggestionBlock) {
clearCurrentSuggestion(); clearCurrentSuggestion();
} else { } else {
const int position = m_cursors.mainCursor().position() - m_suggestionBlock.position(); if (!TextDocumentLayout::updateSuggestion(m_suggestionBlock,
if (!TextDocumentLayout::updateReplacement(m_suggestionBlock, m_cursors.mainCursor().position(),
position, m_document->fontSettings())) {
m_document->fontSettings())) {
clearCurrentSuggestion(); clearCurrentSuggestion();
} }
} }
@@ -1678,7 +1678,7 @@ void TextEditorWidgetPrivate::updateSuggestion()
void TextEditorWidgetPrivate::clearCurrentSuggestion() void TextEditorWidgetPrivate::clearCurrentSuggestion()
{ {
if (TextBlockUserData *userData = TextDocumentLayout::textUserData(m_suggestionBlock)) { if (TextBlockUserData *userData = TextDocumentLayout::textUserData(m_suggestionBlock)) {
userData->clearReplacement(); userData->clearSuggestion();
m_document->updateLayout(); m_document->updateLayout();
} }
m_suggestionBlock = QTextBlock(); m_suggestionBlock = QTextBlock();
@@ -2689,25 +2689,18 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
case Qt::Key_Tab: case Qt::Key_Tab:
case Qt::Key_Backtab: { case Qt::Key_Backtab: {
if (ro) break; if (ro) break;
if (d->m_suggestionBlock.isValid()) {
const int position = TextDocumentLayout::replacementPosition(d->m_suggestionBlock);
if (position >= 0) {
QTextCursor cursor(d->m_suggestionBlock);
cursor.setPosition(d->m_suggestionBlock.position() + position);
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
cursor.insertText(TextDocumentLayout::replacement(d->m_suggestionBlock));
setTextCursor(cursor);
}
d->clearCurrentSuggestion();
e->accept();
return;
}
if (d->m_snippetOverlay->isVisible() && !d->m_snippetOverlay->isEmpty()) { if (d->m_snippetOverlay->isVisible() && !d->m_snippetOverlay->isEmpty()) {
d->snippetTabOrBacktab(e->key() == Qt::Key_Tab); d->snippetTabOrBacktab(e->key() == Qt::Key_Tab);
e->accept(); e->accept();
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()
@@ -4067,10 +4060,10 @@ static TextMarks availableMarks(const TextMarks &marks,
QRectF TextEditorWidgetPrivate::getLastLineLineRect(const QTextBlock &block) QRectF TextEditorWidgetPrivate::getLastLineLineRect(const QTextBlock &block)
{ {
QTextLayout *layout = nullptr; QTextLayout *layout = nullptr;
if (block != m_suggestionBlock) if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block))
layout = suggestion->document()->firstBlock().layout();
else
layout = block.layout(); layout = block.layout();
else if (QTextDocument *replacement = TextDocumentLayout::replacementDocument(block))
layout = replacement->firstBlock().layout();
QTC_ASSERT(layout, layout = block.layout()); QTC_ASSERT(layout, layout = block.layout());
const int lineCount = layout->lineCount(); const int lineCount = layout->lineCount();
@@ -4467,18 +4460,18 @@ void TextEditorWidgetPrivate::paintAdditionalVisualWhitespaces(PaintEventData &d
visualArrow); visualArrow);
} }
if (!nextBlockIsValid) { // paint EOF symbol if (!nextBlockIsValid) { // paint EOF symbol
if (m_suggestionBlock.isValid() && data.block == m_suggestionBlock) { if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(data.block)) {
if (QTextDocument *replacement = TextDocumentLayout::replacementDocument( const QTextBlock lastReplacementBlock = suggestion->document()->lastBlock();
m_suggestionBlock)) { for (QTextBlock block = suggestion->document()->firstBlock();
const QTextBlock lastReplacementBlock = replacement->lastBlock(); block != lastReplacementBlock && block.isValid();
for (QTextBlock block = replacement->firstBlock(); block = block.next()) {
block != lastReplacementBlock && block.isValid(); top += suggestion->document()
block = block.next()) { ->documentLayout()
top += replacement->documentLayout()->blockBoundingRect(block).height(); ->blockBoundingRect(block)
} .height();
layout = lastReplacementBlock.layout();
lineCount = layout->lineCount();
} }
layout = lastReplacementBlock.layout();
lineCount = layout->lineCount();
} }
QTextLine line = layout->lineAt(lineCount - 1); QTextLine line = layout->lineAt(lineCount - 1);
QRectF lineRect = line.naturalTextRect().translated(data.offset.x(), top); QRectF lineRect = line.naturalTextRect().translated(data.offset.x(), top);
@@ -4739,17 +4732,14 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data,
int deltaPos = -1; int deltaPos = -1;
int delta = 0; int delta = 0;
if (m_suggestionBlock == data.block) { if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(data.block)) {
if (QTextDocument *replacement = TextDocumentLayout::replacementDocument( deltaPos = suggestion->currentPosition() - data.block.position();
m_suggestionBlock)) { const QString trailingText = data.block.text().mid(deltaPos);
deltaPos = TextDocumentLayout::replacementPosition(data.block); if (!trailingText.isEmpty()) {
const QString trailingText = data.block.text().mid(deltaPos); const int trailingIndex
if (!trailingText.isEmpty()) { = suggestion->document()->firstBlock().text().indexOf(trailingText, deltaPos);
const int trailingIndex = replacement->firstBlock().text().indexOf(trailingText, if (trailingIndex >= 0)
deltaPos); delta = std::max(trailingIndex - deltaPos, 0);
if (trailingIndex >= 0)
delta = std::max(trailingIndex - deltaPos, 0);
}
} }
} }
@@ -4994,21 +4984,23 @@ void TextEditorWidget::paintBlock(QPainter *painter,
const QVector<QTextLayout::FormatRange> &selections, const QVector<QTextLayout::FormatRange> &selections,
const QRect &clipRect) const const QRect &clipRect) const
{ {
if (QTextDocument *replacement = TextDocumentLayout::replacementDocument(block)) { if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
QTextBlock replacementBlock = replacement->firstBlock(); QTextBlock suggestionBlock = suggestion->document()->firstBlock();
QPointF replacementOffset = offset; QPointF suggestionOffset = offset;
replacementOffset.rx() += document()->documentMargin(); suggestionOffset.rx() += document()->documentMargin();
while (replacementBlock.isValid()) { while (suggestionBlock.isValid()) {
const QVector<QTextLayout::FormatRange> blockSelections const QVector<QTextLayout::FormatRange> blockSelections
= replacementBlock.blockNumber() == 0 ? selections = suggestionBlock.blockNumber() == 0 ? selections
: QVector<QTextLayout::FormatRange>{}; : QVector<QTextLayout::FormatRange>{};
replacementBlock.layout()->draw(painter, suggestionBlock.layout()->draw(painter,
replacementOffset, suggestionOffset,
blockSelections, blockSelections,
clipRect); clipRect);
replacementOffset.ry() suggestionOffset.ry() += suggestion->document()
+= replacement->documentLayout()->blockBoundingRect(replacementBlock).height(); ->documentLayout()
replacementBlock = replacementBlock.next(); ->blockBoundingRect(suggestionBlock)
.height();
suggestionBlock = suggestionBlock.next();
} }
return; return;
} }
@@ -5992,9 +5984,9 @@ void TextEditorWidget::removeHoverHandler(BaseHoverHandler *handler)
d->m_hoverHandlerRunner.handlerRemoved(handler); d->m_hoverHandlerRunner.handlerRemoved(handler);
} }
void TextEditorWidget::insertSuggestion(const QString &suggestion) void TextEditorWidget::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
{ {
d->insertSuggestion(suggestion); d->insertSuggestion(std::move(suggestion));
} }
void TextEditorWidget::clearSuggestion() void TextEditorWidget::clearSuggestion()

View File

@@ -42,15 +42,16 @@ class HighlightScrollBarController;
} }
namespace TextEditor { namespace TextEditor {
class TextDocument;
class TextMark;
class BaseHoverHandler;
class RefactorOverlay;
class SyntaxHighlighter;
class AssistInterface; class AssistInterface;
class BaseHoverHandler;
class CompletionAssistProvider;
class IAssistProvider; class IAssistProvider;
class ICodeStylePreferences; class ICodeStylePreferences;
class CompletionAssistProvider; class RefactorOverlay;
class SyntaxHighlighter;
class TextDocument;
class TextMark;
class TextSuggestion;
using RefactorMarkers = QList<RefactorMarker>; using RefactorMarkers = QList<RefactorMarker>;
using TextMarks = QList<TextMark *>; using TextMarks = QList<TextMark *>;
@@ -470,7 +471,7 @@ public:
void addHoverHandler(BaseHoverHandler *handler); void addHoverHandler(BaseHoverHandler *handler);
void removeHoverHandler(BaseHoverHandler *handler); void removeHoverHandler(BaseHoverHandler *handler);
void insertSuggestion(const QString &suggestion); void insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion);
void clearSuggestion(); void clearSuggestion();
bool suggestionVisible() const; bool suggestionVisible() const;