diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -100,7 +100,6 @@ Vt102Emulation.cpp ZModemDialog.cpp PrintOptions.cpp - konsole_wcwidth.cpp WindowSystemInfo.cpp ${CMAKE_CURRENT_BINARY_DIR}/org.kde.konsole.Window.xml ${CMAKE_CURRENT_BINARY_DIR}/org.kde.konsole.Session.xml) diff --git a/src/Character.h b/src/Character.h --- a/src/Character.h +++ b/src/Character.h @@ -26,6 +26,9 @@ // Konsole #include "CharacterColor.h" +// Qt +#include + namespace Konsole { typedef unsigned char LineProperty; @@ -155,6 +158,27 @@ return QChar(character).isSpace(); } } + + inline int width() const { + return width(character); + } + + static int width(uint ucs4) { + return wcwidth(wchar_t(ucs4)); + } + + static int stringWidth(const uint *ucs4Str, int len) { + int w = 0; + for (int i = 0; i < len; ++i) { + w += width(ucs4Str[i]); + } + return w; + } + + inline static int stringWidth(const QString &str) { + auto ucs4Str = str.toUcs4(); + return stringWidth(ucs4Str.constData(), ucs4Str.length()); + } }; inline bool operator ==(const Character &a, const Character &b) diff --git a/src/Filter.cpp b/src/Filter.cpp --- a/src/Filter.cpp +++ b/src/Filter.cpp @@ -40,7 +40,6 @@ // Konsole #include "Session.h" #include "TerminalCharacterDecoder.h" -#include "konsole_wcwidth.h" using namespace Konsole; @@ -225,8 +224,8 @@ if (_linePositions->value(i) <= position && position < nextLine) { startLine = i; - startColumn = string_width(buffer()->mid(_linePositions->value(i), - position - _linePositions->value(i))); + startColumn = Character::stringWidth(buffer()->mid(_linePositions->value(i), + position - _linePositions->value(i))); return; } } diff --git a/src/Screen.cpp b/src/Screen.cpp --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -27,7 +27,6 @@ #include // Konsole -#include "konsole_wcwidth.h" #include "TerminalCharacterDecoder.h" #include "History.h" #include "ExtendedCharTable.h" @@ -691,7 +690,7 @@ // We indicate the fact that a newline has to be triggered by // putting the cursor one right to the last column of the screen. - int w = konsole_wcwidth(c); + int w = Character::width(c); if (w < 0) { // Non-printable character diff --git a/src/TerminalCharacterDecoder.cpp b/src/TerminalCharacterDecoder.cpp --- a/src/TerminalCharacterDecoder.cpp +++ b/src/TerminalCharacterDecoder.cpp @@ -26,7 +26,6 @@ #include // Konsole -#include "konsole_wcwidth.h" #include "ExtendedCharTable.h" #include "ColorScheme.h" @@ -138,9 +137,8 @@ ushort extendedCharLength = 0; const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(characters[i].character, extendedCharLength); if (chars != nullptr) { - const QString s = QString::fromUcs4(chars, extendedCharLength); - plainText.append(s); - i += qMax(1, string_width(s)); + plainText.append(QString::fromUcs4(chars, extendedCharLength)); + i += qMax(1, Character::stringWidth(chars, extendedCharLength)); } else { ++i; } @@ -154,7 +152,7 @@ // of `dialog --infobox "qwe" 10 10` . if (characters[i].isRealCharacter || i <= realCharacterGuard) { plainText.append(QString::fromUcs4(&characters[i].character, 1)); - i += qMax(1, konsole_wcwidth(characters[i].character)); + i += qMax(1, characters[i].width()); } else { ++i; // should we 'break' directly here? } diff --git a/src/TerminalDisplay.cpp b/src/TerminalDisplay.cpp --- a/src/TerminalDisplay.cpp +++ b/src/TerminalDisplay.cpp @@ -61,7 +61,6 @@ // Konsole #include "Filter.h" #include "konsoledebug.h" -#include "konsole_wcwidth.h" #include "TerminalCharacterDecoder.h" #include "Screen.h" #include "LineFont.h" @@ -1944,7 +1943,7 @@ const int cursorLocation = loc(cursorPosition().x(), cursorPosition().y()); Q_ASSERT(cursorLocation < _imageSize); - int charWidth = konsole_wcwidth(_image[cursorLocation].character); + int charWidth = _image[cursorLocation].width(); QRect cursorRect = imageToWidget(QRect(cursorPosition(), QSize(charWidth, 1))); update(cursorRect); } @@ -3398,7 +3397,7 @@ QRect TerminalDisplay::preeditRect() const { - const int preeditLength = string_width(_inputMethodData.preeditString); + const int preeditLength = Character::stringWidth(_inputMethodData.preeditString); if (preeditLength == 0) { return QRect(); diff --git a/src/autotests/CharacterWidthTest.h b/src/autotests/CharacterWidthTest.h --- a/src/autotests/CharacterWidthTest.h +++ b/src/autotests/CharacterWidthTest.h @@ -31,6 +31,8 @@ private Q_SLOTS: + bool systemUsesUtf8(); + void testWidth_data(); void testWidth(); diff --git a/src/autotests/CharacterWidthTest.cpp b/src/autotests/CharacterWidthTest.cpp --- a/src/autotests/CharacterWidthTest.cpp +++ b/src/autotests/CharacterWidthTest.cpp @@ -19,52 +19,89 @@ // Own #include "CharacterWidthTest.h" +#include + +// System +#include + +// Qt +#include // KDE #include -#include "../konsole_wcwidth.h" -#include "konsoleprivate_export.h" - using namespace Konsole; +bool CharacterWidthTest::systemUsesUtf8() +{ + const char *ctype = setlocale(LC_CTYPE, ""); + if (ctype != nullptr) { + if (QString::fromLatin1(ctype).endsWith(QString::fromLatin1("UTF-8"))) + return true; + } + return false; +} + +static const char * cpToStr(uint cp) +{ + static char sbuf[32]; + qsnprintf(sbuf, sizeof(sbuf), "U+%04X", cp); + return sbuf; +} + void CharacterWidthTest::testWidth_data() { QTest::addColumn("character"); QTest::addColumn("width"); - /* This is a work in progress.... */ - - /* konsole_wcwidth uses -1 C0/C1 and DEL */ - QTest::newRow("0x007F") << uint(0x007F) << -1; - - QTest::newRow("0x0300") << uint(0x0300) << 0; - QTest::newRow("0x070F") << uint(0x070F) << 0; - QTest::newRow("0x1160") << uint(0x1160) << 0; - QTest::newRow("0x10300") << uint(0x10300) << 1; - - QTest::newRow("a") << uint('a') << 1; - QTest::newRow("0x00AD") << uint(0x00AD) << 1; - QTest::newRow("0x00A0") << uint(0x00A0) << 1; - QTest::newRow("0x10FB") << uint(0x10FB) << 1; - QTest::newRow("0xFF76") << uint(0xFF76) << 1; - QTest::newRow("0x103A0") << uint(0x103A0) << 1; - QTest::newRow("0x10A06") << uint(0x10A06) << 0; - - QTest::newRow("0x3000") << uint(0x3000) << 2; - QTest::newRow("0x300a") << uint(0x300a) << 2; - QTest::newRow("0x300b") << uint(0x300b) << 2; - QTest::newRow("0xFF01") << uint(0xFF01) << 2; - QTest::newRow("0xFF5F") << uint(0xFF5F) << 2; - QTest::newRow("0xFF60") << uint(0xFF60) << 2; - QTest::newRow("0xFFe0") << uint(0xFFe6) << 2; + // We can't test too much - results depend on libc version, widths changed in + // the past, and will change in the future. However, code points below are + // probably too common to change. + + // Control characters + for (uint cp = 0x01; cp <= 0x1F; ++cp) { + QTest::newRow(cpToStr(cp)) << cp << -1; + } + // Delete + QTest::newRow(cpToStr(uint(0x7F))) << uint(0x7F) << -1; + + // Basic Latin + for (uint cp = 0x20; cp <= 0x7E; ++cp) { + QTest::newRow(cpToStr(cp)) << cp << 1; + } + + if (systemUsesUtf8()) { + // Hiragana + for (uint cp = 0x3041; cp <= 0x3096; ++cp) { + QTest::newRow(cpToStr(cp)) << cp << 2; + } + + // Combining Diacritical Marks (subset) + for (uint cp = 0x300; cp <= 0x30F; ++cp) { + QTest::newRow(cpToStr(cp)) << cp << 0; + } + + // Test some CP > 0xFFFF to detect truncation + + // Mathematical bold capital A...Z, small A...Z + for (uint cp = 0x1D400; cp <= 0x1D433; ++cp) { + QTest::newRow(cpToStr(cp)) << cp << 1; + } + + // CJK Ideograph Extension B (subset) + for (uint cp = 0x20000; cp <= 0x20010; ++cp) { + QTest::newRow(cpToStr(cp)) << cp << 2; + } + } else { + QWARN("System locale does not use UTF-8. Limiting the test to code points <= 0xFF"); + } } void CharacterWidthTest::testWidth() { QFETCH(uint, character); - QTEST(konsole_wcwidth(character), "width"); + QTEST(Character::width(character), "width"); } QTEST_GUILESS_MAIN(CharacterWidthTest)