TextEditor: filter suggestions while typing

This will remove suggestions that are no longer applyable with the
current editor content.

Change-Id: Ibfc47495d5dda99a9ef2d7fe3f69c83a2d7e3997
Reviewed-by: <lie@spyro-soft.com>
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
David Schulz
2024-10-01 13:27:12 +02:00
parent a0a52941b8
commit 85862d3ca6
6 changed files with 73 additions and 48 deletions

View File

@@ -45,7 +45,6 @@ static void cycleSuggestion(TextEditor::TextEditorWidget *editor, Direction dire
index = suggestion->suggestions().count() - 1; index = suggestion->suggestions().count() - 1;
else if (index >= suggestion->suggestions().count()) else if (index >= suggestion->suggestions().count())
index = 0; index = 0;
suggestion->reset();
editor->insertSuggestion(std::make_unique<TextEditor::CyclicSuggestion>( editor->insertSuggestion(std::make_unique<TextEditor::CyclicSuggestion>(
suggestion->suggestions(), editor->document(), index)); suggestion->suggestions(), editor->document(), index));
} }

View File

@@ -608,26 +608,6 @@ void TextDocumentLayout::updateSuggestionFormats(const QTextBlock &block,
} }
} }
bool TextDocumentLayout::updateSuggestion(const QTextBlock &block,
int position,
const FontSettings &fontSettings)
{
if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
if (position < suggestion->currentPosition())
return false;
const int positionInBlock = position - block.position();
const QString start = block.text().left(positionInBlock);
const QString end = block.text().mid(positionInBlock);
const QString replacement = suggestion->replacementDocument()->firstBlock().text();
if (replacement.startsWith(start) && replacement.indexOf(end, start.size()) >= 0) {
suggestion->setCurrentPosition(position);
TextDocumentLayout::updateSuggestionFormats(block, fontSettings);
return true;
}
}
return false;
}
void TextDocumentLayout::requestExtraAreaUpdate() void TextDocumentLayout::requestExtraAreaUpdate()
{ {
emit updateExtraArea(); emit updateExtraArea();

View File

@@ -188,9 +188,6 @@ public:
static quint8 attributeState(const QTextBlock &block); static quint8 attributeState(const QTextBlock &block);
static void updateSuggestionFormats(const QTextBlock &block, static void updateSuggestionFormats(const QTextBlock &block,
const FontSettings &fontSettings); const FontSettings &fontSettings);
static bool updateSuggestion(const QTextBlock &block,
int position,
const FontSettings &fontSettings);
class TEXTEDITOR_EXPORT FoldValidator class TEXTEDITOR_EXPORT FoldValidator
{ {

View File

@@ -1836,15 +1836,22 @@ void TextEditorWidgetPrivate::updateSuggestion()
{ {
if (!m_suggestionBlock.isValid()) if (!m_suggestionBlock.isValid())
return; return;
if (m_cursors.mainCursor().block() != m_suggestionBlock) { const QTextCursor cursor = m_cursors.mainCursor();
clearCurrentSuggestion(); if (cursor.block() == m_suggestionBlock) {
} else { TextSuggestion *suggestion = TextDocumentLayout::suggestion(m_suggestionBlock);
if (!TextDocumentLayout::updateSuggestion(m_suggestionBlock, if (QTC_GUARD(suggestion)) {
m_cursors.mainCursor().position(), const int pos = cursor.position();
m_document->fontSettings())) { if (pos >= suggestion->currentPosition()) {
clearCurrentSuggestion(); suggestion->setCurrentPosition(pos);
if (suggestion->filterSuggestions(q)) {
TextDocumentLayout::updateSuggestionFormats(
m_suggestionBlock, m_document->fontSettings());
return;
}
}
} }
} }
clearCurrentSuggestion();
} }
void TextEditorWidgetPrivate::clearCurrentSuggestion() void TextEditorWidgetPrivate::clearCurrentSuggestion()

View File

@@ -47,11 +47,11 @@ bool TextSuggestion::applyLine(TextEditorWidget *widget)
return applyPart(Line, widget); return applyPart(Line, widget);
} }
void TextSuggestion::reset() bool TextSuggestion::filterSuggestions(TextEditorWidget *widget)
{ {
QTextCursor c = m_suggestion.position.toTextCursor(sourceDocument()); QTextCursor c = m_suggestion.range.begin.toTextCursor(sourceDocument());
c.setPosition(currentPosition(), QTextCursor::KeepAnchor); c.setPosition(currentPosition(), QTextCursor::KeepAnchor);
c.removeSelectedText(); return m_suggestion.text.startsWith(c.selectedText(), Qt::CaseInsensitive);
} }
bool TextSuggestion::applyPart(Part part, TextEditorWidget *widget) bool TextSuggestion::applyPart(Part part, TextEditorWidget *widget)
@@ -98,6 +98,38 @@ CyclicSuggestion::CyclicSuggestion(
, m_currentSuggestion(currentSuggestion) , m_currentSuggestion(currentSuggestion)
{} {}
bool operator==(const TextSuggestion::Data &lhs, const TextSuggestion::Data &rhs)
{
return lhs.text == rhs.text && lhs.range == rhs.range && lhs.position == rhs.position;
}
bool CyclicSuggestion::filterSuggestions(TextEditorWidget *widget)
{
QList<Data> newSuggestions;
int newIndex = -1;
int currentIndex = 0;
for (auto suggestion : m_suggestions) {
QTextCursor c = suggestion.range.begin.toTextCursor(sourceDocument());
c.setPosition(currentPosition(), QTextCursor::KeepAnchor);
if (suggestion.text.startsWith(c.selectedText())) {
newSuggestions.append(suggestion);
if (currentIndex == m_currentSuggestion)
newIndex = newSuggestions.size() - 1;
} else if (currentIndex == m_currentSuggestion) {
newIndex = 0;
}
++currentIndex;
}
if (newSuggestions.isEmpty())
return false;
if (newSuggestions != m_suggestions) {
widget->insertSuggestion(
std::make_unique<CyclicSuggestion>(newSuggestions, sourceDocument(), newIndex));
}
return true;
}
class SuggestionToolTip : public QToolBar class SuggestionToolTip : public QToolBar
{ {
public: public:
@@ -108,33 +140,41 @@ public:
, m_currentSuggestion(std::max(0, std::min<int>(currentSuggestion, suggestions.size() - 1))) , m_currentSuggestion(std::max(0, std::min<int>(currentSuggestion, suggestions.size() - 1)))
, m_editor(editor) , m_editor(editor)
{ {
auto prev = addAction( m_prev = addAction(Utils::Icons::PREV_TOOLBAR.icon(), Tr::tr("Select Previous Suggestion"));
Utils::Icons::PREV_TOOLBAR.icon(), Tr::tr("Select Previous Suggestion"));
prev->setEnabled(m_suggestions.size() > 1);
addWidget(m_numberLabel); addWidget(m_numberLabel);
auto next m_next = addAction(Utils::Icons::NEXT_TOOLBAR.icon(), Tr::tr("Select Next Suggestion"));
= addAction(Utils::Icons::NEXT_TOOLBAR.icon(), Tr::tr("Select Next Suggestion"));
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(
Tr::tr("Apply Word (%1)").arg(QKeySequence(QKeySequence::MoveToNextWord).toString())); Tr::tr("Apply Word (%1)").arg(QKeySequence(QKeySequence::MoveToNextWord).toString()));
auto applyLine = addAction(Tr::tr("Apply Line")); auto applyLine = addAction(Tr::tr("Apply Line"));
connect(prev, &QAction::triggered, this, &SuggestionToolTip::selectPrevious); connect(m_prev, &QAction::triggered, this, &SuggestionToolTip::selectPrevious);
connect(next, &QAction::triggered, this, &SuggestionToolTip::selectNext); connect(m_next, &QAction::triggered, this, &SuggestionToolTip::selectNext);
connect(apply, &QAction::triggered, this, &SuggestionToolTip::apply); connect(apply, &QAction::triggered, this, &SuggestionToolTip::apply);
connect(applyWord, &QAction::triggered, this, &SuggestionToolTip::applyWord); connect(applyWord, &QAction::triggered, this, &SuggestionToolTip::applyWord);
connect(applyLine, &QAction::triggered, this, &SuggestionToolTip::applyLine); connect(applyLine, &QAction::triggered, this, &SuggestionToolTip::applyLine);
connect(editor->document(), &QTextDocument::contentsChange, this, &SuggestionToolTip::contentsChanged);
updateLabels(); updateSuggestionSelector();
} }
private: private:
void updateLabels() void contentsChanged()
{
if (auto *suggestion = dynamic_cast<CyclicSuggestion *>(m_editor->currentSuggestion())) {
m_suggestions = suggestion->suggestions();
m_currentSuggestion = suggestion->currentSuggestion();
updateSuggestionSelector();
}
}
void updateSuggestionSelector()
{ {
m_numberLabel->setText( m_numberLabel->setText(
Tr::tr("%1 of %2").arg(m_currentSuggestion + 1).arg(m_suggestions.count())); Tr::tr("%1 of %2").arg(m_currentSuggestion + 1).arg(m_suggestions.count()));
m_prev->setEnabled(m_suggestions.size() > 1);
m_next->setEnabled(m_suggestions.size() > 1);
} }
void selectPrevious() void selectPrevious()
@@ -155,9 +195,7 @@ private:
void setCurrentSuggestion() void setCurrentSuggestion()
{ {
updateLabels(); updateSuggestionSelector();
if (TextSuggestion *suggestion = m_editor->currentSuggestion())
suggestion->reset();
m_editor->insertSuggestion(std::make_unique<CyclicSuggestion>( m_editor->insertSuggestion(std::make_unique<CyclicSuggestion>(
m_suggestions, m_editor->document(), m_currentSuggestion)); m_suggestions, m_editor->document(), m_currentSuggestion));
} }
@@ -190,6 +228,8 @@ private:
} }
QLabel *m_numberLabel; QLabel *m_numberLabel;
QAction *m_prev;
QAction *m_next;
QList<TextSuggestion::Data> m_suggestions; QList<TextSuggestion::Data> m_suggestions;
int m_currentSuggestion = 0; int m_currentSuggestion = 0;
TextEditorWidget *m_editor; TextEditorWidget *m_editor;

View File

@@ -35,7 +35,7 @@ public:
// Returns true if the suggestion was applied completely, false if it was only partially applied. // Returns true if the suggestion was applied completely, false if it was only partially applied.
virtual bool applyWord(TextEditorWidget *widget); virtual bool applyWord(TextEditorWidget *widget);
virtual bool applyLine(TextEditorWidget *widget); virtual bool applyLine(TextEditorWidget *widget);
virtual void reset(); virtual bool filterSuggestions(TextEditorWidget *widget);
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; }
@@ -63,6 +63,8 @@ public:
int currentSuggestion() const { return m_currentSuggestion; } int currentSuggestion() const { return m_currentSuggestion; }
private: private:
bool filterSuggestions(TextEditorWidget *widget) override;
QList<Data> m_suggestions; QList<Data> m_suggestions;
int m_currentSuggestion = 0; int m_currentSuggestion = 0;
}; };