diff --git a/docs/changelog.rst b/docs/changelog.rst index e8ed65be7..8f000bd0c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -54,6 +54,8 @@ Detailed list of changes - kitten icat: Fix image being displayed one cell to the right when using both ``--place`` and ``--unicode-placeholder`` (:iss:`6556`) +- kitten run-shell: Make kitty terminfo database available if needed before starting the shell + - macOS: Fix keyboard shortcuts in the Apple global menubar not being changed when reloading the config - Fix a crash when resizing an OS Window that is displaying more than one image and the new size is smaller than the image needs (:iss:`6555`) diff --git a/docs/shell-integration.rst b/docs/shell-integration.rst index a5750055a..1e4c2cc47 100644 --- a/docs/shell-integration.rst +++ b/docs/shell-integration.rst @@ -233,6 +233,8 @@ somewhere in the PATH, then you can log into the container with: docker exec -ti container-id kitten run-shell --shell=/path/to/your/shell/in/the/container +The kitten will even take care of making the kitty terminfo database available +in the container automatically. .. _clone_shell: diff --git a/gen-go-code.py b/gen-go-code.py index c8c33c244..25b902fa1 100755 --- a/gen-go-code.py +++ b/gen-go-code.py @@ -562,7 +562,6 @@ def load_ref_map() -> Dict[str, Dict[str, str]]: def generate_constants() -> str: from kittens.hints.main import DEFAULT_REGEX from kitty.fast_data_types import FILE_TRANSFER_CODE - from kitty.options.types import Options from kitty.options.utils import allowed_shell_integration_values del sys.modules['kittens.hints.main'] ref_map = load_ref_map() @@ -589,6 +588,7 @@ const IsFrozenBuild bool = false const IsStandaloneBuild bool = false const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]} const HintsDefaultRegex = `{DEFAULT_REGEX}` +const DefaultTermName = `{Options.term}` var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}} var DefaultPager []string = []string{{ {dp} }} var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)} @@ -811,7 +811,7 @@ def generate_unicode_names(src: TextIO, dest: BinaryIO) -> None: def generate_ssh_kitten_data() -> None: files = { - 'terminfo/kitty.terminfo', 'terminfo/x/xterm-kitty', + 'terminfo/kitty.terminfo', 'terminfo/x/' + Options.term, } for dirpath, dirnames, filenames in os.walk('shell-integration'): for f in filenames: diff --git a/kittens/ssh/main.go b/kittens/ssh/main.go index 7fed0deed..e9f0d098f 100644 --- a/kittens/ssh/main.go +++ b/kittens/ssh/main.go @@ -351,7 +351,7 @@ func make_tarfile(cd *connection_data, get_local_env func(string) (string, bool) } err = add_entries(path.Join("home", ".terminfo"), shell_integration.Data()["terminfo/kitty.terminfo"]) if err == nil { - err = add_entries(path.Join("home", ".terminfo", "x"), shell_integration.Data()["terminfo/x/xterm-kitty"]) + err = add_entries(path.Join("home", ".terminfo", "x"), shell_integration.Data()["terminfo/x/"+kitty.DefaultTermName]) } if err == nil { err = tw.Close() diff --git a/kittens/ssh/main_test.go b/kittens/ssh/main_test.go index 7656eeea9..da3fd1d83 100644 --- a/kittens/ssh/main_test.go +++ b/kittens/ssh/main_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io/fs" + "kitty" "kitty/tools/utils/shm" "os" "os/exec" @@ -133,7 +134,7 @@ func TestSSHTarfile(t *testing.T) { if !seen["data.sh"] { t.Fatalf("data.sh missing") } - for _, x := range []string{".terminfo/kitty.terminfo", ".terminfo/x/xterm-kitty"} { + for _, x := range []string{".terminfo/kitty.terminfo", ".terminfo/x/" + kitty.DefaultTermName} { if !seen["home/"+x] { t.Fatalf("%s missing", x) } diff --git a/kitty/terminfo.py b/kitty/terminfo.py index d9523b7bf..56aa9cd4b 100644 --- a/kitty/terminfo.py +++ b/kitty/terminfo.py @@ -3,10 +3,9 @@ import re from binascii import hexlify, unhexlify -from typing import TYPE_CHECKING, Dict, Generator, Optional, cast +from typing import Dict, Generator, Optional, cast -if TYPE_CHECKING: - from .options.types import Options +from kitty.options.types import Options def modify_key_bytes(keybytes: bytes, amt: int) -> bytes: @@ -25,7 +24,7 @@ def encode_keystring(keybytes: bytes) -> str: return keybytes.decode('ascii').replace('\033', r'\E') -names = 'xterm-kitty', 'KovIdTTY' +names = Options.term, 'KovIdTTY' termcap_aliases = { 'TN': 'name' diff --git a/tools/cmd/run_shell/main.go b/tools/cmd/run_shell/main.go index 1a3005f06..a3a60a545 100644 --- a/tools/cmd/run_shell/main.go +++ b/tools/cmd/run_shell/main.go @@ -4,11 +4,13 @@ package run_shell import ( "fmt" + "kitty" "os" "strings" "kitty/tools/cli" "kitty/tools/tui" + "kitty/tools/tui/shell_integration" ) var _ = fmt.Print @@ -38,6 +40,19 @@ func main(args []string, opts *Options) (rc int, err error) { } changed = true } + if os.Getenv("TERM") == "" { + os.Setenv("TERM", kitty.DefaultTermName) + } + if term := os.Getenv("TERM"); term == kitty.DefaultTermName && shell_integration.PathToTerminfoDb(term) == "" { + if terminfo_dir, err := shell_integration.EnsureTerminfoFiles(); err == nil { + os.Unsetenv("TERMINFO") + existing := os.Getenv("TERMINFO_DIRS") + if existing != "" { + existing = string(os.PathListSeparator) + existing + } + os.Setenv("TERMINFO_DIRS", terminfo_dir+existing) + } + } err = tui.RunShell(tui.ResolveShell(opts.Shell), tui.ResolveShellIntegration(opts.ShellIntegration)) if changed { os.Clearenv() diff --git a/tools/tui/shell_integration/api.go b/tools/tui/shell_integration/api.go index 3dd133c16..f3903a21c 100644 --- a/tools/tui/shell_integration/api.go +++ b/tools/tui/shell_integration/api.go @@ -6,6 +6,7 @@ import ( "archive/tar" "bytes" "fmt" + "kitty" "kitty/tools/utils" "os" "os/exec" @@ -55,6 +56,12 @@ func extract_shell_integration_for(shell_name string, dest_dir string) (err erro } func extract_terminfo(dest_dir string) (err error) { + var s os.FileInfo + if s, err = os.Stat(filepath.Join(dest_dir, "terminfo", "x", kitty.DefaultTermName)); err == nil && s.Mode().IsRegular() { + if s, err = os.Stat(filepath.Join(dest_dir, "terminfo", "78", kitty.DefaultTermName)); err == nil && s.Mode().IsRegular() { + return + } + } if err = extract_files("terminfo/", dest_dir); err == nil { dest := filepath.Join(dest_dir, "terminfo", "78") err = os.Symlink("x", dest) @@ -62,6 +69,76 @@ func extract_terminfo(dest_dir string) (err error) { return } +func PathToTerminfoDb(term string) (ans string) { + // see man terminfo for the algorithm ncurses uses for this + + seen := utils.NewSet[string]() + check_dir := func(path string) string { + if seen.Has(path) { + return `` + } + seen.Add(path) + q := filepath.Join(path, term[:1], term) + if s, err := os.Stat(q); err == nil && s.Mode().IsRegular() { + return q + } + if entries, err := os.ReadDir(filepath.Join(path)); err == nil { + for _, x := range entries { + q := filepath.Join(path, x.Name(), term) + if s, err := os.Stat(q); err == nil && s.Mode().IsRegular() { + return q + } + } + } + return `` + } + + if td := os.Getenv("TERMINFO"); td != "" { + if ans = check_dir(td); ans != "" { + return ans + } + } + + if ans = check_dir(utils.Expanduser("~/.terminfo")); ans != "" { + return ans + } + if td := os.Getenv("TERMINFO_DIRS"); td != "" { + for _, q := range strings.Split(td, string(os.PathListSeparator)) { + if q == "" { + q = "/usr/share/terminfo" + } + if ans = check_dir(q); ans != "" { + return ans + } + } + } + for _, q := range []string{"/usr/share/terminfo", "/usr/lib/terminfo", "/usr/share/lib/terminfo"} { + if ans = check_dir(q); ans != "" { + return ans + } + } + return +} + +func EnsureTerminfoFiles() (terminfo_dir string, err error) { + if kid := os.Getenv("KITTY_INSTALLATION_DIR"); kid != "" { + if s, e := os.Stat(kid); e == nil && s.IsDir() { + q := filepath.Join(kid, "terminfo") + if s, e := os.Stat(q); e == nil && s.IsDir() { + return q, nil + } + } + } + base := filepath.Join(utils.CacheDir(), "extracted-kti") + if err = os.MkdirAll(base, 0o755); err != nil { + return "", err + } + if err = extract_terminfo(base); err != nil { + return "", fmt.Errorf("Failed to extract terminfo files with error: %w", err) + } + return filepath.Join(base, "terminfo"), nil +} + func EnsureShellIntegrationFilesFor(shell_name string) (shell_integration_dir_for_shell string, err error) { if kid := os.Getenv("KITTY_INSTALLATION_DIR"); kid != "" { if s, e := os.Stat(kid); e == nil && s.IsDir() { diff --git a/tools/tui/shell_integration/api_test.go b/tools/tui/shell_integration/api_test.go index 23c472e3f..868bdaf04 100644 --- a/tools/tui/shell_integration/api_test.go +++ b/tools/tui/shell_integration/api_test.go @@ -5,6 +5,7 @@ package shell_integration import ( "bytes" "fmt" + "kitty" "os" "path/filepath" "testing" @@ -43,7 +44,7 @@ func TestExtractShellIntegration(t *testing.T) { if err = extract_terminfo(tdir); err != nil { t.Fatal(err) } - if _, err := os.Stat(filepath.Join(tdir, "terminfo", "78", "xterm-kitty")); err != nil { + if _, err := os.Stat(filepath.Join(tdir, "terminfo", "78", kitty.DefaultTermName)); err != nil { t.Fatal(err) } }