forked from qt-creator/qt-creator
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:
@@ -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");
|
||||||
|
|||||||
@@ -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); }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {});
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
{
|
|
||||||
if (QTextDocument *replacement = replacementDocument(block)) {
|
|
||||||
QTextCursor cursor(replacement);
|
|
||||||
const int position = replacementPosition(block);
|
|
||||||
cursor.setPosition(position);
|
|
||||||
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
|
|
||||||
return cursor.selectedText();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
int position,
|
||||||
const FontSettings &fontSettings)
|
const FontSettings &fontSettings)
|
||||||
{
|
{
|
||||||
if (QTextDocument *replacementDocument = TextDocumentLayout::replacementDocument(block)) {
|
if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
|
||||||
const QString start = block.text().left(position);
|
auto positionInBlock = position - block.position();
|
||||||
const QString end = block.text().mid(position);
|
const QString start = block.text().left(positionInBlock);
|
||||||
const QString replacement = replacementDocument->firstBlock().text();
|
const QString end = block.text().mid(positionInBlock);
|
||||||
if (replacement.startsWith(start) && replacement.endsWith(end)) {
|
const QString replacement = suggestion->document()->firstBlock().text();
|
||||||
userData(block)->setReplacementPosition(position);
|
if (replacement.startsWith(start) && replacement.indexOf(end, start.size()) >= 0) {
|
||||||
TextDocumentLayout::updateReplacementFormats(block, fontSettings);
|
suggestion->setCurrentPosition(position);
|
||||||
|
TextDocumentLayout::updateSuggestionFormats(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
|
||||||
|
|||||||
@@ -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,12 +196,10 @@ 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);
|
|
||||||
static int replacementPosition(const QTextBlock &block);
|
|
||||||
static bool updateReplacement(const QTextBlock &block,
|
|
||||||
int position,
|
int position,
|
||||||
const FontSettings &fontSettings);
|
const FontSettings &fontSettings);
|
||||||
|
|
||||||
|
|||||||
@@ -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,9 +1667,8 @@ 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,19 +4460,19 @@ 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();
|
|
||||||
for (QTextBlock block = replacement->firstBlock();
|
|
||||||
block != lastReplacementBlock && block.isValid();
|
block != lastReplacementBlock && block.isValid();
|
||||||
block = block.next()) {
|
block = block.next()) {
|
||||||
top += replacement->documentLayout()->blockBoundingRect(block).height();
|
top += suggestion->document()
|
||||||
|
->documentLayout()
|
||||||
|
->blockBoundingRect(block)
|
||||||
|
.height();
|
||||||
}
|
}
|
||||||
layout = lastReplacementBlock.layout();
|
layout = lastReplacementBlock.layout();
|
||||||
lineCount = layout->lineCount();
|
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);
|
||||||
int h = 4;
|
int h = 4;
|
||||||
@@ -4739,19 +4732,16 @@ 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)) {
|
|
||||||
deltaPos = TextDocumentLayout::replacementPosition(data.block);
|
|
||||||
const QString trailingText = data.block.text().mid(deltaPos);
|
const QString trailingText = data.block.text().mid(deltaPos);
|
||||||
if (!trailingText.isEmpty()) {
|
if (!trailingText.isEmpty()) {
|
||||||
const int trailingIndex = replacement->firstBlock().text().indexOf(trailingText,
|
const int trailingIndex
|
||||||
deltaPos);
|
= suggestion->document()->firstBlock().text().indexOf(trailingText, deltaPos);
|
||||||
if (trailingIndex >= 0)
|
if (trailingIndex >= 0)
|
||||||
delta = std::max(trailingIndex - deltaPos, 0);
|
delta = std::max(trailingIndex - deltaPos, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < data.context.selections.size(); ++i) {
|
for (int i = 0; i < data.context.selections.size(); ++i) {
|
||||||
const QAbstractTextDocumentLayout::Selection &range = data.context.selections.at(i);
|
const QAbstractTextDocumentLayout::Selection &range = data.context.selections.at(i);
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user