Terminal: Rewrite rendering

The rendering has been rewritten to use cached GlyphRuns instead
of text layouts. The VTerm specific code was moved into
TerminalSurface.

Change-Id: I10caa3db4ee932414987c9ddae2dcb777dc1f6e7
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Marcus Tillmanns
2023-03-03 17:18:56 +01:00
parent bb4d9c92e7
commit 098c76832a
18 changed files with 1592 additions and 800 deletions

View File

@@ -496,7 +496,7 @@ void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTer
typedef struct {
unsigned int bold : 1;
unsigned int underline : 2;
unsigned int underline : 3;
unsigned int italic : 1;
unsigned int blink : 1;
unsigned int reverse : 1;
@@ -514,6 +514,8 @@ enum {
VTERM_UNDERLINE_SINGLE,
VTERM_UNDERLINE_DOUBLE,
VTERM_UNDERLINE_CURLY,
VTERM_UNDERLINE_DOTTED,
VTERM_UNDERLINE_DASHED
};
enum {

View File

@@ -323,6 +323,12 @@ INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argco
case 3:
state->pen.underline = VTERM_UNDERLINE_CURLY;
break;
case 4:
state->pen.underline = VTERM_UNDERLINE_DOTTED;
break;
case 5:
state->pen.underline = VTERM_UNDERLINE_DASHED;
break;
}
}
setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);

View File

@@ -18,7 +18,7 @@ typedef struct
VTermColor fg, bg;
unsigned int bold : 1;
unsigned int underline : 2;
unsigned int underline : 3;
unsigned int italic : 1;
unsigned int blink : 1;
unsigned int reverse : 1;

View File

@@ -41,7 +41,7 @@ struct VTermPen
VTermColor fg;
VTermColor bg;
unsigned int bold:1;
unsigned int underline:2;
unsigned int underline:3;
unsigned int italic:1;
unsigned int blink:1;
unsigned int reverse:1;

View File

@@ -3,16 +3,18 @@ add_qtc_plugin(Terminal
PLUGIN_DEPENDS Core
DEPENDS libvterm
SOURCES
celllayout.cpp celllayout.h
celliterator.cpp celliterator.h
glyphcache.cpp glyphcache.h
keys.cpp keys.h
scrollback.cpp scrollback.h
shellmodel.cpp shellmodel.h
terminal.qrc
terminalplugin.cpp terminalplugin.h
terminaltr.h
terminalpane.cpp terminalpane.h
terminalwidget.cpp terminalwidget.h
terminalplugin.cpp terminalplugin.h
terminalprocessinterface.cpp terminalprocessinterface.h
terminalsettings.cpp terminalsettings.h
terminalsettingspage.cpp terminalsettingspage.h
scrollback.h scrollback.cpp
shellmodel.cpp shellmodel.h
keys.cpp keys.h
terminalsurface.cpp terminalsurface.h
terminaltr.h
terminalwidget.cpp terminalwidget.h
)

View File

@@ -0,0 +1,89 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "celliterator.h"
#include "terminalsurface.h"
#include <stdexcept>
namespace Terminal::Internal {
CellIterator::CellIterator(const TerminalSurface *surface, QPoint pos)
: m_state(State::INSIDE)
, m_surface(surface)
{
m_pos = (pos.x()) + (pos.y() * surface->liveSize().width());
m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1;
updateChar();
}
CellIterator::CellIterator(const TerminalSurface *surface, int pos)
: m_state(State::INSIDE)
, m_surface(surface)
{
m_pos = pos;
m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1;
updateChar();
}
CellIterator::CellIterator(const TerminalSurface *surface, State state)
: m_state(state)
, m_surface(surface)
, m_pos()
{
m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1;
if (state == State::END) {
m_pos = m_maxpos + 1;
}
}
QPoint CellIterator::gridPos() const
{
return m_surface->posToGrid(m_pos);
}
void CellIterator::updateChar()
{
QPoint cell = m_surface->posToGrid(m_pos);
m_char = m_surface->fetchCharAt(cell.x(), cell.y());
if (m_char == 0)
m_char = U' ';
}
CellIterator &CellIterator::operator-=(int n)
{
if (n == 0)
return *this;
if (m_pos - n < 0)
throw new std::runtime_error("-= n too big!");
m_pos -= n;
updateChar();
m_state = State::INSIDE;
if (m_pos == 0) {
m_state = State::BEGIN;
}
return *this;
}
CellIterator &CellIterator::operator+=(int n)
{
if (n == 0)
return *this;
if (m_pos + n < m_maxpos) {
m_state = State::INSIDE;
m_pos += n;
updateChar();
} else {
*this = m_surface->end();
}
return *this;
}
} // namespace Terminal::Internal

View File

@@ -0,0 +1,95 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <string>
#include <QPoint>
namespace Terminal::Internal {
class TerminalSurface;
class CellIterator
{
public:
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = std::u32string::value_type;
using pointer = std::u32string::value_type *;
// We need to return copies for std::reverse_iterator to work
using reference = std::u32string::value_type;
enum class State { BEGIN, INSIDE, END } m_state{};
public:
CellIterator(const TerminalSurface *surface, QPoint pos);
CellIterator(const TerminalSurface *surface, int pos);
CellIterator(const TerminalSurface *surface, State state);
public:
QPoint gridPos() const;
public:
CellIterator &operator-=(int n);
CellIterator &operator+=(int n);
reference operator*() const { return m_char; }
pointer operator->() { return &m_char; }
CellIterator &operator++() { return *this += 1; }
CellIterator operator++(int)
{
CellIterator tmp = *this;
++(*this);
return tmp;
}
CellIterator &operator--() { return *this -= 1; }
CellIterator operator--(int)
{
CellIterator tmp = *this;
--(*this);
return tmp;
}
bool operator!=(const CellIterator &other) const
{
if (other.m_state != m_state)
return true;
if (other.m_pos != m_pos)
return true;
return false;
}
bool operator==(const CellIterator &other) const { return !operator!=(other); }
CellIterator operator-(int n) const
{
CellIterator result = *this;
result -= n;
return result;
}
CellIterator operator+(int n) const
{
CellIterator result = *this;
result += n;
return result;
}
int position() const { return m_pos; }
private:
void updateChar();
const TerminalSurface *m_surface{nullptr};
int m_pos{-1};
int m_maxpos{-1};
mutable std::u32string::value_type m_char;
};
} // namespace Terminal::Internal

View File

@@ -1,146 +0,0 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "celllayout.h"
#include <QFontMetrics>
namespace Terminal::Internal {
QColor toQColor(const VTermColor &c)
{
return QColor(qRgb(c.rgb.red, c.rgb.green, c.rgb.blue));
};
std::u32string cellToString(const VTermScreenCell &cell)
{
if (cell.chars[0] != 0xFFFFFFFF) {
QString ch = QString::fromUcs4(cell.chars);
if (ch.size() > 1)
ch = ch.normalized(QString::NormalizationForm_C);
QList<uint> asUcs4 = ch.toUcs4();
std::u32string chUcs = std::u32string(asUcs4.begin(), asUcs4.end());
if (chUcs.size() > 0) {
if (chUcs[0] == 0)
chUcs[0] = 0x00a0;
return chUcs;
}
}
return std::u32string(1, (char32_t) 0x00a0);
}
void createTextLayout(QTextLayout &textLayout,
std::u32string *resultText,
VTermColor defaultBg,
QRect cellRect,
qreal lineSpacing,
std::function<const VTermScreenCell *(int x, int y)> fetchCell)
{
QList<QTextLayout::FormatRange> formats;
QTextCharFormat currentFormat;
int currentFormatStart = 0;
currentFormat.setForeground(QColor(0xff, 0xff, 0xff));
currentFormat.clearBackground();
QString layoutText;
if (resultText)
resultText->clear();
for (int y = cellRect.y(); y < cellRect.bottom() + 1; y++) {
QTextCharFormat format;
const auto setNewFormat = [&formats, &currentFormatStart, &layoutText, &currentFormat](
const QTextCharFormat &format) {
if (layoutText.size() != currentFormatStart) {
QTextLayout::FormatRange fr;
fr.start = currentFormatStart;
fr.length = layoutText.size() - currentFormatStart;
fr.format = currentFormat;
formats.append(fr);
currentFormat = format;
currentFormatStart = layoutText.size();
} else {
currentFormat = format;
}
};
for (int x = cellRect.x(); x < cellRect.right() + 1; x++) {
const VTermScreenCell *cell = fetchCell(x, y);
const VTermColor *bg = &cell->bg;
const VTermColor *fg = &cell->fg;
if (static_cast<bool>(cell->attrs.reverse)) {
bg = &cell->fg;
fg = &cell->bg;
}
format = QTextCharFormat();
format.setForeground(toQColor(*fg));
if (!vterm_color_is_equal(bg, &defaultBg))
format.setBackground(toQColor(*bg));
else
format.clearBackground();
if (cell->attrs.bold)
format.setFontWeight(QFont::Bold);
if (cell->attrs.underline)
format.setFontUnderline(true);
if (cell->attrs.italic)
format.setFontItalic(true);
if (cell->attrs.strike)
format.setFontStrikeOut(true);
if (format != currentFormat)
setNewFormat(format);
if (cell->chars[0] != 0xFFFFFFFF) {
QString ch = QString::fromUcs4(cell->chars);
if (ch == ' ')
ch = QChar::Nbsp;
if (ch.size() > 0) {
layoutText += ch;
} else {
layoutText += QChar::Nbsp;
}
}
if (resultText)
*resultText += cellToString(*cell);
} // for x
setNewFormat(format);
if (y != cellRect.bottom())
layoutText.append(QChar::LineSeparator);
} // for y
QTextLayout::FormatRange fr;
fr.start = currentFormatStart;
fr.length = (layoutText.size() - 1) - currentFormatStart;
fr.format = currentFormat;
formats.append(fr);
textLayout.setText(layoutText);
textLayout.setFormats(formats);
qreal height = 0;
textLayout.beginLayout();
while (1) {
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
// Just give it a number that is definitely larger than
// the number of columns in a line.
line.setNumColumns(std::numeric_limits<int>::max());
line.setPosition(QPointF(0, height));
height += lineSpacing;
}
textLayout.endLayout();
}
} // namespace Terminal::Internal

View File

@@ -1,29 +0,0 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <vterm.h>
#include <QColor>
#include <QFont>
#include <QRect>
#include <QString>
#include <QTextLayout>
#include <functional>
namespace Terminal::Internal {
QColor toQColor(const VTermColor &c);
void createTextLayout(QTextLayout &textLayout,
std::u32string *resultText,
VTermColor defaultBg,
QRect cellRect,
qreal lineSpacing,
std::function<const VTermScreenCell *(int x, int y)> fetchCell);
std::u32string cellToString(const VTermScreenCell &cell);
} // namespace Terminal::Internal

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "glyphcache.h"
#include <QTextLayout>
namespace Terminal::Internal {
size_t qHash(const GlyphCacheKey &key, size_t seed = 0)
{
return qHash(key.font, seed) ^ qHash(key.text, seed);
}
GlyphCache &GlyphCache::instance()
{
static GlyphCache cache(5000);
return cache;
}
const QGlyphRun *GlyphCache::get(const QFont &font, const QString &text)
{
GlyphCacheKey key{font, text};
if (auto *run = object(key))
return run;
QTextLayout layout;
layout.setText(text);
layout.setFont(font);
layout.beginLayout();
layout.createLine().setNumColumns(std::numeric_limits<int>::max());
layout.endLayout();
if (layout.lineCount() > 0) {
QGlyphRun *run = new QGlyphRun(layout.lineAt(0).glyphRuns().first());
insert(key, run);
return run;
}
return nullptr;
}
} // namespace Terminal::Internal

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <QCache>
#include <QFont>
#include <QGlyphRun>
#include <QString>
namespace Terminal::Internal {
struct GlyphCacheKey
{
QFont font;
QString text;
bool operator==(const GlyphCacheKey &other) const
{
return font == other.font && text == other.text;
}
};
class GlyphCache : public QCache<GlyphCacheKey, QGlyphRun>
{
public:
using QCache::QCache;
static GlyphCache &instance();
const QGlyphRun *get(const QFont &font, const QString &text);
};
} // namespace Terminal::Internal

View File

@@ -3,7 +3,6 @@
// SPDX-License-Identifier: BSD-3-Clause
#include "scrollback.h"
#include "celllayout.h"
#include <cassert>
#include <cstring>
@@ -15,15 +14,6 @@ Scrollback::Line::Line(int cols, const VTermScreenCell *cells, VTermState *vts)
: m_cols(cols)
, m_cells(std::make_unique<VTermScreenCell[]>(cols))
{
m_textFuture = std::async(std::launch::async, [this, cols] {
std::u32string text;
text.reserve(cols);
for (int i = 0; i < cols; ++i) {
text += cellToString(m_cells[i]);
}
return text;
});
memcpy(m_cells.get(), cells, cols * sizeof(cells[0]));
for (int i = 0; i < cols; ++i) {
vterm_state_convert_color_to_rgb(vts, &m_cells[i].fg);
@@ -37,34 +27,6 @@ const VTermScreenCell *Scrollback::Line::cell(int i) const
return &m_cells[i];
}
const QTextLayout &Scrollback::Line::layout(int version, const QFont &font, qreal lineSpacing) const
{
if (!m_layout)
m_layout = std::make_unique<QTextLayout>();
if (m_layoutVersion != version) {
VTermColor defaultBg;
defaultBg.type = VTERM_COLOR_DEFAULT_BG;
m_layout->clearLayout();
m_layout->setFont(font);
createTextLayout(*m_layout,
nullptr,
defaultBg,
QRect(0, 0, m_cols, 1),
lineSpacing,
[this](int x, int) { return &m_cells[x]; });
m_layoutVersion = version;
}
return *m_layout;
}
const std::u32string &Scrollback::Line::text() const
{
if (!m_text)
m_text = m_textFuture.get();
return *m_text;
}
Scrollback::Scrollback(size_t capacity)
: m_capacity(capacity)
{}
@@ -95,26 +57,9 @@ void Scrollback::popto(int cols, VTermScreenCell *cells)
m_deque.pop_front();
}
size_t Scrollback::scroll(int delta)
{
m_offset = std::min(std::max(0, static_cast<int>(m_offset) + delta),
static_cast<int>(m_deque.size()));
return m_offset;
}
void Scrollback::clear()
{
m_offset = 0;
m_deque.clear();
}
std::u32string Scrollback::currentText()
{
std::u32string currentText;
for (auto it = m_deque.rbegin(); it != m_deque.rend(); ++it) {
currentText += it->text();
}
return currentText;
}
} // namespace Terminal::Internal

View File

@@ -29,25 +29,17 @@ public:
const VTermScreenCell *cell(int i) const;
const VTermScreenCell *cells() const { return &m_cells[0]; };
const QTextLayout &layout(int version, const QFont &font, qreal lineSpacing) const;
const std::u32string &text() const;
private:
int m_cols;
std::unique_ptr<VTermScreenCell[]> m_cells;
mutable std::unique_ptr<QTextLayout> m_layout;
mutable int m_layoutVersion{-1};
mutable std::optional<std::u32string> m_text;
mutable std::future<std::u32string> m_textFuture;
};
public:
Scrollback(size_t capacity);
Scrollback() = delete;
size_t capacity() const { return m_capacity; };
size_t size() const { return m_deque.size(); };
size_t offset() const { return m_offset; };
int capacity() const { return m_capacity; };
int size() const { return static_cast<int>(m_deque.size()); };
const Line &line(size_t index) const { return m_deque.at(index); };
const std::deque<Line> &lines() const { return m_deque; };
@@ -56,16 +48,11 @@ public:
const VTermScreenCell *cells,
VTermState *vts);
void popto(int cols, VTermScreenCell *cells);
size_t scroll(int delta);
void unscroll() { m_offset = 0; };
void clear();
std::u32string currentText();
private:
size_t m_capacity;
size_t m_offset{0};
std::deque<Line> m_deque;
};

View File

@@ -0,0 +1,504 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "terminalsurface.h"
#include "keys.h"
#include "scrollback.h"
#include <utils/qtcassert.h>
#include <vterm.h>
#include <QLoggingCategory>
Q_LOGGING_CATEGORY(log, "qtc.terminal.surface", QtWarningMsg);
namespace Terminal::Internal {
QColor toQColor(const VTermColor &c)
{
return QColor(qRgb(c.rgb.red, c.rgb.green, c.rgb.blue));
};
struct TerminalSurfacePrivate
{
TerminalSurfacePrivate(TerminalSurface *surface, const QSize &initialGridSize)
: m_vterm(vterm_new(initialGridSize.height(), initialGridSize.width()), vterm_free)
, m_vtermScreen(vterm_obtain_screen(m_vterm.get()))
, m_scrollback(std::make_unique<Internal::Scrollback>(5000))
, q(surface)
{
vterm_set_utf8(m_vterm.get(), true);
static auto writeToPty = [](const char *s, size_t len, void *user) {
auto p = static_cast<TerminalSurfacePrivate *>(user);
emit p->q->writeToPty(QByteArray(s, static_cast<int>(len)));
};
vterm_output_set_callback(m_vterm.get(), writeToPty, this);
memset(&m_vtermScreenCallbacks, 0, sizeof(m_vtermScreenCallbacks));
m_vtermScreenCallbacks.damage = [](VTermRect rect, void *user) {
auto p = static_cast<TerminalSurfacePrivate *>(user);
rect.start_row += p->m_scrollback->size();
rect.end_row += p->m_scrollback->size();
p->invalidate(rect);
return 1;
};
m_vtermScreenCallbacks.sb_pushline = [](int cols, const VTermScreenCell *cells, void *user) {
auto p = static_cast<TerminalSurfacePrivate *>(user);
return p->sb_pushline(cols, cells);
};
m_vtermScreenCallbacks.sb_popline = [](int cols, VTermScreenCell *cells, void *user) {
auto p = static_cast<TerminalSurfacePrivate *>(user);
return p->sb_popline(cols, cells);
};
m_vtermScreenCallbacks.settermprop = [](VTermProp prop, VTermValue *val, void *user) {
auto p = static_cast<TerminalSurfacePrivate *>(user);
return p->setTerminalProperties(prop, val);
};
m_vtermScreenCallbacks.movecursor =
[](VTermPos pos, VTermPos oldpos, int visible, void *user) {
auto p = static_cast<TerminalSurfacePrivate *>(user);
return p->movecursor(pos, oldpos, visible);
};
m_vtermScreenCallbacks.sb_clear = [](void *user) {
auto p = static_cast<TerminalSurfacePrivate *>(user);
return p->sb_clear();
};
vterm_screen_set_callbacks(m_vtermScreen, &m_vtermScreenCallbacks, this);
vterm_screen_set_damage_merge(m_vtermScreen, VTERM_DAMAGE_SCROLL);
vterm_screen_enable_altscreen(m_vtermScreen, true);
VTermState *vts = vterm_obtain_state(m_vterm.get());
vterm_state_set_bold_highbright(vts, true);
vterm_screen_reset(m_vtermScreen, 1);
}
QSize liveSize() const
{
int rows;
int cols;
vterm_get_size(m_vterm.get(), &rows, &cols);
return QSize(cols, rows);
}
TerminalCell toCell(const VTermScreenCell &cell)
{
TerminalCell result;
result.width = cell.width;
result.text = QString::fromUcs4(cell.chars);
const VTermColor *bg = &cell.bg;
const VTermColor *fg = &cell.fg;
if (static_cast<bool>(cell.attrs.reverse))
std::swap(fg, bg);
const QColor cellBgColor = toQColor(*bg);
const QColor cellFgColor = toQColor(*fg);
if (cellBgColor != m_defaultBgColor)
result.background = toQColor(*bg);
result.foreground = cellFgColor;
result.bold = cell.attrs.bold;
result.strikeOut = cell.attrs.strike;
if (cell.attrs.underline > 0) {
result.underlineStyle = QTextCharFormat::NoUnderline;
switch (cell.attrs.underline) {
case VTERM_UNDERLINE_SINGLE:
result.underlineStyle = QTextCharFormat::SingleUnderline;
break;
case VTERM_UNDERLINE_DOUBLE:
// TODO: Double underline
result.underlineStyle = QTextCharFormat::SingleUnderline;
break;
case VTERM_UNDERLINE_CURLY:
result.underlineStyle = QTextCharFormat::WaveUnderline;
break;
case VTERM_UNDERLINE_DASHED:
result.underlineStyle = QTextCharFormat::DashUnderline;
break;
case VTERM_UNDERLINE_DOTTED:
result.underlineStyle = QTextCharFormat::DotLine;
break;
}
}
result.strikeOut = cell.attrs.strike;
return result;
}
VTermColor defaultBgColor() const
{
VTermColor defaultBg;
if (!m_altscreen) {
VTermColor defaultFg;
vterm_state_get_default_colors(vterm_obtain_state(m_vterm.get()),
&defaultFg,
&defaultBg);
// We want to compare the cell bg against this later and cells don't
// set DEFAULT_BG
defaultBg.type = VTERM_COLOR_RGB;
return defaultBg;
} // This is a slightly better guess when in an altscreen
VTermPos vtp{0, 0};
static VTermScreenCell refCell{};
vterm_screen_get_cell(m_vtermScreen, vtp, &refCell);
return refCell.bg;
}
// Callbacks from vterm
void invalidate(VTermRect rect)
{
emit q->invalidated(
QRect{QPoint{rect.start_col, rect.start_row}, QPoint{rect.end_col, rect.end_row - 1}});
}
int sb_pushline(int cols, const VTermScreenCell *cells)
{
m_scrollback->emplace(cols, cells, vterm_obtain_state(m_vterm.get()));
emit q->fullSizeChanged(q->fullSize());
return 1;
}
int sb_popline(int cols, VTermScreenCell *cells)
{
if (m_scrollback->size() == 0)
return 0;
m_scrollback->popto(cols, cells);
emit q->fullSizeChanged(q->fullSize());
return 1;
}
int sb_clear()
{
m_scrollback->clear();
emit q->fullSizeChanged(q->fullSize());
return 1;
}
int setTerminalProperties(VTermProp prop, VTermValue *val)
{
switch (prop) {
case VTERM_PROP_CURSORVISIBLE:
m_cursor.visible = val->boolean;
break;
case VTERM_PROP_CURSORBLINK:
qCDebug(log) << "Ignoring VTERM_PROP_CURSORBLINK" << val->boolean;
break;
case VTERM_PROP_CURSORSHAPE:
qCDebug(log) << "Ignoring VTERM_PROP_CURSORSHAPE" << val->number;
break;
case VTERM_PROP_ICONNAME:
//emit iconTextChanged(val->string);
break;
case VTERM_PROP_TITLE:
break;
case VTERM_PROP_ALTSCREEN:
m_altscreen = val->boolean;
emit q->altscreenChanged(m_altscreen);
//setSelection(std::nullopt);
break;
case VTERM_PROP_MOUSE:
qCDebug(log) << "Ignoring VTERM_PROP_MOUSE" << val->number;
break;
case VTERM_PROP_REVERSE:
qCDebug(log) << "Ignoring VTERM_PROP_REVERSE" << val->boolean;
break;
case VTERM_N_PROPS:
break;
}
return 1;
}
int movecursor(VTermPos pos, VTermPos oldpos, int visible)
{
Q_UNUSED(oldpos);
Cursor oldCursor = q->cursor();
m_cursor = {{pos.col, pos.row}, visible > 0};
q->cursorChanged(oldCursor, q->cursor());
return 1;
}
const VTermScreenCell *cellAt(int x, int y)
{
QTC_ASSERT(y >= 0 && x >= 0, return nullptr);
QTC_ASSERT(y < liveSize().height() + m_scrollback->size() && x < liveSize().width(),
return nullptr);
if (y < m_scrollback->size()) {
const auto &sbl = m_scrollback->line((m_scrollback->size() - 1) - y);
if (x < sbl.cols()) {
return sbl.cell(x);
}
return nullptr;
}
y -= m_scrollback->size();
static VTermScreenCell refCell{};
VTermPos vtp{y, x};
vterm_screen_get_cell(m_vtermScreen, vtp, &refCell);
vterm_screen_convert_color_to_rgb(m_vtermScreen, &refCell.fg);
vterm_screen_convert_color_to_rgb(m_vtermScreen, &refCell.bg);
return &refCell;
}
std::unique_ptr<VTerm, void (*)(VTerm *)> m_vterm;
VTermScreen *m_vtermScreen;
VTermScreenCallbacks m_vtermScreenCallbacks;
QColor m_defaultBgColor;
Cursor m_cursor;
bool m_altscreen{false};
std::unique_ptr<Internal::Scrollback> m_scrollback;
TerminalSurface *q;
};
TerminalSurface::TerminalSurface(QSize initialGridSize)
: d(std::make_unique<TerminalSurfacePrivate>(this, initialGridSize))
{}
TerminalSurface::~TerminalSurface() = default;
int TerminalSurface::cellWidthAt(int x, int y) const
{
const VTermScreenCell *cell = d->cellAt(x, y);
if (!cell)
return 0;
return cell->width;
}
QSize TerminalSurface::liveSize() const
{
return d->liveSize();
}
QSize TerminalSurface::fullSize() const
{
return QSize{d->liveSize().width(), d->liveSize().height() + d->m_scrollback->size()};
}
std::u32string::value_type TerminalSurface::fetchCharAt(int x, int y) const
{
const VTermScreenCell *cell = d->cellAt(x, y);
if (!cell)
return 0;
if (cell->width == 0)
return 0;
QString s = QString::fromUcs4(cell->chars, 6).normalized(QString::NormalizationForm_C);
const QList<uint> ucs4 = s.toUcs4();
return std::u32string(ucs4.begin(), ucs4.end()).front();
}
TerminalCell TerminalSurface::fetchCell(int x, int y) const
{
static TerminalCell emptyCell{1, {}, {}};
QTC_ASSERT(y >= 0, return emptyCell);
QTC_ASSERT(y < d->liveSize().height() + d->m_scrollback->size(), return emptyCell);
const VTermScreenCell *refCell = d->cellAt(x, y);
if (!refCell)
return emptyCell;
return d->toCell(*refCell);
}
void TerminalSurface::clearAll()
{
// Fake a scrollback clearing
QByteArray data{"\x1b[3J"};
vterm_input_write(d->m_vterm.get(), data.constData(), data.size());
// Send Ctrl+L which will clear the screen
emit writeToPty(QByteArray("\f"));
}
void TerminalSurface::resize(QSize newSize)
{
vterm_set_size(d->m_vterm.get(), newSize.height(), newSize.width());
}
QPoint TerminalSurface::posToGrid(int pos) const
{
return {pos % d->liveSize().width(), pos / d->liveSize().width()};
}
int TerminalSurface::gridToPos(QPoint gridPos) const
{
return gridPos.y() * d->liveSize().width() + gridPos.x();
}
void TerminalSurface::dataFromPty(const QByteArray &data)
{
vterm_input_write(d->m_vterm.get(), data.constData(), data.size());
vterm_screen_flush_damage(d->m_vtermScreen);
}
void TerminalSurface::flush()
{
vterm_screen_flush_damage(d->m_vtermScreen);
}
void TerminalSurface::setColors(QColor foreground, QColor background)
{
VTermState *vts = vterm_obtain_state(d->m_vterm.get());
VTermColor fg;
VTermColor bg;
vterm_color_rgb(&fg, foreground.red(), foreground.green(), foreground.blue());
vterm_color_rgb(&bg, background.red(), background.green(), background.blue());
d->m_defaultBgColor = background;
vterm_state_set_default_colors(vts, &fg, &bg);
vterm_screen_reset(d->m_vtermScreen, 1);
}
void TerminalSurface::setAnsiColor(int index, QColor color)
{
VTermState *vts = vterm_obtain_state(d->m_vterm.get());
VTermColor col;
vterm_color_rgb(&col, color.red(), color.green(), color.blue());
vterm_state_set_palette_color(vts, index, &col);
vterm_screen_reset(d->m_vtermScreen, 1);
}
void TerminalSurface::pasteFromClipboard(const QString &clipboardText)
{
if (clipboardText.isEmpty())
return;
vterm_keyboard_start_paste(d->m_vterm.get());
for (unsigned int ch : clipboardText.toUcs4())
vterm_keyboard_unichar(d->m_vterm.get(), ch, VTERM_MOD_NONE);
vterm_keyboard_end_paste(d->m_vterm.get());
if (!d->m_altscreen) {
emit unscroll();
}
}
void TerminalSurface::sendKey(Qt::Key key)
{
if (key == Qt::Key_Escape)
vterm_keyboard_key(d->m_vterm.get(), VTERM_KEY_ESCAPE, VTERM_MOD_NONE);
}
void TerminalSurface::sendKey(const QString &text)
{
for (const unsigned int ch : text.toUcs4())
vterm_keyboard_unichar(d->m_vterm.get(), ch, VTERM_MOD_NONE);
}
void TerminalSurface::sendKey(QKeyEvent *event)
{
bool keypad = event->modifiers() & Qt::KeypadModifier;
VTermModifier mod = Internal::qtModifierToVTerm(event->modifiers());
VTermKey key = Internal::qtKeyToVTerm(Qt::Key(event->key()), keypad);
if (key != VTERM_KEY_NONE) {
if (mod == VTERM_MOD_SHIFT && (key == VTERM_KEY_ESCAPE || key == VTERM_KEY_BACKSPACE))
mod = VTERM_MOD_NONE;
vterm_keyboard_key(d->m_vterm.get(), key, mod);
} else if (event->text().length() == 1) {
// This maps to delete word and is way to easy to mistakenly type
// if (event->key() == Qt::Key_Space && mod == VTERM_MOD_SHIFT)
// mod = VTERM_MOD_NONE;
// Per https://github.com/justinmk/neovim/commit/317d5ca7b0f92ef42de989b3556ca9503f0a3bf6
// libvterm prefers we send the full keycode rather than sending the
// ctrl modifier. This helps with ncurses applications which otherwise
// do not recognize ctrl+<key> and in the shell for getting common control characters
// like ctrl+i for tab or ctrl+j for newline.
// Workaround for "ALT+SHIFT+/" (\ on german mac keyboards)
if (mod == (VTERM_MOD_SHIFT | VTERM_MOD_ALT) && event->key() == Qt::Key_Slash) {
mod = VTERM_MOD_NONE;
}
vterm_keyboard_unichar(d->m_vterm.get(), event->text().toUcs4()[0], VTERM_MOD_NONE);
// TODO: ??
//setSelection(std::nullopt);
} else if (mod == VTERM_MOD_CTRL && event->key() >= Qt::Key_A && event->key() < Qt::Key_Z) {
vterm_keyboard_unichar(d->m_vterm.get(), 'a' + (event->key() - Qt::Key_A), mod);
}
}
Cursor TerminalSurface::cursor() const
{
Cursor cursor = d->m_cursor;
cursor.position.setY(cursor.position.y() + d->m_scrollback->size());
return cursor;
}
QColor TerminalSurface::defaultBgColor() const
{
return toQColor(d->defaultBgColor());
}
CellIterator TerminalSurface::begin() const
{
auto res = CellIterator(this, {0, 0});
res.m_state = CellIterator::State::BEGIN;
return res;
}
CellIterator TerminalSurface::end() const
{
return CellIterator(this, CellIterator::State::END);
}
std::reverse_iterator<CellIterator> TerminalSurface::rbegin() const
{
return std::make_reverse_iterator(end());
}
std::reverse_iterator<CellIterator> TerminalSurface::rend() const
{
return std::make_reverse_iterator(begin());
}
CellIterator TerminalSurface::iteratorAt(QPoint pos) const
{
return CellIterator(this, pos);
}
CellIterator TerminalSurface::iteratorAt(int pos) const
{
return CellIterator(this, pos);
}
std::reverse_iterator<CellIterator> TerminalSurface::rIteratorAt(QPoint pos) const
{
return std::make_reverse_iterator(iteratorAt(pos));
}
std::reverse_iterator<CellIterator> TerminalSurface::rIteratorAt(int pos) const
{
return std::make_reverse_iterator(iteratorAt(pos));
}
} // namespace Terminal::Internal

View File

@@ -0,0 +1,103 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "celliterator.h"
#include <QKeyEvent>
#include <QSize>
#include <QTextCharFormat>
#include <memory>
namespace Terminal::Internal {
class Scrollback;
struct TerminalSurfacePrivate;
struct TerminalCell
{
int width;
QString text;
bool bold{false};
bool italic{false};
QColor foreground;
std::optional<QColor> background;
QTextCharFormat::UnderlineStyle underlineStyle{QTextCharFormat::NoUnderline};
bool strikeOut{false};
};
struct Cursor
{
QPoint position;
bool visible;
};
class TerminalSurface : public QObject
{
Q_OBJECT;
public:
TerminalSurface(QSize initialGridSize);
~TerminalSurface();
public:
CellIterator begin() const;
CellIterator end() const;
std::reverse_iterator<CellIterator> rbegin() const;
std::reverse_iterator<CellIterator> rend() const;
CellIterator iteratorAt(QPoint pos) const;
CellIterator iteratorAt(int pos) const;
std::reverse_iterator<CellIterator> rIteratorAt(QPoint pos) const;
std::reverse_iterator<CellIterator> rIteratorAt(int pos) const;
public:
void clearAll();
void resize(QSize newSize);
TerminalCell fetchCell(int x, int y) const;
std::u32string::value_type fetchCharAt(int x, int y) const;
int cellWidthAt(int x, int y) const;
QSize liveSize() const;
QSize fullSize() const;
QPoint posToGrid(int pos) const;
int gridToPos(QPoint gridPos) const;
void dataFromPty(const QByteArray &data);
void flush();
void setColors(QColor foreground, QColor background);
void setAnsiColor(int index, QColor color);
void pasteFromClipboard(const QString &text);
void sendKey(Qt::Key key);
void sendKey(QKeyEvent *event);
void sendKey(const QString &text);
int invertedScrollOffset() const;
Cursor cursor() const;
QColor defaultBgColor() const;
signals:
void writeToPty(const QByteArray &data);
void invalidated(QRect grid);
void fullSizeChanged(QSize newSize);
void cursorChanged(Cursor oldCursor, Cursor newCursor);
void altscreenChanged(bool altScreen);
void unscroll();
private:
std::unique_ptr<TerminalSurfacePrivate> d;
};
} // namespace Terminal::Internal

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
#pragma once
#include "scrollback.h"
#include "terminalsurface.h"
#include <utils/qtcprocess.h>
#include <utils/terminalhooks.h>
@@ -13,8 +13,6 @@
#include <QTextLayout>
#include <QTimer>
#include <vterm.h>
#include <chrono>
#include <memory>
@@ -22,6 +20,7 @@ namespace Terminal {
class TerminalWidget : public QAbstractScrollArea
{
friend class CellIterator;
Q_OBJECT
public:
TerminalWidget(QWidget *parent = nullptr,
@@ -49,8 +48,23 @@ public:
struct Selection
{
QPoint start;
QPoint end;
int start;
int end;
bool operator!=(const Selection &other) const
{
return start != other.start || end != other.end;
}
};
struct LinkSelection : public Selection
{
Utils::FilePath filePath;
bool operator!=(const LinkSelection &other) const
{
return filePath != other.filePath || Selection::operator!=(other);
}
};
QString shellName() const;
@@ -80,7 +94,7 @@ protected:
protected:
void onReadyRead(bool forceFlush);
void setupVTerm();
void setupSurface();
void setupFont();
void setupPty();
void setupColors();
@@ -88,23 +102,27 @@ protected:
void writeToPty(const QByteArray &data);
void createTextLayout();
// Callbacks from vterm
void invalidate(VTermRect rect);
int sb_pushline(int cols, const VTermScreenCell *cells);
int sb_popline(int cols, VTermScreenCell *cells);
int sb_clear();
int setTerminalProperties(VTermProp prop, VTermValue *val);
int movecursor(VTermPos pos, VTermPos oldpos, int visible);
const VTermScreenCell *fetchCell(int x, int y) const;
int paintCell(QPainter &p,
const QRectF &cellRect,
QPoint gridPos,
const Internal::TerminalCell &cell,
QFont &f) const;
void paintCells(QPainter &painter, QPaintEvent *event) const;
void paintCursor(QPainter &painter) const;
void paintPreedit(QPainter &painter) const;
void paintSelection(QPainter &painter) const;
void paintDebugSelection(QPainter &painter, const Selection &selection) const;
qreal topMargin() const;
QPoint viewportToGlobal(QPoint p) const;
QPoint globalToViewport(QPoint p) const;
QPoint globalToGrid(QPoint p) const;
QPoint globalToGrid(QPointF p) const;
QPointF gridToGlobal(QPoint p, bool bottom = false, bool right = false) const;
QRect gridToViewport(QRect rect) const;
void updateViewport();
void updateViewport(const QRect &rect);
int textLineFromPixel(int y) const;
std::optional<int> textPosFromPoint(const QTextLayout &textLayout, QPoint p) const;
@@ -112,6 +130,17 @@ protected:
std::optional<QTextLayout::FormatRange> selectionToFormatRange(
TerminalWidget::Selection selection, const QTextLayout &layout, int rowOffset) const;
void checkLinkAt(const QPoint &pos);
struct TextAndOffsets
{
int start;
int end;
std::u32string text;
};
TextAndOffsets textAt(const QPoint &pos) const;
void applySizeChange();
void updateScrollBars();
@@ -122,38 +151,25 @@ protected:
private:
std::unique_ptr<Utils::QtcProcess> m_process;
std::unique_ptr<Internal::TerminalSurface> m_surface;
QString m_shellName;
std::unique_ptr<VTerm, void (*)(VTerm *)> m_vterm;
VTermScreen *m_vtermScreen;
QSize m_vtermSize;
QFont m_font;
QSizeF m_cellSize;
qreal m_cellBaseline;
qreal m_lineSpacing;
bool m_altscreen{false};
bool m_ignoreScroll{false};
QString m_preEditString;
std::optional<Selection> m_selection;
QPoint m_selectionStartPos;
std::unique_ptr<Internal::Scrollback> m_scrollback;
QTextLayout m_textLayout;
std::optional<LinkSelection> m_linkSelection;
struct
{
int row{0};
int col{0};
bool visible{false};
} m_cursor;
VTermScreenCallbacks m_vtermScreenCallbacks;
QPoint start;
QPoint end;
} m_activeMouseSelect;
QAction m_copyAction;
QAction m_pasteAction;
@@ -165,8 +181,6 @@ private:
QTimer m_flushDelayTimer;
int m_layoutVersion{0};
std::array<QColor, 18> m_currentColors;
Utils::Terminal::OpenTerminalParameters m_openParameters;
@@ -174,8 +188,6 @@ private:
std::chrono::system_clock::time_point m_lastFlush;
std::chrono::system_clock::time_point m_lastDoubleClick;
bool m_selectLineMode{false};
std::u32string m_currentLiveText;
};
} // namespace Terminal

112
src/plugins/terminal/tests/colors Executable file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/python3
# Source: https://gist.github.com/lilydjwg/fdeaf79e921c2f413f44b6f613f6ad53
from functools import partial
def colors16():
for bold in [0, 1]:
for i in range(30, 38):
for j in range(40, 48):
print(f'\x1b[{bold};{i};{j}m {bold};{i};{j} |\x1b[0m', end='')
print()
print()
for bold in [0, 1]:
for i in range(90, 98):
for j in range(100, 108):
print(f'\x1b[{bold};{i};{j}m {bold};{i};{j} |\x1b[0m', end='')
print()
print()
def color1(c, n=0):
print(f'\x1b[{n};38;5;{c}m{c:4}\x1b[0m', end='')
def color1_sep(c):
if (c - 15) % 18 == 0:
print()
def color2(c):
print(f'\x1b[48;5;{c}m \x1b[0m', end='')
def color2_sep(c):
if (c - 15) % 36 == 0:
print()
elif (c - 15) % 6 == 0:
print(' ', end='')
def colors256(color, sepfunc):
for i in range(0, 8):
color(i)
print()
for i in range(8, 16):
color(i)
print('\n')
for i in range(16, 232):
color(i)
sepfunc(i)
print()
for i in range(232, 256):
color(i)
print('\n')
def colors_gradient():
s = '/\\' * 40
for col in range(0, 77):
r = 255 - col * 255 // 76
g = col * 510 // 76
b = col * 255 // 76
if g > 255:
g = 510 - g
print(
f'\x1b[48;2;{r};{g};{b}m\x1b[38;2;{255-r};{255-g};{255-b}m{s[col]}\x1b[0m', end='')
print()
def other_attributes():
for i in range(0, 10):
print(f' \x1b[{i}mSGR {i:2}\x1b[m', end=' ')
print(' \x1b[53mSGR 53\x1b[m', end=' ') # overline
print('\n')
# https://askubuntu.com/a/985386/235132
for i in range(1, 6):
print(f' \x1b[4:{i}mSGR 4:{i}\x1b[m', end=' ')
print(' \x1b[21mSGR 21\x1b[m', end=' ')
print(
' \x1b[4:3m\x1b[58;2;135;0;255mtruecolor underline\x1b[59m\x1b[4:0m', end=' ')
print(' \x1b]8;;https://askubuntu.com/a/985386/235132\x1b\\hyperlink\x1b]8;;\x1b\\')
if __name__ == '__main__':
print('basic 16 colors, foreground & background:\n')
colors16()
print('256 colors:\n')
colors256(color1, color1_sep)
print('256 colors, bold:\n')
colors256(partial(color1, n=1), color1_sep)
print('256 colors, dim:\n')
colors256(partial(color1, n=2), color1_sep)
print('256 colors, bold dim:\n')
colors256(partial(color1, n='1;2'), color1_sep)
print('256 colors, solid background:\n')
colors256(color2, color2_sep)
print('true colors gradient:\n')
colors_gradient()
print('other attributes:\n')
other_attributes()