diff --git a/CMakeLists.txt b/CMakeLists.txt index eb40db7..ad2f2df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,10 @@ set (project_VERSION "${project_VERSION_MAJOR}") set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}") set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}") +# Configuration +include (CheckFunctionExists) +CHECK_FUNCTION_EXISTS ("wcwidth" HAVE_WCWIDTH) + # Dependecies find_package (PkgConfig REQUIRED) pkg_check_modules (dependencies REQUIRED ncursesw glib-2.0 gio-2.0 pango) diff --git a/config.h.in b/config.h.in index adc32d8..a5fd1be 100644 --- a/config.h.in +++ b/config.h.in @@ -8,5 +8,7 @@ #define GETTEXT_PACKAGE PROJECT_NAME #define GETTEXT_DIRNAME "${CMAKE_INSTALL_PREFIX}/share/locale" +#cmakedefine HAVE_WCWIDTH + #endif /* ! CONFIG_H */ diff --git a/src/sdtui.c b/src/sdtui.c index 502aec3..f875389 100644 --- a/src/sdtui.c +++ b/src/sdtui.c @@ -18,7 +18,8 @@ * */ -#define _XOPEN_SOURCE_EXTENDED /**< Yes, we want ncursesw. */ +#define _XOPEN_SOURCE 500 //!< wcwidth +#define _XOPEN_SOURCE_EXTENDED //!< Yes, we want ncursesw. #include #include @@ -98,6 +99,18 @@ struct curses_event MEVENT mouse; }; +static size_t +unichar_width (gunichar ch) +{ + if (g_unichar_iszerowidth (ch)) + return 0; + return 1 + g_unichar_iswide (ch); +} + +#ifndef HAVE_WCWIDTH +#define wcwidth(x) 1 +#endif // ! HAVE_WCWIDTH + static gboolean is_character_in_locale (wchar_t c) { @@ -353,31 +366,68 @@ app_add_utf8_string (Application *self, const gchar *str, int n) ssize_t wide_len = wcslen (wide_str); wchar_t padding = L' ', error = L'?', ellipsis = L'…'; - if (n < 0) - n = wide_len; + if (!n) + return 0; - if (wide_len > n) + // Compute how many wide characters fit in the limit + gint cols, i; + for (cols = i = 0; i < wide_len; i++) { - if (is_character_in_locale (ellipsis) && n > 0) - wide_str[n - 1] = ellipsis; - else if (n >= 3) + if (!is_character_in_locale (wide_str[i])) + wide_str[i] = error; + + gint width = wcwidth (wide_str[i]); + if (n >= 0 && cols + width > n) + break; + cols += width; + } + + if (n < 0) + n = cols; + + // Append ellipsis if the whole string didn't fit + gint len = i; + if (len != wide_len) + { + if (is_character_in_locale (ellipsis)) { - wide_str[n - 1] = L'.'; - wide_str[n - 2] = L'.'; - wide_str[n - 3] = L'.'; + if (cols + wcwidth (ellipsis) > n) + cols -= wcwidth (wide_str[len - 1]); + else + len++; + + wide_str[len - 1] = ellipsis; + cols += wcwidth (ellipsis); + } + else if (n >= 3 && len >= 3) + { + // With zero-width characters this overflows + // It's just a fallback anyway + cols -= wcwidth (wide_str[len - 1]); + cols -= wcwidth (wide_str[len - 2]); + cols -= wcwidth (wide_str[len - 3]); + cols += 3; + + wide_str[len - 1] = L'.'; + wide_str[len - 2] = L'.'; + wide_str[len - 3] = L'.'; } } - gint i; cchar_t cch; - for (i = 0; i < n; i++) + for (i = 0; i < len; i++) { - if (setcchar (&cch, (i < wide_len ? &wide_str[i] : &padding), - A_NORMAL, 0, NULL) == ERR) - setcchar (&cch, &error, A_NORMAL, 0, NULL); - add_wch (&cch); + if (setcchar (&cch, &wide_str[i], A_NORMAL, 0, NULL) == OK) + add_wch (&cch); + else + // This shouldn't happen + cols -= wcwidth (wide_str[i]); } + setcchar (&cch, &padding, A_NORMAL, 0, NULL); + while (cols++ < n) + add_wch (&cch); + g_free (wide_str); return n; } @@ -399,7 +449,12 @@ app_redraw_top (Application *self) app_add_utf8_string (self, input_utf8, COLS - indent); g_free (input_utf8); - move (0, indent + self->input_pos); + guint offset, i; + for (offset = i = 0; i < self->input_pos; i++) + // This may be inconsistent with the output of app_add_utf8_string() + offset += unichar_width (g_array_index (self->input, gunichar, i)); + + move (0, indent + offset); refresh (); }