forked from qt-creator/qt-creator
LUA: TextEditor bindings refactor of CyclicSuggestion and SuggestionToolTip
Change-Id: Iba0f68a7ff9510224d696005301cfea5b16e4ecf Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
@@ -22,11 +22,7 @@ namespace {
|
|||||||
class Suggestion
|
class Suggestion
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Suggestion(
|
Suggestion(Text::Position start, Text::Position end, Text::Position position, const QString &text)
|
||||||
Text::Position start,
|
|
||||||
Text::Position end,
|
|
||||||
Text::Position position,
|
|
||||||
const QString &text)
|
|
||||||
: m_start(start)
|
: m_start(start)
|
||||||
, m_end(end)
|
, m_end(end)
|
||||||
, m_position(position)
|
, m_position(position)
|
||||||
@@ -52,8 +48,7 @@ QTextCursor toTextCursor(QTextDocument *doc, const Text::Position &position)
|
|||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
QTextCursor toSelection(
|
QTextCursor toSelection(QTextDocument *doc, const Text::Position &start, const Text::Position &end)
|
||||||
QTextDocument *doc, const Text::Position &start, const Text::Position &end)
|
|
||||||
{
|
{
|
||||||
QTC_ASSERT(doc, return {});
|
QTC_ASSERT(doc, return {});
|
||||||
QTextCursor cursor = toTextCursor(doc, start);
|
QTextCursor cursor = toTextCursor(doc, start);
|
||||||
@@ -62,20 +57,27 @@ QTextCursor toSelection(
|
|||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CyclicSuggestion : public TextEditor::TextSuggestion
|
class CyclicSuggestion : public QObject, public TextEditor::TextSuggestion
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CyclicSuggestion(
|
CyclicSuggestion(
|
||||||
const QList<Suggestion> &suggestions, QTextDocument *origin, int current_suggestion = 0)
|
const QList<Suggestion> &suggestions,
|
||||||
: m_current_suggestion(current_suggestion)
|
TextEditor::TextDocument *origin_document,
|
||||||
|
int current_suggestion = 0,
|
||||||
|
bool is_locked = false)
|
||||||
|
: m_currentSuggestion(current_suggestion)
|
||||||
, m_suggestions(suggestions)
|
, m_suggestions(suggestions)
|
||||||
|
, m_originDocument(origin_document)
|
||||||
|
, m_locked(is_locked)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(current_suggestion < suggestions.size(), return);
|
QTC_ASSERT(current_suggestion < m_suggestions.size(), return);
|
||||||
const auto &suggestion = m_suggestions.at(m_current_suggestion);
|
const auto &suggestion = m_suggestions.at(m_currentSuggestion);
|
||||||
const auto start = suggestion.start();
|
const auto start = suggestion.start();
|
||||||
const auto end = suggestion.end();
|
const auto end = suggestion.end();
|
||||||
|
|
||||||
QString text = toTextCursor(origin, start).block().text();
|
QString text = toTextCursor(origin_document->document(), start).block().text();
|
||||||
int length = text.length() - start.column;
|
int length = text.length() - start.column;
|
||||||
if (start.line == end.line)
|
if (start.line == end.line)
|
||||||
length = end.column - start.column;
|
length = end.column - start.column;
|
||||||
@@ -83,16 +85,16 @@ public:
|
|||||||
text.replace(start.column, length, suggestion.text());
|
text.replace(start.column, length, suggestion.text());
|
||||||
document()->setPlainText(text);
|
document()->setPlainText(text);
|
||||||
|
|
||||||
m_start = toTextCursor(origin, suggestion.position());
|
m_start = toTextCursor(origin_document->document(), suggestion.position());
|
||||||
m_start.setKeepPositionOnInsert(true);
|
m_start.setKeepPositionOnInsert(true);
|
||||||
setCurrentPosition(m_start.position());
|
setCurrentPosition(m_start.position());
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool apply() override
|
virtual bool apply() override
|
||||||
{
|
{
|
||||||
QTC_ASSERT(m_current_suggestion < m_suggestions.size(), return false);
|
QTC_ASSERT(m_currentSuggestion < m_suggestions.size(), return false);
|
||||||
reset();
|
reset();
|
||||||
const auto &suggestion = m_suggestions.at(m_current_suggestion);
|
const auto &suggestion = m_suggestions.at(m_currentSuggestion);
|
||||||
QTextCursor cursor = toSelection(m_start.document(), suggestion.start(), suggestion.end());
|
QTextCursor cursor = toSelection(m_start.document(), suggestion.start(), suggestion.end());
|
||||||
cursor.insertText(suggestion.text());
|
cursor.insertText(suggestion.text());
|
||||||
return true;
|
return true;
|
||||||
@@ -101,8 +103,10 @@ 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(TextEditor::TextEditorWidget *widget) override
|
virtual bool applyWord(TextEditor::TextEditorWidget *widget) override
|
||||||
{
|
{
|
||||||
QTC_ASSERT(m_current_suggestion < m_suggestions.size(), return false);
|
QTC_ASSERT(m_currentSuggestion < m_suggestions.size(), return true);
|
||||||
const auto &suggestion = m_suggestions.at(m_current_suggestion);
|
|
||||||
|
lockCurrentSuggestion();
|
||||||
|
const auto &suggestion = m_suggestions.at(m_currentSuggestion);
|
||||||
QTextCursor cursor = toSelection(m_start.document(), suggestion.start(), suggestion.end());
|
QTextCursor cursor = toSelection(m_start.document(), suggestion.start(), suggestion.end());
|
||||||
QTextCursor currentCursor = widget->textCursor();
|
QTextCursor currentCursor = widget->textCursor();
|
||||||
const QString text = suggestion.text();
|
const QString text = suggestion.text();
|
||||||
@@ -127,42 +131,99 @@ public:
|
|||||||
|
|
||||||
virtual int position() override { return m_start.selectionEnd(); }
|
virtual int position() override { return m_start.selectionEnd(); }
|
||||||
|
|
||||||
const QList<Suggestion> &suggestions() const { return m_suggestions; }
|
qsizetype size() const { return m_suggestions.size(); }
|
||||||
|
|
||||||
int currentSuggestion() const { return m_current_suggestion; }
|
bool isEmpty() const { return m_suggestions.isEmpty(); }
|
||||||
|
|
||||||
|
bool isLocked() const { return m_locked; }
|
||||||
|
|
||||||
|
int currentSuggestion() const { return m_currentSuggestion; }
|
||||||
|
|
||||||
|
void selectPrevious()
|
||||||
|
{
|
||||||
|
if (m_suggestions.size() <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_currentSuggestion = (m_currentSuggestion - 1 + m_suggestions.size())
|
||||||
|
% m_suggestions.size();
|
||||||
|
emit update();
|
||||||
|
refreshTextEditorSuggestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectNext()
|
||||||
|
{
|
||||||
|
if (m_suggestions.size() <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_currentSuggestion = (m_currentSuggestion + 1) % m_suggestions.size();
|
||||||
|
emit update();
|
||||||
|
refreshTextEditorSuggestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void update();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_current_suggestion;
|
// Be causious with this function, it should be the last called in the chain
|
||||||
|
// since it replaces this object.
|
||||||
|
// Potential alternative solution: implement update on TextDocument site if possible.
|
||||||
|
void refreshTextEditorSuggestion()
|
||||||
|
{
|
||||||
|
// Reinserting itself for update purpose.
|
||||||
|
m_originDocument->insertSuggestion(std::make_unique<CyclicSuggestion>(
|
||||||
|
m_suggestions, m_originDocument, m_currentSuggestion, m_locked));
|
||||||
|
}
|
||||||
|
|
||||||
|
void lockCurrentSuggestion()
|
||||||
|
{
|
||||||
|
m_locked = true;
|
||||||
|
if (m_suggestions.size() <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_suggestions = QList<Suggestion>{m_suggestions.at(m_currentSuggestion)};
|
||||||
|
m_currentSuggestion = 0;
|
||||||
|
emit update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_currentSuggestion;
|
||||||
QTextCursor m_start;
|
QTextCursor m_start;
|
||||||
QList<Suggestion> m_suggestions;
|
QList<Suggestion> m_suggestions;
|
||||||
};
|
TextEditor::TextDocument *m_originDocument;
|
||||||
|
bool m_locked = false;
|
||||||
|
}; // class CyclicSuggestion
|
||||||
|
|
||||||
class SuggestionToolTip : public QToolBar
|
class SuggestionToolTip : public QToolBar
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SuggestionToolTip(
|
SuggestionToolTip(QTextBlock block, TextEditor::TextEditorWidget *editor)
|
||||||
const QList<Suggestion> &suggestions,
|
|
||||||
int currentSuggestion,
|
|
||||||
TextEditor::TextEditorWidget *editor)
|
|
||||||
: m_numberLabel(new QLabel)
|
: m_numberLabel(new QLabel)
|
||||||
, m_suggestions(suggestions)
|
|
||||||
, m_currentSuggestion(std::max(0, std::min<int>(currentSuggestion, suggestions.size() - 1)))
|
|
||||||
, m_editor(editor)
|
, m_editor(editor)
|
||||||
|
, m_block(block)
|
||||||
{
|
{
|
||||||
QAction *prev
|
auto suggestion = currentSuggestion();
|
||||||
|
QTC_ASSERT(suggestion, return);
|
||||||
|
const auto suggestions_count = suggestion->size();
|
||||||
|
|
||||||
|
m_prev
|
||||||
= addAction(Utils::Icons::PREV_TOOLBAR.icon(), Lua::Tr::tr("Select Previous Suggestion"));
|
= addAction(Utils::Icons::PREV_TOOLBAR.icon(), Lua::Tr::tr("Select Previous Suggestion"));
|
||||||
prev->setEnabled(m_suggestions.size() > 1);
|
m_prev->setEnabled(suggestions_count > 1);
|
||||||
addWidget(m_numberLabel);
|
addWidget(m_numberLabel);
|
||||||
QAction *next
|
m_next = addAction(Utils::Icons::NEXT_TOOLBAR.icon(), Lua::Tr::tr("Select Next Suggestion"));
|
||||||
= addAction(Utils::Icons::NEXT_TOOLBAR.icon(), Lua::Tr::tr("Select Next Suggestion"));
|
m_next->setEnabled(suggestions_count > 1);
|
||||||
next->setEnabled(m_suggestions.size() > 1);
|
|
||||||
|
|
||||||
auto apply = addAction(Lua::Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString()));
|
auto apply = addAction(Lua::Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString()));
|
||||||
auto applyWord = addAction(Lua::Tr::tr("Apply Word (%1)")
|
auto applyWord = addAction(Lua::Tr::tr("Apply Word (%1)")
|
||||||
.arg(QKeySequence(QKeySequence::MoveToNextWord).toString()));
|
.arg(QKeySequence(QKeySequence::MoveToNextWord).toString()));
|
||||||
|
|
||||||
connect(prev, &QAction::triggered, this, &SuggestionToolTip::selectPrevious);
|
connect(m_prev, &QAction::triggered, [this]() {
|
||||||
connect(next, &QAction::triggered, this, &SuggestionToolTip::selectNext);
|
if (auto cs = currentSuggestion())
|
||||||
|
cs->selectPrevious();
|
||||||
|
});
|
||||||
|
connect(m_next, &QAction::triggered, [this]() {
|
||||||
|
if (auto cs = currentSuggestion())
|
||||||
|
cs->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);
|
||||||
|
|
||||||
@@ -170,58 +231,69 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
CyclicSuggestion *currentSuggestion()
|
||||||
|
{
|
||||||
|
CyclicSuggestion *cyclic_suggestion = dynamic_cast<CyclicSuggestion *>(
|
||||||
|
TextEditor::TextDocumentLayout::suggestion(m_block));
|
||||||
|
|
||||||
|
if (!cyclic_suggestion)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
if (cyclic_suggestion != m_connectedSuggestion) {
|
||||||
|
connect(
|
||||||
|
cyclic_suggestion,
|
||||||
|
&CyclicSuggestion::update,
|
||||||
|
this,
|
||||||
|
&SuggestionToolTip::updateLabels);
|
||||||
|
m_connectedSuggestion = cyclic_suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cyclic_suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
void updateLabels()
|
void updateLabels()
|
||||||
{
|
{
|
||||||
m_numberLabel->setText(
|
if (auto cs = currentSuggestion())
|
||||||
Lua::Tr::tr("%1 of %2").arg(m_currentSuggestion + 1).arg(m_suggestions.count()));
|
m_numberLabel->setText(
|
||||||
}
|
Lua::Tr::tr("%1 of %2").arg(cs->currentSuggestion() + 1).arg(cs->size()));
|
||||||
|
|
||||||
void selectPrevious()
|
|
||||||
{
|
|
||||||
m_currentSuggestion = (m_currentSuggestion - 1 + m_suggestions.size())
|
|
||||||
% m_suggestions.size();
|
|
||||||
setCurrentSuggestion();
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectNext()
|
|
||||||
{
|
|
||||||
m_currentSuggestion = (m_currentSuggestion + 1) % m_suggestions.size();
|
|
||||||
setCurrentSuggestion();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCurrentSuggestion()
|
|
||||||
{
|
|
||||||
updateLabels();
|
|
||||||
if (TextEditor::TextSuggestion *suggestion = m_editor->currentSuggestion())
|
|
||||||
suggestion->reset();
|
|
||||||
|
|
||||||
m_editor->insertSuggestion(std::make_unique<CyclicSuggestion>(
|
|
||||||
m_suggestions, m_editor->document(), m_currentSuggestion));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply()
|
void apply()
|
||||||
{
|
{
|
||||||
if (TextEditor::TextSuggestion *suggestion = m_editor->currentSuggestion()) {
|
if (auto cs = currentSuggestion()) {
|
||||||
if (!suggestion->apply())
|
if (!cs->apply())
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ToolTip::hide();
|
Utils::ToolTip::hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void lockOnSingleSuggestion()
|
||||||
|
{
|
||||||
|
m_prev->setEnabled(false);
|
||||||
|
m_next->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
void applyWord()
|
void applyWord()
|
||||||
{
|
{
|
||||||
if (TextEditor::TextSuggestion *suggestion = m_editor->currentSuggestion()) {
|
if (auto cs = currentSuggestion()) {
|
||||||
if (!suggestion->applyWord(m_editor))
|
if (cs->size() > 1)
|
||||||
|
lockOnSingleSuggestion();
|
||||||
|
|
||||||
|
if (!cs->applyWord(m_editor))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::ToolTip::hide();
|
Utils::ToolTip::hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
QLabel *m_numberLabel;
|
QLabel *m_numberLabel;
|
||||||
QList<Suggestion> m_suggestions;
|
|
||||||
int m_currentSuggestion;
|
|
||||||
TextEditor::TextEditorWidget *m_editor;
|
TextEditor::TextEditorWidget *m_editor;
|
||||||
};
|
QAction *m_prev = nullptr;
|
||||||
|
QAction *m_next = nullptr;
|
||||||
|
QTextBlock m_block;
|
||||||
|
CyclicSuggestion *m_connectedSuggestion = nullptr;
|
||||||
|
}; // class SuggestionToolTip
|
||||||
|
|
||||||
class SuggestionHoverHandler final : public TextEditor::BaseHoverHandler
|
class SuggestionHoverHandler final : public TextEditor::BaseHoverHandler
|
||||||
{
|
{
|
||||||
@@ -242,7 +314,7 @@ protected:
|
|||||||
|
|
||||||
auto *cyclic_suggestion = dynamic_cast<CyclicSuggestion *>(
|
auto *cyclic_suggestion = dynamic_cast<CyclicSuggestion *>(
|
||||||
TextEditor::TextDocumentLayout::suggestion(m_block));
|
TextEditor::TextDocumentLayout::suggestion(m_block));
|
||||||
if (!cyclic_suggestion || cyclic_suggestion->suggestions().isEmpty())
|
if (!cyclic_suggestion || cyclic_suggestion->isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
cleanup.dismiss();
|
cleanup.dismiss();
|
||||||
@@ -258,9 +330,7 @@ protected:
|
|||||||
if (!cyclic_suggestion)
|
if (!cyclic_suggestion)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto tooltipWidget = new SuggestionToolTip(
|
auto tooltipWidget = new SuggestionToolTip(m_block, editorWidget);
|
||||||
cyclic_suggestion->suggestions(), cyclic_suggestion->currentSuggestion(), editorWidget);
|
|
||||||
|
|
||||||
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
|
const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor());
|
||||||
QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft())
|
QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft())
|
||||||
- Utils::ToolTip::offsetFromPosition();
|
- Utils::ToolTip::offsetFromPosition();
|
||||||
@@ -452,10 +522,12 @@ void setupTextEditorModule()
|
|||||||
if (!widget)
|
if (!widget)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
widget->insertSuggestion(
|
|
||||||
std::make_unique<CyclicSuggestion>(suggestions, document->document()));
|
|
||||||
|
|
||||||
static SuggestionHoverHandler hover_handler;
|
static SuggestionHoverHandler hover_handler;
|
||||||
|
|
||||||
|
widget->removeHoverHandler(&hover_handler);
|
||||||
|
widget->clearSuggestion();
|
||||||
|
|
||||||
|
widget->insertSuggestion(std::make_unique<CyclicSuggestion>(suggestions, document));
|
||||||
widget->addHoverHandler(&hover_handler);
|
widget->addHoverHandler(&hover_handler);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user