Skip to content

Commit

Permalink
Add support of extcmdline
Browse files Browse the repository at this point in the history
Adds a basic implementation of the external Neovim commandline. Utilizes basic
Qt Widgets (QLineEdit, QTextEdit) to display a floating command prompt.
  • Loading branch information
jgehrig committed Jan 15, 2021
1 parent 8e167e1 commit 886f407
Show file tree
Hide file tree
Showing 14 changed files with 704 additions and 14 deletions.
3 changes: 2 additions & 1 deletion src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ else()
)
endif()

add_subdirectory(cmdline)
add_subdirectory(shellwidget)

include(GNUInstallDirs)
Expand All @@ -50,7 +51,7 @@ add_library(neovim-qt-gui
treeview.cpp
${SRCS_PLATFORM}
${NEOVIM_RCC_SOURCES})
target_link_libraries(neovim-qt-gui ${QTLIBS} qshellwidget neovim-qt)
target_link_libraries(neovim-qt-gui ${QTLIBS} extcmdline qshellwidget neovim-qt)

if(APPLE)
set_property(SOURCE app.cpp PROPERTY COMPILE_DEFINITIONS
Expand Down
20 changes: 20 additions & 0 deletions src/gui/cmdline/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
project(qcommandline)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

find_package(Qt5Widgets REQUIRED)

if (WIN32 AND USE_STATIC_QT)
add_definitions(-DUSE_STATIC_QT)
endif ()

set(SOURCES
blockdisplay.cpp
linemodel.cpp
extcmdlinewidget.cpp
)

add_library(extcmdline STATIC ${SOURCES})
target_link_libraries(extcmdline Qt5::Widgets)
target_include_directories(extcmdline PUBLIC ../shellwidget)

38 changes: 38 additions & 0 deletions src/gui/cmdline/blockdisplay.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "blockdisplay.h"

#include <QList>
#include <QDebug>
#include <QStringList>

namespace NeovimQt { namespace Cmdline {

BlockDisplay::BlockDisplay()
{
setVisible(false);
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
setReadOnly(true);
setFocusPolicy(Qt::NoFocus);
}

QSize BlockDisplay::sizeHint() const
{
QSize parentSizeHint = Super::sizeHint();
int padding = contentsMargins().top() + contentsMargins().bottom();
parentSizeHint.setHeight(document()->size().height() + padding);
return parentSizeHint;
}

int BlockDisplay::GetMaxLineLength() const
{
QStringList allLines = toPlainText().split('\n');

int maxChars = 0;
for (const auto& line : allLines) {
maxChars = qMax(line.size(), maxChars);
}

return maxChars;
}

} } // namespace NeovimQt::Cmdline
17 changes: 17 additions & 0 deletions src/gui/cmdline/blockdisplay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <QTextEdit>

namespace NeovimQt { namespace Cmdline {

class BlockDisplay : public QTextEdit {
using Super = QTextEdit;

public:
BlockDisplay();

virtual QSize sizeHint() const override;
virtual int GetMaxLineLength() const;
};

} } // namespace NeovimQt::Cmdline
294 changes: 294 additions & 0 deletions src/gui/cmdline/extcmdlinewidget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
#include "extcmdlinewidget.h"

#include <QDebug>
#include <QCoreApplication>

namespace NeovimQt { namespace Cmdline {

ExtCmdlineWidget::ExtCmdlineWidget(ShellWidget& parent)
{
setParent(&parent);
setVisible(false);

m_cmdTextBoxFrame = new QFrame();
m_cmdTextBoxFrame->setFrameShape(QFrame::StyledPanel);
m_cmdTextBoxFrame->setFrameShadow(QFrame::Raised);
QVBoxLayout* frameLayout = new QVBoxLayout(m_cmdTextBoxFrame);

m_cmdTextBox = new ShellWidget();
frameLayout->addWidget(m_cmdTextBox);

m_cmdBlockText = new BlockDisplay();
m_cmdTextBox->setIgnoreFocus(true);

m_vLayout = new QVBoxLayout();
m_vLayout->addWidget(m_cmdBlockText);
m_vLayout->addWidget(m_cmdTextBoxFrame);
setLayout(m_vLayout);

// The size of this widget must change when text content changes.
connect(m_cmdTextBox, SIGNAL(textChanged(QString)), this,
SLOT(resizeLineEditToContent()));
}

void ExtCmdlineWidget::handleCmdlineShow(
const QVariantList& content,
int pos,
const QString& firstc,
const QString& prompt,
int indent,
int level)
{
ShellWidget* parentShellWidget{ static_cast<ShellWidget*>(parentWidget()) };

m_cmdTextBox->CopyCursorStyle(parentShellWidget->getCursor());

QString cmdlineContent;

LineModel lineModel{ content, pos, firstc, prompt, indent, level };
if (level > m_model.size()) {
m_model.append(std::move(lineModel));
}
else {
m_model[level - 1] = std::move(lineModel);
}

m_cmdTextBox->clearShell();

const QString& text{ m_model.last().getPromptText() };

auto getShellSize = [&]() noexcept
{
const int cellSize{ m_cmdTextBox->cellSize().width() };
const int columns{ m_cmdTextBox->size().width() / cellSize };
const int rows{ text.size() / columns + 1 };

return QSize{ std::max(columns, 1), rows};
};

const QSize cmdSize{ getShellSize() };
m_cmdTextBox->resizeShell(cmdSize);

for (int i=0;i<cmdSize.height();i++) {
int startPos = i * cmdSize.width();
QString row{ text.mid(startPos, cmdSize.width()) };
m_cmdTextBox->put(row, i, 0);
}

const int cols{ cmdSize.width() };

m_cmdTextBox->setNeovimCursor(pos / cols, pos % cols + 1);
m_cmdTextBox->update();

updateGeometry();
show();
}

void ExtCmdlineWidget::handleCmdlinePos(
int pos,
int level)
{
if (level > m_model.size()) {
qDebug() << "Invalid value for 'level'" << level;
return;
}

m_model[level - 1].m_position = pos;
Cursor cursor;
//setCursorPosition(pos);
qDebug() << "Handle Cursor Position";
m_cmdTextBox->setNeovimCursor(0, pos);
m_cmdTextBox->resizeShell(1, m_model[level -1].getPromptText().size());
//m_cmdTextBox->setCursorColors(Qt::blue, Qt::red);
//m_cmdTextBox->setCursorStuff();
update();
}

void ExtCmdlineWidget::handleCmdlineSpecialChar(
const QString& c,
bool shift,
int level)
{
if (level > m_model.size()) {
qDebug() << "Invalid value for 'level'" << level;
}

LineModel& line = m_model[level - 1];

// Special characters are inserted in two steps
if (shift) {
// Step One: insert a placeholder character.
line.m_content.insert(line.m_position, c);
}
else {
// Step Two: replace the placeholder with the desired character.
line.m_content.replace(line.m_position, 1, c);
}

m_cmdTextBox->put(line.getPromptText(),0,0); // FIXME
}

void ExtCmdlineWidget::handleCmdlineHide()
{
if (!m_model.empty()) {
m_model.pop_back();
}

hide();
}

void ExtCmdlineWidget::handleCmdlineBlockShow(const QVariantList& lines)
{
QString blockText;
for (const auto& varLine: lines) {
QVariantList line = varLine.toList();
qDebug() << line;
for (const auto& varSegment : line) {
QVariantList segment = varSegment.toList();
const QString segmentText = segment.at(1).toString();

blockText += segmentText;
}
}
m_cmdBlockText->setText(blockText);
m_cmdBlockText->updateGeometry();
m_cmdBlockText->show();
}

void ExtCmdlineWidget::handleCmdlineBlockAppend(const QVariantList& line)
{
for (const auto& varSegment : line) {
QVariantList segment = varSegment.toList();
const QString segmentText = segment.at(1).toString();

m_cmdBlockText->append(segmentText);
}

m_cmdBlockText->updateGeometry();
}

void ExtCmdlineWidget::handleCmdlineBlockHide()
{
m_cmdBlockText->hide();
}

void ExtCmdlineWidget::resizeLineEditToContent()
{
updateGeometry();
}

void ExtCmdlineWidget::updateGeometry()
{
if (!parentWidget()) {
qDebug() << "No parentWidget, cannot update size/position";
return;
}

ShellWidget* parentShellWidget{ static_cast<ShellWidget*>(parentWidget()) };

const int maxWidth = parentShellWidget->width() * 0.75;
const int maxHeight = parentShellWidget->height() * 0.75;

const QSize sizeHintThis = sizeHint();
const int width = qMin(sizeHintThis.width(), maxWidth);
const int height = qMin(sizeHintThis.height(), maxHeight);

const int anchorX = (parentShellWidget->width() - width) / 2;
const int anchorY = (parentShellWidget->height() - height) / 2;

setGeometry(anchorX, anchorY, width, height);
setMaximumWidth(maxWidth);
setMaximumHeight(maxHeight);
QWidget::updateGeometry();
}

void ExtCmdlineWidget::setCursorPosition(int pos)
{
if (!parentWidget()) {
qDebug() << "No parentWidget, cannot update cursor position";
return;
}

ShellWidget* parentShellWidget{ static_cast<ShellWidget*>(parentWidget()) };

// Neovim cursor position is set by inverting the foreground/background colors, Qt cursor hidden.
const int posCursorReal = pos + m_model.back().m_prompt.size() + m_model.back().m_indent + 1;
QTextCharFormat fmtInverse;
fmtInverse.setBackground(parentShellWidget->foreground());
fmtInverse.setForeground(parentShellWidget->background());
//m_cmdTextBox->setTextFormat({ { posCursorReal, 1, fmtInverse} });

// Ensures ':' is shown when users scrolls full left
if (pos == 0) {
m_cmdTextBox->setNeovimCursor(0,0);
return;
}

// Ensure the entire nvim cursor is visble by setting the Qt cursor position.
// Moving left: left edge of cursor. Moving right: right edge of cursor.
static int last_pos = 0;

int cursorPositionQt = posCursorReal;
if (last_pos <= pos) {
++cursorPositionQt;
}
m_cmdTextBox->setNeovimCursor(0,cursorPositionQt);

last_pos = pos;
}

int ExtCmdlineWidget::getMaxPromptLength() const
{
int maxLineLength = 0;
for (const auto& line : m_model) {
maxLineLength = qMax(line.getPromptText().size(), maxLineLength);
}

return qMax(m_cmdBlockText->GetMaxLineLength(), maxLineLength);
}

QSize ExtCmdlineWidget::sizeHint() const
{
QSize sizeHint = QFrame::sizeHint();

QFontMetrics fm = fontMetrics();

const int maxLineLength = getMaxPromptLength();

const int widthHint = fm.averageCharWidth() * (maxLineLength + 2) +
layout()->contentsMargins().left() + layout()->contentsMargins().right();

constexpr int minWidth = 300;

sizeHint.setWidth(qMax(widthHint, minWidth));

return sizeHint;
}

void ExtCmdlineWidget::updatePalette()
{
if (!parentWidget()) {
qDebug() << "No parentWidget, cannot set palette";
return;
}

ShellWidget* parentShellWidget{ static_cast<ShellWidget*>(parentWidget()) };

m_palette.setColor(QPalette::Base, parentShellWidget->background());
m_palette.setColor(QPalette::Text, parentShellWidget->foreground());
m_palette.setColor(QPalette::Background, parentShellWidget->foreground());

m_cmdBlockText->setPalette(m_palette);

m_cmdTextBox->setBackground(parentShellWidget->background());
m_cmdTextBox->setForeground(parentShellWidget->foreground());
m_cmdTextBox->setStyleSheet(".ShellWidget{background-color: red; border: 1px solid black; border-radius: 10px;}");
}

void ExtCmdlineWidget::setFont(const QFont &font)
{
m_cmdBlockText->setFont(font);
//m_cmdTextBox->setFont(font);
}

} } // namespace NeovimQt::Cmdline
Loading

0 comments on commit 886f407

Please sign in to comment.