The editor now has context aware completions
This commit is contained in:
parent
1a1bbd0838
commit
960967c8a6
18 changed files with 1984 additions and 0 deletions
|
|
@ -1,5 +1,6 @@
|
|||
#include "CodeEditor.h"
|
||||
#include "EditorGutter.h"
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
#include <QTextBlock>
|
||||
#include <QDebug>
|
||||
|
|
@ -124,9 +125,125 @@ bool CodeEditor::lineHasError(int blockNumber) const
|
|||
return errorLines.count(blockNumber) > 0;
|
||||
}
|
||||
|
||||
void CodeEditor::setCompletionCallback(EditorCompletionCallback callback)
|
||||
{
|
||||
m_completionCallback = std::move(callback);
|
||||
if (!m_completionCallback)
|
||||
hideCompletions();
|
||||
}
|
||||
|
||||
void CodeEditor::showCompletions()
|
||||
{
|
||||
updateCompletions(true);
|
||||
}
|
||||
|
||||
void CodeEditor::hideCompletions()
|
||||
{
|
||||
if (m_completionPopup)
|
||||
m_completionPopup->hide();
|
||||
}
|
||||
|
||||
bool CodeEditor::updateCompletions(bool explicitRequest)
|
||||
{
|
||||
if (!m_completionCallback) {
|
||||
hideCompletions();
|
||||
return false;
|
||||
}
|
||||
const int pos = textCursor().position();
|
||||
EditorCompletionResult result = m_completionCallback(toPlainText(), pos);
|
||||
bool show = !result.items.empty();
|
||||
if (show && !explicitRequest) {
|
||||
// do not bother the user while nothing identifier like has been typed
|
||||
if (result.replaceLen == 0 && !result.hasQualifier)
|
||||
show = false;
|
||||
// the only candidate is what has been typed already
|
||||
else if (result.items.size() == 1 && result.replaceLen > 0) {
|
||||
QTextCursor c = textCursor();
|
||||
c.setPosition(result.replaceStart);
|
||||
c.setPosition(result.replaceStart + result.replaceLen, QTextCursor::KeepAnchor);
|
||||
if (c.selectedText() == result.items[0].insertText)
|
||||
show = false;
|
||||
}
|
||||
}
|
||||
if (!show) {
|
||||
hideCompletions();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_completionPopup) {
|
||||
m_completionPopup = new CompletionPopup(this);
|
||||
connect(m_completionPopup, &CompletionPopup::itemAccepted, this, &CodeEditor::acceptCompletion);
|
||||
}
|
||||
m_completionResult = std::move(result);
|
||||
m_completionPopup->setFont(font());
|
||||
m_completionPopup->setItems(m_completionResult.items);
|
||||
|
||||
QTextCursor c = textCursor();
|
||||
c.setPosition(m_completionResult.replaceStart);
|
||||
QRect r = cursorRect(c);
|
||||
m_completionPopup->showAt(viewport()->mapToGlobal(r.bottomLeft()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CodeEditor::acceptCompletion(int row)
|
||||
{
|
||||
if (row < 0 || row >= static_cast<int>(m_completionResult.items.size()))
|
||||
return;
|
||||
const QString text = m_completionResult.items[row].insertText;
|
||||
QTextCursor c = textCursor();
|
||||
c.beginEditBlock();
|
||||
c.setPosition(m_completionResult.replaceStart);
|
||||
c.setPosition(m_completionResult.replaceStart + m_completionResult.replaceLen, QTextCursor::KeepAnchor);
|
||||
c.insertText(text);
|
||||
c.endEditBlock();
|
||||
setTextCursor(c);
|
||||
hideCompletions();
|
||||
}
|
||||
|
||||
void CodeEditor::focusOutEvent(QFocusEvent *e)
|
||||
{
|
||||
hideCompletions();
|
||||
QPlainTextEdit::focusOutEvent(e);
|
||||
}
|
||||
|
||||
void CodeEditor::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
hideCompletions();
|
||||
QPlainTextEdit::mousePressEvent(e);
|
||||
}
|
||||
|
||||
void CodeEditor::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
auto k = e->key();
|
||||
const bool popupVisible = m_completionPopup && m_completionPopup->isVisible();
|
||||
if (popupVisible) {
|
||||
switch (k) {
|
||||
case Qt::Key_Down:
|
||||
m_completionPopup->moveSelection(1);
|
||||
return;
|
||||
case Qt::Key_Up:
|
||||
m_completionPopup->moveSelection(-1);
|
||||
return;
|
||||
case Qt::Key_PageDown:
|
||||
m_completionPopup->moveSelection(10);
|
||||
return;
|
||||
case Qt::Key_PageUp:
|
||||
m_completionPopup->moveSelection(-10);
|
||||
return;
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Tab:
|
||||
acceptCompletion(m_completionPopup->currentRow());
|
||||
return;
|
||||
case Qt::Key_Escape:
|
||||
hideCompletions();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (k == Qt::Key_Space && e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
showCompletions();
|
||||
return;
|
||||
}
|
||||
switch (k) {
|
||||
case Qt::Key_Tab:
|
||||
if (indentSelection(true))
|
||||
|
|
@ -155,6 +272,20 @@ void CodeEditor::keyPressEvent(QKeyEvent *e)
|
|||
break;
|
||||
}
|
||||
QPlainTextEdit::keyPressEvent(e);
|
||||
|
||||
if (m_completionCallback) {
|
||||
if (popupVisible) {
|
||||
if (k == Qt::Key_Backspace || k == Qt::Key_Delete || !e->text().isEmpty())
|
||||
updateCompletions(false);
|
||||
else if (k == Qt::Key_Left || k == Qt::Key_Right || k == Qt::Key_Home || k == Qt::Key_End)
|
||||
hideCompletions();
|
||||
}
|
||||
else if (!e->text().isEmpty() && !e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
const QChar ch = e->text()[0];
|
||||
if (ch.isLetterOrNumber() || ch == QLatin1Char('_') || ch == QLatin1Char('.') || ch == QLatin1Char('"'))
|
||||
updateCompletions(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue