Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion apps/desktop/src/components/ProjectSettingsMenuAction.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down
97 changes: 70 additions & 27 deletions apps/desktop/src/components/profileSettings/GeneralSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -174,32 +191,58 @@
{/if}
<Spacer />

<SectionCard orientation="row" centerAlign>
{#snippet title()}
Default code editor
{/snippet}
{#snippet actions()}
<Select
value={$userSettings.defaultCodeEditor.schemeIdentifer}
options={editorOptionsForSelect}
onselect={(value) => {
const selected = editorOptions.find((option) => option.schemeIdentifer === value);
if (selected) {
userSettings.update((s) => ({ ...s, defaultCodeEditor: selected }));
}
}}
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem
selected={item.value === $userSettings.defaultCodeEditor.schemeIdentifer}
{highlighted}
>
{item.label}
</SelectItem>
{/snippet}
</Select>
{/snippet}
</SectionCard>
<div class="stack-v">
<SectionCard orientation="row" centerAlign roundedBottom={false}>
{#snippet title()}
Default code editor
{/snippet}
{#snippet actions()}
<Select
value={$userSettings.defaultCodeEditor.schemeIdentifer}
options={editorOptionsForSelect}
onselect={(value) => {
const selected = editorOptions.find((option) => option.schemeIdentifer === value);
if (selected) {
userSettings.update((s) => ({ ...s, defaultCodeEditor: selected }));
}
}}
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem
selected={item.value === $userSettings.defaultCodeEditor.schemeIdentifer}
{highlighted}
>
{item.label}
</SelectItem>
{/snippet}
</Select>
{/snippet}
</SectionCard>

<SectionCard orientation="row" centerAlign roundedTop={false}>
{#snippet title()}
Default terminal
{/snippet}
{#snippet actions()}
<Select
value={$userSettings.defaultTerminal.appName}
options={terminalOptionsForSelect}
onselect={(value) => {
const selected = terminalOptions.find((option) => option.appName === value);
if (selected) {
userSettings.update((s) => ({ ...s, defaultTerminal: selected }));
}
}}
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem selected={item.value === $userSettings.defaultTerminal.appName} {highlighted}>
{item.label}
</SelectItem>
{/snippet}
</Select>
{/snippet}
</SectionCard>
</div>

<SectionCard labelFor="disable-auto-checks" orientation="row">
{#snippet title()}
Expand Down
8 changes: 7 additions & 1 deletion apps/desktop/src/lib/settings/userSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export type CodeEditorSettings = {
schemeIdentifer: string;
displayName: string;
};
export type TerminalSettings = {
appName: string;
displayName: string;
};

export interface Settings {
aiSummariesEnabled?: boolean;
Expand All @@ -30,6 +34,7 @@ export interface Settings {
inlineUnifiedDiffs: boolean;
diffContrast: 'light' | 'medium' | 'strong';
defaultCodeEditor: CodeEditorSettings;
defaultTerminal: TerminalSettings;
}

const defaults: Settings = {
Expand All @@ -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<Settings> {
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/lib/utils/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>('open_in_terminal', { appName, path });
}
21 changes: 21 additions & 0 deletions crates/but-api/src/commands/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Copy link
Collaborator

Choose a reason for hiding this comment

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

If open::with_command(…) would be used instead, I think this would be portable provided viable app-names. The .app suffix on MacOS also wouldn't be needed then, I think

Generally I think it would be OK to start supporting MacOS and add Windows/Linux later. This probably would be gated where the available app-names are configured.

.arg("-a")
.arg(&params.app_name)
.arg(&params.path)
.status()
.with_context(|| format!("Failed to show '{}' in Finder", params.path))?;
}

Ok(())
}
1 change: 1 addition & 0 deletions crates/gitbutler-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion crates/gitbutler-tauri/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ pub fn build<R: Runtime>(
.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")]
{
Expand Down Expand Up @@ -322,6 +323,11 @@ pub fn handle_event<R: Runtime>(
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;
Expand Down
6 changes: 6 additions & 0 deletions crates/gitbutler-tauri/src/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
}
Loading