Skip to content

Commit

Permalink
fix: Open files on editor in Windows
Browse files Browse the repository at this point in the history
In order to open the files using the editor url, we need to use forward slashes for the path, even on Windows.
  • Loading branch information
estib-vega committed Oct 29, 2024
1 parent fedd318 commit ad345a7
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import * as events from '$lib/utils/events';
import { unsubscribe } from '$lib/utils/unsubscribe';
import { openExternalUrl } from '$lib/utils/url';
import { getEditorUri, openExternalUrl } from '$lib/utils/url';
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
import { getContext } from '@gitbutler/shared/context';
import { onMount } from 'svelte';
Expand All @@ -23,7 +23,11 @@
const unsubscribeopenInEditor = listen<string>(
'menu://project/open-in-vscode/clicked',
async () => {
const path = `${$userSettings.defaultCodeEditor.schemeIdentifer}://file${project.vscodePath}?windowId=_blank`;
const path = getEditorUri({
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
path: [project.vscodePath],
searchParams: { windowId: '_blank' }
});
openExternalUrl(path);
}
);
Expand Down
11 changes: 7 additions & 4 deletions apps/desktop/src/lib/components/BoardEmptyState.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { BaseBranch } from '$lib/baseBranch/baseBranch';
import { getGitHost } from '$lib/gitHost/interface/gitHost';
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { openExternalUrl } from '$lib/utils/url';
import { getEditorUri, openExternalUrl } from '$lib/utils/url';
import { BranchController } from '$lib/vbranches/branchController';
import { getContext, getContextStore, getContextStoreBySymbol } from '@gitbutler/shared/context';
import Icon from '@gitbutler/ui/Icon.svelte';
Expand All @@ -18,9 +18,12 @@
const project = getContext(Project);
async function openInEditor() {
openExternalUrl(
`${$userSettings.defaultCodeEditor.schemeIdentifer}://file${project.vscodePath}/?windowId=_blank`
);
const path = getEditorUri({
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
path: [project.vscodePath],
searchParams: { windowId: '_blank' }
});
openExternalUrl(path);
}
</script>

Expand Down
10 changes: 6 additions & 4 deletions apps/desktop/src/lib/components/EditMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { UncommitedFilesWatcher } from '$lib/uncommitedFiles/watcher';
import { openExternalUrl } from '$lib/utils/url';
import { getEditorUri, openExternalUrl } from '$lib/utils/url';
import { Commit, type RemoteFile } from '$lib/vbranches/types';
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
import { getContext } from '@gitbutler/shared/context';
Expand All @@ -16,7 +16,6 @@
import InfoButton from '@gitbutler/ui/InfoButton.svelte';
import Avatar from '@gitbutler/ui/avatar/Avatar.svelte';
import FileListItem from '@gitbutler/ui/file/FileListItem.svelte';
import { join } from '@tauri-apps/api/path';
import type { FileStatus } from '@gitbutler/ui/file/types';
import type { Writable } from 'svelte/store';
Expand Down Expand Up @@ -164,8 +163,11 @@
async function openAllConflictedFiles() {
for (const file of conflictedFiles) {
const absPath = await join(project.vscodePath, file.path);
openExternalUrl(`${$userSettings.defaultCodeEditor.schemeIdentifer}://file${absPath}`);
const path = getEditorUri({
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
path: [project.vscodePath, file.path]
});
openExternalUrl(path);
}
}
</script>
Expand Down
11 changes: 6 additions & 5 deletions apps/desktop/src/lib/file/FileContextMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { computeFileStatus } from '$lib/utils/fileStatus';
import * as toasts from '$lib/utils/toasts';
import { openExternalUrl } from '$lib/utils/url';
import { getEditorUri, openExternalUrl } from '$lib/utils/url';
import { BranchController } from '$lib/vbranches/branchController';
import { isAnyFile, LocalFile } from '$lib/vbranches/types';
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
Expand Down Expand Up @@ -108,10 +108,11 @@
try {
if (!project) return;
for (let file of item.files) {
const absPath = await join(project.vscodePath, file.path);
openExternalUrl(
`${$userSettings.defaultCodeEditor.schemeIdentifer}://file${absPath}`
);
const path = getEditorUri({
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
path: [project.vscodePath, file.path]
});
openExternalUrl(path);
}
contextMenu.close();
} catch {
Expand Down
11 changes: 7 additions & 4 deletions apps/desktop/src/lib/hunk/HunkContextMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import ContextMenuItem from '$lib/components/contextmenu/ContextMenuItem.svelte';
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { openExternalUrl } from '$lib/utils/url';
import { getEditorUri, openExternalUrl } from '$lib/utils/url';
import { BranchController } from '$lib/vbranches/branchController';
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
import { getContext } from '@gitbutler/shared/context';
Expand Down Expand Up @@ -49,9 +49,12 @@
label="Open in {$userSettings.defaultCodeEditor.displayName}"
onclick={() => {
if (projectPath) {
openExternalUrl(
`${$userSettings.defaultCodeEditor.schemeIdentifer}://file${projectPath}/${filePath}:${item.lineNumber}`
);
const path = getEditorUri({
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
path: [projectPath, filePath],
line: item.lineNumber
});
openExternalUrl(path);
}
contextMenu?.close();
}}
Expand Down
70 changes: 69 additions & 1 deletion apps/desktop/src/lib/utils/url.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { remoteUrlIsHttp, convertRemoteToWebUrl } from '$lib/utils/url';
import { remoteUrlIsHttp, convertRemoteToWebUrl, getEditorUri } from '$lib/utils/url';
import { describe, expect, test } from 'vitest';

describe.concurrent('cleanUrl', () => {
Expand Down Expand Up @@ -42,3 +42,71 @@ describe.concurrent('cleanUrl', () => {
expect(remoteUrlIsHttp(remoteUrl)).toBe(false);
});
});

describe.concurrent('getEditorUri', () => {
test('it should handle editor path with no search params', () => {
expect(getEditorUri({ schemeId: 'vscode', path: ['/path', 'to', 'file'] })).toEqual(
'vscode://file/path/to/file'
);
});

test('it should handle editor path with search params', () => {
expect(
getEditorUri({
schemeId: 'vscode',
path: ['/path', 'to', 'file'],
searchParams: { something: 'cool' }
})
).toEqual('vscode://file/path/to/file?something=cool');
});

test('it should handle editor path with search params with special characters', () => {
expect(
getEditorUri({
schemeId: 'vscode',
path: ['/path', 'to', 'file'],
searchParams: {
search: 'hello world',
what: 'bye-&*%*\\ded-yeah'
}
})
).toEqual('vscode://file/path/to/file?search=hello+world&what=bye-%26*%25*%5Cded-yeah');
});

test('it should handle editor path with search params with line number', () => {
expect(
getEditorUri({
schemeId: 'vscode',
path: ['/path', 'to', 'file'],
line: 10
})
).toEqual('vscode://file/path/to/file:10');
});

test('it should handle editor path with search params with line and column number', () => {
expect(
getEditorUri({
schemeId: 'vscode',
path: ['/path', 'to', 'file'],
searchParams: {
another: 'thing'
},
line: 10,
column: 20
})
).toEqual('vscode://file/path/to/file:10:20?another=thing');
});

test('it should ignore the column if there is no line number', () => {
expect(
getEditorUri({
schemeId: 'vscode',
path: ['/path', 'to', 'file'],
searchParams: {
another: 'thing'
},
column: 20
})
).toEqual('vscode://file/path/to/file?another=thing');
});
});
29 changes: 29 additions & 0 deletions apps/desktop/src/lib/utils/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { showToast } from '$lib/notifications/toasts';
import GitUrlParse from 'git-url-parse';
import { posthog } from 'posthog-js';

const SEPARATOR = '/';

export async function openExternalUrl(href: string) {
try {
await invoke<void>('open_url', { url: href });
Expand Down Expand Up @@ -39,3 +41,30 @@ export function remoteUrlIsHttp(url: string): boolean {

return httpProtocols.includes(gitRemote.protocol);
}

export interface EditorUriParams {
schemeId: string;
path: string[];
searchParams?: Record<string, string>;
line?: number;
column?: number;
}

export function getEditorUri(params: EditorUriParams): string {
const searchParamsString = new URLSearchParams(params.searchParams).toString();
// Separator is always a forward slash for editor paths, even on Windows
const pathString = params.path.join(SEPARATOR);

let positionSuffix = '';
if (params.line !== undefined) {
positionSuffix += `:${params.line}`;
// Column is only valid if line is present
if (params.column !== undefined) {
positionSuffix += `:${params.column}`;
}
}

const searchSuffix = searchParamsString ? `?${searchParamsString}` : '';

return `${params.schemeId}://file${pathString}${positionSuffix}${searchSuffix}`;
}

0 comments on commit ad345a7

Please sign in to comment.