Skip to content

Commit 819b127

Browse files
committed
Add support of extcmdline
Adds a basic implementation of the external Neovim commandline. Utilizes basic Qt Widgets (QLineEdit, QTextEdit) to display a floating command prompt.
1 parent fc27a4e commit 819b127

File tree

13 files changed

+662
-3
lines changed

13 files changed

+662
-3
lines changed

src/gui/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ elseif(APPLE)
1717
set(MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
1818
endif()
1919

20+
add_subdirectory(cmdline)
2021
add_subdirectory(shellwidget)
2122

2223
include(GNUInstallDirs)
@@ -26,7 +27,7 @@ add_library(neovim-qt-gui shell.cpp input.cpp treeview.cpp errorwidget.cpp mainw
2627
popupmenumodel.cpp
2728
popupmenu.cpp
2829
${NEOVIM_RCC_SOURCES})
29-
target_link_libraries(neovim-qt-gui qshellwidget neovim-qt)
30+
target_link_libraries(neovim-qt-gui qshellwidget extcmdline neovim-qt)
3031

3132
if(NOT APPLE)
3233
set_property(SOURCE app.cpp PROPERTY COMPILE_DEFINITIONS

src/gui/cmdline/CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
project(qcommandline)
2+
3+
set(CMAKE_INCLUDE_CURRENT_DIR ON)
4+
5+
find_package(Qt5Widgets REQUIRED)
6+
7+
if (WIN32 AND USE_STATIC_QT)
8+
add_definitions(-DUSE_STATIC_QT)
9+
endif ()
10+
11+
set(SOURCES
12+
blockdisplay.cpp
13+
linemodel.cpp
14+
extcmdlinewidget.cpp
15+
qrichlineedit.cpp
16+
)
17+
18+
add_library(extcmdline STATIC ${SOURCES})
19+
target_link_libraries(extcmdline Qt5::Widgets)
20+
target_include_directories(extcmdline PUBLIC ../shellwidget)
21+

src/gui/cmdline/blockdisplay.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "blockdisplay.h"
2+
3+
#include <QList>
4+
#include <QDebug>
5+
#include <QStringList>
6+
7+
namespace NeovimQt { namespace Cmdline {
8+
9+
BlockDisplay::BlockDisplay()
10+
{
11+
setVisible(false);
12+
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
13+
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
14+
setReadOnly(true);
15+
setFocusPolicy(Qt::NoFocus);
16+
}
17+
18+
QSize BlockDisplay::sizeHint() const
19+
{
20+
QSize parentSizeHint = Super::sizeHint();
21+
int padding = contentsMargins().top() + contentsMargins().bottom();
22+
parentSizeHint.setHeight(document()->size().height() + padding);
23+
return parentSizeHint;
24+
}
25+
26+
int BlockDisplay::GetMaxLineLength() const
27+
{
28+
QStringList allLines = toPlainText().split('\n');
29+
30+
int maxChars = 0;
31+
for (const auto& line : allLines) {
32+
maxChars = qMax(line.size(), maxChars);
33+
}
34+
35+
return maxChars;
36+
}
37+
38+
} } // namespace NeovimQt::Cmdline

src/gui/cmdline/blockdisplay.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <QTextEdit>
4+
5+
namespace NeovimQt { namespace Cmdline {
6+
7+
class BlockDisplay : public QTextEdit {
8+
using Super = QTextEdit;
9+
10+
public:
11+
BlockDisplay();
12+
13+
virtual QSize sizeHint() const override;
14+
virtual int GetMaxLineLength() const;
15+
};
16+
17+
} } // namespace NeovimQt::Cmdline
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#include "extcmdlinewidget.h"
2+
3+
#include <QDebug>
4+
#include <QCoreApplication>
5+
6+
namespace NeovimQt { namespace Cmdline {
7+
8+
ExtCmdlineWidget::ExtCmdlineWidget(ShellWidget* parent)
9+
{
10+
setParent(parent);
11+
setVisible(false);
12+
13+
m_cmdTextBox = new QRichLineEdit();
14+
m_cmdBlockText = new BlockDisplay();
15+
16+
m_vLayout = new QVBoxLayout();
17+
m_vLayout->addWidget(m_cmdBlockText);
18+
m_vLayout->addWidget(m_cmdTextBox);
19+
setLayout(m_vLayout);
20+
21+
// The size of this widget must change when text content changes.
22+
connect(m_cmdTextBox, SIGNAL(textChanged(QString)), this,
23+
SLOT(resizeLineEditToContent()));
24+
}
25+
26+
void ExtCmdlineWidget::handleCmdlineShow(
27+
const QVariantList& content,
28+
int pos,
29+
const QString& firstc,
30+
const QString& prompt,
31+
int indent,
32+
int level)
33+
{
34+
QString cmdlineContent;
35+
36+
LineModel lineModel{ content, pos, firstc, prompt, indent, level };
37+
if (level > m_model.size()) {
38+
m_model.append(std::move(lineModel));
39+
}
40+
else {
41+
m_model[level - 1] = std::move(lineModel);
42+
}
43+
44+
m_cmdTextBox->setText(m_model.last().getPromptText());
45+
46+
updateGeometry();
47+
show();
48+
49+
setCursorPosition(pos);
50+
}
51+
52+
void ExtCmdlineWidget::handleCmdlinePos(
53+
int pos,
54+
int level)
55+
{
56+
if (level > m_model.size()) {
57+
qDebug() << "Invalid value for 'level'" << level;
58+
return;
59+
}
60+
61+
m_model[level - 1].m_position = pos;
62+
setCursorPosition(pos);
63+
}
64+
65+
void ExtCmdlineWidget::handleCmdlineSpecialChar(
66+
const QString& c,
67+
bool shift,
68+
int level)
69+
{
70+
if (level > m_model.size()) {
71+
qDebug() << "Invalid value for 'level'" << level;
72+
}
73+
74+
LineModel& line = m_model[level - 1];
75+
76+
// Special characters are inserted in two steps
77+
if (shift) {
78+
// Step One: insert a placeholder character.
79+
line.m_content.insert(line.m_position, c);
80+
}
81+
else {
82+
// Step Two: replace the placeholder with the desired character.
83+
line.m_content.replace(line.m_position, 1, c);
84+
}
85+
86+
m_cmdTextBox->setText(line.getPromptText());
87+
}
88+
89+
void ExtCmdlineWidget::handleCmdlineHide()
90+
{
91+
if (!m_model.empty()) {
92+
m_model.pop_back();
93+
}
94+
95+
hide();
96+
}
97+
98+
void ExtCmdlineWidget::handleCmdlineBlockShow(const QVariantList& lines)
99+
{
100+
QString blockText;
101+
for (const auto& varLine: lines) {
102+
QVariantList line = varLine.toList();
103+
qDebug() << line;
104+
for (const auto& varSegment : line) {
105+
QVariantList segment = varSegment.toList();
106+
const QString segmentText = segment.at(1).toString();
107+
108+
blockText += segmentText;
109+
}
110+
}
111+
m_cmdBlockText->setText(blockText);
112+
m_cmdBlockText->updateGeometry();
113+
m_cmdBlockText->show();
114+
}
115+
116+
void ExtCmdlineWidget::handleCmdlineBlockAppend(const QVariantList& line)
117+
{
118+
for (const auto& varSegment : line) {
119+
QVariantList segment = varSegment.toList();
120+
const QString segmentText = segment.at(1).toString();
121+
122+
m_cmdBlockText->append(segmentText);
123+
}
124+
125+
m_cmdBlockText->updateGeometry();
126+
}
127+
128+
void ExtCmdlineWidget::handleCmdlineBlockHide()
129+
{
130+
m_cmdBlockText->hide();
131+
}
132+
133+
void ExtCmdlineWidget::resizeLineEditToContent()
134+
{
135+
updateGeometry();
136+
}
137+
138+
void ExtCmdlineWidget::updateGeometry()
139+
{
140+
if (!parentWidget()) {
141+
qDebug() << "No parentWidget, cannot update size/position";
142+
return;
143+
}
144+
145+
ShellWidget* parentShellWidget{ static_cast<ShellWidget*>(parentWidget()) };
146+
147+
const int maxWidth = parentShellWidget->width() * 0.75;
148+
const int maxHeight = parentShellWidget->height() * 0.75;
149+
150+
const QSize sizeHintThis = sizeHint();
151+
const int width = qMin(sizeHintThis.width(), maxWidth);
152+
const int height = qMin(sizeHintThis.height(), maxHeight);
153+
154+
const int anchorX = (parentShellWidget->width() - width) / 2;
155+
const int anchorY = (parentShellWidget->height() - height) / 2;
156+
157+
setGeometry(anchorX, anchorY, width, height);
158+
setMaximumWidth(maxWidth);
159+
setMaximumHeight(maxHeight);
160+
QWidget::updateGeometry();
161+
}
162+
163+
void ExtCmdlineWidget::setCursorPosition(int pos)
164+
{
165+
if (!parentWidget()) {
166+
qDebug() << "No parentWidget, cannot update cursor position";
167+
return;
168+
}
169+
170+
ShellWidget* parentShellWidget{ static_cast<ShellWidget*>(parentWidget()) };
171+
172+
// Neovim cursor position is set by inverting the foreground/background colors, Qt cursor hidden.
173+
const int posCursorReal = pos + m_model.back().m_prompt.size() + m_model.back().m_indent + 1;
174+
QTextCharFormat fmtInverse;
175+
fmtInverse.setBackground(parentShellWidget->foreground());
176+
fmtInverse.setForeground(parentShellWidget->background());
177+
m_cmdTextBox->setTextFormat({ { posCursorReal, 1, fmtInverse} });
178+
179+
// Ensures ':' is shown when users scrolls full left
180+
if (pos == 0) {
181+
m_cmdTextBox->setCursorPosition(0);
182+
return;
183+
}
184+
185+
// Ensure the entire nvim cursor is visble by setting the Qt cursor position.
186+
// Moving left: left edge of cursor. Moving right: right edge of cursor.
187+
static int last_pos = 0;
188+
189+
int cursorPositionQt = posCursorReal;
190+
if (last_pos <= pos) {
191+
++cursorPositionQt;
192+
}
193+
m_cmdTextBox->setCursorPosition(cursorPositionQt);
194+
195+
last_pos = pos;
196+
}
197+
198+
int ExtCmdlineWidget::getMaxPromptLength() const
199+
{
200+
int maxLineLength = 0;
201+
for (const auto& line : m_model) {
202+
maxLineLength = qMax(line.getPromptText().size(), maxLineLength);
203+
}
204+
205+
return qMax(m_cmdBlockText->GetMaxLineLength(), maxLineLength);
206+
}
207+
208+
QSize ExtCmdlineWidget::sizeHint() const
209+
{
210+
QSize sizeHint = QFrame::sizeHint();
211+
212+
QFontMetrics fm = fontMetrics();
213+
214+
const int maxLineLength = getMaxPromptLength();
215+
216+
const int widthHint = fm.averageCharWidth() * (maxLineLength + 2) +
217+
layout()->contentsMargins().left() + layout()->contentsMargins().right();
218+
219+
constexpr int minWidth = 300;
220+
221+
sizeHint.setWidth(qMax(widthHint, minWidth));
222+
223+
return sizeHint;
224+
}
225+
226+
void ExtCmdlineWidget::updatePalette()
227+
{
228+
if (!parentWidget()) {
229+
qDebug() << "No parentWidget, cannot set palette";
230+
return;
231+
}
232+
233+
ShellWidget* parentShellWidget{ static_cast<ShellWidget*>(parentWidget()) };
234+
235+
m_palette.setColor(QPalette::Base, parentShellWidget->background());
236+
m_palette.setColor(QPalette::Text, parentShellWidget->foreground());
237+
m_palette.setColor(QPalette::Background, parentShellWidget->foreground());
238+
239+
m_cmdTextBox->setPalette(m_palette);
240+
m_cmdBlockText->setPalette(m_palette);
241+
}
242+
243+
void ExtCmdlineWidget::setFont(const QFont &font)
244+
{
245+
m_cmdBlockText->setFont(font);
246+
m_cmdTextBox->setFont(font);
247+
}
248+
249+
} } // namespace NeovimQt::Cmdline

0 commit comments

Comments
 (0)