Skip to content

Commit 8b6f9bc

Browse files
committed
CommandScreen: wrap at wide character boundaries
* create separate functions for UTF-8/ASCII scanning * expand line buffer to handle UTF-8 combining marks * skip UTF-8 codepoint decoding for ASCII characters * add missing include and ifdef guard for CRT_utf8 * check mbrtowc before char width calculation, thanks to @Explorer09 * ensure sufficient room to handle UTF-8 code sequences, especially unicode characters with arbitrary combining marks or diacritics.
1 parent 218f2df commit 8b6f9bc

1 file changed

Lines changed: 98 additions & 19 deletions

File tree

CommandScreen.c

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,45 +12,124 @@ in the source distribution for its full text.
1212

1313
#include <assert.h>
1414
#include <stdlib.h>
15+
#include <stdio.h>
1516
#include <string.h>
17+
#include <wchar.h>
18+
#include <wctype.h>
19+
#include <locale.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);
26-
27-
const char* p = Process_getCommand(this->process);
28-
char line[COLS + 1];
29-
int line_offset = 0, last_spc = -1, len;
30-
for (; *p != '\0'; p++, line_offset++) {
31-
assert(line_offset >= 0 && (size_t)line_offset < sizeof(line));
32-
line[line_offset] = *p;
33-
if (*p == ' ') {
34-
last_spc = line_offset;
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;
3534
}
3635

3736
if (line_offset == COLS) {
38-
len = (last_spc == -1) ? line_offset : last_spc;
39-
line[len] = '\0';
37+
line_size = (last_spc_offset == -1) ? line_offset : last_spc_offset;
38+
line[line_size] = '\0';
4039
InfoScreen_addLine(this, line);
4140

42-
line_offset -= len;
43-
last_spc = -1;
44-
memcpy(line, p - line_offset, line_offset + 1);
41+
line_offset -= line_size;
42+
last_spc_offset = -1;
43+
memcpy(line, p + i - line_offset, line_offset + 1);
44+
}
45+
}
46+
47+
return line_offset;
48+
}
49+
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+
}
77+
78+
i += bytes;
79+
line_offset += bytes;
80+
line_cols += width;
81+
if (line_cols < COLS) {
82+
continue;
83+
}
84+
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;
95+
}
96+
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);
45102
}
46103
}
47104

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);
48127
if (line_offset > 0) {
49128
line[line_offset] = '\0';
50129
InfoScreen_addLine(this, line);
51130
}
52131

53-
Panel_setSelected(panel, idx);
132+
Panel_setSelected(panel, MAXIMUM(idx, 0));
54133
}
55134

56135
static void CommandScreen_draw(InfoScreen* this) {

0 commit comments

Comments
 (0)