// License: GPLv3 Copyright: 2023, Kovid Goyal, package tui import ( "fmt" "kitty" "os" "os/exec" "path/filepath" "runtime" "strings" "golang.org/x/sys/unix" "kitty/tools/config" "kitty/tools/tty" "kitty/tools/tui/loop" "kitty/tools/tui/shell_integration" "kitty/tools/utils" "kitty/tools/utils/shlex" ) var _ = fmt.Print type KittyOpts struct { Shell, Shell_integration string } func read_relevant_kitty_opts(path string) KittyOpts { ans := KittyOpts{Shell: kitty.KittyConfigDefaults.Shell, Shell_integration: kitty.KittyConfigDefaults.Shell_integration} handle_line := func(key, val string) error { switch key { case "shell": ans.Shell = strings.TrimSpace(val) case "shell_integration": ans.Shell_integration = strings.TrimSpace(val) } return nil } cp := config.ConfigParser{LineHandler: handle_line} cp.ParseFiles(path) if ans.Shell == "" { ans.Shell = kitty.KittyConfigDefaults.Shell } return ans } func get_effective_ksi_env_var(x string) string { parts := strings.Split(strings.TrimSpace(strings.ToLower(x)), " ") current := utils.NewSetWithItems(parts...) if current.Has("disabled") { return "" } allowed := utils.NewSetWithItems(kitty.AllowedShellIntegrationValues...) if !current.IsSubsetOf(allowed) { return relevant_kitty_opts().Shell_integration } return x } var relevant_kitty_opts = utils.Once(func() KittyOpts { return read_relevant_kitty_opts(filepath.Join(utils.ConfigDir(), "kitty.conf")) }) func ResolveShell(shell string) []string { if shell == "" { shell = relevant_kitty_opts().Shell if shell == "." { s, e := utils.LoginShellForCurrentUser() if e != nil { shell = "/bin/sh" } else { shell = s } } } shell_cmd, err := shlex.Split(shell) if err != nil { shell_cmd = []string{shell} } exe := utils.FindExe(shell_cmd[0]) if unix.Access(exe, unix.X_OK) != nil { shell_cmd = []string{"/bin/sh"} } return shell_cmd } func ResolveShellIntegration(shell_integration string) string { if shell_integration == "" { shell_integration = relevant_kitty_opts().Shell_integration } return get_effective_ksi_env_var(shell_integration) } func get_shell_name(argv0 string) (ans string) { ans = filepath.Base(argv0) if strings.HasSuffix(strings.ToLower(ans), ".exe") { ans = ans[:len(ans)-4] } if strings.HasPrefix(ans, "-") { ans = ans[1:] } return } func rc_modification_allowed(ksi string) bool { for _, x := range strings.Split(ksi, " ") { switch x { case "disabled", "no-rc": return false } } return ksi != "" } func RunShell(shell_cmd []string, shell_integration_env_var_val string) (err error) { shell_name := get_shell_name(shell_cmd[0]) var shell_env map[string]string if rc_modification_allowed(shell_integration_env_var_val) && shell_integration.IsSupportedShell(shell_name) { oenv := os.Environ() env := make(map[string]string, len(oenv)) for _, x := range oenv { if k, v, found := strings.Cut(x, "="); found { env[k] = v } } argv, env, err := shell_integration.Setup(shell_name, shell_cmd, env) if err != nil { return err } shell_cmd = argv shell_env = env } exe := shell_cmd[0] if runtime.GOOS == "darwin" { // ensure shell runs in login mode shell_cmd[0] = "-" + filepath.Base(shell_cmd[0]) } var env []string if shell_env != nil { env := make([]string, 0, len(shell_env)) for k, v := range shell_env { env = append(env, fmt.Sprintf("%s=%s", k, v)) } } else { env = os.Environ() } return unix.Exec(utils.FindExe(exe), shell_cmd, env) } func RunCommandRestoringTerminalToSaneStateAfter(cmd []string) { exe := utils.FindExe(cmd[0]) c := exec.Command(exe, cmd[1:]...) c.Stdout = os.Stdout c.Stdin = os.Stdin c.Stderr = os.Stderr term, err := tty.OpenControllingTerm() if err == nil { var state_before unix.Termios if term.Tcgetattr(&state_before) == nil { term.WriteString(loop.SAVE_PRIVATE_MODE_VALUES) defer func() { term.WriteString(strings.Join([]string{ loop.RESTORE_PRIVATE_MODE_VALUES, "\x1b[=u", // reset kitty keyboard protocol to legacy "\x1b[1 q", // blinking block cursor loop.DECTCEM.EscapeCodeToSet(), // cursor visible "\x1b]112\a", // reset cursor color }, "")) term.Tcsetattr(tty.TCSANOW, &state_before) term.Close() }() } else { defer term.Close() } } err = c.Run() if err != nil { fmt.Fprintln(os.Stderr, cmd[0], "failed with error:", err) } }