commit d825e5116981da249720a4a5f18f11939d8d00fd
parent 2621066104acc2d738763ce82f740b74a7f217ec
Author: bfredl <bjorn.linse@gmail.com>
Date: Fri, 31 Oct 2025 10:17:02 +0100
Merge pull request #36381 from bfredl/nobilium
feat(build): HAVE_UNIBILIUM
Diffstat:
10 files changed, 95 insertions(+), 63 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -2,7 +2,10 @@
#include <stdbool.h>
#include <string.h>
-#include <unibilium.h>
+
+#ifdef HAVE_UNIBILIUM
+# include <unibilium.h>
+#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
@@ -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
@@ -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)