Skip to content

Conversation

@sebastianhuus
Copy link

@sebastianhuus sebastianhuus commented Jan 18, 2026

🧢 Changes

  • Apply path escaping to Windows paths for opening in terminal
  • Show error if opening terminal fails
  • Replace default terminal with sentinel value to trigger OS-specific selection
  • Register open in terminal for but server for browser
  • Document open_in_terminal function
  • Check if Open in Terminal command was successful
  • Expose open in terminal action in menu
  • Add terminal selection to general settings
  • Port open in terminal logic

Tested on Windows 11 and MacOS 15 so far. Can test Ubuntu 24 LTS in the coming week.

☕️ Reasoning

Validation checklist

MacOS (Tested on MacOS 15.7.3)

  • Terminal (default)
  • iTerm2
  • Ghostty
  • Warp
  • Alacritty
  • WezTerm
  • Hyper

Windows

  •  Terminal (default)
  • Powershell
  • cmd

Linux (Tested on Ubuntu 24 LTS)

  • Gnome
  • Konsole
  • Xfce4-terminal
  • Alacritty
  • WezTerm

🧢 Todos

  • I need to squash some of the commits but GB is acting up. Will do that once changes are finalized.

✨ Future Enhancements

  • Add “Select Custom App” option in Terminal selector inside settings. This would let us drop the hard-coded list and allow user to select their desired terminal. However, this should also require a change in the “Default Code Editor” selector, which is also hard-coded currently.
image

PR-revival in action on latest nightly:

Windows

image image

Tested with Terminal app, Powershell app and cmd app. Note that a plain Windows install will launch all three options in the Terminal app unless configured to do otherwise.

Macos

image image

Tested with Terminal and Warp.

Settings menu
image

Linux (Ubuntu)

Screenshot from 2026-01-20 15-25-08 Screenshot from 2026-01-20 15-30-51

Copilot AI review requested due to automatic review settings January 18, 2026 22:19
@vercel
Copy link

vercel bot commented Jan 18, 2026

@sebastianhuus is attempting to deploy a commit to the GitButler Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds functionality to open a project directory in a terminal application directly from GitButler. It ports and updates stale work from PR #9719, implementing both backend terminal launch logic and frontend UI settings for terminal selection.

Changes:

  • Added Rust backend function to launch various terminal applications on macOS, Windows, and Linux
  • Integrated "Open in Terminal" menu item in the Project menu
  • Added terminal selection UI in General Settings with platform-specific options

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
crates/but-api/src/legacy/open.rs Implements open_in_terminal function with platform-specific terminal launch logic
crates/gitbutler-tauri/src/main.rs Registers the open_in_terminal command in Tauri application
crates/gitbutler-tauri/src/menu.rs Adds "Open in Terminal" menu item and event handler
apps/desktop/src/lib/settings/userSettings.ts Defines TerminalSettings type and adds defaultTerminal to Settings interface
apps/desktop/src/components/profileSettings/GeneralSettings.svelte Implements terminal selection UI with platform filtering and validation
apps/desktop/src/components/ProjectSettingsMenuAction.svelte Adds shortcut handler to invoke open_in_terminal backend command

legacy::modes::tauri_edit_initial_index_state::edit_initial_index_state,
legacy::modes::tauri_edit_changes_from_initial::edit_changes_from_initial,
legacy::open::tauri_open_url::open_url,
legacy::open::tauri_open_in_terminal::open_in_terminal,
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The open_in_terminal command is registered in the Tauri application but appears to be missing from the but-server command handler in crates/but-server/src/lib.rs. If the web application needs to support opening projects in the terminal, this command should also be registered in the web server's command handler, similar to how show_in_finder and open_url are registered.

Copilot uses AI. Check for mistakes.
Command::new("powershell")
.arg("-NoExit")
.arg("-Command")
.arg(format!("cd '{}'", path))
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path is embedded directly in the PowerShell command string using single quotes. If the path contains a single quote character, it could break out of the string and potentially execute arbitrary commands. Consider escaping single quotes in the path by replacing ' with '' (two single quotes), or use a safer method to pass the path to PowerShell.

Copilot uses AI. Check for mistakes.
@sebastianhuus sebastianhuus force-pushed the open-in-terminal-support branch 2 times, most recently from 0fdff4f to dae9156 Compare January 19, 2026 08:22
@sebastianhuus
Copy link
Author

Will address the feedback from Copilot, totally forgot to consider the web client. Will have to test that too

Copilot AI review requested due to automatic review settings January 19, 2026 08:51
@sebastianhuus sebastianhuus force-pushed the open-in-terminal-support branch from dae9156 to 5b9acf5 Compare January 19, 2026 08:51
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment on lines +176 to +228
// Escape single quotes by doubling them for PowerShell
let escaped_path = path.replace('\'', "''");
cmd.arg("-NoExit")
.arg("-Command")
.arg(format!("cd '{}'", escaped_path));
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path escaping for PowerShell only handles single quotes but doesn't address other special characters that could cause command injection vulnerabilities. PowerShell has many special characters like backticks, dollar signs, and semicolons that could be exploited if a malicious path is constructed. Consider using proper escaping or passing the path as a separate argument instead of embedding it in a format string. A safer approach would be to use cmd.arg("-NoExit").arg("-Command").arg("Set-Location").arg("-LiteralPath").arg(&path) to avoid the need for manual escaping entirely.

Suggested change
// Escape single quotes by doubling them for PowerShell
let escaped_path = path.replace('\'', "''");
cmd.arg("-NoExit")
.arg("-Command")
.arg(format!("cd '{}'", escaped_path));
cmd.arg("-NoExit")
.arg("-Command")
.arg("Set-Location")
.arg("-LiteralPath")
.arg(&path);

Copilot uses AI. Check for mistakes.
Comment on lines +185 to +235
// Escape double quotes by doubling them for CMD
let escaped_path = path.replace('"', "\"\"");
cmd.arg("/K").arg(format!("cd /d \"{}\"", escaped_path));
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CMD path escaping only handles double quotes but doesn't address other special characters that could cause issues. CMD has special characters like ampersands, pipes, carets, and percent signs that may not be properly escaped. While the doubling of quotes is a good start, consider using a more robust escaping mechanism or passing the path separately to avoid injection risks. For CMD, paths with special characters might need additional escaping or you could explore using pushd command which handles UNC paths better.

Suggested change
// Escape double quotes by doubling them for CMD
let escaped_path = path.replace('"', "\"\"");
cmd.arg("/K").arg(format!("cd /d \"{}\"", escaped_path));
cmd.arg("/K");
cmd.current_dir(&path);

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +95
const allTerminalOptions: TerminalSettings[] = [
// macOS
{ identifier: 'terminal', displayName: 'Terminal', platform: 'macos' },
{ identifier: 'iterm2', displayName: 'iTerm2', platform: 'macos' },
{ identifier: 'ghostty', displayName: 'Ghostty', platform: 'macos' },
{ identifier: 'warp', displayName: 'Warp', platform: 'macos' },
{ identifier: 'alacritty-mac', displayName: 'Alacritty', platform: 'macos' },
{ identifier: 'wezterm-mac', displayName: 'WezTerm', platform: 'macos' },
{ identifier: 'hyper', displayName: 'Hyper', platform: 'macos' },
// Windows
{ identifier: 'wt', displayName: 'Windows Terminal', platform: 'windows' },
{ identifier: 'powershell', displayName: 'PowerShell', platform: 'windows' },
{ identifier: 'cmd', displayName: 'Command Prompt', platform: 'windows' },
// Linux
{ identifier: 'gnome-terminal', displayName: 'GNOME Terminal', platform: 'linux' },
{ identifier: 'konsole', displayName: 'Konsole', platform: 'linux' },
{ identifier: 'xfce4-terminal', displayName: 'XFCE Terminal', platform: 'linux' },
{ identifier: 'alacritty-linux', displayName: 'Alacritty', platform: 'linux' },
{ identifier: 'wezterm-linux', displayName: 'WezTerm', platform: 'linux' }
];
const terminalOptions = allTerminalOptions.filter(
(t) => t.platform === 'all' || t.platform === platformName
);
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terminal options list is hardcoded in the frontend component and duplicated with the backend implementation. This creates a maintenance burden where changes to supported terminals must be made in two places (frontend GeneralSettings.svelte and backend open.rs). Consider exposing a backend API endpoint that returns the list of available terminals for the current platform, or moving this list to a shared configuration file that both frontend and backend can reference to maintain a single source of truth.

Suggested change
const allTerminalOptions: TerminalSettings[] = [
// macOS
{ identifier: 'terminal', displayName: 'Terminal', platform: 'macos' },
{ identifier: 'iterm2', displayName: 'iTerm2', platform: 'macos' },
{ identifier: 'ghostty', displayName: 'Ghostty', platform: 'macos' },
{ identifier: 'warp', displayName: 'Warp', platform: 'macos' },
{ identifier: 'alacritty-mac', displayName: 'Alacritty', platform: 'macos' },
{ identifier: 'wezterm-mac', displayName: 'WezTerm', platform: 'macos' },
{ identifier: 'hyper', displayName: 'Hyper', platform: 'macos' },
// Windows
{ identifier: 'wt', displayName: 'Windows Terminal', platform: 'windows' },
{ identifier: 'powershell', displayName: 'PowerShell', platform: 'windows' },
{ identifier: 'cmd', displayName: 'Command Prompt', platform: 'windows' },
// Linux
{ identifier: 'gnome-terminal', displayName: 'GNOME Terminal', platform: 'linux' },
{ identifier: 'konsole', displayName: 'Konsole', platform: 'linux' },
{ identifier: 'xfce4-terminal', displayName: 'XFCE Terminal', platform: 'linux' },
{ identifier: 'alacritty-linux', displayName: 'Alacritty', platform: 'linux' },
{ identifier: 'wezterm-linux', displayName: 'WezTerm', platform: 'linux' }
];
const terminalOptions = allTerminalOptions.filter(
(t) => t.platform === 'all' || t.platform === platformName
);
const terminalOptionsByPlatform: Record<string, TerminalSettings[]> = {
macos: [
{ identifier: 'terminal', displayName: 'Terminal', platform: 'macos' },
{ identifier: 'iterm2', displayName: 'iTerm2', platform: 'macos' },
{ identifier: 'ghostty', displayName: 'Ghostty', platform: 'macos' },
{ identifier: 'warp', displayName: 'Warp', platform: 'macos' },
{ identifier: 'alacritty-mac', displayName: 'Alacritty', platform: 'macos' },
{ identifier: 'wezterm-mac', displayName: 'WezTerm', platform: 'macos' },
{ identifier: 'hyper', displayName: 'Hyper', platform: 'macos' }
],
windows: [
{ identifier: 'wt', displayName: 'Windows Terminal', platform: 'windows' },
{ identifier: 'powershell', displayName: 'PowerShell', platform: 'windows' },
{ identifier: 'cmd', displayName: 'Command Prompt', platform: 'windows' }
],
linux: [
{ identifier: 'gnome-terminal', displayName: 'GNOME Terminal', platform: 'linux' },
{ identifier: 'konsole', displayName: 'Konsole', platform: 'linux' },
{ identifier: 'xfce4-terminal', displayName: 'XFCE Terminal', platform: 'linux' },
{ identifier: 'alacritty-linux', displayName: 'Alacritty', platform: 'linux' },
{ identifier: 'wezterm-linux', displayName: 'WezTerm', platform: 'linux' }
]
};
const terminalOptions =
terminalOptionsByPlatform[platformName] ?? ([] as TerminalSettings[]);

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea. this will also be helpful to make the addition to allow for the 'Open in Custom App' UX easier to implement later

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the same duplication exists for the Default Editor settings

// Backend (open.rs:11-22) - validates allowed URL schemes:                                                                                      
  if ![                                                                                                                                                    
      "http", "https", "mailto",                                                                                                                           
      "vscode", "vscodium", "vscode-insiders",                                                                                                             
      "zed", "windsurf", "cursor", "trae",                                                                                                                 
  ].contains(&target_url.scheme())                                                                                                                         
                                                                                                                                                           
  Frontend (GeneralSettings.svelte:58-66) - dropdown options:                                                                                              
  const editorOptions: CodeEditorSettings[] = [                                                                                                            
      { schemeIdentifer: 'vscodium', displayName: 'VSCodium' },                                                                                            
      { schemeIdentifer: 'vscode', displayName: 'VSCode' },                                                                                                
      // ...                                                                                                                                               
  ];  

I think maybe its better to address both Default Terminal and Default Editor in a separate PR

@sebastianhuus sebastianhuus force-pushed the open-in-terminal-support branch 3 times, most recently from 1dafbee to fa7df5e Compare January 19, 2026 09:29
@Byron
Copy link
Collaborator

Byron commented Jan 19, 2026

Thanks a lot for reviving this topic! I think this a very useful feature.

I only looked at the Rust code quickly and thought it was alright, but a more thorough review is to follow once you think it's ready. Meanwhile, I set it back to PR until all Copilot comments are resolved (so I see you saw them).

Regarding the function, it think I ran into a bug. When using "Open in Terminal" without ever having opened the settings, I saw this:

Screenshot 2026-01-19 at 11 34 13

Once I saw the settings, it started working though at least for the terminal.

Screenshot 2026-01-19 at 11 34 33

When trying Alacritty, which I use, I saw this:

Screenshot 2026-01-19 at 11 40 57

Could it be that you work on Windows and all the other options are mostly vibed and thus not tested? If so, maybe you can create a checklist with all options to track if they are validated, and we can together make the most common options work on all platforms we have easy-access to.

Thanks again for your help with this.

@Byron Byron marked this pull request as draft January 19, 2026 10:43
@sebastianhuus sebastianhuus force-pushed the open-in-terminal-support branch from fa7df5e to 0a09eb9 Compare January 19, 2026 15:53
@sebastianhuus
Copy link
Author

@Byron Thanks for reviewing! Yes I only did first class support for Terminals I had installed and vibed in the others assuming they followed similar contracts. I'll look into it and fix it.

I have access to MacOS, Windows and Linux so no problem there. I use Mac so just a bit of a hassle to run on other OSes and test myself (now I know why there was a note in the dev guidelines for Windows :) )

I'll be back once its ready-ready and I've tested. Will add checklist to the PR

@sebastianhuus sebastianhuus force-pushed the open-in-terminal-support branch 6 times, most recently from 83fa58a to 0fd3535 Compare January 20, 2026 14:48
Implements open_in_terminal based on gitbutlerapp#9719 

As a sketch for cross platform implementation, added support for various
terminals across different OSes.
Allows you to set a default terminal in settings

Also added front end logic to ensure default terminal is valid for
current os on first laod
…election

Fixes issue where “Open in Terminal” will fail if you open without ever
visiting the settings panel
Apply path escaping to Windows paths for opening in terminal
Copilot suggested that we follow convention and order the imports as
used elsewhere. However the linter wants it the other way :)
@sebastianhuus sebastianhuus force-pushed the open-in-terminal-support branch from 0fd3535 to e73dec7 Compare January 20, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

@gitbutler/desktop rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants