This commit is contained in:
kofany 2026-01-29 00:17:42 -08:00 committed by GitHub
commit 1544c513d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 580 additions and 81 deletions

6
.gitignore vendored
View file

@ -89,3 +89,9 @@ subprojects/*
Irssi-Dist
setup.cfg
*.egg-info
.specstory/
.cursorindexingignore
.cursorignore
.kombai/
.claude/
inst/

View file

@ -45,6 +45,7 @@
#include <irssi/src/core/servers.h>
#include <irssi/src/core/special-vars.h>
#include <irssi/src/core/write-buffer.h>
#include <irssi/src/core/utf8.h>
#include <irssi/src/core/channels.h>
#include <irssi/src/core/queries.h>
@ -285,6 +286,7 @@ void core_init(void)
i_refstr_init();
special_vars_init();
wcwidth_wrapper_init();
utf8_init();
settings_add_str("misc", "ignore_signals", "");
settings_add_bool("misc", "override_coredump_limit", FALSE);
@ -316,6 +318,7 @@ void core_deinit(void)
signal_remove("chat protocol created", (SIGNAL_FUNC) reread_setup);
signal_remove("irssi init finished", (SIGNAL_FUNC) sig_irssi_init_finished);
utf8_deinit();
wcwidth_wrapper_deinit();
special_vars_deinit();
i_refstr_deinit();

View file

@ -120,6 +120,13 @@ char *recode_in(const SERVER_REC *server, const char *str, const char *target)
else
from = find_conversion(server, target);
/* Don't use TRANSLIT when both terminal and string are UTF-8
* and no specific conversion is configured - preserves emoji variation selectors */
if (from == NULL && term_is_utf8 && str_is_utf8) {
g_debug("recode_in: UTF-8 bypass for: %s", str);
return g_strdup(str);
}
if (from)
recoded = g_convert_with_fallback(str, len, to, from, NULL, NULL, NULL, NULL);
@ -150,7 +157,7 @@ char *recode_out(const SERVER_REC *server, const char *str, const char *target)
const char *from = translit_charset;
const char *to = NULL;
char *translit_to = NULL;
gboolean translit, recode;
gboolean translit, recode, str_is_utf8;
int len;
if (!str)
@ -162,6 +169,9 @@ char *recode_out(const SERVER_REC *server, const char *str, const char *target)
len = strlen(str);
/* Check if string is valid UTF-8 */
str_is_utf8 = g_utf8_validate(str, len, NULL);
translit = settings_get_bool("recode_transliterate");
to = find_conversion(server, target);
@ -170,10 +180,18 @@ char *recode_out(const SERVER_REC *server, const char *str, const char *target)
to = settings_get_str("recode_out_default_charset");
if (to && *to != '\0') {
if (translit && !is_translit(to))
/* Don't use TRANSLIT when both terminal and target are UTF-8
* and string is valid UTF-8 - this preserves emoji variation selectors */
if (translit && !is_translit(to) &&
!(term_is_utf8 && str_is_utf8 && g_ascii_strcasecmp(to, "UTF-8") == 0))
to = translit_to = g_strconcat(to ,"//TRANSLIT", NULL);
recoded = g_convert(str, len, to, from, NULL, NULL, NULL);
} else if (term_is_utf8 && str_is_utf8) {
/* When no specific charset conversion is configured and both terminal
* and string are UTF-8, don't do any conversion to preserve emoji */
g_debug("recode_out: UTF-8 bypass for: %s", str);
recoded = g_strdup(str);
}
g_free(translit_to);
if (!recoded)

View file

@ -27,9 +27,82 @@
/* Provide is_utf8(): */
#include <irssi/src/core/recode.h>
#include <irssi/src/core/signals.h>
#include <irssi/src/core/settings.h>
#ifdef HAVE_LIBUTF8PROC
#include <utf8proc.h>
/* Advance the str pointer one grapheme cluster further when utf8proc is available,
* or fall back to single character advancement. Returns display width. */
static int string_advance_with_grapheme_support(char const **str, int policy)
{
utf8proc_int32_t codepoint, prev_codepoint = 0;
utf8proc_int32_t state = 0;
const char *start = *str;
const char *pos = *str;
int cluster_width = 0;
int has_variation_selector = 0;
utf8proc_ssize_t bytes;
if (policy != TREAT_STRING_AS_UTF8) {
/* Fall back to byte-based processing */
*str += 1;
return 1;
}
if (*pos == '\0') {
return 0;
}
/* Process codepoints until we find a grapheme boundary */
while (*pos != '\0') {
bytes = utf8proc_iterate((const utf8proc_uint8_t *)pos, -1, &codepoint);
if (bytes < 0) {
/* Invalid UTF-8, skip one byte */
*str = pos + 1;
return 1;
}
/* Check if this is a grapheme boundary */
if (pos != start && utf8proc_grapheme_break_stateful(prev_codepoint, codepoint, &state)) {
/* We found the end of the current cluster */
break;
}
/* Check for variation selector */
if (codepoint == 0xFE0F) {
has_variation_selector = 1;
}
/* Add this codepoint's width to the cluster */
if (unichar_isprint(codepoint)) {
int char_width = i_wcwidth(codepoint);
if (char_width > cluster_width) {
cluster_width = char_width;
}
}
prev_codepoint = codepoint;
pos += bytes;
}
/* Special handling for emoji with variation selector */
if (has_variation_selector && cluster_width == 1) {
/* Base emoji (like ❣ U+2763, ♥ U+2665) + variation selector should have width 2 */
cluster_width = 2;
}
*str = pos;
return cluster_width > 0 ? cluster_width : 1;
}
#endif
int string_advance(char const **str, int policy)
{
#ifdef HAVE_LIBUTF8PROC
return string_advance_with_grapheme_support(str, policy);
#else
if (policy == TREAT_STRING_AS_UTF8) {
gunichar c;
@ -43,6 +116,7 @@ int string_advance(char const **str, int policy)
return 1;
}
#endif
}
int string_policy(const char *str)
@ -133,3 +207,218 @@ int string_chars_for_width(const char *str, int policy, unsigned int n, unsigned
}
return char_count;
}
int unichar_width(unichar chr)
{
int width;
/* For individual codepoints, fall back to standard wcwidth.
* This is not grapheme-cluster aware, but better than nothing
* for GUI code that works with unichar arrays instead of UTF-8 strings.
*
* Note: For proper emoji support, use string_advance() on UTF-8 strings
* instead of processing individual codepoints.
*/
if (!unichar_isprint(chr))
return 1;
width = i_wcwidth(chr);
return width < 0 ? 1 : width;
}
int unichar_array_advance_cluster(const unichar *text, int text_len, int *pos)
{
#ifdef HAVE_LIBUTF8PROC
utf8proc_int32_t state = 0;
int cluster_width = 0;
utf8proc_int32_t first_codepoint, codepoint;
int char_width;
int has_variation_selector = 0;
if (*pos >= text_len) {
return 0;
}
/* Process first codepoint */
first_codepoint = text[*pos];
if (unichar_isprint(first_codepoint)) {
char_width = i_wcwidth(first_codepoint);
if (char_width > cluster_width) {
cluster_width = char_width;
}
}
(*pos)++;
/* Process additional codepoints until we find a grapheme boundary */
while (*pos < text_len) {
codepoint = text[*pos];
/* Check if this is a grapheme boundary */
if (utf8proc_grapheme_break_stateful(text[*pos - 1], codepoint, &state)) {
/* We found the end of the current cluster */
break;
}
/* Check for variation selector */
if (codepoint == 0xFE0F) {
has_variation_selector = 1;
}
/* Add this codepoint's width to the cluster (usually 0 for combining chars) */
if (unichar_isprint(codepoint)) {
char_width = i_wcwidth(codepoint);
if (char_width > cluster_width) {
cluster_width = char_width;
}
}
(*pos)++;
}
/* Special handling for emoji with variation selector */
if (has_variation_selector && cluster_width == 1) {
/* Base emoji (like ❣ U+2763, ♥ U+2665) + variation selector should have width 2 */
cluster_width = 2;
}
return cluster_width > 0 ? cluster_width : 1;
#else
/* Fall back to single character processing when utf8proc unavailable */
unichar chr;
int width;
if (*pos >= text_len) {
return 0;
}
chr = text[*pos];
(*pos)++;
/* Bounds check after increment */
if (*pos > text_len) {
*pos = text_len;
}
if (!unichar_isprint(chr))
return 1;
width = i_wcwidth(chr);
return width < 0 ? 1 : width;
#endif
}
int unichar_array_move_cluster_backward(const unichar *text, int text_len, int *pos)
{
#ifdef HAVE_LIBUTF8PROC
utf8proc_int32_t state = 0;
int cluster_start;
int cluster_width = 0;
int temp_pos;
if (*pos <= 0) {
return 0;
}
/* Move back one codepoint first */
(*pos)--;
/* Find the beginning of the current grapheme cluster by going backwards */
cluster_start = *pos;
/* Go back to find cluster boundary */
while (cluster_start > 0) {
/* Check if there's a grapheme boundary between previous char and current */
if (utf8proc_grapheme_break_stateful(text[cluster_start - 1], text[cluster_start], &state)) {
/* Found boundary, cluster starts here */
break;
}
cluster_start--;
}
/* Calculate width of this cluster */
temp_pos = cluster_start;
while (temp_pos < text_len && temp_pos < *pos + 1) {
unichar codepoint = text[temp_pos];
if (unichar_isprint(codepoint)) {
int char_width = i_wcwidth(codepoint);
if (char_width > cluster_width) {
cluster_width = char_width;
}
}
temp_pos++;
}
*pos = cluster_start;
return cluster_width > 0 ? cluster_width : 1;
#else
/* Fall back to single character processing when utf8proc unavailable */
unichar chr;
int width;
if (*pos <= 0) {
return 0;
}
(*pos)--;
chr = text[*pos];
if (!unichar_isprint(chr))
return 1;
width = i_wcwidth(chr);
return width < 0 ? 1 : width;
#endif
}
int unichar_array_find_cluster_start(const unichar *text, int text_len, int pos)
{
#ifdef HAVE_LIBUTF8PROC
utf8proc_int32_t state = 0;
int cluster_start = pos;
if (pos <= 0 || pos >= text_len) {
return pos;
}
/* Go back to find cluster boundary */
while (cluster_start > 0) {
/* Check if there's a grapheme boundary between previous char and current */
if (utf8proc_grapheme_break_stateful(text[cluster_start - 1], text[cluster_start], &state)) {
/* Found boundary, cluster starts here */
break;
}
cluster_start--;
}
return cluster_start;
#else
/* Fall back to single character processing when utf8proc unavailable */
return pos;
#endif
}
int is_combining_char(unichar c)
{
if (!is_utf8())
return 0;
#ifdef HAVE_LIBUTF8PROC
/* Use utf8proc for precise combining character detection */
return unichar_isprint(c) && utf8proc_charwidth(c) == 0;
#else
/* Fallback to unichar_width for compatibility */
return unichar_isprint(c) && unichar_width(c) == 0;
#endif
}
void utf8_init(void)
{
/* no-op */
}
void utf8_deinit(void)
{
/* Nothing to clean up currently */
}

View file

@ -56,6 +56,32 @@ int string_width(const char *str, int policy);
*/
int string_chars_for_width(const char *str, int policy, unsigned int n, unsigned int *bytes);
/* Calculate display width of a single unichar, considering it might be part of
* a grapheme cluster. For best results, use string_advance() on UTF-8 strings. */
int unichar_width(unichar chr);
/* Advance through unichar array by one grapheme cluster, return display width.
* Updates *pos to point after the cluster. Use for GUI code with unichar arrays. */
int unichar_array_advance_cluster(const unichar *text, int text_len, int *pos);
/* Move backward through unichar array by one grapheme cluster, return display width.
* Updates *pos to point to the start of the previous cluster. */
int unichar_array_move_cluster_backward(const unichar *text, int text_len, int *pos);
/* Find the start of the grapheme cluster containing the given codepoint position.
* Returns the codepoint index of the cluster start. */
int unichar_array_find_cluster_start(const unichar *text, int text_len, int pos);
/* Check if a unichar is a combining character (width 0).
* Returns 1 if the character is combining, 0 otherwise. */
int is_combining_char(unichar c);
/* Initialize UTF-8 debugging system */
void utf8_init(void);
/* Deinitialize UTF-8 debugging system */
void utf8_deinit(void);
#define unichar_isprint(c) (((c) & ~0x80) >= 32)
#define is_utf8_leading(c) (((c) & 0xc0) != 0x80)

View file

@ -23,12 +23,17 @@
#include <irssi/src/core/settings.h>
#include <irssi/src/core/utf8.h>
#include <irssi/src/fe-common/core/formats.h>
#include <irssi/src/fe-common/core/printtext.h>
#include <irssi/src/fe-text/gui-entry.h>
#include <irssi/src/fe-text/gui-printtext.h>
#include <irssi/src/fe-text/term.h>
#include <irssi/src/core/recode.h>
#ifdef HAVE_LIBUTF8PROC
#include <utf8proc.h>
#endif
#undef i_toupper
#undef i_tolower
#undef i_isalnum
@ -52,7 +57,7 @@ static unichar i_tolower(unichar c)
static int i_isalnum(unichar c)
{
if (term_type == TERM_TYPE_UTF8)
return (g_unichar_isalnum(c) || i_wcwidth(c) == 0);
return (g_unichar_isalnum(c) || is_combining_char(c));
return c <= 255 ? isalnum(c) : 0;
}
@ -213,16 +218,24 @@ static int pos2scrpos(GUI_ENTRY_REC *entry, int pos, int cursor)
xpos += scrlen_str(entry->extents[0], entry->utf8);
}
for (i = 0; i < entry->text_len && i < pos; i++) {
unichar c = entry->text[i];
/* Process text using grapheme cluster aware advancement when possible */
i = 0;
while (i < entry->text_len && i < pos) {
const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
int char_width;
if (term_type == TERM_TYPE_BIG5)
xpos += big5_width(c);
else if (entry->utf8)
xpos += unichar_isprint(c) ? i_wcwidth(c) : 1;
else
xpos++;
if (term_type == TERM_TYPE_BIG5) {
char_width = big5_width(entry->text[i]);
i++;
} else if (entry->utf8) {
/* Use grapheme cluster aware advancement */
char_width = unichar_array_advance_cluster(entry->text, entry->text_len, &i);
} else {
char_width = 1;
i++;
}
xpos += char_width;
if (extent != NULL) {
xpos += scrlen_str(extent, entry->utf8);
@ -239,16 +252,21 @@ static int scrpos2pos(GUI_ENTRY_REC *entry, int pos)
xpos += scrlen_str(entry->extents[0], entry->utf8);
}
for (i = 0; i < entry->text_len && xpos < pos; i++) {
unichar c = entry->text[i];
/* Process text using grapheme cluster aware advancement when possible */
i = 0;
while (i < entry->text_len && xpos < pos) {
const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
if (term_type == TERM_TYPE_BIG5)
width = big5_width(c);
else if (entry->utf8)
width = unichar_isprint(c) ? i_wcwidth(c) : 1;
else
if (term_type == TERM_TYPE_BIG5) {
width = big5_width(entry->text[i]);
i++;
} else if (entry->utf8) {
/* Use grapheme cluster aware advancement */
width = unichar_array_advance_cluster(entry->text, entry->text_len, &i);
} else {
width = 1;
i++;
}
xpos += width;
@ -359,25 +377,46 @@ static void gui_entry_draw_from(GUI_ENTRY_REC *entry, int pos)
g_free(tmp);
}
for (; i < entry->text_len; i++) {
unichar c = entry->text[i];
/* Process remaining text using grapheme cluster aware advancement */
while (i < entry->text_len) {
const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
int char_width;
int cluster_start = i;
unichar c;
new_xpos = xpos;
if (entry->hidden)
new_xpos++;
else if (term_type == TERM_TYPE_BIG5)
new_xpos += big5_width(c);
else if (entry->utf8)
new_xpos += unichar_isprint(c) ? i_wcwidth(c) : 1;
else
new_xpos++;
c = entry->text[i];
if (entry->hidden) {
char_width = 1;
i++;
} else if (term_type == TERM_TYPE_BIG5) {
char_width = big5_width(c);
i++;
} else if (entry->utf8) {
/* Use grapheme cluster aware advancement */
char_width = unichar_array_advance_cluster(entry->text, entry->text_len, &i);
} else {
char_width = 1;
i++;
}
new_xpos += char_width;
if (new_xpos > end_xpos)
break;
if (entry->hidden) {
g_string_append_c(str, ' ');
} else if (entry->utf8 && cluster_start != i) {
/* Render entire grapheme cluster for UTF-8 */
for (int j = cluster_start; j < i; j++) {
unichar cluster_char = entry->text[j];
if (unichar_isprint(cluster_char))
g_string_append_unichar(str, cluster_char);
else if (cluster_char == 0)
break;
}
} else if (unichar_isprint(c)) {
if (entry->utf8) {
g_string_append_unichar(str, c);
@ -651,7 +690,7 @@ void gui_entry_insert_char(GUI_ENTRY_REC *entry, unichar chr)
if (chr == 0 || chr == 13 || chr == 10)
return; /* never insert NUL, CR or LF characters */
if (entry->utf8 && entry->pos == 0 && unichar_isprint(chr) && i_wcwidth(chr) == 0)
if (entry->utf8 && entry->pos == 0 && is_combining_char(chr))
return;
gui_entry_redraw_from(entry, entry->pos);
@ -839,7 +878,7 @@ void gui_entry_erase(GUI_ENTRY_REC *entry, int size, CUTBUFFER_UPDATE_OP update_
}
if (entry->utf8)
while (entry->pos > size + w && i_wcwidth(entry->text[entry->pos - size - w]) == 0)
while (entry->pos > size + w && is_combining_char(entry->text[entry->pos - size - w]))
w++;
memmove(entry->text + entry->pos - size, entry->text + entry->pos,
@ -878,7 +917,7 @@ void gui_entry_erase_cell(GUI_ENTRY_REC *entry)
if (entry->utf8)
while (entry->pos+size < entry->text_len &&
i_wcwidth(entry->text[entry->pos+size]) == 0) size++;
is_combining_char(entry->text[entry->pos+size])) size++;
memmove(entry->text + entry->pos, entry->text + entry->pos + size,
(entry->text_len-entry->pos-size+1) * sizeof(unichar));
@ -1144,9 +1183,15 @@ void gui_entry_set_pos(GUI_ENTRY_REC *entry, int pos)
{
g_return_if_fail(entry != NULL);
if (pos >= 0 && pos <= entry->text_len)
if (pos >= 0 && pos <= entry->text_len) {
entry->pos = pos;
/* For UTF-8, ensure we're at the start of a grapheme cluster */
if (entry->utf8) {
entry->pos = unichar_array_find_cluster_start(entry->text, entry->text_len, entry->pos);
}
}
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
@ -1194,14 +1239,33 @@ void gui_entry_move_pos(GUI_ENTRY_REC *entry, int pos)
{
g_return_if_fail(entry != NULL);
if (entry->pos + pos >= 0 && entry->pos + pos <= entry->text_len)
entry->pos += pos;
if (!entry->utf8) {
/* Legacy behavior for non-UTF8 */
if (entry->pos + pos >= 0 && entry->pos + pos <= entry->text_len)
entry->pos += pos;
} else {
/* UTF-8: Move by grapheme clusters for proper UX */
int i, before_advance, cluster_start;
if (entry->utf8) {
int step = pos < 0 ? -1 : 1;
while(i_wcwidth(entry->text[entry->pos]) == 0 &&
entry->pos + step >= 0 && entry->pos + step <= entry->text_len)
entry->pos += step;
if (pos > 0) {
/* Move forward by grapheme clusters */
for (i = 0; i < pos && entry->pos < entry->text_len; i++) {
before_advance = entry->pos;
unichar_array_advance_cluster(entry->text, entry->text_len, &entry->pos);
/* Safety: if position didn't change and we're not at end, stop */
if (entry->pos == before_advance && entry->pos < entry->text_len)
break;
}
} else if (pos < 0) {
/* Move backward by grapheme clusters */
for (i = 0; i > pos && entry->pos > 0; i--) {
unichar_array_move_cluster_backward(entry->text, entry->text_len, &entry->pos);
}
}
/* Ensure we're always at the start of a grapheme cluster */
cluster_start = unichar_array_find_cluster_start(entry->text, entry->text_len, entry->pos);
entry->pos = cluster_start;
}
gui_entry_fix_cursor(entry);

View file

@ -103,6 +103,8 @@ GArray *g_array_copy(GArray *array)
#endif
static void sig_input(void);
static void paste_bracketed_middle(void);
static gboolean process_input_smart(GArray *input_buffer);
void input_listen_init(int handle)
{
@ -867,6 +869,87 @@ static void key_append_next_kill(void)
active_entry->append_next_kill = TRUE;
}
static gboolean process_input_smart(GArray *input_buffer)
{
int pos, cluster_end;
unichar *data;
char *utf8_cluster;
glong items_read, items_written;
GError *error;
int i;
gboolean has_control_chars;
GString *accum;
if (!input_buffer || input_buffer->len == 0 || !active_entry || !active_entry->utf8) {
return FALSE;
}
data = (unichar*)input_buffer->data;
has_control_chars = FALSE;
error = NULL;
/* Check if buffer contains control characters (arrows, enter, etc.) */
for (i = 0; i < input_buffer->len; i++) {
if (data[i] < 32 || data[i] == 127) {
has_control_chars = TRUE;
break;
}
}
/* If we have control characters, don't process - let legacy system handle */
if (has_control_chars) {
return FALSE;
}
pos = 0;
accum = g_string_new(NULL);
/* Process input buffer as grapheme clusters, but insert once */
while (pos < input_buffer->len) {
cluster_end = pos;
/* Advance to end of current grapheme cluster */
unichar_array_advance_cluster(data, input_buffer->len, &cluster_end);
/* If no advancement, move single character to avoid infinite loop */
if (cluster_end == pos) {
cluster_end = pos + 1;
}
/* Convert cluster to UTF-8 and append to accumulator */
utf8_cluster = g_ucs4_to_utf8(&data[pos], cluster_end - pos,
&items_read, &items_written, &error);
if (error == NULL && utf8_cluster && items_written > 0) {
g_string_append(accum, utf8_cluster);
g_free(utf8_cluster);
} else {
/* Fallback: append individual printable characters */
for (i = pos; i < cluster_end; i++) {
if (data[i] >= 32) {
char out[10];
out[g_unichar_to_utf8(data[i], out)] = '\0';
g_string_append(accum, out);
}
}
if (error) {
g_error_free(error);
error = NULL;
}
}
pos = cluster_end;
}
if (accum->len > 0) {
/* Insert entire paste as a single text update to mirror history behavior */
gui_entry_insert_text(active_entry, accum->str);
}
g_string_free(accum, TRUE);
return TRUE;
}
static gboolean paste_timeout(gpointer data)
{
int split_lines;
@ -888,11 +971,16 @@ static gboolean paste_timeout(gpointer data)
/* Take into account the fact that a line may be split every LINE_SPLIT_LIMIT characters */
if (paste_line_count == 0 && split_lines <= paste_verify_line_count) {
int i;
for (i = 0; i < paste_buffer->len; i++) {
unichar key = g_array_index(paste_buffer, unichar, i);
signal_emit("gui key pressed", 1, GINT_TO_POINTER(key));
/* Use smart UTF-8/grapheme cluster processing if available */
if (active_entry && active_entry->utf8 && process_input_smart(paste_buffer)) {
/* Smart processing succeeded */
} else {
/* Fallback to legacy character-by-character processing */
int i;
for (i = 0; i < paste_buffer->len; i++) {
unichar key = g_array_index(paste_buffer, unichar, i);
signal_emit("gui key pressed", 1, GINT_TO_POINTER(key));
}
}
g_array_set_size(paste_buffer, 0);
} else if (paste_verify_line_count > 0 &&
@ -1004,23 +1092,30 @@ static void sig_input(void)
g_source_remove(paste_timeout_id);
paste_timeout_id = g_timeout_add(paste_detect_time, paste_timeout, NULL);
} else if (!paste_bracketed_mode) {
int i;
/* Use smart UTF-8/grapheme cluster processing only for multi-char input (paste-like) */
if (active_entry && active_entry->utf8 && paste_buffer->len > 1 && process_input_smart(paste_buffer)) {
/* Smart processing succeeded - clear buffer */
g_array_set_size(paste_buffer, 0);
paste_line_count = 0;
} else {
/* Fallback to legacy character-by-character processing */
int i;
for (i = 0; i < paste_buffer->len; i++) {
unichar key = g_array_index(paste_buffer, unichar, i);
signal_emit("gui key pressed", 1, GINT_TO_POINTER(key));
for (i = 0; i < paste_buffer->len; i++) {
unichar key = g_array_index(paste_buffer, unichar, i);
signal_emit("gui key pressed", 1, GINT_TO_POINTER(key));
if (paste_bracketed_mode) {
/* just enabled by the signal, remove what was processed so far */
g_array_remove_range(paste_buffer, 0, i + 1);
if (paste_bracketed_mode) {
/* just enabled by the signal, remove what was processed so far */
g_array_remove_range(paste_buffer, 0, i + 1);
/* handle single-line / small pastes here */
paste_bracketed_middle();
return;
/* handle single-line / small pastes here */
paste_bracketed_middle();
return;
}
}
g_array_set_size(paste_buffer, 0);
paste_line_count = 0;
}
g_array_set_size(paste_buffer, 0);
paste_line_count = 0;
}
}
}

View file

@ -514,7 +514,7 @@ void term_add_unichar(TERM_WINDOW *window, unichar chr)
switch (term_type) {
case TERM_TYPE_UTF8:
term_printed_text(unichar_isprint(chr) ? i_wcwidth(chr) : 1);
term_printed_text(unichar_isprint(chr) ? unichar_width(chr) : 1);
term_addch_utf8(window, chr);
break;
case TERM_TYPE_BIG5:
@ -536,8 +536,6 @@ void term_add_unichar(TERM_WINDOW *window, unichar chr)
int term_addstr(TERM_WINDOW *window, const char *str)
{
int len, raw_len;
unichar tmp;
const char *ptr;
if (vcmove) term_move_real();
@ -546,21 +544,9 @@ int term_addstr(TERM_WINDOW *window, const char *str)
/* The string length depends on the terminal encoding */
ptr = str;
if (term_type == TERM_TYPE_UTF8) {
while (*ptr != '\0') {
tmp = g_utf8_get_char_validated(ptr, -1);
/* On utf8 error, treat as single byte and try to
continue interpreting rest of string as utf8 */
if (tmp == (gunichar)-1 || tmp == (gunichar)-2) {
len++;
ptr++;
} else {
len += unichar_isprint(tmp) ? i_wcwidth(tmp) : 1;
ptr = g_utf8_next_char(ptr);
}
}
/* Use string_width for proper grapheme cluster handling */
len = string_width(str, TREAT_STRING_AS_UTF8);
} else
len = raw_len;

View file

@ -154,8 +154,10 @@ static inline unichar read_unichar(const unsigned char *data, const unsigned cha
*next = data + 1;
*width = 1;
} else {
*next = (unsigned char *)g_utf8_next_char(data);
*width = unichar_isprint(chr) ? i_wcwidth(chr) : 1;
/* Use string_advance for proper grapheme cluster handling */
char const *str_ptr = (char const *)data;
*width = string_advance(&str_ptr, TREAT_STRING_AS_UTF8);
*next = (unsigned char *)str_ptr;
}
return chr;
}
@ -580,11 +582,21 @@ static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
xpos += char_width;
if (xpos <= view->width) {
if (unichar_isprint(chr)) {
if (view->utf8)
term_add_unichar(view->window, chr);
else
if (view->utf8) {
/* Send entire grapheme cluster to preserve variation selectors */
char cluster_str[16];
int cluster_len = end - text;
if (cluster_len < sizeof(cluster_str)) {
memcpy(cluster_str, text, cluster_len);
cluster_str[cluster_len] = '\0';
term_addstr(view->window, cluster_str);
} else {
term_add_unichar(view->window, chr);
}
} else {
for (; text < end; text++)
term_addch(view->window, *text);
}
} else {
/* low-ascii */
term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);