mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-04 08:36:11 +02:00
feat: improve word wrapping in ChatLine
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using EchoHub.Core.Models;
|
using EchoHub.Core.Models;
|
||||||
using Terminal.Gui.Drawing;
|
using Terminal.Gui.Drawing;
|
||||||
@@ -18,6 +19,8 @@ public partial class ChatLine
|
|||||||
public string? AttachmentUrl { get; set; }
|
public string? AttachmentUrl { get; set; }
|
||||||
public string? AttachmentFileName { get; set; }
|
public string? AttachmentFileName { get; set; }
|
||||||
public MessageType? Type { get; set; }
|
public MessageType? Type { get; set; }
|
||||||
|
/// <summary>Number of spaces to prepend on continuation lines when this line is word-wrapped.</summary>
|
||||||
|
public int ContinuationIndent { get; set; }
|
||||||
|
|
||||||
public ChatLine(string plainText)
|
public ChatLine(string plainText)
|
||||||
{
|
{
|
||||||
@@ -42,51 +45,78 @@ public partial class ChatLine
|
|||||||
if (width <= 0 || TextLength <= width)
|
if (width <= 0 || TextLength <= width)
|
||||||
return [this];
|
return [this];
|
||||||
|
|
||||||
var results = new List<ChatLine>();
|
var tokens = new List<(string grapheme, Attribute? color)>();
|
||||||
var currentSegments = new List<ChatSegment>();
|
|
||||||
int col = 0;
|
|
||||||
|
|
||||||
foreach (var segment in Segments)
|
foreach (var segment in Segments)
|
||||||
{
|
foreach (var g in GraphemeHelper.GetGraphemes(segment.Text))
|
||||||
var text = segment.Text;
|
tokens.Add((g, segment.Color));
|
||||||
int chunkStart = 0;
|
|
||||||
int charPos = 0;
|
|
||||||
|
|
||||||
foreach (var grapheme in GraphemeHelper.GetGraphemes(text))
|
var results = new List<ChatLine>();
|
||||||
{
|
int pos = 0;
|
||||||
var graphemeCols = Math.Max(grapheme.GetColumns(), 1);
|
bool firstLine = true;
|
||||||
|
|
||||||
if (col + graphemeCols > width)
|
while (pos < tokens.Count)
|
||||||
{
|
{
|
||||||
if (charPos > chunkStart)
|
// First line uses the full width
|
||||||
currentSegments.Add(new ChatSegment(text[chunkStart..charPos], segment.Color));
|
int col = firstLine ? 0 : continuationIndent;
|
||||||
|
int lastSpaceIdx = -1;
|
||||||
|
|
||||||
results.Add(new ChatLine(currentSegments));
|
int i = pos;
|
||||||
currentSegments = [];
|
for (; i < tokens.Count; i++)
|
||||||
|
|
||||||
if (continuationIndent > 0)
|
|
||||||
{
|
{
|
||||||
currentSegments.Add(new ChatSegment(new string(' ', continuationIndent), null));
|
var graphemeCols = Math.Max(tokens[i].grapheme.GetColumns(), 1);
|
||||||
col = continuationIndent;
|
if (col + graphemeCols > width) break;
|
||||||
|
if (tokens[i].grapheme == " ") lastSpaceIdx = i;
|
||||||
|
col += graphemeCols;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lineEnd, nextPos;
|
||||||
|
if (i == tokens.Count)
|
||||||
|
{
|
||||||
|
// All remaining tokens fit on this line.
|
||||||
|
lineEnd = tokens.Count;
|
||||||
|
nextPos = tokens.Count;
|
||||||
|
}
|
||||||
|
else if (lastSpaceIdx >= pos)
|
||||||
|
{
|
||||||
|
// Break at the last space that fit, skip the space itself.
|
||||||
|
lineEnd = lastSpaceIdx;
|
||||||
|
nextPos = lastSpaceIdx + 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
col = 0;
|
// No space found, break word.
|
||||||
|
lineEnd = Math.Max(i, pos + 1);
|
||||||
|
nextPos = lineEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkStart = charPos;
|
var segments = new List<ChatSegment>();
|
||||||
|
if (!firstLine && continuationIndent > 0)
|
||||||
|
segments.Add(new ChatSegment(new string(' ', continuationIndent), null));
|
||||||
|
|
||||||
|
// Rebuild segments by grouping consecutive same-color tokens.
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
int groupStart = pos;
|
||||||
|
while (groupStart < lineEnd)
|
||||||
|
{
|
||||||
|
var color = tokens[groupStart].color;
|
||||||
|
sb.Clear();
|
||||||
|
int groupEnd = groupStart;
|
||||||
|
while (groupEnd < lineEnd && tokens[groupEnd].color == color)
|
||||||
|
{
|
||||||
|
sb.Append(tokens[groupEnd].grapheme);
|
||||||
|
groupEnd++;
|
||||||
|
}
|
||||||
|
segments.Add(new ChatSegment(sb.ToString(), color));
|
||||||
|
groupStart = groupEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
col += graphemeCols;
|
results.Add(new ChatLine(segments));
|
||||||
charPos += grapheme.Length;
|
pos = nextPos;
|
||||||
|
firstLine = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkStart < text.Length)
|
if (results.Count == 0)
|
||||||
currentSegments.Add(new ChatSegment(text[chunkStart..], segment.Color));
|
return [this];
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSegments.Count > 0)
|
|
||||||
results.Add(new ChatLine(currentSegments));
|
|
||||||
|
|
||||||
// Propagate attachment/type metadata to all wrapped lines so they remain clickable
|
// Propagate attachment/type metadata to all wrapped lines so they remain clickable
|
||||||
foreach (var wrapped in results)
|
foreach (var wrapped in results)
|
||||||
|
|||||||
@@ -259,6 +259,9 @@ public sealed class ChatMessageManager
|
|||||||
lines.Add(new ChatLine(ChatColors.SplitMentions(contText)));
|
lines.Add(new ChatLine(ChatColors.SplitMentions(contText)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var l in lines)
|
||||||
|
l.ContinuationIndent = indent.Length;
|
||||||
|
|
||||||
if (message.Embeds is { Count: > 0 })
|
if (message.Embeds is { Count: > 0 })
|
||||||
{
|
{
|
||||||
var chatWidth = _chatWidth > 0 ? _chatWidth : 80;
|
var chatWidth = _chatWidth > 0 ? _chatWidth : 80;
|
||||||
|
|||||||
@@ -772,7 +772,7 @@ public sealed class MainWindow : Runnable
|
|||||||
if (width > 0)
|
if (width > 0)
|
||||||
{
|
{
|
||||||
foreach (var line in messages)
|
foreach (var line in messages)
|
||||||
source.AddRange(line.Wrap(width));
|
source.AddRange(line.Wrap(width, line.ContinuationIndent));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user