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
|
||||
copilotprojectpanel.cpp copilotprojectpanel.h
|
||||
copilotsettings.cpp copilotsettings.h
|
||||
copilotsuggestion.cpp copilotsuggestion.h
|
||||
requests/checkstatus.h
|
||||
requests/getcompletions.h
|
||||
requests/signinconfirm.h
|
||||
|
@@ -23,8 +23,6 @@ QtcPlugin {
|
||||
"copilotprojectpanel.h",
|
||||
"copilotsettings.cpp",
|
||||
"copilotsettings.h",
|
||||
"copilotsuggestion.cpp",
|
||||
"copilotsuggestion.h",
|
||||
"requests/checkstatus.h",
|
||||
"requests/getcompletions.h",
|
||||
"requests/signinconfirm.h",
|
||||
|
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "copilotclient.h"
|
||||
#include "copilotsettings.h"
|
||||
#include "copilotsuggestion.h"
|
||||
#include "copilottr.h"
|
||||
|
||||
#include <languageclient/languageclientinterface.h>
|
||||
@@ -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<CopilotSuggestion>(completions, editor->document()));
|
||||
std::make_unique<TextEditor::CyclicSuggestion>(suggestions, editor->document()));
|
||||
editor->addHoverHandler(&m_hoverHandler);
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@
|
||||
#include "copilothoverhandler.h"
|
||||
|
||||
#include "copilotclient.h"
|
||||
#include "copilotsuggestion.h"
|
||||
#include "copilottr.h"
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
@@ -28,21 +27,21 @@ namespace Copilot::Internal {
|
||||
class CopilotCompletionToolTip : public QToolBar
|
||||
{
|
||||
public:
|
||||
CopilotCompletionToolTip(QList<Completion> completions,
|
||||
int currentCompletion,
|
||||
CopilotCompletionToolTip(QList<CyclicSuggestion::Data> suggestions,
|
||||
int currentSuggestion,
|
||||
TextEditorWidget *editor)
|
||||
: m_numberLabel(new QLabel)
|
||||
, m_completions(completions)
|
||||
, m_currentCompletion(std::max(0, std::min<int>(currentCompletion, completions.size() - 1)))
|
||||
, m_suggestions(suggestions)
|
||||
, m_currentSuggestion(std::max(0, std::min<int>(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<CopilotSuggestion>(m_completions,
|
||||
m_editor->document(),
|
||||
m_currentCompletion));
|
||||
m_editor->insertSuggestion(std::make_unique<CyclicSuggestion>(
|
||||
m_suggestions, m_editor->document(), m_currentSuggestion));
|
||||
}
|
||||
|
||||
void apply()
|
||||
@@ -120,8 +118,8 @@ private:
|
||||
}
|
||||
|
||||
QLabel *m_numberLabel;
|
||||
QList<Completion> m_completions;
|
||||
int m_currentCompletion = 0;
|
||||
QList<CyclicSuggestion::Data> 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<CopilotSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
||||
auto *suggestion = dynamic_cast<CyclicSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
||||
|
||||
if (!suggestion)
|
||||
return;
|
||||
|
||||
const QList<Completion> completions = suggestion->completions();
|
||||
if (completions.isEmpty())
|
||||
const QList<CyclicSuggestion::Data> 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<CopilotSuggestion *>(TextDocumentLayout::suggestion(m_block));
|
||||
auto *suggestion = dynamic_cast<CyclicSuggestion *>(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());
|
||||
|
@@ -6,7 +6,6 @@
|
||||
#include "copiloticons.h"
|
||||
#include "copilotprojectpanel.h"
|
||||
#include "copilotsettings.h"
|
||||
#include "copilotsuggestion.h"
|
||||
#include "copilottr.h"
|
||||
|
||||
#include <coreplugin/actionmanager/actionmanager.h>
|
||||
@@ -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<CopilotSuggestion *>(
|
||||
if (auto suggestion = dynamic_cast<TextEditor::CyclicSuggestion *>(
|
||||
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<CopilotSuggestion>(suggestion->completions(),
|
||||
editor->document(),
|
||||
index));
|
||||
editor->insertSuggestion(std::make_unique<TextEditor::CyclicSuggestion>(
|
||||
suggestion->suggestions(), editor->document(), 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 int position() override { return m_start.selectionEnd(); }
|
||||
|
||||
qsizetype size() const { return m_suggestions.size(); }
|
||||
|
||||
bool isEmpty() const { return m_suggestions.isEmpty(); }
|
||||
|
@@ -407,7 +407,7 @@ QAction *TextDocument::createDiffAgainstCurrentFileAction(
|
||||
void TextDocument::insertSuggestion(std::unique_ptr<TextSuggestion> &&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());
|
||||
|
@@ -1823,8 +1823,7 @@ void TextEditorWidgetPrivate::insertSuggestion(std::unique_ptr<TextSuggestion> &
|
||||
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);
|
||||
|
@@ -4,6 +4,12 @@
|
||||
#include "textsuggestion.h"
|
||||
|
||||
#include "textdocumentlayout.h"
|
||||
#include "texteditor.h"
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/stringutils.h>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace TextEditor {
|
||||
|
||||
@@ -15,4 +21,81 @@ TextSuggestion::TextSuggestion()
|
||||
|
||||
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
|
||||
|
@@ -5,6 +5,9 @@
|
||||
|
||||
#include "texteditor_global.h"
|
||||
|
||||
#include <utils/textutils.h>
|
||||
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
|
||||
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<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
|
||||
|
Reference in New Issue
Block a user