diff --git a/apps/desktop/src/components/ProjectSettingsMenuAction.svelte b/apps/desktop/src/components/ProjectSettingsMenuAction.svelte
index ddd8e2a19c..6a72b493d3 100644
--- a/apps/desktop/src/components/ProjectSettingsMenuAction.svelte
+++ b/apps/desktop/src/components/ProjectSettingsMenuAction.svelte
@@ -6,7 +6,7 @@
import { historyPath } from '$lib/routes/routes.svelte';
import { SETTINGS } from '$lib/settings/userSettings';
import { SHORTCUT_SERVICE } from '$lib/shortcuts/shortcutService';
- import { getEditorUri, openExternalUrl, showFileInFolder } from '$lib/utils/url';
+ import { getEditorUri, openExternalUrl, showFileInFolder, openInTerminal } from '$lib/utils/url';
import { inject } from '@gitbutler/shared/context';
import { mergeUnlisten } from '@gitbutler/ui/utils/mergeUnlisten';
@@ -38,6 +38,13 @@
})
);
}),
+ shortcutService.on('open-in-terminal', async () => {
+ const project = await projectsService.fetchProject(projectId);
+ if (!project) {
+ throw new Error(`Project not found: ${projectId}`);
+ }
+ await openInTerminal($userSettings.defaultTerminal.appName, vscodePath(project.path));
+ }),
shortcutService.on('show-in-finder', async () => {
const project = await projectsService.fetchProject(projectId);
if (!project) {
diff --git a/apps/desktop/src/components/profileSettings/GeneralSettings.svelte b/apps/desktop/src/components/profileSettings/GeneralSettings.svelte
index 988f5cdb61..34e46b5b24 100644
--- a/apps/desktop/src/components/profileSettings/GeneralSettings.svelte
+++ b/apps/desktop/src/components/profileSettings/GeneralSettings.svelte
@@ -7,7 +7,11 @@
import { SETTINGS_SERVICE } from '$lib/config/appSettingsV2';
import { showError } from '$lib/notifications/toasts';
import { PROJECTS_SERVICE } from '$lib/project/projectsService';
- import { SETTINGS, type CodeEditorSettings } from '$lib/settings/userSettings';
+ import {
+ SETTINGS,
+ type CodeEditorSettings,
+ type TerminalSettings
+ } from '$lib/settings/userSettings';
import { UPDATER_SERVICE } from '$lib/updater/updater';
import { USER_SERVICE } from '$lib/user/userService';
@@ -58,6 +62,19 @@
label: option.displayName,
value: option.schemeIdentifer
}));
+ const terminalOptions: TerminalSettings[] = [
+ { appName: 'Terminal.app', displayName: 'Terminal' },
+ { appName: 'Ghostty.app', displayName: 'Ghostty' },
+ { appName: 'Warp.app', displayName: 'Warp' },
+ { appName: 'iTerm.app', displayName: 'iTerm 2' },
+ { appName: 'Alacritty.app', displayName: 'Alacritty' },
+ { appName: 'WezTerm.app', displayName: 'WezTerm' },
+ { appName: 'Hyper.app', displayName: 'Hyper' }
+ ];
+ const terminalOptionsForSelect = terminalOptions.map((option) => ({
+ label: option.displayName,
+ value: option.appName
+ }));
$effect(() => {
if ($user && !loaded) {
@@ -174,32 +191,58 @@
{/if}
-
- {#snippet title()}
- Default code editor
- {/snippet}
- {#snippet actions()}
-
- {/snippet}
-
+
+
+ {#snippet title()}
+ Default code editor
+ {/snippet}
+ {#snippet actions()}
+
+ {/snippet}
+
+
+
+ {#snippet title()}
+ Default terminal
+ {/snippet}
+ {#snippet actions()}
+
+ {/snippet}
+
+
{#snippet title()}
diff --git a/apps/desktop/src/lib/settings/userSettings.ts b/apps/desktop/src/lib/settings/userSettings.ts
index 3083f0b244..31f17e1672 100644
--- a/apps/desktop/src/lib/settings/userSettings.ts
+++ b/apps/desktop/src/lib/settings/userSettings.ts
@@ -9,6 +9,10 @@ export type CodeEditorSettings = {
schemeIdentifer: string;
displayName: string;
};
+export type TerminalSettings = {
+ appName: string;
+ displayName: string;
+};
export interface Settings {
aiSummariesEnabled?: boolean;
@@ -30,6 +34,7 @@ export interface Settings {
inlineUnifiedDiffs: boolean;
diffContrast: 'light' | 'medium' | 'strong';
defaultCodeEditor: CodeEditorSettings;
+ defaultTerminal: TerminalSettings;
}
const defaults: Settings = {
@@ -50,7 +55,8 @@ const defaults: Settings = {
diffLigatures: false,
inlineUnifiedDiffs: false,
diffContrast: 'light',
- defaultCodeEditor: { schemeIdentifer: 'vscode', displayName: 'VSCode' }
+ defaultCodeEditor: { schemeIdentifer: 'vscode', displayName: 'VSCode' },
+ defaultTerminal: { appName: 'Terminal.app', displayName: 'Terminal' }
};
export function loadUserSettings(): Writable {
diff --git a/apps/desktop/src/lib/utils/url.ts b/apps/desktop/src/lib/utils/url.ts
index 20236c46f4..93ff3dedb5 100644
--- a/apps/desktop/src/lib/utils/url.ts
+++ b/apps/desktop/src/lib/utils/url.ts
@@ -51,3 +51,7 @@ export function getEditorUri(params: EditorUriParams): string {
return `${params.schemeId}://file${pathString}${positionSuffix}${searchSuffix}`;
}
+
+export async function openInTerminal(appName: string, path: string) {
+ await invoke('open_in_terminal', { appName, path });
+}
diff --git a/crates/but-api/src/commands/open.rs b/crates/but-api/src/commands/open.rs
index fdcafebdf4..b60f9246be 100644
--- a/crates/but-api/src/commands/open.rs
+++ b/crates/but-api/src/commands/open.rs
@@ -150,3 +150,24 @@ pub fn show_in_finder(_app: &App, params: ShowInFinderParams) -> Result<(), Erro
Ok(())
}
+
+#[derive(Deserialize)]
+pub struct OpenInTerminalParams {
+ pub app_name: String,
+ pub path: String,
+}
+
+pub fn open_in_terminal(_app: &App, params: OpenInTerminalParams) -> Result<(), Error> {
+ #[cfg(target_os = "macos")]
+ {
+ use std::process::Command;
+ Command::new("open")
+ .arg("-a")
+ .arg(¶ms.app_name)
+ .arg(¶ms.path)
+ .status()
+ .with_context(|| format!("Failed to show '{}' in Finder", params.path))?;
+ }
+
+ Ok(())
+}
diff --git a/crates/gitbutler-tauri/src/main.rs b/crates/gitbutler-tauri/src/main.rs
index eddb05745d..c7db0eceb3 100644
--- a/crates/gitbutler-tauri/src/main.rs
+++ b/crates/gitbutler-tauri/src/main.rs
@@ -271,6 +271,7 @@ fn main() {
modes::edit_changes_from_initial,
open::open_url,
open::show_in_finder,
+ open::open_in_terminal,
forge::pr_templates,
forge::pr_template,
settings::get_app_settings,
diff --git a/crates/gitbutler-tauri/src/menu.rs b/crates/gitbutler-tauri/src/menu.rs
index 87b59b90ff..9aa908ee7c 100644
--- a/crates/gitbutler-tauri/src/menu.rs
+++ b/crates/gitbutler-tauri/src/menu.rs
@@ -158,7 +158,8 @@ pub fn build(
.build(handle)?,
)
.separator()
- .text("project/open-in-vscode", "Open in Editor");
+ .text("project/open-in-vscode", "Open in Editor")
+ .text("project/open-in-terminal", "Open in Terminal");
#[cfg(target_os = "macos")]
{
@@ -322,6 +323,11 @@ pub fn handle_event(
return;
}
+ if event.id() == "project/open-in-terminal" {
+ emit(webview, SHORTCUT_EVENT, "open-in-terminal");
+ return;
+ }
+
if event.id() == "project/show-in-finder" {
emit(webview, SHORTCUT_EVENT, "show-in-finder");
return;
diff --git a/crates/gitbutler-tauri/src/open.rs b/crates/gitbutler-tauri/src/open.rs
index 6f4f20b36f..691dcabeaa 100644
--- a/crates/gitbutler-tauri/src/open.rs
+++ b/crates/gitbutler-tauri/src/open.rs
@@ -15,3 +15,9 @@ pub fn open_url(app: State<'_, App>, url: String) -> Result<(), Error> {
pub fn show_in_finder(app: State<'_, App>, path: String) -> Result<(), Error> {
open::show_in_finder(&app, open::ShowInFinderParams { path })
}
+
+#[tauri::command(async)]
+#[instrument(skip(app), err(Debug))]
+pub fn open_in_terminal(app: State<'_, App>, app_name: String, path: String) -> Result<(), Error> {
+ open::open_in_terminal(&app, open::OpenInTerminalParams { app_name, path })
+}