repo_description: ☄🌌️  The minimal, blazing-fast, and infinitely customizable prompt for any shell!
repo_topics: bash fish fish-prompt fish-theme oh-my-zsh powershell rust shell-prompt starship zsh zsh-prompt zsh-theme
code: use crate::shadow;
  use crate::utils::{self, DEFAULT_COMMAND_TIMEOUT_MS, exec_cmd};
  use nu_ansi_term::Style;
  
  use std::fs;
  use std::path::PathBuf;
  use std::time::Duration;
  
  pub fn create() {
      println!("{}\n", shadow::VERSION.trim());
      let os_info = os_info::get();
  
      let environment = Environment {
          os_type: os_info.os_type(),
          os_version: os_info.version().clone(),
          shell_info: get_shell_info(),
          terminal_info: get_terminal_info(),
          starship_config: get_starship_config(),
      };
  
      let issue_body = get_github_issue_body(&environment);
  
      println!(
          "{}\n{issue_body}\n\n",
          Style::new().bold().paint("Generated bug report:")
      );
      println!("Forward the pre-filled report above to GitHub in your browser?");
      println!(
          "{} To avoid any sensitive data from being exposed, please review the included information before proceeding. Data forwarded to GitHub is subject to GitHub's privacy policy.",
          Style::new().bold().paint("Warning:")
      );
      println!(
          "Enter `{}` to accept, or anything else to decline, and `{}` to confirm your choice:\n",
          Style::new().bold().paint("y"),
          Style::new().bold().paint("Enter key")
      );
  
      let mut input = String::new();
      let _ = std::io::stdin().read_line(&mut input);
  
      if input.trim().to_lowercase() == "y" {
          let link = make_github_issue_link(&issue_body);
          if let Err(e) = open::that(&link) {
              println!("Failed to open issue report in your browser: {e}");
              println!(
                  "Please copy the above report and open an issue manually, or try opening the following link:\n{link}"
              );
          }
      } else {
          println!(
              "Will not open an issue in your browser! Please copy the above report and open an issue manually."
          );
      }
      println!("Thanks for using the Starship bug report tool!");
  }
  
  const UNKNOWN_SHELL: &str = "<unknown shell>";
  const UNKNOWN_TERMINAL: &str = "<unknown terminal>";
  const UNKNOWN_VERSION: &str = "<unknown version>";
  const UNKNOWN_CONFIG: &str = "<unknown config>";
  const GITHUB_CHAR_LIMIT: usize = 8100; // Magic number accepted by Github
  
  struct Environment {
      os_type: os_info::Type,
      os_version: os_info::Version,
      shell_info: ShellInfo,
      terminal_info: TerminalInfo,
      starship_config: String,
  }
  
  fn get_pkg_branch_tag() -> &'static str {
      #[allow(clippy::const_is_empty)]
      if !shadow::TAG.is_empty() {
          return shadow::TAG;
      }
      shadow::BRANCH
  }
  
  fn get_github_issue_body(environment: &Environment) -> String {
      let shell_syntax = match environment.shell_info.name.as_ref() {
          "powershell" | "pwsh" => "pwsh",
          "fish" => "fish",
          "cmd" => "lua",
          // GitHub does not seem to support elvish syntax highlighting.
          "elvish" => "bash",
          _ => "bash",
      };
  
      format!("#### Current Behavior
  <!-- A clear and concise description of the behavior. -->
  
  #### Expected Behavior
  <!-- A clear and concise description of what you expected to happen. -->
  
  #### Additional context/Screenshots
  <!-- Add any other context about the problem here. If applicable, add screenshots to help explain. -->
  
  #### Possible Solution
  <!--- Only if you have suggestions on a fix for the bug -->
  
  #### Environment
  - Starship version: {starship_version}
  - {shell_name} version: {shell_version}
  - Operating system: {os_name} {os_version}
  - Terminal emulator: {terminal_name} {terminal_version}
  - Git Commit Hash: {git_commit_hash}
  - Branch/Tag: {pkg_branch_tag}
  - Rust Version: {rust_version}
  - Rust channel: {rust_channel} {build_rust_channel}
  - Build Time: {build_time}
  #### Relevant Shell Configuration
  
  ```{shell_syntax}
  {shell_config}
  ```
  
  #### Starship Configuration
  
  ```toml
  {starship_config}
  ```",
          starship_version = shadow::PKG_VERSION,
          shell_name = environment.shell_info.name,
          shell_version = environment.shell_info.version,
          terminal_name = environment.terminal_info.name,
          terminal_version = environment.terminal_info.version,
          os_name = environment.os_type,
          os_version = environment.os_version,
          shell_config = environment.shell_info.config,
          starship_config = environment.starship_config,
          git_commit_hash =  shadow::SHORT_COMMIT,
          pkg_branch_tag =  get_pkg_branch_tag(),
          rust_version =  shadow::RUST_VERSION,
          rust_channel =  shadow::RUST_CHANNEL,
          build_rust_channel =  shadow::BUILD_RUST_CHANNEL,
          build_time =  shadow::BUILD_TIME,
          shell_syntax = shell_syntax,
      )
  }
  
  fn make_github_issue_link(body: &str) -> String {
      let escaped = urlencoding::encode(body).replace("%20", "+");
  
      format!(
          "https://github.com/starship/starship/issues/new?template={}&body={}",
          urlencoding::encode("Bug_report.md"),
          escaped
      )
      .chars()
      .take(GITHUB_CHAR_LIMIT)
      .collect()
  }
  
  #[derive(Debug)]
  struct ShellInfo {
      name: String,
      version: String,
      config: String,
  }
  
  fn get_shell_info() -> ShellInfo {
      let shell = std::env::var("STARSHIP_SHELL");
      if shell.is_err() {
          return ShellInfo {
              name: UNKNOWN_SHELL.to_string(),
              version: UNKNOWN_VERSION.to_string(),
              config: UNKNOWN_CONFIG.to_string(),
          };
      }
  
      let shell = shell.unwrap();
  
      let version = get_shell_version(&shell);
  
      let config = get_config_path(&shell)
          .and_then(|config_path| fs::read_to_string(config_path).ok())
          .map_or_else(
              || UNKNOWN_CONFIG.to_string(),
              |config| config.trim().to_string(),
          );
  
      ShellInfo {
          name: shell,
          version,
          config,
      }
  }
  
  #[derive(Debug)]
  struct TerminalInfo {
      name: String,
      version: String,
  }
  
  fn get_terminal_info() -> TerminalInfo {
      let terminal = std::env::var("TERM_PROGRAM")
          .or_else(|_| std::env::var("LC_TERMINAL"))
          .unwrap_or_else(|_| UNKNOWN_TERMINAL.to_string());
  
      let version = std::env::var("TERM_PROGRAM_VERSION")
          .or_else(|_| std::env::var("LC_TERMINAL_VERSION"))
          .unwrap_or_else(|_| UNKNOWN_VERSION.to_string());
  
      TerminalInfo {
          name: terminal,
          version,
      }
  }
  
  fn get_config_path(shell: &str) -> Option<PathBuf> {
      if shell == "nu" {
          return dirs::config_dir().map(|config_dir| config_dir.join("nushell").join("config.nu"));
      }
  
      utils::home_dir().and_then(|home_dir| {
          match shell {
              "bash" => Some(".bashrc"),
              "fish" => Some(".config/fish/config.fish"),
              "ion" => Some(".config/ion/initrc"),
              "powershell" | "pwsh" => {
                  if cfg!(windows) {
                      Some("Documents/PowerShell/Microsoft.PowerShell_profile.ps1")
                  } else {
                      Some(".config/powershell/Microsoft.PowerShell_profile.ps1")
                  }
              }
              "zsh" => Some(".zshrc"),
              "elvish" => Some(".elvish/rc.elv"),
              "tcsh" => Some(".tcshrc"),
              "xonsh" => Some(".xonshrc"),
              "cmd" => Some("AppData/Local/clink/starship.lua"),
              _ => None,
          }
          .map(|path| home_dir.join(path))
      })
  }
  
  fn get_starship_config() -> String {
      std::env::var("STARSHIP_CONFIG")
          .map(PathBuf::from)
          .ok()
          .or_else(|| {
              utils::home_dir().map(|mut home_dir| {
                  home_dir.push(".config/starship.toml");
                  home_dir
              })
          })
          .and_then(|config_path| fs::read_to_string(config_path).ok())
          .unwrap_or_else(|| UNKNOWN_CONFIG.to_string())
  }
  
  fn get_shell_version(shell: &str) -> String {
      let time_limit = Duration::from_millis(DEFAULT_COMMAND_TIMEOUT_MS);
      match shell {
          "powershell" => exec_cmd(
              shell,
              &["(Get-Host | Select Version | Format-Table -HideTableHeaders | Out-String).trim()"],
              time_limit,
          ),
          _ => exec_cmd(shell, &["--version"], time_limit),
      }
      .map_or_else(
          || UNKNOWN_VERSION.to_string(),
          |output| output.stdout.trim().to_string(),
      )
  }
  
  #[cfg(test)]
  mod tests {
      use super::*;
      use std::env;
  
      #[test]
      fn test_make_github_link() {
          let environment = Environment {
              os_type: os_info::Type::Linux,
              os_version: os_info::Version::Semantic(1, 2, 3),
              shell_info: ShellInfo {
                  name: "test_shell".to_string(),
                  version: "2.3.4".to_string(),
                  config: "No config".to_string(),
              },
              terminal_info: TerminalInfo {
                  name: "test_terminal".to_string(),
                  version: "5.6.7".to_string(),
              },
              starship_config: "No Starship config".to_string(),
          };
  
          let body = get_github_issue_body(&environment);
          let link = make_github_issue_link(&body);
  
          assert!(link.contains(clap::crate_version!()));
          assert!(link.contains("Linux"));
          assert!(link.contains("1.2.3"));
          assert!(link.contains("test_shell"));
          assert!(link.contains("2.3.4"));
          assert!(link.contains("No+config"));
          assert!(link.contains("No+Starship+config"));
      }
  
      #[test]
      #[cfg(not(windows))]
      fn test_get_config_path() {
          let config_path = get_config_path("bash");
          assert_eq!(
              utils::home_dir().unwrap().join(".bashrc"),
              config_path.unwrap()
          );
      }
  }
  