Skip to content

Commit 46c3d55

Browse files
ryenusBenBE
andcommitted
CommandScreen: wrap at multi-byte character boundaries
- add missing include and ifdef guard for CRT_utf8 - adjust the order of include directives - create separate functions for UTF-8/ASCII scanning - check mbrtowc before char width calculation - skip UTF-8 codepoint decoding for ASCII characters - expand line buffer to handle UTF-8 combining marks To ensure sufficient space to handle UTF-8 code sequences, especially unicode characters with arbitrary combining marks or diacritics, which might take more bytes than the number of visual columns. Co-authored-by: BenBE <[email protected]>
1 parent ae59443 commit 46c3d55

File tree

1 file changed

+95
-28
lines changed

1 file changed

+95
-28
lines changed

CommandScreen.c

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,58 +11,125 @@ in the source distribution for its full text.
1111
#include "CommandScreen.h"
1212

1313
#include <assert.h>
14+
#include <locale.h>
15+
#include <stdio.h>
1416
#include <stdlib.h>
1517
#include <string.h>
18+
#include <wchar.h>
19+
#include <wctype.h>
1620

21+
#include "CRT.h"
1722
#include "Macros.h"
1823
#include "Panel.h"
1924
#include "ProvideCurses.h"
2025

2126

22-
static void CommandScreen_scan(InfoScreen* this) {
23-
Panel* panel = this->display;
24-
int idx = MAXIMUM(Panel_getSelectedIndex(panel), 0);
25-
Panel_prune(panel);
27+
static int CommandScreen_scanAscii(InfoScreen* this, const char* p, size_t total, char* line) {
28+
int line_offset = 0, line_size = 0, last_spc_offset = -1;
29+
for (size_t i = 0; i < total; i++, line_offset++) {
30+
assert(line_offset >= 0 && (size_t)line_offset <= total);
31+
char c = line[line_offset] = p[i];
32+
if (c == ' ') {
33+
last_spc_offset = line_offset;
34+
}
2635

27-
const char* p = Process_getCommand(this->process);
36+
if (line_offset == COLS) {
37+
line_size = (last_spc_offset == -1) ? line_offset : last_spc_offset;
38+
line[line_size] = '\0';
39+
InfoScreen_addLine(this, line);
2840

29-
size_t line_maxlen = COLS < 40 ? 40 : COLS;
30-
size_t line_offset = 0;
31-
size_t last_space = 0;
32-
char* line = xCalloc(line_maxlen + 1, sizeof(char));
41+
line_offset -= line_size;
42+
last_spc_offset = -1;
43+
memcpy(line, p + i - line_offset, line_offset + 1);
44+
}
45+
}
3346

34-
for (; *p != '\0'; p++) {
35-
if (line_offset >= line_maxlen) {
36-
assert(line_offset <= line_maxlen);
37-
assert(last_space <= line_maxlen);
47+
return line_offset;
48+
}
3849

39-
size_t line_len = last_space <= 0 ? line_offset : last_space;
40-
char tmp = line[line_len];
41-
line[line_len] = '\0';
42-
InfoScreen_addLine(this, line);
43-
line[line_len] = tmp;
50+
#ifdef HAVE_LIBNCURSESW
51+
52+
static int CommandScreen_scanWide(InfoScreen* this, const char* p, size_t total, char* line) {
53+
mbstate_t state;
54+
memset(&state, 0, sizeof(state));
55+
int line_cols = 0, line_offset = 0, line_size = 0, width = 1;
56+
int last_spc_cols = -1, last_spc_offset = -1;
57+
for (size_t i = 0, bytes = 1; i < total; bytes = 1, width = 1) {
58+
assert(line_offset >= 0 && (size_t)line_offset <= total);
59+
unsigned char c = (unsigned char)p[i];
60+
if (c < 0x80) { // skip mbrtowc for ASCII characters
61+
line[line_offset] = c;
62+
if (c == ' ') {
63+
last_spc_offset = line_offset;
64+
last_spc_cols = line_cols;
65+
}
66+
} else {
67+
wchar_t wc;
68+
bytes = mbrtowc(&wc, p + i, total - i, &state);
69+
if (bytes != (size_t)-1 && bytes != (size_t)-2) {
70+
width = wcwidth(wc);
71+
width = MAXIMUM(width, 1);
72+
} else {
73+
bytes = 1;
74+
}
75+
memcpy(line + line_offset, p + i, bytes);
76+
}
4477

45-
assert(line_len <= line_offset);
46-
line_offset -= line_len;
47-
memmove(line, line + line_len, line_offset);
78+
i += bytes;
79+
line_offset += bytes;
80+
line_cols += width;
81+
if (line_cols < COLS) {
82+
continue;
83+
}
4884

49-
last_space = 0;
85+
if (last_spc_offset >= 0) { // wrap by last space
86+
line_size = last_spc_offset;
87+
line_cols -= last_spc_cols;
88+
last_spc_offset = last_spc_cols = -1;
89+
} else if (line_cols > COLS) { // wrap current wide char to next line
90+
line_size = line_offset - (int) bytes;
91+
line_cols = width;
92+
} else { // wrap by current character
93+
line_size = line_offset;
94+
line_cols = 0;
5095
}
5196

52-
line[line_offset++] = *p;
53-
if (*p == ' ') {
54-
last_space = line_offset;
97+
line[line_size] = '\0';
98+
InfoScreen_addLine(this, line);
99+
line_offset -= line_size;
100+
if (line_offset > 0) {
101+
memcpy(line, p + i - line_offset, line_offset + 1);
55102
}
56103
}
57104

105+
return line_offset;
106+
}
107+
108+
#endif // HAVE_LIBNCURSESW
109+
110+
static void CommandScreen_scan(InfoScreen* this) {
111+
Panel* panel = this->display;
112+
int idx = Panel_getSelectedIndex(panel);
113+
Panel_prune(panel);
114+
115+
const char* p = Process_getCommand(this->process);
116+
assert(p != NULL);
117+
size_t total = strlen(p);
118+
char line[total + 1];
119+
120+
int line_offset =
121+
#ifdef HAVE_LIBNCURSESW
122+
CRT_utf8 ? CommandScreen_scanWide(this, p, total, line) :
123+
#endif
124+
CommandScreen_scanAscii(this, p, total, line);
125+
126+
assert(line_offset >= 0 && (size_t)line_offset <= total);
58127
if (line_offset > 0) {
59128
line[line_offset] = '\0';
60129
InfoScreen_addLine(this, line);
61130
}
62131

63-
free(line);
64-
65-
Panel_setSelected(panel, idx);
132+
Panel_setSelected(panel, MAXIMUM(idx, 0));
66133
}
67134

68135
static void CommandScreen_draw(InfoScreen* this) {

0 commit comments

Comments
 (0)