Skip to content

Commit e5c4dcb

Browse files
committed
feat(git): 新增「在浏览器打开仓库主页」
useGitPermalink 增加 openRepoOnWeb:取 origin 远程并规范化为网页地址(SSH/HTTPS 皆可)后打开; 命令面板可触发,无远程时提示。
1 parent 1a6966c commit e5c4dcb

4 files changed

Lines changed: 40 additions & 2 deletions

File tree

src/App.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ const handleCopyRelativePath = (path: string) => {
751751
const rootDir = ref<string | null>(null)
752752
753753
// 远程仓库永久链接(复制 / 在浏览器打开)
754-
const {copyPermalink, openPermalink} = useGitPermalink(rootDir, currentFilePath, cursorInfo)
754+
const {copyPermalink, openPermalink, openRepoOnWeb} = useGitPermalink(rootDir, currentFilePath, cursorInfo)
755755
756756
// 多根工作区:额外挂载的文件夹(Git/搜索仍走主根 rootDir)
757757
const {extraRoots, addWorkspaceFolder, removeWorkspaceFolder, resetExtraRoots} = useWorkspaceRoots(rootDir)
@@ -2321,6 +2321,7 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
23212321
{id: 'revealInTree', label: t('command.revealInTree'), icon: FolderOpen, run: () => revealInTree()},
23222322
{id: 'copyPermalink', label: t('command.copyPermalink'), icon: GitBranch, run: () => copyPermalink()},
23232323
{id: 'openPermalink', label: t('command.openPermalink'), icon: GitBranch, run: () => openPermalink()},
2324+
{id: 'openRepoOnWeb', label: t('command.openRepoOnWeb'), icon: GitBranch, run: () => openRepoOnWeb()},
23242325
{id: 'sortLinesAsc', label: t('command.sortLinesAsc'), group: t('command.groupText'), icon: ArrowDownAZ, run: () => sortLines(false)},
23252326
{id: 'sortLinesDesc', label: t('command.sortLinesDesc'), group: t('command.groupText'), icon: ArrowUpAZ, run: () => sortLines(true)},
23262327
{id: 'toUpperCase', label: t('command.toUpperCase'), group: t('command.groupText'), icon: CaseUpper, run: () => transformSelectionOrLine(s => s.toUpperCase())},

src/composables/useGitPermalink.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,38 @@ export function useGitPermalink(
5454
}
5555
}
5656

57-
return {copyPermalink, openPermalink}
57+
// 把 git 远程地址规范化为网页地址(git@host:owner/repo(.git) 或 https://... → https://host/owner/repo)
58+
const remoteToWebUrl = (raw: string): string | null => {
59+
let u = raw.trim().replace(/\.git$/, '')
60+
const scp = u.match(/^git@([^:]+):(.+)$/)
61+
if (scp) {
62+
return `https://${scp[1]}/${scp[2]}`
63+
}
64+
u = u.replace(/^ssh:\/\/(git@)?/, 'https://').replace(/^git:\/\//, 'https://')
65+
if (u.startsWith('http://') || u.startsWith('https://')) {
66+
return u.replace(/^http:\/\//, 'https://')
67+
}
68+
return null
69+
}
70+
71+
const openRepoOnWeb = async () => {
72+
if (!rootDir.value) {
73+
return
74+
}
75+
try {
76+
const remotes = await invoke<{ name: string, url: string }[]>('git_remotes', {root: rootDir.value})
77+
const origin = remotes.find(r => r.name === 'origin') || remotes[0]
78+
const web = origin && remoteToWebUrl(origin.url)
79+
if (!web) {
80+
toast.info(t('app.noRemoteWebUrl'))
81+
return
82+
}
83+
await openExternalUrl(web)
84+
}
85+
catch (error) {
86+
toast.error(t('app.permalinkFailed') + ': ' + error)
87+
}
88+
}
89+
90+
return {copyPermalink, openPermalink, openRepoOnWeb}
5891
}

src/i18n/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,7 @@
712712
"groupBookmark": "Bookmarks",
713713
"copyPermalink": "Copy remote permalink (current line)",
714714
"openPermalink": "Open remote link in browser (current line)",
715+
"openRepoOnWeb": "Open repository in browser",
715716
"sortLinesAsc": "Sort lines ascending",
716717
"sortLinesDesc": "Sort lines descending",
717718
"toUpperCase": "Transform to uppercase",
@@ -1377,6 +1378,7 @@
13771378
"permalinkCopied": "Remote permalink copied",
13781379
"permalinkFailed": "Failed to build permalink",
13791380
"permalinkOutside": "Current file is not inside the workspace repository",
1381+
"noRemoteWebUrl": "No remote repository URL to open",
13801382
"aiPredictOff": "AI code prediction disabled",
13811383
"aiPredictOnNoKey": "AI code prediction enabled, but no API Key is configured. Fill it in \"Settings → AI\"",
13821384
"aiPredictOn": "AI code prediction enabled; pause typing to see the gray completion, press Tab to accept",

src/i18n/locales/zh-CN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,7 @@
712712
"groupBookmark": "书签",
713713
"copyPermalink": "复制远程永久链接(当前行)",
714714
"openPermalink": "在浏览器打开远程链接(当前行)",
715+
"openRepoOnWeb": "在浏览器打开仓库主页",
715716
"sortLinesAsc": "排序行(升序)",
716717
"sortLinesDesc": "排序行(降序)",
717718
"toUpperCase": "转为大写",
@@ -1377,6 +1378,7 @@
13771378
"permalinkCopied": "已复制远程永久链接",
13781379
"permalinkFailed": "生成永久链接失败",
13791380
"permalinkOutside": "当前文件不在工作区仓库内",
1381+
"noRemoteWebUrl": "未找到可打开的远程仓库地址",
13801382
"aiPredictOff": "AI 代码预测已关闭",
13811383
"aiPredictOnNoKey": "AI 代码预测已开启,但尚未配置 API Key,请在「设置 → AI」中填写",
13821384
"aiPredictOn": "AI 代码预测已开启,停顿打字即可看到灰色补全,Tab 接受",

0 commit comments

Comments
 (0)