Unicode improvements and version bump.
- Added full UTF-8 support for terminal rendering, including multi-width character handling. - Improved font handling in ImGui with expanded glyph support (Greek, Mathematical Operators). - Updated locale initialization to enable proper character rendering. - Bumped version to 1.5.8.
This commit is contained in:
@@ -4,7 +4,7 @@ project(kte)
|
|||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(KTE_VERSION "1.5.7")
|
set(KTE_VERSION "1.5.8")
|
||||||
|
|
||||||
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
||||||
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
||||||
|
|||||||
@@ -357,14 +357,43 @@ GUIFrontend::LoadGuiFont_(const char * /*path*/, const float size_px)
|
|||||||
{
|
{
|
||||||
const ImGuiIO &io = ImGui::GetIO();
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
io.Fonts->Clear();
|
io.Fonts->Clear();
|
||||||
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
|
|
||||||
|
ImFontConfig config;
|
||||||
|
config.MergeMode = false;
|
||||||
|
|
||||||
|
// Load Basic Latin + Latin Supplement
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
kte::Fonts::DefaultFontData,
|
kte::Fonts::DefaultFontData,
|
||||||
kte::Fonts::DefaultFontSize,
|
kte::Fonts::DefaultFontSize,
|
||||||
size_px);
|
size_px,
|
||||||
if (!font) {
|
&config,
|
||||||
font = io.Fonts->AddFontDefault();
|
io.Fonts->GetGlyphRangesDefault());
|
||||||
}
|
|
||||||
(void) font;
|
// Merge Greek and Coptic
|
||||||
|
config.MergeMode = true;
|
||||||
|
static const ImWchar greek_ranges[] = {
|
||||||
|
0x0370, 0x03FF, // Greek and Coptic
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
|
kte::Fonts::DefaultFontData,
|
||||||
|
kte::Fonts::DefaultFontSize,
|
||||||
|
size_px,
|
||||||
|
&config,
|
||||||
|
greek_ranges);
|
||||||
|
|
||||||
|
// Merge Mathematical Operators
|
||||||
|
static const ImWchar math_ranges[] = {
|
||||||
|
0x2200, 0x22FF, // Mathematical Operators
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
|
kte::Fonts::DefaultFontData,
|
||||||
|
kte::Fonts::DefaultFontSize,
|
||||||
|
size_px,
|
||||||
|
&config,
|
||||||
|
math_ranges);
|
||||||
|
|
||||||
io.Fonts->Build();
|
io.Fonts->Build();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#include <clocale>
|
||||||
|
#define _XOPEN_SOURCE_EXTENDED 1
|
||||||
|
#include <cwchar>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -157,35 +160,52 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
// Map to simple attributes; search highlight uses A_STANDOUT which takes precedence below
|
// Map to simple attributes; search highlight uses A_STANDOUT which takes precedence below
|
||||||
attrset(A_NORMAL);
|
attrset(A_NORMAL);
|
||||||
switch (k) {
|
switch (k) {
|
||||||
case kte::TokenKind::Keyword:
|
case kte::TokenKind::Keyword:
|
||||||
case kte::TokenKind::Type:
|
case kte::TokenKind::Type:
|
||||||
case kte::TokenKind::Constant:
|
case kte::TokenKind::Constant:
|
||||||
case kte::TokenKind::Function:
|
case kte::TokenKind::Function:
|
||||||
attron(A_BOLD);
|
attron(A_BOLD);
|
||||||
break;
|
break;
|
||||||
case kte::TokenKind::Comment:
|
case kte::TokenKind::Comment:
|
||||||
attron(A_DIM);
|
attron(A_DIM);
|
||||||
break;
|
break;
|
||||||
case kte::TokenKind::String:
|
case kte::TokenKind::String:
|
||||||
case kte::TokenKind::Char:
|
case kte::TokenKind::Char:
|
||||||
case kte::TokenKind::Number:
|
case kte::TokenKind::Number:
|
||||||
// standout a bit using A_UNDERLINE if available
|
// standout a bit using A_UNDERLINE if available
|
||||||
attron(A_UNDERLINE);
|
attron(A_UNDERLINE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
while (written < cols) {
|
while (written < cols) {
|
||||||
char ch = ' ';
|
|
||||||
bool from_src = false;
|
bool from_src = false;
|
||||||
|
wchar_t wch = L' ';
|
||||||
|
int wch_len = 1;
|
||||||
|
int disp_w = 1;
|
||||||
|
|
||||||
if (src_i < line.size()) {
|
if (src_i < line.size()) {
|
||||||
unsigned char c = static_cast<unsigned char>(line[src_i]);
|
// Decode UTF-8
|
||||||
if (c == '\t') {
|
std::mbstate_t state = std::mbstate_t();
|
||||||
|
size_t res = std::mbrtowc(
|
||||||
|
&wch, &line[src_i], line.size() - src_i, &state);
|
||||||
|
if (res == (size_t) -1 || res == (size_t) -2) {
|
||||||
|
// Invalid or incomplete; treat as single byte
|
||||||
|
wch = static_cast<unsigned char>(line[src_i]);
|
||||||
|
wch_len = 1;
|
||||||
|
} else if (res == 0) {
|
||||||
|
wch = L'\0';
|
||||||
|
wch_len = 1;
|
||||||
|
} else {
|
||||||
|
wch_len = static_cast<int>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wch == L'\t') {
|
||||||
std::size_t next_tab = tabw - (render_col % tabw);
|
std::size_t next_tab = tabw - (render_col % tabw);
|
||||||
if (render_col + next_tab <= coloffs) {
|
if (render_col + next_tab <= coloffs) {
|
||||||
render_col += next_tab;
|
render_col += next_tab;
|
||||||
++src_i;
|
src_i += wch_len;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Emit spaces for tab
|
// Emit spaces for tab
|
||||||
@@ -194,7 +214,7 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
std::size_t to_skip = std::min<std::size_t>(
|
std::size_t to_skip = std::min<std::size_t>(
|
||||||
next_tab, coloffs - render_col);
|
next_tab, coloffs - render_col);
|
||||||
render_col += to_skip;
|
render_col += to_skip;
|
||||||
next_tab -= to_skip;
|
next_tab -= to_skip;
|
||||||
}
|
}
|
||||||
// Now render visible spaces
|
// Now render visible spaces
|
||||||
while (next_tab > 0 && written < cols) {
|
while (next_tab > 0 && written < cols) {
|
||||||
@@ -233,23 +253,34 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
++render_col;
|
++render_col;
|
||||||
--next_tab;
|
--next_tab;
|
||||||
}
|
}
|
||||||
++src_i;
|
src_i += wch_len;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
// normal char
|
// normal char
|
||||||
|
disp_w = wcwidth(wch);
|
||||||
|
if (disp_w < 0)
|
||||||
|
disp_w = 1; // non-printable or similar
|
||||||
|
|
||||||
if (render_col < coloffs) {
|
if (render_col < coloffs) {
|
||||||
++render_col;
|
render_col += disp_w;
|
||||||
++src_i;
|
src_i += wch_len;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ch = static_cast<char>(c);
|
|
||||||
from_src = true;
|
from_src = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// beyond EOL, fill spaces
|
// beyond EOL, fill spaces
|
||||||
ch = ' ';
|
wch = L' ';
|
||||||
|
wch_len = 1;
|
||||||
|
disp_w = 1;
|
||||||
from_src = false;
|
from_src = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (written + disp_w > cols) {
|
||||||
|
// would overflow, just break
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
bool in_hl = search_mode && from_src && is_src_in_hl(src_i);
|
bool in_hl = search_mode && from_src && is_src_in_hl(src_i);
|
||||||
bool in_cur =
|
bool in_cur =
|
||||||
has_current && li == cur_my && from_src && src_i >= cur_mx && src_i <
|
has_current && li == cur_my && from_src && src_i >= cur_mx && src_i <
|
||||||
@@ -273,11 +304,20 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
if (!in_hl && from_src) {
|
if (!in_hl && from_src) {
|
||||||
apply_token_attr(token_at(src_i));
|
apply_token_attr(token_at(src_i));
|
||||||
}
|
}
|
||||||
addch(static_cast<unsigned char>(ch));
|
|
||||||
++written;
|
if (from_src) {
|
||||||
++render_col;
|
cchar_t cch;
|
||||||
|
wchar_t warr[2] = {wch, L'\0'};
|
||||||
|
setcchar(&cch, warr, A_NORMAL, 0, nullptr);
|
||||||
|
add_wch(&cch);
|
||||||
|
} else {
|
||||||
|
addch(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
written += disp_w;
|
||||||
|
render_col += disp_w;
|
||||||
if (from_src)
|
if (from_src)
|
||||||
++src_i;
|
src_i += wch_len;
|
||||||
if (src_i >= line.size() && written >= cols)
|
if (src_i >= line.size() && written >= cols)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -297,23 +337,35 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
// Place terminal cursor at logical position accounting for tabs and coloffs.
|
// Place terminal cursor at logical position accounting for tabs and coloffs.
|
||||||
// Recompute the rendered X using the same logic as the drawing loop to avoid
|
// Recompute the rendered X using the same logic as the drawing loop to avoid
|
||||||
// any drift between the command-layer computation and the terminal renderer.
|
// any drift between the command-layer computation and the terminal renderer.
|
||||||
std::size_t cy = buf->Cury();
|
std::size_t cy = buf->Cury();
|
||||||
std::size_t cx = buf->Curx();
|
std::size_t cx = buf->Curx();
|
||||||
int cur_y = static_cast<int>(cy) - static_cast<int>(buf->Rowoffs());
|
int cur_y = static_cast<int>(cy) - static_cast<int>(buf->Rowoffs());
|
||||||
std::size_t rx_recomputed = 0;
|
std::size_t rx_recomputed = 0;
|
||||||
if (cy < lines.size()) {
|
if (cy < lines.size()) {
|
||||||
const std::string line_for_cursor = static_cast<std::string>(lines[cy]);
|
const std::string line_for_cursor = static_cast<std::string>(lines[cy]);
|
||||||
std::size_t src_i_cur = 0;
|
std::size_t src_i_cur = 0;
|
||||||
std::size_t render_col_cur = 0;
|
std::size_t render_col_cur = 0;
|
||||||
while (src_i_cur < line_for_cursor.size() && src_i_cur < cx) {
|
while (src_i_cur < line_for_cursor.size() && src_i_cur < cx) {
|
||||||
unsigned char ccur = static_cast<unsigned char>(line_for_cursor[src_i_cur]);
|
std::mbstate_t state = std::mbstate_t();
|
||||||
if (ccur == '\t') {
|
wchar_t wch;
|
||||||
std::size_t next_tab = tabw - (render_col_cur % tabw);
|
size_t res = std::mbrtowc(
|
||||||
render_col_cur += next_tab;
|
&wch, &line_for_cursor[src_i_cur], line_for_cursor.size() - src_i_cur,
|
||||||
++src_i_cur;
|
&state);
|
||||||
|
|
||||||
|
if (res == (size_t) -1 || res == (size_t) -2) {
|
||||||
|
render_col_cur += 1;
|
||||||
|
src_i_cur += 1;
|
||||||
|
} else if (res == 0) {
|
||||||
|
src_i_cur += 1;
|
||||||
} else {
|
} else {
|
||||||
++render_col_cur;
|
if (wch == L'\t') {
|
||||||
++src_i_cur;
|
std::size_t next_tab = tabw - (render_col_cur % tabw);
|
||||||
|
render_col_cur += next_tab;
|
||||||
|
} else {
|
||||||
|
int dw = wcwidth(wch);
|
||||||
|
render_col_cur += (dw < 0) ? 1 : dw;
|
||||||
|
}
|
||||||
|
src_i_cur += res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rx_recomputed = render_col_cur;
|
rx_recomputed = render_col_cur;
|
||||||
@@ -403,9 +455,9 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
{
|
{
|
||||||
const char *app = "kte";
|
const char *app = "kte";
|
||||||
left.reserve(256);
|
left.reserve(256);
|
||||||
left += app;
|
left += app;
|
||||||
left += " ";
|
left += " ";
|
||||||
left += KTE_VERSION_STR; // already includes leading 'v'
|
left += KTE_VERSION_STR; // already includes leading 'v'
|
||||||
const Buffer *b = buf;
|
const Buffer *b = buf;
|
||||||
std::string fname;
|
std::string fname;
|
||||||
if (b) {
|
if (b) {
|
||||||
@@ -426,11 +478,11 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
std::size_t total = ed.BufferCount();
|
std::size_t total = ed.BufferCount();
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // human-friendly 1-based
|
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // human-friendly 1-based
|
||||||
left += "[";
|
left += "[";
|
||||||
left += std::to_string(static_cast<unsigned long long>(idx1));
|
left += std::to_string(static_cast<unsigned long long>(idx1));
|
||||||
left += "/";
|
left += "/";
|
||||||
left += std::to_string(static_cast<unsigned long long>(total));
|
left += std::to_string(static_cast<unsigned long long>(total));
|
||||||
left += "] ";
|
left += "] ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
left += fname;
|
left += fname;
|
||||||
@@ -442,9 +494,9 @@ TerminalRenderer::Draw(Editor &ed)
|
|||||||
// Append total line count as "<n>L"
|
// Append total line count as "<n>L"
|
||||||
if (b) {
|
if (b) {
|
||||||
unsigned long lcount = static_cast<unsigned long>(b->Rows().size());
|
unsigned long lcount = static_cast<unsigned long>(b->Rows().size());
|
||||||
left += " ";
|
left += " ";
|
||||||
left += std::to_string(lcount);
|
left += std::to_string(lcount);
|
||||||
left += "L";
|
left += "L";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,16 +8,43 @@ Font::Load(const float size) const
|
|||||||
{
|
{
|
||||||
const ImGuiIO &io = ImGui::GetIO();
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
io.Fonts->Clear();
|
io.Fonts->Clear();
|
||||||
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
|
|
||||||
|
ImFontConfig config;
|
||||||
|
config.MergeMode = false;
|
||||||
|
|
||||||
|
// Load Basic Latin + Latin Supplement
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
this->data_,
|
this->data_,
|
||||||
this->size_,
|
this->size_,
|
||||||
size);
|
size,
|
||||||
|
&config,
|
||||||
|
io.Fonts->GetGlyphRangesDefault());
|
||||||
|
|
||||||
if (!font) {
|
// Merge Greek and Coptic
|
||||||
font = io.Fonts->AddFontDefault();
|
config.MergeMode = true;
|
||||||
}
|
static const ImWchar greek_ranges[] = {
|
||||||
|
0x0370, 0x03FF, // Greek and Coptic
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
|
this->data_,
|
||||||
|
this->size_,
|
||||||
|
size,
|
||||||
|
&config,
|
||||||
|
greek_ranges);
|
||||||
|
|
||||||
|
// Merge Mathematical Operators
|
||||||
|
static const ImWchar math_ranges[] = {
|
||||||
|
0x2200, 0x22FF, // Mathematical Operators
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
io.Fonts->AddFontFromMemoryCompressedTTF(
|
||||||
|
this->data_,
|
||||||
|
this->size_,
|
||||||
|
size,
|
||||||
|
&config,
|
||||||
|
math_ranges);
|
||||||
|
|
||||||
(void) font;
|
|
||||||
io.Fonts->Build();
|
io.Fonts->Build();
|
||||||
}
|
}
|
||||||
} // namespace kte::Fonts
|
} // namespace kte::Fonts
|
||||||
3
main.cc
3
main.cc
@@ -1,3 +1,4 @@
|
|||||||
|
#include <clocale>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -113,6 +114,8 @@ RunStressHighlighter(unsigned seconds)
|
|||||||
int
|
int
|
||||||
main(int argc, const char *argv[])
|
main(int argc, const char *argv[])
|
||||||
{
|
{
|
||||||
|
std::setlocale(LC_ALL, "");
|
||||||
|
|
||||||
Editor editor;
|
Editor editor;
|
||||||
|
|
||||||
// CLI parsing using getopt_long
|
// CLI parsing using getopt_long
|
||||||
|
|||||||
Reference in New Issue
Block a user