Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Vim Cursor #28

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion include/internal/QCodeEditor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,27 @@ class QCodeEditor : public QTextEdit
*/
void clearSquiggle();

/**
* @brief Enables or disables Vim Like cursor
*/
void setVimCursor(bool value);

/**
* @brief Checks if cursor type is Vim Cursor
*/
bool vimCursor() const;

/**
* @brief Enables or disables current line highlighting
* @note In vim mode this cannot enable line highlighting
*/
void setHighlightCurrentLine(bool enabled);

/**
* @brief Checks if current line is being higlighted in non vim mode
*/
bool isHighlightingCurrentLine() const;

Q_SIGNALS:
/**
* @brief Signal, the font is changed by the wheel event.
Expand Down Expand Up @@ -274,6 +295,13 @@ class QCodeEditor : public QTextEdit
*/
void focusInEvent(QFocusEvent *e) override;

/**
* @brief Method, that's called on focus loss
* It's required for setting block cursor
* in fakevim mode.
*/
void focusOutEvent(QFocusEvent *e) override;

/**
* @brief Method for tooltip generation
*/
Expand Down Expand Up @@ -384,10 +412,12 @@ class QCodeEditor : public QTextEdit
bool m_autoIndentation;
bool m_replaceTab;
bool m_extraBottomMargin;
bool m_vimCursor;
bool m_highlightCurrentLine;
QString m_tabReplace;

QList<QTextEdit::ExtraSelection> extra1, extra2, extra_squiggles;

QRect m_cursorRect;
QVector<SquiggleInformation> m_squiggler;

QVector<Parenthesis> m_parentheses;
Expand Down
220 changes: 160 additions & 60 deletions src/internal/QCodeEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <QFontDatabase>
#include <QMimeData>
#include <QPaintEvent>
#include <QPainter>
#include <QScrollBar>
#include <QShortcut>
#include <QTextBlock>
Expand All @@ -26,9 +27,9 @@

QCodeEditor::QCodeEditor(QWidget *widget)
: QTextEdit(widget), m_highlighter(nullptr), m_syntaxStyle(nullptr), m_lineNumberArea(new QLineNumberArea(this)),
m_completer(nullptr), m_autoIndentation(true), m_replaceTab(true), m_extraBottomMargin(true),
m_tabReplace(QString(4, ' ')), extra1(), extra2(), extra_squiggles(), m_squiggler(),
m_parentheses({{'(', ')'}, {'{', '}'}, {'[', ']'}, {'\"', '\"'}, {'\'', '\''}})
m_completer(nullptr), m_autoIndentation(true), m_replaceTab(true), m_extraBottomMargin(true), m_vimCursor(false),
m_highlightCurrentLine(true), m_tabReplace(QString(4, ' ')), extra1(), extra2(), extra_squiggles(),
m_cursorRect(), m_squiggler(), m_parentheses({{'(', ')'}, {'{', '}'}, {'[', ']'}, {'\"', '\"'}, {'\'', '\''}})
{
initFont();
performConnections();
Expand Down Expand Up @@ -418,87 +419,90 @@ void QCodeEditor::toggleBlockComment()

void QCodeEditor::highlightParenthesis()
{
auto currentSymbol = charUnderCursor();
auto prevSymbol = charUnderCursor(-1);
const auto text = toPlainText();
const int currentPosition = textCursor().position();

for (auto &p : m_parentheses)
{
int direction;

QChar counterSymbol;
QChar activeSymbol;
auto position = textCursor().position();
QChar activeSymbol;
int activePosition;
QChar counterSymbol;
int direction = 0;

if (p.left == currentSymbol)
{
direction = 1;
counterSymbol = p.right;
activeSymbol = currentSymbol;
}
else if (p.right == prevSymbol)
{
direction = -1;
counterSymbol = p.left;
activeSymbol = prevSymbol;
position--;
}
else
{
// check both the current character and the previous character if the cursor is between two characters
for (activePosition = currentPosition; activePosition >= (overwriteMode() ? currentPosition : currentPosition - 1);
--activePosition)
{
if (activePosition < 0 || activePosition >= text.length())
continue;
}

auto counter = 1;
activeSymbol = text[activePosition];

while (counter != 0 && position > 0 && position < (document()->characterCount() - 1))
for (const auto &p : m_parentheses)
{
// Moving position
position += direction;

auto character = document()->characterAt(position);
// Checking symbol under position
if (character == activeSymbol)
if (p.left == p.right)
continue;
if (activeSymbol == p.left)
{
++counter;
direction = 1;
counterSymbol = p.right;
break;
}
else if (character == counterSymbol)
if (activeSymbol == p.right)
{
--counter;
direction = -1;
counterSymbol = p.left;
break;
}
}

auto format = m_syntaxStyle->getFormat("Parentheses");

// Found
if (counter == 0)
{
ExtraSelection selection{};

auto directionEnum = direction < 0 ? QTextCursor::MoveOperation::Left : QTextCursor::MoveOperation::Right;
if (direction != 0)
break;
}

selection.format = format;
selection.cursor = textCursor();
selection.cursor.clearSelection();
selection.cursor.movePosition(directionEnum, QTextCursor::MoveMode::MoveAnchor,
qAbs(textCursor().position() - position));
if (direction == 0) // not a parenthesis
return;

selection.cursor.movePosition(QTextCursor::MoveOperation::Right, QTextCursor::MoveMode::KeepAnchor, 1);
int matchPosition = -1;
int count = 1;
int singleQuoteCounter = 0;
int doubleQuoteCounter = 0;

extra1.append(selection);
for (int i = activePosition + direction; i >= 0 && i < text.length(); i += direction)
{
if (text[i] == "'")
singleQuoteCounter++;
else if (text[i] == "\"")
doubleQuoteCounter++;
else if (text[i] == activeSymbol && singleQuoteCounter % 2 == 0 && doubleQuoteCounter % 2 == 0)
++count;
else if (text[i] == counterSymbol && singleQuoteCounter % 2 == 0 && doubleQuoteCounter % 2 == 0)
--count;
if (count == 0)
{
matchPosition = i;
break;
}
}

if (matchPosition >= 0) // Match found
{
auto addExtra = [&](int pos) {
ExtraSelection selection;
selection.format = m_syntaxStyle->getFormat("Parentheses");
selection.cursor = textCursor();
selection.cursor.clearSelection();
selection.cursor.movePosition(directionEnum, QTextCursor::MoveMode::KeepAnchor, 1);

selection.cursor.setPosition(pos);
selection.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
extra1.append(selection);
}
};

break;
addExtra(activePosition);
addExtra(matchPosition);
}
}

void QCodeEditor::highlightCurrentLine()
{
if (!isReadOnly())
if (m_highlightCurrentLine && !isReadOnly() && !m_vimCursor)
{
QTextEdit::ExtraSelection selection{};

Expand All @@ -507,7 +511,6 @@ void QCodeEditor::highlightCurrentLine()
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();

extra1.append(selection);
}
}
Expand Down Expand Up @@ -544,6 +547,43 @@ void QCodeEditor::paintEvent(QPaintEvent *e)
{
updateLineNumberArea(e->rect());
QTextEdit::paintEvent(e);

if (m_vimCursor)
{
if (!m_cursorRect.isNull() && e->rect().intersects(m_cursorRect))
{
QRect rect = m_cursorRect;
m_cursorRect = QRect();
viewport()->update(rect);
}

// Draw text cursor.
QRect rect = cursorRect();
if (e->rect().intersects(rect))
{
QPainter painter(viewport());

if (overwriteMode())
{
QFontMetrics fm(font());
const int position = textCursor().position();
const QChar c = document()->characterAt(position);
rect.setWidth(fm.horizontalAdvance(c));
painter.setPen(Qt::NoPen);
auto cursorColor = m_syntaxStyle->getFormat("Text").foreground().color();
painter.setBrush(m_syntaxStyle->name() == "Default" ? Qt::white : cursorColor);
painter.setCompositionMode(QPainter::CompositionMode_Difference);
}
else
{
rect.setWidth(cursorWidth());
painter.setPen(m_syntaxStyle->getFormat("Text").foreground().color());
}

painter.drawRect(rect);
m_cursorRect = rect;
}
}
}

int QCodeEditor::getFirstVisibleBlock()
Expand Down Expand Up @@ -642,6 +682,12 @@ void QCodeEditor::keyPressEvent(QKeyEvent *e)

if (!completerSkip)
{
if (m_vimCursor)
{
QTextEdit::keyPressEvent(e);
proceedCompleterEnd(e);
return;
}
if ((e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) && e->modifiers() != Qt::NoModifier)
{
QKeyEvent pureEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
Expand Down Expand Up @@ -740,6 +786,27 @@ void QCodeEditor::keyPressEvent(QKeyEvent *e)
return;
}

// Toggle Overwrite and insert mode in non-vim modes.
if (e->key() == Qt::Key_Insert && !m_vimCursor)
{
setOverwriteMode(!overwriteMode());
if (overwriteMode())
{
QFontMetrics fm(QTextEdit::font());
const int position = QTextEdit::textCursor().position();
const QChar c = QTextEdit::document()->characterAt(position);
setCursorWidth(fm.horizontalAdvance(c));
}
else
{
auto rect = cursorRect();
setCursorWidth(1);
viewport()->update(rect);
}

return;
}

if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::NoModifier && !textCursor().hasSelection())
{
auto pre = charUnderCursor(-1);
Expand Down Expand Up @@ -875,6 +942,29 @@ void QCodeEditor::setTabReplace(bool enabled)
m_replaceTab = enabled;
}

void QCodeEditor::setHighlightCurrentLine(bool enabled)
{
m_highlightCurrentLine = enabled;
}

void QCodeEditor::setVimCursor(bool enabled)
{
m_vimCursor = enabled;

setOverwriteMode(false);
setCursorWidth(enabled ? 0 : 1);
}

bool QCodeEditor::isHighlightingCurrentLine() const
{
return m_highlightCurrentLine;
}

bool QCodeEditor::vimCursor() const
{
return m_vimCursor;
}

bool QCodeEditor::tabReplace() const
{
return m_replaceTab;
Expand Down Expand Up @@ -921,6 +1011,16 @@ void QCodeEditor::focusInEvent(QFocusEvent *e)
QTextEdit::focusInEvent(e);
}

void QCodeEditor::focusOutEvent(QFocusEvent *e)
{
if (m_vimCursor)
{
setOverwriteMode(true); // makes a block cursor when focus is lost
}
coder3101 marked this conversation as resolved.
Show resolved Hide resolved

QTextEdit::focusOutEvent(e);
}

bool QCodeEditor::event(QEvent *event)
{
if (event->type() == QEvent::ToolTip)
Expand Down