From e1ffa8d33b5e8b497ea1748b717a530dc48db6dc Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 28 Oct 2025 10:05:34 +0100 Subject: [PATCH] feat(build): HAVE_UNIBILIUM compile time features are hot again. Note: this changes the &term value for builtin definition from 'builtin_xterm' to just 'xterm'. It's an xterm regardless of we use an external definition or an internal. Prior to this commit the vast majority of POSIX users will have used external terminfo, so plugins and scripts are only going to have checked for &term == 'xterm' or 'tmux' or whatever. The status of external loading is still available in "nvim -V3" output. --- BUILD.md | 12 ++++ CMakeLists.txt | 1 + build.zig | 9 ++- build.zig.zon | 2 +- runtime/doc/news.txt | 5 ++ runtime/doc/vim_diff.txt | 5 +- src/nvim/CMakeLists.txt | 8 ++- src/nvim/tui/terminfo.c | 93 ++++++++++++++------------- src/nvim/tui/tui.c | 11 ++-- test/functional/terminal/tui_spec.lua | 12 ++-- 10 files changed, 95 insertions(+), 63 deletions(-) diff --git a/BUILD.md b/BUILD.md index 3cccad9eb7..7f99ff9254 100644 --- a/BUILD.md +++ b/BUILD.md @@ -288,6 +288,18 @@ cmake --build build - Using `ninja` is strongly recommended. 4. If treesitter parsers are not bundled, they need to be available in a `parser/` runtime directory (e.g. `/usr/share/nvim/runtime/parser/`). +### How to build without unibilium + +Unibilium is the only dependency which is licensed under LGPLv3 (there are no +GPLv3-only dependencies). This library is used for loading the terminfo database at +runtime, and can be disabled if the internal definitions for common terminals +are good enough. To avoid this dependency, build with support for loading +custom terminfo at runtime, use + +```sh +make CMAKE_EXTRA_FLAGS="-DENABLE_UNIBILIUM=0" BUNDLED_CMAKE_FLAG="-DUSE_BUNDLED_UNIBILIUM=0" +``` + ### How to build static binary (on Linux) 1. Use a linux distribution which uses musl C. We will use Alpine Linux but any distro with musl should work. (glibc does not support static linking) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5bfdb214c..f0a58a828f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,6 +130,7 @@ else() option(ENABLE_LTO "enable link time optimization" ON) endif() option(ENABLE_LIBINTL "enable libintl" ON) +option(ENABLE_UNIBILIUM "enable unibilium" ON) option(ENABLE_WASMTIME "enable wasmtime" OFF) message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") diff --git a/build.zig b/build.zig index 13f5d8eeb1..73406123fc 100644 --- a/build.zig +++ b/build.zig @@ -46,6 +46,8 @@ pub fn build(b: *std.Build) !void { // without cross_compiling we like to reuse libluv etc at the same optimize level const optimize_host = if (cross_compiling) .ReleaseSafe else optimize; + const use_unibilium = b.option(bool, "unibilium", "use unibilium") orelse true; + // puc lua 5.1 is not ReleaseSafe "safe" const optimize_lua = if (optimize == .Debug or optimize == .ReleaseSafe) .ReleaseSmall else optimize; @@ -92,7 +94,7 @@ pub fn build(b: *std.Build) !void { } else libluv; const utf8proc = b.dependency("utf8proc", .{ .target = target, .optimize = optimize }); - const unibilium = b.dependency("unibilium", .{ .target = target, .optimize = optimize }); + const unibilium = if (use_unibilium) b.lazyDependency("unibilium", .{ .target = target, .optimize = optimize }) else null; // TODO(bfredl): fix upstream bugs with UBSAN const treesitter = b.dependency("treesitter", .{ .target = target, .optimize = .ReleaseFast }); @@ -250,7 +252,7 @@ pub fn build(b: *std.Build) !void { libuv.getEmittedIncludeTree(), libluv.getEmittedIncludeTree(), utf8proc.artifact("utf8proc").getEmittedIncludeTree(), - unibilium.artifact("unibilium").getEmittedIncludeTree(), + if (unibilium) |u| u.artifact("unibilium").getEmittedIncludeTree() else b.path("UNUSED_PATH/"), // :p treesitter.artifact("tree-sitter").getEmittedIncludeTree(), if (iconv) |dep| dep.artifact("iconv").getEmittedIncludeTree() else b.path("UNUSED_PATH/"), }; @@ -279,7 +281,7 @@ pub fn build(b: *std.Build) !void { nvim_exe.linkLibrary(libluv); if (iconv) |dep| nvim_exe.linkLibrary(dep.artifact("iconv")); nvim_exe.linkLibrary(utf8proc.artifact("utf8proc")); - nvim_exe.linkLibrary(unibilium.artifact("unibilium")); + if (unibilium) |u| nvim_exe.linkLibrary(u.artifact("unibilium")); nvim_exe.linkLibrary(treesitter.artifact("tree-sitter")); if (is_windows) { nvim_exe.linkSystemLibrary("netapi32"); @@ -317,6 +319,7 @@ pub fn build(b: *std.Build) !void { if (is_windows) "-DMSWIN" else "", if (is_windows) "-DWIN32_LEAN_AND_MEAN" else "", if (is_windows) "-DUTF8PROC_STATIC" else "", + if (use_unibilium) "-DHAVE_UNIBILIUM" else "", }; nvim_exe.addCSourceFiles(.{ .files = src_paths, .flags = &flags }); diff --git a/build.zig.zon b/build.zig.zon index a73ab6d855..b69580650a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -30,7 +30,7 @@ .hash = "libuv-1.51.0-htqqv6liAADxBLIBCZT-qUh_3nRRwtNYsOFQOUmrd_sx", }, .utf8proc = .{ .path = "./deps/utf8proc/" }, - .unibilium = .{ .path = "./deps/unibilium/" }, + .unibilium = .{ .path = "./deps/unibilium/", .lazy = true }, .libiconv = .{ .url = "git+https://github.com/allyourcodebase/libiconv#9def4c8a1743380e85bcedb80f2c15b455e236f3", .hash = "libiconv-1.18.0-p9sJwWnqAACzVYeWgXB5r5lOQ74XwTPlptixV0JPRO28", diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 975564460f..02fe076fd8 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -153,7 +153,12 @@ BUILD • A Zig-based build system has been added as an alternative to CMake. It is currently limited in functionality, and CMake remains the recommended option for the time being. +• Nvim can be built without Unibilium (terminfo implementation), in which case + the user's terminfo database won't be loaded and only internal definitions + for the most common terminals are used. > + make distclean && make CMAKE_EXTRA_FLAGS="-DENABLE_UNIBILIUM=0" BUNDLED_CMAKE_FLAG="-DUSE_BUNDLED_UNIBILIUM=0" +< DEFAULTS • 'diffopt' default value now includes "indent-heuristic" and "inline:char". diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 86fc4937be..2b486c40ca 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -473,8 +473,9 @@ TUI: - Note: If you want to detect when Nvim is running in a terminal, use `has('gui_running')` |has()| or see |nvim_list_uis()| for an example of how to inspect the UI channel. -- "builtin_x" means one of the |builtin-terms| was chosen, because the expected - terminfo file was not found on the system. +- Nvim might optionally be compiled with unibilium, in which case the terminfo + database will be used. Otherwise, or if the terminal was not found in + the database, a table of builtin terminal definitions will be used. - Nvim will use 256-colour capability on Linux virtual terminals. Vim uses only 8 colours plus bright foreground on Linux VTs. - Vim combines what is in its |builtin-terms| with what it reads from terminfo, diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 3d5e3e188a..3fbed68bf2 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -32,14 +32,12 @@ find_package(Iconv REQUIRED) find_package(Libuv 1.28.0 REQUIRED) find_package(Lpeg REQUIRED) find_package(Treesitter 0.25.0 REQUIRED) -find_package(Unibilium 2.0 REQUIRED) find_package(UTF8proc REQUIRED) target_link_libraries(main_lib INTERFACE iconv lpeg treesitter - unibilium utf8proc) target_link_libraries(nlua0 PUBLIC lpeg) @@ -48,6 +46,12 @@ if(ENABLE_LIBINTL) target_link_libraries(main_lib INTERFACE libintl) endif() +if(ENABLE_UNIBILIUM) + find_package(Unibilium 2.0 REQUIRED) + target_compile_definitions(nvim_bin PRIVATE HAVE_UNIBILIUM) + target_link_libraries(main_lib INTERFACE unibilium) +endif() + if(ENABLE_WASMTIME) find_package(Wasmtime 29.0.1 EXACT REQUIRED) target_link_libraries(main_lib INTERFACE wasmtime) diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 89d999d857..a1d5712962 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -2,7 +2,10 @@ #include #include -#include + +#ifdef HAVE_UNIBILIUM +# include +#endif #include "klib/kvec.h" #include "nvim/api/private/defs.h" @@ -68,71 +71,60 @@ bool terminfo_is_bsd_console(const char *term) const TerminfoEntry *terminfo_from_builtin(const char *term, char **termname) { if (terminfo_is_term_family(term, "xterm")) { - *termname = "builtin_xterm"; + *termname = "xterm"; return &xterm_256colour_terminfo; } else if (terminfo_is_term_family(term, "screen")) { - *termname = "builtin_screen"; + *termname = "screen"; return &screen_256colour_terminfo; } else if (terminfo_is_term_family(term, "tmux")) { - *termname = "builtin_tmux"; + *termname = "tmux"; return &tmux_256colour_terminfo; } else if (terminfo_is_term_family(term, "rxvt")) { - *termname = "builtin_rxvt"; + *termname = "rxvt"; return &rxvt_256colour_terminfo; } else if (terminfo_is_term_family(term, "putty")) { - *termname = "builtin_putty"; + *termname = "putty"; return &putty_256colour_terminfo; } else if (terminfo_is_term_family(term, "linux")) { - *termname = "builtin_linux"; + *termname = "linux"; return &linux_16colour_terminfo; } else if (terminfo_is_term_family(term, "interix")) { - *termname = "builtin_interix"; + *termname = "interix"; return &interix_8colour_terminfo; } else if (terminfo_is_term_family(term, "iterm") || terminfo_is_term_family(term, "iterm2") || terminfo_is_term_family(term, "iTerm.app") || terminfo_is_term_family(term, "iTerm2.app")) { - *termname = "builtin_iterm"; + *termname = "iterm"; return &iterm_256colour_terminfo; } else if (terminfo_is_term_family(term, "st")) { - *termname = "builtin_st"; + *termname = "st"; return &st_256colour_terminfo; } else if (terminfo_is_term_family(term, "gnome") || terminfo_is_term_family(term, "vte")) { - *termname = "builtin_vte"; + *termname = "vte"; return &vte_256colour_terminfo; } else if (terminfo_is_term_family(term, "cygwin")) { - *termname = "builtin_cygwin"; + *termname = "cygwin"; return &cygwin_terminfo; } else if (terminfo_is_term_family(term, "win32con")) { - *termname = "builtin_win32con"; + *termname = "win32con"; return &win32con_terminfo; } else if (terminfo_is_term_family(term, "conemu")) { - *termname = "builtin_conemu"; + *termname = "conemu"; return &conemu_terminfo; } else if (terminfo_is_term_family(term, "vtpcon")) { - *termname = "builtin_vtpcon"; + *termname = "vtpcon"; return &vtpcon_terminfo; } else { - *termname = "builtin_ansi"; + *termname = "ansi"; return &ansi_terminfo; } } -static ssize_t unibi_find_ext_str(unibi_term *ut, const char *name) -{ - size_t max = unibi_count_ext_str(ut); - for (size_t i = 0; i < max; i++) { - const char *n = unibi_get_ext_str_name(ut, i); - if (n && 0 == strcmp(n, name)) { - return (ssize_t)i; - } - } - return -1; -} - -bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena) +bool terminfo_from_database(TerminfoEntry *ti, char *termname, Arena *arena) { +#ifdef HAVE_UNIBILIUM unibi_term *ut = unibi_from_term(termname); if (!ut) { return false; @@ -156,9 +148,9 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena) } static const enum unibi_string uni_ids[] = { -#define X(name) unibi_##name, +# define X(name) unibi_##name, XLIST_TERMINFO_BUILTIN -#undef X +# undef X }; for (size_t i = 0; i < ARRAY_SIZE(uni_ids); i++) { @@ -167,26 +159,31 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena) } static const char *uni_ext[] = { -#define X(informal_name, terminfo_name) #terminfo_name, +# define X(informal_name, terminfo_name) #terminfo_name, XLIST_TERMINFO_EXT -#undef X +# undef X }; + size_t max = unibi_count_ext_str(ut); for (size_t i = 0; i < ARRAY_SIZE(uni_ext); i++) { - ssize_t val = unibi_find_ext_str(ut, uni_ext[i]); - if (val >= 0) { - const char *data = unibi_get_ext_str(ut, (size_t)val); - ti->defs[kTermExtOffset + i] = data ? arena_strdup(arena, data) : NULL; + const char *name = uni_ext[i]; + for (size_t val = 0; val < max; val++) { + const char *n = unibi_get_ext_str_name(ut, val); + if (n && strequal(n, name)) { + const char *data = unibi_get_ext_str(ut, val); + ti->defs[kTermExtOffset + i] = data ? arena_strdup(arena, data) : NULL; + break; + } } } -#define X(name) { unibi_key_##name, unibi_string_begin_ }, -#define Y(name) { unibi_key_##name, unibi_key_s##name }, +# define X(name) { unibi_key_##name, unibi_string_begin_ }, +# define Y(name) { unibi_key_##name, unibi_key_s##name }, static const enum unibi_string uni_keys[][2] = { XYLIST_TERMINFO_KEYS }; -#undef X -#undef Y +# undef X +# undef Y for (size_t i = 0; i < ARRAY_SIZE(uni_keys); i++) { const char *val = unibi_get_str(ut, uni_keys[i][0]); @@ -200,9 +197,9 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena) } static const enum unibi_string uni_fkeys[] = { -#define X(name) unibi_key_##name, +# define X(name) unibi_key_##name, XLIST_TERMINFO_FKEYS -#undef X +# undef X }; for (size_t i = 0; i < ARRAY_SIZE(uni_fkeys); i++) { @@ -212,6 +209,9 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena) unibi_destroy(ut); return true; +#else + return false; +#endif } static const char *fmt(bool val) @@ -223,11 +223,16 @@ static const char *fmt(bool val) /// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). /// /// @return allocated string -String terminfo_info_msg(const TerminfoEntry *ti, const char *termname) +String terminfo_info_msg(const TerminfoEntry *ti, const char *termname, bool from_db) { StringBuilder data = KV_INITIAL_VALUE; kv_printf(data, "&term: %s\n", termname); + if (from_db) { + kv_printf(data, "using terminfo database\n"); + } else { + kv_printf(data, "using builtin terminfo\n"); + } kv_printf(data, "\n"); kv_printf(data, "Boolean capabilities:\n"); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e90de6f062..0540734e1f 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -86,6 +86,7 @@ struct TUIData { int row, col; int out_fd; int pending_resize_events; + bool terminfo_found_in_db; bool can_change_scroll_region; bool has_left_and_right_margin_mode; bool has_sync_mode; @@ -382,15 +383,15 @@ static void terminfo_start(TUIData *tui) #endif // Set up terminfo. - bool found_in_db = false; + tui->terminfo_found_in_db = false; if (term) { - if (terminfo_from_unibilium(&tui->ti, term, &tui->ti_arena)) { + if (terminfo_from_database(&tui->ti, term, &tui->ti_arena)) { tui->term = arena_strdup(&tui->ti_arena, term); - found_in_db = true; + tui->terminfo_found_in_db = true; } } - if (!found_in_db) { + if (!tui->terminfo_found_in_db) { const TerminfoEntry *new = terminfo_from_builtin(term, &tui->term); // we will patch it below, so make a copy memcpy(&tui->ti, new, sizeof tui->ti); @@ -1596,7 +1597,7 @@ static void show_verbose_terminfo(TUIData *tui) ADD_C(title, CSTR_AS_OBJ("Title")); ADD_C(chunks, ARRAY_OBJ(title)); MAXSIZE_TEMP_ARRAY(info, 1); - String str = terminfo_info_msg(&tui->ti, tui->term); + String str = terminfo_info_msg(&tui->ti, tui->term, tui->terminfo_found_in_db); ADD_C(info, STRING_OBJ(str)); ADD_C(chunks, ARRAY_OBJ(info)); MAXSIZE_TEMP_ARRAY(end_fold, 2); diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 43f0c64567..4de9ab16d8 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -2177,7 +2177,7 @@ describe('TUI', function() it('in nvim_list_uis(), sets nvim_set_client_info()', function() -- $TERM in :terminal. - local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color' + local exp_term = is_os('bsd') and 'xterm' or 'xterm-256color' local ui_chan = 1 local expected = { { @@ -3447,14 +3447,14 @@ describe("TUI 'term' option", function() end it('gets builtin term if $TERM is invalid', function() - assert_term('foo', 'builtin_ansi') + assert_term('foo', 'ansi') end) it('gets system-provided term if $TERM is valid', function() if is_os('openbsd') then assert_term('xterm', 'xterm') elseif is_os('bsd') then -- BSD lacks terminfo, builtin is always used. - assert_term('xterm', 'builtin_xterm') + assert_term('xterm', 'xterm') elseif is_os('mac') then local status, _ = pcall(assert_term, 'xterm', 'xterm') if not status then @@ -3467,9 +3467,9 @@ describe("TUI 'term' option", function() it('builtin terms', function() -- These non-standard terminfos are always builtin. - assert_term('win32con', 'builtin_win32con') - assert_term('conemu', 'builtin_conemu') - assert_term('vtpcon', 'builtin_vtpcon') + assert_term('win32con', 'win32con') + assert_term('conemu', 'conemu') + assert_term('vtpcon', 'vtpcon') end) end)