Repository :
https://github.com/FarGroup/FarManager
On branch : master
Link :
https://github.com/FarGroup/FarManager/commit/74470b8eb0204c9eb03945e0896a784d45843164
>---------------------------------------------------------------
commit 74470b8eb0204c9eb03945e0896a784d45843164
Author: Alex Alabuzhev <
alab...@gmail.com>
Date: Wed Jan 21 00:56:10 2026 +0000
gh-1065: Continue fullwidth-aware rendering
>---------------------------------------------------------------
74470b8eb0204c9eb03945e0896a784d45843164
far/changelog | 5 ++++
far/char_width.cpp | 11 +++++++++
far/filefilterparams.cpp | 24 +++++++++---------
far/hilight.cpp | 18 +++++++++++---
far/interf.cpp | 64 ++++++++++++++++++++----------------------------
far/interf.hpp | 2 ++
far/strmix.cpp | 10 ++++----
far/vbuild.m4 | 2 +-
8 files changed, 77 insertions(+), 59 deletions(-)
diff --git a/far/changelog b/far/changelog
index 51af602f5..d11ddb09b 100644
--- a/far/changelog
+++ b/far/changelog
@@ -1,3 +1,8 @@
+--------------------------------------------------------------------------------
+drkns 2026-01-21 00:46:34+00:00 - build 6636
+
+1. gh-1065: Continue fullwidth-aware rendering.
+
--------------------------------------------------------------------------------
zg 2026-01-18 20:23:30+02:00 - build 6635
diff --git a/far/char_width.cpp b/far/char_width.cpp
index a86a3ac74..f71e586ca 100644
--- a/far/char_width.cpp
+++ b/far/char_width.cpp
@@ -480,6 +480,14 @@ namespace
return codepoint_width::wide;
}
+
+ [[nodiscard]]
+ bool is_legacy_rendering()
+ {
+ DWORD Mode;
+ static const auto IsRedirected = !console.GetMode(console.GetOutputHandle(), Mode);
+ return !IsRedirected && !console.IsVtActive();
+ }
}
namespace char_width
@@ -487,6 +495,9 @@ namespace char_width
[[nodiscard]]
size_t get(codepoint const Codepoint)
{
+ if (!is_bmp(Codepoint) && is_legacy_rendering())
+ return 2; // Classic grid mode, nothing we can do :(
+
switch (s_FullWidthState)
{
default:
diff --git a/far/filefilterparams.cpp b/far/filefilterparams.cpp
index 993a817b9..1125db8f5 100644
--- a/far/filefilterparams.cpp
+++ b/far/filefilterparams.cpp
@@ -478,16 +478,16 @@ WARNING_POP()
const auto MaxMarkSize = 4;
- const auto
- MarkAmpFix = visual_string_length(Mark) - HiStrlen(Mark),
- NameAmpFix = visual_string_length(Name) - HiStrlen(Name);
+ const auto MarkAmpFix = visual_string_length(Mark) - HiStrlen(Mark);
+ const auto AlignedMark = fit_to_left(Mark, MaxMarkSize + MarkAmpFix);
- return far::format(L"{1:{2}.{2}} {0} {3:{4}.{4}} {0} {5} {6} {0} {7}"sv,
+ const auto NameAmpFix = visual_string_length(Name) - HiStrlen(Name);
+ const auto AlignedName = fit_to_left(string(Name), MaxNameSize + NameAmpFix);
+
+ return far::format(L"{1} {0} {2} {0} {3} {4} {0} {5}"sv,
BoxSymbols[BS_V1],
- Mark,
- MaxMarkSize + MarkAmpFix,
- Name,
- MaxNameSize + NameAmpFix,
+ AlignedMark,
+ AlignedName,
AttrStr,
OtherFlags,
Mask
@@ -495,13 +495,13 @@ WARNING_POP()
}
const auto HotkeyStr = Hotkey? far::format(L"&{}. "sv, Hotkey) : bPanelType? L" "s : L""s;
- const auto AmpFix = Hotkey? 1 : visual_string_length(Name) - HiStrlen(Name);
+ const auto NameAmpFix = Hotkey? 1 : visual_string_length(Name) - HiStrlen(Name);
+ const auto AlignedName = fit_to_left(string(Name), MaxNameSize + NameAmpFix);
- return far::format(L"{1}{2:{3}.{3}} {0} {4} {5} {0} {6}"sv,
+ return far::format(L"{1}{2} {0} {3} {4} {0} {5}"sv,
BoxSymbols[BS_V1],
HotkeyStr,
- Name,
- MaxNameSize + AmpFix - HotkeyStr.size(),
+ AlignedName,
AttrStr,
OtherFlags,
Mask
diff --git a/far/hilight.cpp b/far/hilight.cpp
index a677c04c1..c028c4ed0 100644
--- a/far/hilight.cpp
+++ b/far/hilight.cpp
@@ -647,9 +647,21 @@ void HighlightDlgUpdateUserControl(matrix_view<FAR_CHAR_INFO> const& VBufColorEx
if (!Colors.Mark.Mark.empty() && !Colors.Mark.Inherit)
{
- Iterator->Char = Colors.Mark.Mark.front();
- Iterator->Attributes = BakedColors[ColorIndex].MarkColor;
- ++Iterator;
+ for (const auto& Cell: text_to_char_info(Colors.Mark.Mark, Row.size() - 2))
+ {
+ const auto Reserved0 = Cell.Reserved0;
+ const auto Reserved1 = Cell.Reserved1;
+ const auto RawAttributes = Cell.Attributes.Flags & FCF_RAWATTR_MASK;
+
+ Iterator->Char = Cell.Char;
+ Iterator->Attributes = BakedColors[ColorIndex].MarkColor;
+
+ Iterator->Reserved0 = Reserved0;
+ Iterator->Reserved1 = Reserved1;
+ Iterator->Attributes.Flags |= RawAttributes;
+
+ ++Iterator;
+ }
}
const std::span FileArea(Iterator, Row.end() - 1);
diff --git a/far/interf.cpp b/far/interf.cpp
index 6ac0af5ee..6394fd52f 100644
--- a/far/interf.cpp
+++ b/far/interf.cpp
@@ -868,35 +868,17 @@ static void string_to_cells_full_width_aware(string_view Str, size_t& CharsConsu
if (Char[1])
{
// It's a surrogate pair that occupies one cell only. Here be dragons.
- if (console.IsVtActive())
- {
- std::visit(overload
- {
- [&](size_t&){},
- [&](std::vector<FAR_CHAR_INFO>& Buffer)
- {
- // Put *one* fake character:
- Buffer.back().Char = encoding::replace_char;
- // Stash the actual codepoint. The drawing code will restore it from here:
- Buffer.back().Reserved1 = Codepoint;
- }
- }, Cells);
- }
- else
+ std::visit(overload
{
- // Classic grid mode, nothing we can do :(
- // Expect the broken UI
-
- if (get_cells_count() == CellsAvailable)
+ [&](size_t&){},
+ [&](std::vector<FAR_CHAR_INFO>& Buffer)
{
- // No space left for the trailing char
- CharsConsumedNow = 0;
- pop_back();
- break;
+ // Put *one* fake character:
+ Buffer.back().Char = encoding::replace_char;
+ // Stash the actual codepoint. The drawing code will restore it from here:
+ Buffer.back().Reserved1 = Codepoint;
}
-
- push_back(Char[1]);
- }
+ }, Cells);
}
else
{
@@ -924,21 +906,24 @@ void chars_to_cells(string_view Str, size_t& CharsConsumed, size_t const CellsAv
#endif
}
-size_t Text(string_view Str, size_t const CellsAvailable)
+std::vector<FAR_CHAR_INFO> text_to_char_info(string_view Str, size_t CellsAvailable)
{
- if (Str.empty())
- return 0;
-
cells Cells;
- const auto& Buffer = Cells.emplace<1>();
+ auto& Buffer = Cells.emplace<1>();
- size_t CharsConsumed = 0;
+ if (Str.empty())
+ return Buffer;
+ size_t CharsConsumed = 0;
string_to_cells(Str, CharsConsumed, Cells, CellsAvailable);
+ return Buffer;
+}
+size_t Text(string_view Str, size_t const CellsAvailable)
+{
+ auto Buffer = text_to_char_info(Str, CellsAvailable);
Global->ScrBuf->Write(CurX, CurY, Buffer);
CurX += static_cast<int>(Buffer.size());
-
return Buffer.size();
}
@@ -1692,24 +1677,27 @@ size_t HiStrlen(string_view const Str)
{
if (encoding::utf16::is_high_surrogate(Char))
{
+ if (First)
+ ++Result;
+
First = Char;
return true;
}
const auto IsLow = encoding::utf16::is_low_surrogate(Char);
- if (!IsLow)
- First.reset();
+ if (First && !IsLow)
+ ++Result;
const auto Codepoint = First && IsLow? encoding::utf16::extract_codepoint(*First, Char) : Char;
-
Result += char_width::get(Codepoint);
+
+ First.reset();
+
return true;
});
if (First)
- {
++Result;
- }
return Result;
}
diff --git a/far/interf.hpp b/far/interf.hpp
index 7cf4b31b2..ee7976369 100644
--- a/far/interf.hpp
+++ b/far/interf.hpp
@@ -175,6 +175,8 @@ void chars_to_cells(string_view Str, size_t& CharsConsumed, size_t CellsAvailabl
bool is_valid_surrogate_pair(string_view Str);
bool is_valid_surrogate_pair(wchar_t First, wchar_t Second);
+std::vector<FAR_CHAR_INFO> text_to_char_info(string_view Str, size_t CellsAvailable);
+
void Text(point Where, const FarColor& Color, string_view Str);
size_t Text(string_view Str, size_t CellsAvailable);
diff --git a/far/strmix.cpp b/far/strmix.cpp
index 551831b03..f4ea7f908 100644
--- a/far/strmix.cpp
+++ b/far/strmix.cpp
@@ -221,7 +221,7 @@ void inplace::pad_right(std::wstring& Str, size_t const CellsAvailable, wchar_t
void inplace::fit_to_left(std::wstring& Str, size_t const CellsAvailable)
{
- cut_right(Str, CellsAvailable);
+ truncate_right(Str, CellsAvailable);
pad_right(Str, CellsAvailable);
}
@@ -238,13 +238,13 @@ void inplace::fit_to_center(std::wstring& Str, size_t const CellsAvailable)
}
else
{
- cut_right(Str, CellsAvailable);
+ truncate_right(Str, CellsAvailable);
}
}
void inplace::fit_to_right(std::wstring& Str, size_t const CellsAvailable)
{
- cut_right(Str, CellsAvailable);
+ truncate_right(Str, CellsAvailable);
pad_left(Str, CellsAvailable);
}
@@ -2039,8 +2039,8 @@ TEST_CASE("strmix.fit")
{ L"1"sv, 2, L"1 "sv, L"1 "sv, L" 1"sv, },
{ L"12345"sv, 0, {}, {}, {}, },
- { L"12345"sv, 1, L"1"sv, L"1"sv, L"1"sv, },
- { L"12345"sv, 3, L"123"sv, L"123"sv, L"123"sv, },
+ { L"12345"sv, 1, L"…"sv, L"…"sv, L"…"sv, },
+ { L"12345"sv, 3, L"12…"sv, L"12…"sv, L"12…"sv, },
{ L"12345"sv, 5, L"12345"sv, L"12345"sv, L"12345"sv, },
{ L"12345"sv, 7, L"12345 "sv, L" 12345 "sv, L" 12345"sv, },
};
diff --git a/far/vbuild.m4 b/far/vbuild.m4
index d5db18849..ae30c90c4 100644
--- a/far/vbuild.m4
+++ b/far/vbuild.m4
@@ -1 +1 @@
-6635
+6636