Merge pull request #35952 from bfredl/neocurses

perf(tui): faster implementation of terminfo
This commit is contained in:
bfredl
2025-10-18 12:07:31 +02:00
committed by GitHub
10 changed files with 2133 additions and 3579 deletions

View File

@@ -1,87 +0,0 @@
#!/usr/bin/env bash
#
# usage: ./scripts/update_terminfo.sh
#
# This script does:
#
# 1. Download Dickey's terminfo.src
# 2. Compile temporary terminfo database from terminfo.src
# 3. Use database to generate src/nvim/tui/terminfo_defs.h
#
set -e
url='https://invisible-island.net/datafiles/current/terminfo.src.gz'
target='src/nvim/tui/terminfo_defs.h'
readonly -A entries=(
[ansi]=ansi_terminfo
[interix]=interix_8colour_terminfo
[iterm2]=iterm_256colour_terminfo
[linux]=linux_16colour_terminfo
[putty-256color]=putty_256colour_terminfo
[rxvt-256color]=rxvt_256colour_terminfo
[screen-256color]=screen_256colour_terminfo
[st-256color]=st_256colour_terminfo
[tmux-256color]=tmux_256colour_terminfo
[vte-256color]=vte_256colour_terminfo
[xterm-256color]=xterm_256colour_terminfo
[cygwin]=cygwin_terminfo
[win32con]=win32con_terminfo
[conemu]=conemu_terminfo
[vtpcon]=vtpcon_terminfo
)
db="$(mktemp -du)"
print_bold() {
printf "\\e[1m%b\\e[0m" "$*"
}
cd "$(git rev-parse --show-toplevel)"
#
# Get terminfo.src
#
print_bold '[*] Get terminfo.src\n'
curl -O "$url"
gunzip -f terminfo.src.gz
#
# Build terminfo database
#
print_bold '[*] Build terminfo database\n'
cat terminfo.src scripts/windows.ti | tic -x -o "$db" -
rm -f terminfo.src
#
# Write src/nvim/tui/terminfo_defs.h
#
print_bold "[*] Writing $target... "
sorted_terms="$(echo "${!entries[@]}" | tr ' ' '\n' | sort | xargs)"
cat > "$target" <<EOF
// uncrustify:off
// Generated by scripts/update_terminfo.sh and $(tic -V)
#pragma once
#include <stdint.h>
EOF
for term in $sorted_terms; do
path="$(find "$db" -name "$term")"
if [ -z "$path" ]; then
>&2 echo "Not found: $term. Skipping."
continue
fi
printf '\n'
infocmp -L -x -1 -A "$db" "$term" | sed -e '1d' -e 's#^#// #' | tr '\t' ' '
printf 'static const int8_t %s[] = {\n' "${entries[$term]}"
printf ' '
od -v -t d1 < "$path" | cut -c9- | xargs | tr ' ' ','
printf '};\n'
done >> "$target"
print_bold 'done\n'

204
src/gen/gen_terminfo.lua Normal file
View File

@@ -0,0 +1,204 @@
-- usage: nvim -ll src/gen/gen_terminfo.lua
--
-- This script does:
--
-- 1. Download Dickey's terminfo.src
-- 2. Compile temporary terminfo database from terminfo.src
-- 3. Use database to generate src/nvim/tui/terminfo_defs.h
local url = 'https://invisible-island.net/datafiles/current/terminfo.src.gz'
local target_gen = 'src/nvim/tui/terminfo_defs.h'
local target_enum = 'src/nvim/tui/terminfo_enum_defs.h'
local entries = {
{ 'ansi', 'ansi_terminfo' },
{ 'interix', 'interix_8colour_terminfo' },
{ 'iterm2', 'iterm_256colour_terminfo' },
{ 'linux', 'linux_16colour_terminfo' },
{ 'putty-256color', 'putty_256colour_terminfo' },
{ 'rxvt-256color', 'rxvt_256colour_terminfo' },
{ 'screen-256color', 'screen_256colour_terminfo' },
{ 'st-256color', 'st_256colour_terminfo' },
{ 'tmux-256color', 'tmux_256colour_terminfo' },
{ 'vte-256color', 'vte_256colour_terminfo' },
{ 'xterm-256color', 'xterm_256colour_terminfo' },
{ 'cygwin', 'cygwin_terminfo' },
{ 'win32con', 'win32con_terminfo' },
{ 'conemu', 'conemu_terminfo' },
{ 'vtpcon', 'vtpcon_terminfo' },
}
local wanted_numbers = { 'max_colors', 'lines', 'columns' }
local wanted_strings = {
'carriage_return',
'change_scroll_region',
'clear_screen',
'clr_eol',
'clr_eos',
'cursor_address',
'cursor_down',
'cursor_invisible',
'cursor_left',
'cursor_home',
'cursor_normal',
'cursor_up',
'cursor_right',
'delete_line',
'enter_bold_mode',
'enter_ca_mode',
'enter_italics_mode',
'enter_reverse_mode',
'enter_standout_mode',
'enter_underline_mode',
'erase_chars',
'exit_attribute_mode',
'exit_ca_mode',
'from_status_line',
'insert_line',
'keypad_local',
'keypad_xmit',
'parm_delete_line',
'parm_down_cursor',
'parm_insert_line',
'parm_left_cursor',
'parm_right_cursor',
'parm_up_cursor',
'set_a_background',
'set_a_foreground',
'set_attributes',
'set_lr_margin',
'to_status_line',
}
local wanted_strings_ext = {
-- the following are our custom name for extensions, see "extmap"
{ 'reset_cursor_style', 'se' },
{ 'set_cursor_style', 'Ss' },
-- terminfo describes strikethrough modes as rmxx/smxx with respect
-- to the ECMA-48 strikeout/crossed-out attributes.
{ 'enter_strikethrough_mode', 'smxx' },
{ 'set_rgb_foreground', 'setrgbf' },
{ 'set_rgb_background', 'setrgbb' },
{ 'set_cursor_color', 'Cs' },
{ 'reset_cursor_color', 'Cr' },
{ 'set_underline_style', 'Smulx' },
}
local db = '/tmp/nvim_terminfo'
if vim.uv.fs_stat(db) == nil then
local function sys(cmd)
print(cmd)
os.execute(cmd)
end
sys('curl -O ' .. url)
sys('gunzip -f terminfo.src.gz')
sys('cat terminfo.src scripts/windows.ti | tic -x -o "' .. db .. '" -')
sys('rm -f terminfo.src')
else
print('using cached terminfo in ' .. db)
end
local function enumify(str)
return 'kTerm_' .. str
end
local function quote(str)
if str == nil then
return 'NULL'
end
-- remungle the strings to look like C strings
str = string.gsub(str, '\\E', '\\033')
str = string.gsub(str, '%^G', '\\a')
str = string.gsub(str, '%^H', '\\b')
str = string.gsub(str, '%^O', '\\017') -- o dod
-- str = string.gsub(str, "\\", "\\\\")
str = string.gsub(str, '"', '\\"')
return '"' .. str .. '"'
end
local dbg = function() end
-- dbg = print
local f_enum = io.open(target_enum, 'wb')
f_enum:write('// genenerated by src/gen/gen_terminfo.lua\n\n')
f_enum:write('#pragma once\n\n')
f_enum:write('typedef enum {\n')
for _, name in ipairs(wanted_strings) do
f_enum:write(' ' .. enumify(name) .. ',\n')
end
f_enum:write('#define kTermExtOffset ' .. enumify(wanted_strings_ext[1][1]) .. '\n')
for _, item in ipairs(wanted_strings_ext) do
f_enum:write(' ' .. enumify(item[1]) .. ',\n')
end
f_enum:write(' kTermCount, // sentinel\n')
f_enum:write('} TerminfoDef;\n')
f_enum:close()
local f_defs = io.open(target_gen, 'wb')
f_defs:write('// uncrustify:off\n\n')
local version = io.popen('infocmp -V'):read '*a'
f_defs:write('// Generated by src/gen/gen_terminfo.lua and ' .. version .. '\n')
f_defs:write('#pragma once\n\n')
f_defs:write('#include "nvim/tui/terminfo.h"\n')
for _, entry in ipairs(entries) do
local term, target = unpack(entry)
local fil = io.popen('infocmp -L -x -1 -A ' .. db .. ' ' .. term):read '*a'
local lines = vim.split(fil, '\n')
local prepat = '^%s*([%w_]+)'
local boolpat = prepat .. ','
local numpat = prepat .. '#([^,]+),'
local strpat = prepat .. '=([^,]+),'
local bools, nums, strs = {}, {}, {}
for i, line in ipairs(lines) do
local boolmatch = string.match(line, boolpat)
local nummatch, numval = string.match(line, numpat)
local strmatch, strval = string.match(line, strpat)
if boolmatch then
dbg('boolean: ' .. boolmatch)
bools[boolmatch] = true
elseif nummatch then
dbg('number: ' .. nummatch .. ' is ' .. numval)
nums[nummatch] = numval
elseif strmatch then
dbg('string: ' .. strmatch .. ' is ' .. strval)
strs[strmatch] = strval
else
dbg('UNKNOWN:', i, line)
end
end
f_defs:write('\nstatic const TerminfoEntry ' .. target .. ' = {\n')
f_defs:write(' .bce = ' .. tostring(bools.back_color_erase or false) .. ',\n')
local has_Tc_or_RGB = (bools.Tc or bools.RGB) or false
f_defs:write(' .has_Tc_or_RGB = ' .. tostring(has_Tc_or_RGB or false) .. ',\n')
f_defs:write(' .Su = ' .. tostring(bools.Su or false) .. ',\n')
for _, name in ipairs(wanted_numbers) do
f_defs:write(' .' .. name .. ' = ' .. (nums[name] or '-1') .. ',\n')
end
f_defs:write(' .defs = {\n')
for _, name in ipairs(wanted_strings) do
f_defs:write(' [' .. enumify(name) .. '] = ' .. quote(strs[name]) .. ',\n')
end
for _, item in ipairs(wanted_strings_ext) do
f_defs:write(' [' .. enumify(item[1]) .. '] = ' .. quote(strs[item[2]]) .. ',\n')
end
f_defs:write(' },\n')
f_defs:write('};\n')
end
f_defs:write('\n#define XLIST_TERMINFO_BUILTIN \\\n')
for _, name in ipairs(wanted_strings) do
f_defs:write(' X(' .. name .. ') \\\n')
end
f_defs:write('// end of list\n\n')
f_defs:write('#define XLIST_TERMINFO_EXT \\\n')
for _, item in ipairs(wanted_strings_ext) do
f_defs:write(' X(' .. item[1] .. ', ' .. item[2] .. ') \\\n')
end
f_defs:write('// end of list\n')
f_defs:close()

View File

@@ -2198,7 +2198,7 @@ DictAs(eval_statusline_ret) nvim_eval_statusline(String str, Dict(eval_statuslin
grpname = syn_id2name(-sp->userhl);
} else {
snprintf(user_group, sizeof(user_group), "User%d", sp->userhl);
grpname = arena_memdupz(arena, user_group, strlen(user_group));
grpname = arena_strdup(arena, user_group);
}
const char *combine = sp->item == STL_SIGNCOL ? syn_id2name(scl_hl_id)

View File

@@ -813,12 +813,19 @@ char *arena_allocz(Arena *arena, size_t size)
}
char *arena_memdupz(Arena *arena, const char *buf, size_t size)
FUNC_ATTR_NONNULL_ARG(2)
{
char *mem = arena_allocz(arena, size);
memcpy(mem, buf, size);
return mem;
}
char *arena_strdup(Arena *arena, const char *str)
FUNC_ATTR_NONNULL_ARG(2)
{
return arena_memdupz(arena, str, strlen(str));
}
#if defined(EXITFREE)
# include "nvim/autocmd.h"

View File

@@ -17,6 +17,12 @@
# include "nvim/os/os.h"
#endif
typedef struct {
long nums[20];
char *strings[20];
size_t offset;
} TPSTACK;
#include "tui/terminfo.c.generated.h"
bool terminfo_is_term_family(const char *term, const char *family)
@@ -57,167 +63,606 @@ bool terminfo_is_bsd_console(const char *term)
/// We do not attempt to detect xterm pretenders here.
///
/// @param term $TERM value
/// @param[out,allocated] termname decided builtin 'term' name
/// @param[out,static] termname decided builtin 'term' name
/// @return [allocated] terminfo structure
static unibi_term *terminfo_builtin(const char *term, char **termname)
const TerminfoEntry *terminfo_from_builtin(const char *term, char **termname)
{
if (terminfo_is_term_family(term, "xterm")) {
*termname = xstrdup("builtin_xterm");
return unibi_from_mem((const char *)xterm_256colour_terminfo,
sizeof xterm_256colour_terminfo);
*termname = "builtin_xterm";
return &xterm_256colour_terminfo;
} else if (terminfo_is_term_family(term, "screen")) {
*termname = xstrdup("builtin_screen");
return unibi_from_mem((const char *)screen_256colour_terminfo,
sizeof screen_256colour_terminfo);
*termname = "builtin_screen";
return &screen_256colour_terminfo;
} else if (terminfo_is_term_family(term, "tmux")) {
*termname = xstrdup("builtin_tmux");
return unibi_from_mem((const char *)tmux_256colour_terminfo,
sizeof tmux_256colour_terminfo);
*termname = "builtin_tmux";
return &tmux_256colour_terminfo;
} else if (terminfo_is_term_family(term, "rxvt")) {
*termname = xstrdup("builtin_rxvt");
return unibi_from_mem((const char *)rxvt_256colour_terminfo,
sizeof rxvt_256colour_terminfo);
*termname = "builtin_rxvt";
return &rxvt_256colour_terminfo;
} else if (terminfo_is_term_family(term, "putty")) {
*termname = xstrdup("builtin_putty");
return unibi_from_mem((const char *)putty_256colour_terminfo,
sizeof putty_256colour_terminfo);
*termname = "builtin_putty";
return &putty_256colour_terminfo;
} else if (terminfo_is_term_family(term, "linux")) {
*termname = xstrdup("builtin_linux");
return unibi_from_mem((const char *)linux_16colour_terminfo,
sizeof linux_16colour_terminfo);
*termname = "builtin_linux";
return &linux_16colour_terminfo;
} else if (terminfo_is_term_family(term, "interix")) {
*termname = xstrdup("builtin_interix");
return unibi_from_mem((const char *)interix_8colour_terminfo,
sizeof interix_8colour_terminfo);
*termname = "builtin_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 = xstrdup("builtin_iterm");
return unibi_from_mem((const char *)iterm_256colour_terminfo,
sizeof iterm_256colour_terminfo);
*termname = "builtin_iterm";
return &iterm_256colour_terminfo;
} else if (terminfo_is_term_family(term, "st")) {
*termname = xstrdup("builtin_st");
return unibi_from_mem((const char *)st_256colour_terminfo,
sizeof st_256colour_terminfo);
*termname = "builtin_st";
return &st_256colour_terminfo;
} else if (terminfo_is_term_family(term, "gnome")
|| terminfo_is_term_family(term, "vte")) {
*termname = xstrdup("builtin_vte");
return unibi_from_mem((const char *)vte_256colour_terminfo,
sizeof vte_256colour_terminfo);
*termname = "builtin_vte";
return &vte_256colour_terminfo;
} else if (terminfo_is_term_family(term, "cygwin")) {
*termname = xstrdup("builtin_cygwin");
return unibi_from_mem((const char *)cygwin_terminfo,
sizeof cygwin_terminfo);
*termname = "builtin_cygwin";
return &cygwin_terminfo;
} else if (terminfo_is_term_family(term, "win32con")) {
*termname = xstrdup("builtin_win32con");
return unibi_from_mem((const char *)win32con_terminfo,
sizeof win32con_terminfo);
*termname = "builtin_win32con";
return &win32con_terminfo;
} else if (terminfo_is_term_family(term, "conemu")) {
*termname = xstrdup("builtin_conemu");
return unibi_from_mem((const char *)conemu_terminfo,
sizeof conemu_terminfo);
*termname = "builtin_conemu";
return &conemu_terminfo;
} else if (terminfo_is_term_family(term, "vtpcon")) {
*termname = xstrdup("builtin_vtpcon");
return unibi_from_mem((const char *)vtpcon_terminfo,
sizeof vtpcon_terminfo);
*termname = "builtin_vtpcon";
return &vtpcon_terminfo;
} else {
*termname = xstrdup("builtin_ansi");
return unibi_from_mem((const char *)ansi_terminfo,
sizeof ansi_terminfo);
*termname = "builtin_ansi";
return &ansi_terminfo;
}
}
/// @param term $TERM value
/// @param[out,allocated] termname decided builtin 'term' name
/// @return [allocated] terminfo structure
unibi_term *terminfo_from_builtin(const char *term, char **termname)
static ssize_t unibi_find_ext_str(unibi_term *ut, const char *name)
{
unibi_term *ut = terminfo_builtin(term, termname);
if (*termname == NULL) {
*termname = xstrdup("builtin_?");
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 ut;
return -1;
}
bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena)
{
unibi_term *ut = unibi_from_term(termname);
if (!ut) {
return false;
}
ti->bce = unibi_get_bool(ut, unibi_back_color_erase);
ti->max_colors = unibi_get_num(ut, unibi_max_colors);
ti->lines = unibi_get_num(ut, unibi_lines);
ti->columns = unibi_get_num(ut, unibi_columns);
// Check for Tc or RGB
ti->has_Tc_or_RGB = false;
ti->Su = false;
for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) {
const char *n = unibi_get_ext_bool_name(ut, i);
if (n && (!strcmp(n, "Tc") || !strcmp(n, "RGB"))) {
ti->has_Tc_or_RGB = true;
} else if (n && !strcmp(n, "Su")) {
ti->Su = true;
}
}
static const enum unibi_string uni_ids[] = {
#define X(name) unibi_##name,
XLIST_TERMINFO_BUILTIN
#undef X
};
for (size_t i = 0; i < ARRAY_SIZE(uni_ids); i++) {
const char *val = unibi_get_str(ut, uni_ids[i]);
ti->defs[i] = val ? arena_strdup(arena, val) : NULL;
}
static const char *uni_ext[] = {
#define X(informal_name, terminfo_name) #terminfo_name,
XLIST_TERMINFO_EXT
#undef X
};
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;
}
}
unibi_destroy(ut);
return true;
}
static const char *fmt(bool val)
{
return val ? "true" : "false";
}
/// Dumps termcap info to the messages area.
/// Serves a similar purpose as Vim `:set termcap` (removed in Nvim).
///
/// @note adapted from unibilium unibi-dump.c
/// @return allocated string
String terminfo_info_msg(const unibi_term *ut, const char *termname)
String terminfo_info_msg(const TerminfoEntry *ti, const char *termname)
{
StringBuilder data = KV_INITIAL_VALUE;
kv_printf(data, "&term: %s\n", termname);
kv_printf(data, "Description: %s\n", unibi_get_name(ut));
const char **a = unibi_get_aliases(ut);
if (*a) {
kv_printf(data, "Aliases: ");
do {
kv_printf(data, "%s%s\n", *a, a[1] ? " | " : "");
a++;
} while (*a);
}
kv_printf(data, "\n");
kv_printf(data, "Boolean capabilities:\n");
for (enum unibi_boolean i = unibi_boolean_begin_ + 1;
i < unibi_boolean_end_; i++) {
kv_printf(data, " %-25s %-10s = %s\n", unibi_name_bool(i),
unibi_short_name_bool(i),
unibi_get_bool(ut, i) ? "true" : "false");
}
kv_printf(data, " back_color_erase: %s\n", fmt(ti->bce));
kv_printf(data, " truecolor ('Tc' or 'RGB'): %s\n", fmt(ti->has_Tc_or_RGB));
kv_printf(data, " extended underline ('Su'): %s\n", fmt(ti->Su));
kv_printf(data, "\n");
kv_printf(data, "Numeric capabilities:\n");
for (enum unibi_numeric i = unibi_numeric_begin_ + 1;
i < unibi_numeric_end_; i++) {
int n = unibi_get_num(ut, i); // -1 means "empty"
kv_printf(data, " %-25s %-10s = %d\n", unibi_name_num(i),
unibi_short_name_num(i), n);
}
kv_printf(data, "Numeric capabilities: (-1 for unknown)\n");
kv_printf(data, " lines: %d\n", ti->lines);
kv_printf(data, " columns: %d\n", ti->columns);
kv_printf(data, " max_colors: %d\n", ti->columns);
kv_printf(data, "\n");
kv_printf(data, "String capabilities:\n");
for (enum unibi_string i = unibi_string_begin_ + 1;
i < unibi_string_end_; i++) {
const char *s = unibi_get_str(ut, i);
static const char *string_names[] = {
#define X(name) #name,
XLIST_TERMINFO_BUILTIN
#undef X
#define X(internal_name, terminfo_name) (#internal_name " (" #terminfo_name ")"),
XLIST_TERMINFO_EXT
#undef X
};
for (size_t i = 0 + 1; i < ARRAY_SIZE(string_names); i++) {
const char *s = ti->defs[i];
if (s) {
kv_printf(data, " %-25s %-10s = ", unibi_name_str(i),
unibi_short_name_str(i));
kv_printf(data, " %-31s = ", string_names[i]);
// Most of these strings will contain escape sequences.
kv_transstr(&data, s, false);
kv_push(data, '\n');
}
}
if (unibi_count_ext_bool(ut)) {
kv_printf(data, "Extended boolean capabilities:\n");
for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) {
kv_printf(data, " %-25s = %s\n",
unibi_get_ext_bool_name(ut, i),
unibi_get_ext_bool(ut, i) ? "true" : "false");
}
}
if (unibi_count_ext_num(ut)) {
kv_printf(data, "Extended numeric capabilities:\n");
for (size_t i = 0; i < unibi_count_ext_num(ut); i++) {
kv_printf(data, " %-25s = %d\n",
unibi_get_ext_num_name(ut, i),
unibi_get_ext_num(ut, i));
}
}
if (unibi_count_ext_str(ut)) {
kv_printf(data, "Extended string capabilities:\n");
for (size_t i = 0; i < unibi_count_ext_str(ut); i++) {
kv_printf(data, " %-25s = ", unibi_get_ext_str_name(ut, i));
// NOTE: unibi_get_ext_str(ut, i) might be NULL, as termcap
// might include junk data on mac os. kv_transstr will handle this.
kv_transstr(&data, unibi_get_ext_str(ut, i), false);
kv_push(data, '\n');
}
}
kv_push(data, NUL);
return cbuf_as_string(data.items, data.size - 1);
}
// The implementation of terminfo_fmt() is based on NetBSD libterminfo,
// with full license reproduced below
// Copyright (c) 2009, 2011, 2013 The NetBSD Foundation, Inc.
//
// This code is derived from software contributed to The NetBSD Foundation
// by Roy Marples.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// nvim modifications:
// - use typesafe param args instead of va_args and piss
// - caller provides the output buffer
// - static variables are not preserved
static int push(long num, char *string, TPSTACK *stack)
{
if (stack->offset >= sizeof(stack->nums)) {
return -1;
}
stack->nums[stack->offset] = num;
stack->strings[stack->offset] = string;
stack->offset++;
return 0;
}
static int pop(long *num, char **string, TPSTACK *stack)
{
if (stack->offset == 0) {
if (num) {
*num = 0;
}
if (string) {
*string = NULL;
}
return -1;
}
stack->offset--;
if (num) {
*num = stack->nums[stack->offset];
}
if (string) {
*string = stack->strings[stack->offset];
}
return 0;
}
static bool ochar(char **buf, const char *buf_end, int c)
{
if (c == 0) {
c = 0200;
}
// Check we have space and a terminator
if (buf_end - *buf < 2) {
return 0;
}
*(*buf)++ = (char)c;
return 1;
}
static bool onum(char **buf, const char *buf_end, const char *fmt, int num, size_t len)
{
const size_t LONG_STR_MAX = 21;
len = MAX(len, LONG_STR_MAX);
if (buf_end - *buf < (ssize_t)(len + 2)) {
return 0;
}
int l = snprintf(*buf, len + 2, fmt, num);
if (l == -1) {
return 0;
}
*buf += l;
return true;
}
/// @return number of chars printed or 0 for any error
size_t terminfo_fmt(char *buf_start, char *buf_end, const char *str, TPVAR params[9])
{
char c, fmt[64], *fp, *ostr;
long val, val2;
long dnums[26]; // dynamic variables a-z, not preserved
long snums[26]; // static variables a-z, not preserved EITHER HAHA
memset(dnums, 0, sizeof snums);
memset(snums, 0, sizeof snums);
char *buf = buf_start;
size_t l, width, precision, olen;
TPSTACK stack;
unsigned done, dot, minus;
memset(&stack, 0, sizeof(stack));
while ((c = *str++) != '\0') {
if (c != '%' || (c = *str++) == '%') {
if (c == '\0') {
break;
}
if (!ochar(&buf, buf_end, c)) {
return false;
}
continue;
}
// Handle formatting.
fp = fmt;
*fp++ = '%';
done = dot = minus = 0;
width = precision = 0;
val = 0;
while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
switch (c) {
case 'c':
case 's':
*fp++ = c;
done = 1;
break;
case 'd':
case 'o':
case 'x':
case 'X':
*fp++ = 'l';
*fp++ = c;
done = 1;
break;
case '#':
case ' ':
*fp++ = c;
break;
case '.':
*fp++ = c;
if (dot == 0) {
dot = 1;
width = (size_t)val;
} else {
done = 2;
}
val = 0;
break;
case ':':
minus = 1;
break;
case '-':
if (minus) {
*fp++ = c;
} else {
done = 1;
}
break;
default:
if (isdigit((unsigned char)c)) {
val = (val * 10) + (c - '0');
if (val > 10000) {
done = 2;
} else {
*fp++ = c;
}
} else {
done = 1;
}
}
if (done == 0) {
c = *str++;
}
}
if (done == 2) {
// Found an error in the format
fp = fmt + 1;
*fp = *str;
olen = 0;
} else {
if (dot == 0) {
width = (size_t)val;
} else {
precision = (size_t)val;
}
olen = MAX(width, precision);
}
*fp++ = '\0';
// Handle commands
switch (c) {
case 'c':
pop(&val, NULL, &stack);
if (!ochar(&buf, buf_end, (unsigned char)val)) {
return false;
}
break;
case 's':
pop(NULL, &ostr, &stack);
if (ostr != NULL) {
int r;
l = strlen(ostr);
if (l < olen) {
l = olen;
}
if ((size_t)(buf_end - buf) < (l + 1)) {
return false;
}
r = snprintf(buf, l + 1,
fmt, ostr);
if (r != -1) {
buf += (size_t)r;
}
}
break;
case 'l':
pop(NULL, &ostr, &stack);
if (ostr == NULL) {
l = 0;
} else {
l = strlen(ostr);
}
push((long)l, NULL, &stack);
break;
case 'd':
case 'o':
case 'x':
case 'X':
pop(&val, NULL, &stack);
if (onum(&buf, buf_end, fmt, (int)val, olen) == 0) {
return 0;
}
break;
case 'p':
if (*str < '1' || *str > '9') {
break;
}
l = (size_t)(*str++ - '1');
if (push(params[l].num, params[l].string, &stack)) {
return 0;
}
break;
case 'P':
pop(&val, NULL, &stack);
if (*str >= 'a' && *str <= 'z') {
dnums[*str - 'a'] = val;
} else if (*str >= 'A' && *str <= 'Z') {
snums[*str - 'A'] = val;
}
break;
case 'g':
if (*str >= 'a' && *str <= 'z') {
if (push(dnums[*str - 'a'], NULL, &stack)) {
return 0;
}
} else if (*str >= 'A' && *str <= 'Z') {
if (push(snums[*str - 'A'], NULL, &stack)) {
return 0;
}
}
break;
case 'i':
params[0].num++;
params[1].num++;
break;
case '\'':
if (push((long)(unsigned char)(*str++), NULL, &stack)) {
return 0;
}
while (*str != '\0' && *str != '\'') {
str++;
}
if (*str == '\'') {
str++;
}
break;
case '{':
val = 0;
for (; isdigit((unsigned char)(*str)); str++) {
val = (val * 10) + (*str - '0');
}
if (push(val, NULL, &stack)) {
return 0;
}
while (*str != '\0' && *str != '}') {
str++;
}
if (*str == '}') {
str++;
}
break;
case '+':
case '-':
case '*':
case '/':
case 'm':
case 'A':
case 'O':
case '&':
case '|':
case '^':
case '=':
case '<':
case '>':
pop(&val, NULL, &stack);
pop(&val2, NULL, &stack);
switch (c) {
case '+':
val = val + val2;
break;
case '-':
val = val2 - val;
break;
case '*':
val = val * val2;
break;
case '/':
val = val ? val2 / val : 0;
break;
case 'm':
val = val ? val2 % val : 0;
break;
case 'A':
val = val && val2;
break;
case 'O':
val = val || val2;
break;
case '&':
val = val & val2;
break;
case '|':
val = val | val2;
break;
case '^':
val = val ^ val2;
break;
case '=':
val = val == val2;
break;
case '<':
val = val2 < val;
break;
case '>':
val = val2 > val;
break;
}
if (push(val, NULL, &stack)) {
return 0;
}
break;
case '!':
case '~':
pop(&val, NULL, &stack);
switch (c) {
case '!':
val = !val;
break;
case '~':
val = ~val;
break;
}
if (push(val, NULL, &stack)) {
return 0;
}
break;
case '?': // if
break;
case 't': // then
pop(&val, NULL, &stack);
if (val == 0) {
l = 0;
for (; *str != '\0'; str++) {
if (*str != '%') {
continue;
}
str++;
if (*str == '?') {
l++;
} else if (*str == ';') {
if (l > 0) {
l--;
} else {
str++;
break;
}
} else if (*str == 'e' && l == 0) {
str++;
break;
}
}
}
break;
case 'e': // else
l = 0;
for (; *str != '\0'; str++) {
if (*str != '%') {
continue;
}
str++;
if (*str == '?') {
l++;
} else if (*str == ';') {
if (l > 0) {
l--;
} else {
str++;
break;
}
}
}
break;
case ';': // fi
break;
}
}
return (size_t)(buf - buf_start);
}

View File

@@ -1,7 +1,23 @@
#pragma once
#include <unibilium.h> // IWYU pragma: keep
#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/tui/terminfo_enum_defs.h"
typedef struct {
bool bce;
// these extended booleans indicate likely 24-color support
bool has_Tc_or_RGB;
bool Su;
int max_colors;
int lines;
int columns;
const char *defs[kTermCount];
} TerminfoEntry;
typedef struct {
long num;
char *string;
} TPVAR;
#include "tui/terminfo.h.generated.h"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,54 @@
// genenerated by src/gen/gen_terminfo.lua
#pragma once
typedef enum {
kTerm_carriage_return,
kTerm_change_scroll_region,
kTerm_clear_screen,
kTerm_clr_eol,
kTerm_clr_eos,
kTerm_cursor_address,
kTerm_cursor_down,
kTerm_cursor_invisible,
kTerm_cursor_left,
kTerm_cursor_home,
kTerm_cursor_normal,
kTerm_cursor_up,
kTerm_cursor_right,
kTerm_delete_line,
kTerm_enter_bold_mode,
kTerm_enter_ca_mode,
kTerm_enter_italics_mode,
kTerm_enter_reverse_mode,
kTerm_enter_standout_mode,
kTerm_enter_underline_mode,
kTerm_erase_chars,
kTerm_exit_attribute_mode,
kTerm_exit_ca_mode,
kTerm_from_status_line,
kTerm_insert_line,
kTerm_keypad_local,
kTerm_keypad_xmit,
kTerm_parm_delete_line,
kTerm_parm_down_cursor,
kTerm_parm_insert_line,
kTerm_parm_left_cursor,
kTerm_parm_right_cursor,
kTerm_parm_up_cursor,
kTerm_set_a_background,
kTerm_set_a_foreground,
kTerm_set_attributes,
kTerm_set_lr_margin,
kTerm_to_status_line,
#define kTermExtOffset kTerm_reset_cursor_style
kTerm_reset_cursor_style,
kTerm_set_cursor_style,
kTerm_enter_strikethrough_mode,
kTerm_set_rgb_foreground,
kTerm_set_rgb_background,
kTerm_set_cursor_color,
kTerm_reset_cursor_color,
kTerm_set_underline_style,
kTermCount, // sentinel
} TerminfoDef;

File diff suppressed because it is too large Load Diff

View File

@@ -4005,7 +4005,9 @@ describe('TUI client', function()
screen_client:expect({ any = 'GUI Running: 0' })
if is_os('mac') then
assert_log('uv_tty_set_mode failed: Unknown system error %-102', testlog)
-- this might either be "Unknown system error %-102" or
-- "inappropriate ioctl for device" depending on the phase of the moon
assert_log('uv_tty_set_mode failed', testlog)
end
end)
@@ -4037,7 +4039,7 @@ describe('TUI client', function()
ffi.C.ui_call_set_title(title)
]=])
screen_client:expect_unchanged()
assert_log('TUI: escape sequence for ext%.set_title too long', testlog)
assert_log('set_title: title string too long!', testlog)
eq(bufname, api.nvim_buf_get_var(0, 'term_title'))
-- Following escape sequences are not affected.