main.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package run_shell
  3. import (
  4. "fmt"
  5. "kitty"
  6. "os"
  7. "strings"
  8. "kitty/tools/cli"
  9. "kitty/tools/tty"
  10. "kitty/tools/tui"
  11. "kitty/tools/tui/shell_integration"
  12. )
  13. var _ = fmt.Print
  14. type Options struct {
  15. Shell string
  16. ShellIntegration string
  17. Env []string
  18. Cwd string
  19. }
  20. func main(args []string, opts *Options) (rc int, err error) {
  21. if len(args) > 0 {
  22. tui.RunCommandRestoringTerminalToSaneStateAfter(args)
  23. }
  24. env_before := os.Environ()
  25. changed := false
  26. for _, entry := range opts.Env {
  27. k, v, found := strings.Cut(entry, "=")
  28. if found {
  29. if err := os.Setenv(k, v); err != nil {
  30. return 1, fmt.Errorf("Failed to set the env var %s with error: %w", k, err)
  31. }
  32. } else {
  33. if err := os.Unsetenv(k); err != nil {
  34. return 1, fmt.Errorf("Failed to unset the env var %s with error: %w", k, err)
  35. }
  36. }
  37. changed = true
  38. }
  39. if os.Getenv("TERM") == "" {
  40. os.Setenv("TERM", kitty.DefaultTermName)
  41. }
  42. if term := os.Getenv("TERM"); term == kitty.DefaultTermName && shell_integration.PathToTerminfoDb(term) == "" {
  43. if terminfo_dir, err := shell_integration.EnsureTerminfoFiles(); err == nil {
  44. os.Unsetenv("TERMINFO")
  45. existing := os.Getenv("TERMINFO_DIRS")
  46. if existing != "" {
  47. existing = string(os.PathListSeparator) + existing
  48. }
  49. os.Setenv("TERMINFO_DIRS", terminfo_dir+existing)
  50. }
  51. }
  52. err = tui.RunShell(tui.ResolveShell(opts.Shell), tui.ResolveShellIntegration(opts.ShellIntegration), opts.Cwd)
  53. if changed {
  54. os.Clearenv()
  55. for _, entry := range env_before {
  56. k, v, _ := strings.Cut(entry, "=")
  57. os.Setenv(k, v)
  58. }
  59. }
  60. if err != nil {
  61. rc = 1
  62. }
  63. return
  64. }
  65. var debugprintln = tty.DebugPrintln
  66. var _ = debugprintln
  67. func EntryPoint(root *cli.Command) *cli.Command {
  68. sc := root.AddSubCommand(&cli.Command{
  69. Name: "run-shell",
  70. Usage: "[options] [optional cmd to run before running the shell ...]",
  71. ShortDescription: "Run the user's shell with shell integration enabled",
  72. HelpText: "Run the users's configured shell. If the shell supports shell integration, enable it based on the user's configured shell_integration setting.",
  73. Run: func(cmd *cli.Command, args []string) (ret int, err error) {
  74. opts := &Options{}
  75. err = cmd.GetOptionValues(opts)
  76. if err != nil {
  77. return 1, err
  78. }
  79. return main(args, opts)
  80. },
  81. })
  82. sc.Add(cli.OptionSpec{
  83. Name: "--shell-integration",
  84. Help: "Specify a value for the :opt:`shell_integration` option, overriding the one from :file:`kitty.conf`.",
  85. })
  86. sc.Add(cli.OptionSpec{
  87. Name: "--shell",
  88. Default: ".",
  89. Help: "Specify the shell command to run. The default value of :code:`.` will use the parent shell if recognized, falling back to the value of the :opt:`shell` option from :file:`kitty.conf`.",
  90. })
  91. sc.Add(cli.OptionSpec{
  92. Name: "--env",
  93. Help: "Specify an env var to set before running the shell. Of the form KEY=VAL. Can be specified multiple times. If no = is present KEY is unset.",
  94. Type: "list",
  95. })
  96. sc.Add(cli.OptionSpec{
  97. Name: "--cwd",
  98. Help: "The working directory to use when executing the shell.",
  99. })
  100. return sc
  101. }