diff --git a/CMakeLists.txt b/CMakeLists.txt index 5eff8a6..0f50518 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,14 +18,13 @@ set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}") set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}") # For custom modules -set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake) # Dependencies find_package (Ncursesw REQUIRED) find_package (PkgConfig REQUIRED) find_package (Unistring REQUIRED) -set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake) include (AddThreads) find_package (Termo QUIET NO_MODULE) diff --git a/cmake/FindNcursesw.cmake b/cmake/FindNcursesw.cmake deleted file mode 100644 index 88c1d01..0000000 --- a/cmake/FindNcursesw.cmake +++ /dev/null @@ -1,17 +0,0 @@ -# Public Domain - -find_package (PkgConfig REQUIRED) -pkg_check_modules (NCURSESW QUIET ncursesw) - -# OpenBSD doesn't provide a pkg-config file -set (required_vars NCURSESW_LIBRARIES) -if (NOT NCURSESW_FOUND) - find_library (NCURSESW_LIBRARIES NAMES ncursesw) - find_path (NCURSESW_INCLUDE_DIRS ncurses.h) - list (APPEND required_vars NCURSESW_INCLUDE_DIRS) -endif (NOT NCURSESW_FOUND) - -include (FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS (NCURSESW DEFAULT_MSG ${required_vars}) - -mark_as_advanced (NCURSESW_LIBRARIES NCURSESW_INCLUDE_DIRS) diff --git a/cmake/FindUnistring.cmake b/cmake/FindUnistring.cmake deleted file mode 100644 index 6b74efb..0000000 --- a/cmake/FindUnistring.cmake +++ /dev/null @@ -1,10 +0,0 @@ -# Public Domain - -find_path (UNISTRING_INCLUDE_DIRS unistr.h) -find_library (UNISTRING_LIBRARIES NAMES unistring libunistring) - -include (FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS (UNISTRING DEFAULT_MSG - UNISTRING_INCLUDE_DIRS UNISTRING_LIBRARIES) - -mark_as_advanced (UNISTRING_LIBRARIES UNISTRING_INCLUDE_DIRS) diff --git a/hex.c b/hex.c index 6756d8a..ffbf5b1 100644 --- a/hex.c +++ b/hex.c @@ -58,6 +58,7 @@ enum #define LIBERTY_WANT_ASYNC #define LIBERTY_WANT_PROTO_HTTP #include "liberty/liberty.c" +#include "liberty/liberty-tui.c" #include #include @@ -65,7 +66,6 @@ enum #include #endif // ! TIOCGWINSZ -#include "tui.c" #include "termo.h" #ifdef HAVE_LUA @@ -101,23 +101,6 @@ update_curses_terminal_size (void) #endif // HAVE_RESIZETERM && TIOCGWINSZ } -// --- Simple array support ---------------------------------------------------- - -// The most basic helper macros to make working with arrays not suck - -#define ARRAY(type, name) type *name; size_t name ## _len, name ## _size; -#define ARRAY_INIT_SIZED(a, n) \ - BLOCK_START \ - (a) = xcalloc (sizeof *(a), (a ## _size) = (n)); \ - (a ## _len) = 0; \ - BLOCK_END -#define ARRAY_INIT(a) ARRAY_INIT_SIZED (a, 16) -#define ARRAY_RESERVE(a, n) \ - BLOCK_START \ - while ((a ## _size) - (a ## _len) < n) \ - (a) = xreallocarray ((a), sizeof *(a), (a ## _size) <<= 1); \ - BLOCK_END - // --- Application ------------------------------------------------------------- enum @@ -1277,8 +1260,8 @@ app_lua_init (void) luaL_setfuncs (g_ctx.L, app_lua_chunk_table, 0); lua_pop (g_ctx.L, 1); - struct str_vector v; - str_vector_init (&v); + struct strv v; + strv_init (&v); get_xdg_data_dirs (&v); for (size_t i = 0; i < v.len; i++) { @@ -1287,7 +1270,7 @@ app_lua_init (void) app_lua_load_plugins (path); free (path); } - str_vector_free (&v); + strv_free (&v); } #endif // HAVE_LUA @@ -1971,7 +1954,7 @@ main (int argc, char *argv[]) while (buf.len < (size_t) size_limit) { - str_ensure_space (&buf, 8192); + str_reserve (&buf, 8192); ssize_t n_read = read (input_fd, buf.str + buf.len, MIN (size_limit - buf.len, buf.alloc - buf.len)); if (!n_read) diff --git a/liberty b/liberty index f53b717..084e964 160000 --- a/liberty +++ b/liberty @@ -1 +1 @@ -Subproject commit f53b717f3bba27ca1c42486d3742c91967f3fe93 +Subproject commit 084e964286bfcd13ee6a25a2ee35dfba9da1072e diff --git a/tui.c b/tui.c deleted file mode 100644 index d313bcb..0000000 --- a/tui.c +++ /dev/null @@ -1,266 +0,0 @@ -// This file is to be moved to liberty, along with FindUnistring.cmake, -// and then used in both hex and nncmpp - -// This file includes some common stuff to build TUI applications with - -#include - -// It is surprisingly hard to find a good library to handle Unicode shenanigans, -// and there's enough of those for it to be impractical to reimplement them. -// -// GLib ICU libunistring utf8proc -// Decently sized . . x x -// Grapheme breaks . x . x -// Character width x . x x -// Locale handling . . x . -// Liberal license . x . x -// -// Also note that the ICU API is icky and uses UTF-16 for its primary encoding. -// -// Currently we're chugging along with libunistring but utf8proc seems viable. -// Non-Unicode locales can mostly be handled with simple iconv like in sdtui. -// Similarly grapheme breaks can be guessed at using character width (a basic -// test here is Zalgo text). -// -// None of this is ever going to work too reliably anyway because terminals -// and Unicode don't go awfully well together. In particular, character cell -// devices have some problems with double-wide characters. - -#include -#include -#include -#include - -// --- Configurable display attributes ----------------------------------------- - -struct attrs -{ - short fg; ///< Foreground colour index - short bg; ///< Background colour index - chtype attrs; ///< Other attributes -}; - -/// Decode attributes in the value using a subset of the git config format, -/// ignoring all errors since it doesn't affect functionality -static struct attrs -attrs_decode (const char *value) -{ - struct str_vector v; - str_vector_init (&v); - cstr_split (value, " ", true, &v); - - int colors = 0; - struct attrs attrs = { -1, -1, 0 }; - for (char **it = v.vector; *it; it++) - { - char *end = NULL; - long n = strtol (*it, &end, 10); - if (*it != end && !*end && n >= SHRT_MIN && n <= SHRT_MAX) - { - if (colors == 0) attrs.fg = n; - if (colors == 1) attrs.bg = n; - colors++; - } - else if (!strcmp (*it, "bold")) attrs.attrs |= A_BOLD; - else if (!strcmp (*it, "dim")) attrs.attrs |= A_DIM; - else if (!strcmp (*it, "ul")) attrs.attrs |= A_UNDERLINE; - else if (!strcmp (*it, "blink")) attrs.attrs |= A_BLINK; - else if (!strcmp (*it, "reverse")) attrs.attrs |= A_REVERSE; -#ifdef A_ITALIC - else if (!strcmp (*it, "italic")) attrs.attrs |= A_ITALIC; -#endif // A_ITALIC - } - str_vector_free (&v); - return attrs; -} - -// --- Terminal output --------------------------------------------------------- - -// Necessary abstraction to simplify aligned, formatted character output - -// This callback you need to implement in the application -static bool app_is_character_in_locale (ucs4_t ch); - -struct row_char -{ - ucs4_t c; ///< Unicode codepoint - chtype attrs; ///< Special attributes - int width; ///< How many cells this takes -}; - -struct row_buffer -{ - // TODO: rewrite this using ARRAY - struct row_char *chars; ///< Characters - size_t chars_len; ///< Character count - size_t chars_alloc; ///< Characters allocated - int total_width; ///< Total width of all characters -}; - -static void -row_buffer_init (struct row_buffer *self) -{ - memset (self, 0, sizeof *self); - self->chars = xcalloc (sizeof *self->chars, (self->chars_alloc = 256)); -} - -static void -row_buffer_free (struct row_buffer *self) -{ - free (self->chars); -} - -/// Replace invalid chars and push all codepoints to the array w/ attributes. -static void -row_buffer_append (struct row_buffer *self, const char *str, chtype attrs) -{ - // The encoding is only really used internally for some corner cases - const char *encoding = locale_charset (); - - // Note that this function is a hotspot, try to keep it decently fast - struct row_char current = { .attrs = attrs }; - struct row_char invalid = { .attrs = attrs, .c = '?', .width = 1 }; - const uint8_t *next = (const uint8_t *) str; - while ((next = u8_next (¤t.c, next))) - { - if (self->chars_len >= self->chars_alloc) - self->chars = xreallocarray (self->chars, - sizeof *self->chars, (self->chars_alloc <<= 1)); - - current.width = uc_width (current.c, encoding); - if (current.width < 0 || !app_is_character_in_locale (current.c)) - current = invalid; - - self->chars[self->chars_len++] = current; - self->total_width += current.width; - } -} - -static void -row_buffer_append_args (struct row_buffer *self, const char *s, ...) - ATTRIBUTE_SENTINEL; - -static void -row_buffer_append_args (struct row_buffer *self, const char *s, ...) -{ - va_list ap; - va_start (ap, s); - - while (s) - { - row_buffer_append (self, s, va_arg (ap, chtype)); - s = va_arg (ap, const char *); - } - va_end (ap); -} - -static void -row_buffer_append_buffer (struct row_buffer *self, const struct row_buffer *rb) -{ - while (self->chars_alloc - self->chars_len < rb->chars_len) - self->chars = xreallocarray (self->chars, - sizeof *self->chars, (self->chars_alloc <<= 1)); - - memcpy (self->chars + self->chars_len, rb->chars, - rb->chars_len * sizeof *rb->chars); - - self->chars_len += rb->chars_len; - self->total_width += rb->total_width; -} - -/// Pop as many codepoints as needed to free up "space" character cells. -/// Given the suffix nature of combining marks, this should work pretty fine. -static int -row_buffer_pop_cells (struct row_buffer *self, int space) -{ - int made = 0; - while (self->chars_len && made < space) - made += self->chars[--self->chars_len].width; - self->total_width -= made; - return made; -} - -static void -row_buffer_space (struct row_buffer *self, int width, chtype attrs) -{ - if (width < 0) - return; - - while (self->chars_len + width >= self->chars_alloc) - self->chars = xreallocarray (self->chars, - sizeof *self->chars, (self->chars_alloc <<= 1)); - - struct row_char space = { .attrs = attrs, .c = ' ', .width = 1 }; - self->total_width += width; - while (width-- > 0) - self->chars[self->chars_len++] = space; -} - -static void -row_buffer_ellipsis (struct row_buffer *self, int target) -{ - if (self->total_width <= target - || !row_buffer_pop_cells (self, self->total_width - target)) - return; - - // We use attributes from the last character we've removed, - // assuming that we don't shrink the array (and there's no real need) - ucs4_t ellipsis = 0x2026; // … - if (app_is_character_in_locale (ellipsis)) - { - if (self->total_width >= target) - row_buffer_pop_cells (self, 1); - if (self->total_width + 1 <= target) - row_buffer_append (self, "…", self->chars[self->chars_len].attrs); - } - else if (target >= 3) - { - if (self->total_width >= target) - row_buffer_pop_cells (self, 3); - if (self->total_width + 3 <= target) - row_buffer_append (self, "...", self->chars[self->chars_len].attrs); - } -} - -static void -row_buffer_align (struct row_buffer *self, int target, chtype attrs) -{ - row_buffer_ellipsis (self, target); - row_buffer_space (self, target - self->total_width, attrs); -} - -static void -row_buffer_print (uint32_t *ucs4, chtype attrs) -{ - // This assumes that we can reset the attribute set without consequences - char *str = u32_strconv_to_locale (ucs4); - if (str) - { - attrset (attrs); - addstr (str); - attrset (0); - free (str); - } -} - -static void -row_buffer_flush (struct row_buffer *self) -{ - if (!self->chars_len) - return; - - // We only NUL-terminate the chunks because of the libunistring API - uint32_t chunk[self->chars_len + 1], *insertion_point = chunk; - for (size_t i = 0; i < self->chars_len; i++) - { - struct row_char *iter = self->chars + i; - if (i && iter[0].attrs != iter[-1].attrs) - { - row_buffer_print (chunk, iter[-1].attrs); - insertion_point = chunk; - } - *insertion_point++ = iter->c; - *insertion_point = 0; - } - row_buffer_print (chunk, self->chars[self->chars_len - 1].attrs); -}