mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-30 11:32:04 +03:00
Merge pull request #5349 from gitbutlerapp/e-branch-4
fix: Open files on editor in Windows
This commit is contained in:
commit
a216ccedaf
@ -5,7 +5,7 @@
|
|||||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
||||||
import * as events from '$lib/utils/events';
|
import * as events from '$lib/utils/events';
|
||||||
import { unsubscribe } from '$lib/utils/unsubscribe';
|
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 { getContextStoreBySymbol } from '@gitbutler/shared/context';
|
||||||
import { getContext } from '@gitbutler/shared/context';
|
import { getContext } from '@gitbutler/shared/context';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@ -23,7 +23,11 @@
|
|||||||
const unsubscribeopenInEditor = listen<string>(
|
const unsubscribeopenInEditor = listen<string>(
|
||||||
'menu://project/open-in-vscode/clicked',
|
'menu://project/open-in-vscode/clicked',
|
||||||
async () => {
|
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);
|
openExternalUrl(path);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import { BaseBranch } from '$lib/baseBranch/baseBranch';
|
import { BaseBranch } from '$lib/baseBranch/baseBranch';
|
||||||
import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
||||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
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 { BranchController } from '$lib/vbranches/branchController';
|
||||||
import { getContext, getContextStore, getContextStoreBySymbol } from '@gitbutler/shared/context';
|
import { getContext, getContextStore, getContextStoreBySymbol } from '@gitbutler/shared/context';
|
||||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||||
@ -18,9 +18,12 @@
|
|||||||
const project = getContext(Project);
|
const project = getContext(Project);
|
||||||
|
|
||||||
async function openInEditor() {
|
async function openInEditor() {
|
||||||
openExternalUrl(
|
const path = getEditorUri({
|
||||||
`${$userSettings.defaultCodeEditor.schemeIdentifer}://file${project.vscodePath}/?windowId=_blank`
|
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
|
||||||
);
|
path: [project.vscodePath],
|
||||||
|
searchParams: { windowId: '_blank' }
|
||||||
|
});
|
||||||
|
openExternalUrl(path);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
|
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
|
||||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
||||||
import { UncommitedFilesWatcher } from '$lib/uncommitedFiles/watcher';
|
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 { Commit, type RemoteFile } from '$lib/vbranches/types';
|
||||||
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
|
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
|
||||||
import { getContext } from '@gitbutler/shared/context';
|
import { getContext } from '@gitbutler/shared/context';
|
||||||
@ -16,7 +16,6 @@
|
|||||||
import InfoButton from '@gitbutler/ui/InfoButton.svelte';
|
import InfoButton from '@gitbutler/ui/InfoButton.svelte';
|
||||||
import Avatar from '@gitbutler/ui/avatar/Avatar.svelte';
|
import Avatar from '@gitbutler/ui/avatar/Avatar.svelte';
|
||||||
import FileListItem from '@gitbutler/ui/file/FileListItem.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 { FileStatus } from '@gitbutler/ui/file/types';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
@ -164,8 +163,11 @@
|
|||||||
|
|
||||||
async function openAllConflictedFiles() {
|
async function openAllConflictedFiles() {
|
||||||
for (const file of conflictedFiles) {
|
for (const file of conflictedFiles) {
|
||||||
const absPath = await join(project.vscodePath, file.path);
|
const path = getEditorUri({
|
||||||
openExternalUrl(`${$userSettings.defaultCodeEditor.schemeIdentifer}://file${absPath}`);
|
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
|
||||||
|
path: [project.vscodePath, file.path]
|
||||||
|
});
|
||||||
|
openExternalUrl(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
||||||
import { computeFileStatus } from '$lib/utils/fileStatus';
|
import { computeFileStatus } from '$lib/utils/fileStatus';
|
||||||
import * as toasts from '$lib/utils/toasts';
|
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 { BranchController } from '$lib/vbranches/branchController';
|
||||||
import { isAnyFile, LocalFile } from '$lib/vbranches/types';
|
import { isAnyFile, LocalFile } from '$lib/vbranches/types';
|
||||||
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
|
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
|
||||||
@ -108,10 +108,11 @@
|
|||||||
try {
|
try {
|
||||||
if (!project) return;
|
if (!project) return;
|
||||||
for (let file of item.files) {
|
for (let file of item.files) {
|
||||||
const absPath = await join(project.vscodePath, file.path);
|
const path = getEditorUri({
|
||||||
openExternalUrl(
|
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
|
||||||
`${$userSettings.defaultCodeEditor.schemeIdentifer}://file${absPath}`
|
path: [project.vscodePath, file.path]
|
||||||
);
|
});
|
||||||
|
openExternalUrl(path);
|
||||||
}
|
}
|
||||||
contextMenu.close();
|
contextMenu.close();
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import ContextMenuItem from '$lib/components/contextmenu/ContextMenuItem.svelte';
|
import ContextMenuItem from '$lib/components/contextmenu/ContextMenuItem.svelte';
|
||||||
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
||||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
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 { BranchController } from '$lib/vbranches/branchController';
|
||||||
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
|
import { getContextStoreBySymbol } from '@gitbutler/shared/context';
|
||||||
import { getContext } from '@gitbutler/shared/context';
|
import { getContext } from '@gitbutler/shared/context';
|
||||||
@ -49,9 +49,12 @@
|
|||||||
label="Open in {$userSettings.defaultCodeEditor.displayName}"
|
label="Open in {$userSettings.defaultCodeEditor.displayName}"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if (projectPath) {
|
if (projectPath) {
|
||||||
openExternalUrl(
|
const path = getEditorUri({
|
||||||
`${$userSettings.defaultCodeEditor.schemeIdentifer}://file${projectPath}/${filePath}:${item.lineNumber}`
|
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
|
||||||
);
|
path: [projectPath, filePath],
|
||||||
|
line: item.lineNumber
|
||||||
|
});
|
||||||
|
openExternalUrl(path);
|
||||||
}
|
}
|
||||||
contextMenu?.close();
|
contextMenu?.close();
|
||||||
}}
|
}}
|
||||||
|
@ -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';
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
describe.concurrent('cleanUrl', () => {
|
describe.concurrent('cleanUrl', () => {
|
||||||
@ -42,3 +42,71 @@ describe.concurrent('cleanUrl', () => {
|
|||||||
expect(remoteUrlIsHttp(remoteUrl)).toBe(false);
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -3,6 +3,8 @@ import { showToast } from '$lib/notifications/toasts';
|
|||||||
import GitUrlParse from 'git-url-parse';
|
import GitUrlParse from 'git-url-parse';
|
||||||
import { posthog } from 'posthog-js';
|
import { posthog } from 'posthog-js';
|
||||||
|
|
||||||
|
const SEPARATOR = '/';
|
||||||
|
|
||||||
export async function openExternalUrl(href: string) {
|
export async function openExternalUrl(href: string) {
|
||||||
try {
|
try {
|
||||||
await invoke<void>('open_url', { url: href });
|
await invoke<void>('open_url', { url: href });
|
||||||
@ -39,3 +41,30 @@ export function remoteUrlIsHttp(url: string): boolean {
|
|||||||
|
|
||||||
return httpProtocols.includes(gitRemote.protocol);
|
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}`;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user