feat: add copy remote URL to line number context menu (#253)
* feat: add copy remote URL to line number context menu Add 'Copy Remote URL' option to the line number gutter right-click menu, allowing users to quickly copy a shareable link to a file and line on GitHub, GitLab, Bitbucket, or other hosted git providers. - Install hosted-git-info to parse remote URLs and generate browse URLs - Add getRemoteFileUrl() to resolve file URLs using default branch - Wire up IPC handler, preload bridge, and renderer UI - Use ExternalLink icon to distinguish from path copy options * refactor: use @types/hosted-git-info instead of custom type declarations Replace the custom hosted-git-info.d.ts with the @types package from DefinitelyTyped. Use a targeted cast for browseFile() which the @types package doesn't include yet (stuck on v3 API). * refactor: revert to local type declarations for hosted-git-info The @types/hosted-git-info package is stuck on the v3 API and lacks browseFile(). A local .d.ts is cleaner than casting around stale types.
This commit is contained in:
parent
e8e7d2ea8f
commit
2ec433f6df
|
|
@ -45,6 +45,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"electron-updater": "^6.8.3",
|
||||
"hosted-git-info": "^9.0.2",
|
||||
"lucide-react": "^0.577.0",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"node-pty": "^1.1.0",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ importers:
|
|||
electron-updater:
|
||||
specifier: ^6.8.3
|
||||
version: 6.8.3
|
||||
hosted-git-info:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
lucide-react:
|
||||
specifier: ^0.577.0
|
||||
version: 0.577.0(react@19.2.4)
|
||||
|
|
@ -931,48 +934,56 @@ packages:
|
|||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-arm64-musl@0.41.0':
|
||||
resolution: {integrity: sha512-VfVZxL0+6RU86T8F8vKiDBa+iHsr8PAjQmKGBzSCAX70b6x+UOMFl+2dNihmKmUwqkCazCPfYjt6SuAPOeQJ3g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.41.0':
|
||||
resolution: {integrity: sha512-bwzokz2eGvdfJbc0i+zXMJ4BBjQPqg13jyWpEEZDOrBCQ91r8KeY2Mi2kUeuMTZNFXju+jcAbAbpyJxRGla0eg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.41.0':
|
||||
resolution: {integrity: sha512-POLM//PCH9uqDeNDwWL3b3DkMmI3oI2cU6hwc2lnztD1o7dzrQs3R9nq555BZ6wI7t2lyhT9CS+CRaz5X0XqLA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.41.0':
|
||||
resolution: {integrity: sha512-NNK7PzhFqLUwx/G12Xtm6scGv7UITvyGdAR5Y+TlqsG+essnuRWR4jRNODWRjzLZod0T3SayRbnkSIWMBov33w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.41.0':
|
||||
resolution: {integrity: sha512-qVf/zDC5cN9eKe4qI/O/m445er1IRl6swsSl7jHkqmOSVfknwCe5JXitYjZca+V/cNJSU/xPlC5EFMabMMFDpw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-gnu@0.41.0':
|
||||
resolution: {integrity: sha512-ojxYWu7vUb6ysYqVCPHuAPVZHAI40gfZ0PDtZAMwVmh2f0V8ExpPIKoAKr7/8sNbAXJBBpZhs2coypIo2jJX4w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-musl@0.41.0':
|
||||
resolution: {integrity: sha512-O2exZLBxoCMIv2vlvcbkdedazJPTdG0VSup+0QUCfYQtx751zCZNboX2ZUOiQ/gDTdhtXvSiot0h6GEGkOyalA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-openharmony-arm64@0.41.0':
|
||||
resolution: {integrity: sha512-N+31/VoL+z+NNBt8viy3I4NaIdPbiYeOnB884LKqvXldaE2dRztdPv3q5ipfZYv0RwFp7JfqS4I27K/DSHCakg==}
|
||||
|
|
@ -1045,48 +1056,56 @@ packages:
|
|||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-arm64-musl@1.56.0':
|
||||
resolution: {integrity: sha512-rkTZkBfJ4TYLjansjSzL6mgZOdN5IvUnSq3oNJSLwBcNvy3dlgQtpHPrRxrCEbbcp7oQ6If0tkNaqfOsphYZ9g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.56.0':
|
||||
resolution: {integrity: sha512-uqL1kMH3u69/e1CH2EJhP3CP28jw2ExLsku4o8RVAZ7fySo9zOyI2fy9pVlTAp4voBLVgzndXi3SgtdyCTa2aA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.56.0':
|
||||
resolution: {integrity: sha512-j0CcMBOgV6KsRaBdsebIeiy7hCjEvq2KdEsiULf2LZqAq0v1M1lWjelhCV57LxsqaIGChXFuFJ0RiFrSRHPhSg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-musl@1.56.0':
|
||||
resolution: {integrity: sha512-7VDOiL8cDG3DQ/CY3yKjbV1c4YPvc4vH8qW09Vv+5ukq3l/Kcyr6XGCd5NvxUmxqDb2vjMpM+eW/4JrEEsUetA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-s390x-gnu@1.56.0':
|
||||
resolution: {integrity: sha512-JGRpX0M+ikD3WpwJ7vKcHKV6Kg0dT52BW2Eu2BupXotYeqGXBrbY+QPkAyKO6MNgKozyTNaRh3r7g+VWgyAQYQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-gnu@1.56.0':
|
||||
resolution: {integrity: sha512-dNaICPvtmuxFP/VbqdofrLqdS3bM/AKJN3LMJD52si44ea7Be1cBk6NpfIahaysG9Uo+L98QKddU9CD5L8UHnQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-musl@1.56.0':
|
||||
resolution: {integrity: sha512-pF1vOtM+GuXmbklM1hV8WMsn6tCNPvkUzklj/Ej98JhlanbmA2RB1BILgOpwSuCTRTIYx2MXssmEyQQ90QF5aA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-openharmony-arm64@1.56.0':
|
||||
resolution: {integrity: sha512-bp8NQ4RE6fDIFLa4bdBiOA+TAvkNkg+rslR+AvvjlLTYXLy9/uKAYLQudaQouWihLD/hgkrXIKKzXi5IXOewwg==}
|
||||
|
|
@ -1847,66 +1866,79 @@ packages:
|
|||
resolution: {integrity: sha512-KUizqxpwaR2AZdAUsMWfL/C94pUu7TKpoPd88c8yFVixJ+l9hejkrwoK5Zj3wiNh65UeyryKnJyxL1b7yNqFQA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.59.1':
|
||||
resolution: {integrity: sha512-MZoQ/am77ckJtZGFAtPucgUuJWiop3m2R3lw7tC0QCcbfl4DRhQUBUkHWCkcrT3pqy5Mzv5QQgY6Dmlba6iTWg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.59.1':
|
||||
resolution: {integrity: sha512-Sez95TP6xGjkWB1608EfhCX1gdGrO5wzyN99VqzRtC17x/1bhw5VU1V0GfKUwbW/Xr1J8mSasoFoJa6Y7aGGSA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.59.1':
|
||||
resolution: {integrity: sha512-9Cs2Seq98LWNOJzR89EGTZoiP8EkZ9UbQhBlDgfAkM6asVna1xJ04W2CLYWDN/RpUgOjtQvcv8wQVi1t5oQazA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.59.1':
|
||||
resolution: {integrity: sha512-n9yqttftgFy7IrNEnHy1bOp6B4OSe8mJDiPkT7EqlM9FnKOwUMnCK62ixW0Kd9Clw0/wgvh8+SqaDXMFvw3KqQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.59.1':
|
||||
resolution: {integrity: sha512-SfpNXDzVTqs/riak4xXcLpq5gIQWsqGWMhN1AGRQKB4qGSs4r0sEs3ervXPcE1O9RsQ5bm8Muz6zmQpQnPss1g==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.59.1':
|
||||
resolution: {integrity: sha512-LjaChED0wQnjKZU+tsmGbN+9nN1XhaWUkAlSbTdhpEseCS4a15f/Q8xC2BN4GDKRzhhLZpYtJBZr2NZhR0jvNw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.59.1':
|
||||
resolution: {integrity: sha512-ojW7iTJSIs4pwB2xV6QXGwNyDctvXOivYllttuPbXguuKDX5vwpqYJsHc6D2LZzjDGHML414Tuj3LvVPe1CT1A==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.59.1':
|
||||
resolution: {integrity: sha512-FP+Q6WTcxxvsr0wQczhSE+tOZvFPV8A/mUE6mhZYFW9/eea/y/XqAgRoLLMuE9Cz0hfX5bi7p116IWoB+P237A==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.59.1':
|
||||
resolution: {integrity: sha512-L1uD9b/Ig8Z+rn1KttCJjwhN1FgjRMBKsPaBsDKkfUl7GfFq71pU4vWCnpOsGljycFEbkHWARZLf4lMYg3WOLw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.59.1':
|
||||
resolution: {integrity: sha512-EZc9NGTk/oSUzzOD4nYY4gIjteo2M3CiozX6t1IXGCOdgxJTlVu/7EdPeiqeHPSIrxkLhavqpBAUCfvC6vBOug==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.59.1':
|
||||
resolution: {integrity: sha512-NQ9KyU1Anuy59L8+HHOKM++CoUxrQWrZWXRik4BJFm+7i5NP6q/SW43xIBr80zzt+PDBJ7LeNmloQGfa0JGk0w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.59.1':
|
||||
resolution: {integrity: sha512-GZkLk2t6naywsveSFBsEb0PLU+JC9ggVjbndsbG20VPhar6D1gkMfCx4NfP9owpovBXTN+eRdqGSkDGIxPHhmQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.59.1':
|
||||
resolution: {integrity: sha512-1hjG9Jpl2KDOetr64iQd8AZAEjkDUUK5RbDkYWsViYLC1op1oNzdjMJeFiofcGhqbNTaY2kfgqowE7DILifsrA==}
|
||||
|
|
@ -1994,24 +2026,28 @@ packages:
|
|||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.2.2':
|
||||
resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.2.2':
|
||||
resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.2.2':
|
||||
resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.2.2':
|
||||
resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
|
||||
|
|
@ -3157,6 +3193,10 @@ packages:
|
|||
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
hosted-git-info@9.0.2:
|
||||
resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
|
||||
html-url-attributes@3.0.1:
|
||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
||||
|
||||
|
|
@ -3464,24 +3504,28 @@ packages:
|
|||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
|
|
@ -3546,6 +3590,10 @@ packages:
|
|||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
lru-cache@11.2.7:
|
||||
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
|
|
@ -8053,6 +8101,10 @@ snapshots:
|
|||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
|
||||
hosted-git-info@9.0.2:
|
||||
dependencies:
|
||||
lru-cache: 11.2.7
|
||||
|
||||
html-url-attributes@3.0.1: {}
|
||||
|
||||
http-cache-semantics@4.2.0: {}
|
||||
|
|
@ -8367,6 +8419,8 @@ snapshots:
|
|||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@11.2.7: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { execFile, execSync } from 'child_process'
|
|||
import { existsSync, statSync } from 'fs'
|
||||
import { join, basename } from 'path'
|
||||
import { promisify } from 'util'
|
||||
import hostedGitInfo from 'hosted-git-info'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
|
|
@ -321,3 +322,36 @@ export async function getBranchConflictKind(
|
|||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a hosted URL (e.g. GitHub, GitLab, Bitbucket) for a specific file
|
||||
* and line in the repo. Returns null when the remote isn't a recognized host.
|
||||
*
|
||||
* Why hosted-git-info: it handles SSH, HTTPS, and shorthand remote URLs
|
||||
* across multiple providers, so we don't have to maintain our own URL parser.
|
||||
*/
|
||||
export function getRemoteFileUrl(
|
||||
repoPath: string,
|
||||
relativePath: string,
|
||||
line: number
|
||||
): string | null {
|
||||
const remoteUrl = getRemoteUrl(repoPath)
|
||||
if (!remoteUrl) {
|
||||
return null
|
||||
}
|
||||
|
||||
const info = hostedGitInfo.fromUrl(remoteUrl)
|
||||
if (!info) {
|
||||
return null
|
||||
}
|
||||
|
||||
const defaultBranch = getDefaultBaseRef(repoPath).replace(/^origin\//, '')
|
||||
const browseUrl = info.browseFile(relativePath, { committish: defaultBranch })
|
||||
if (!browseUrl) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Why: hosted-git-info lowercases the fragment, but GitHub convention
|
||||
// uses uppercase L for line links (e.g. #L42). Append manually.
|
||||
return `${browseUrl}#L${line}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
getBranchCompare,
|
||||
getBranchDiff
|
||||
} from '../git/status'
|
||||
import { getRemoteFileUrl } from '../git/repo'
|
||||
import {
|
||||
resolveAuthorizedPath,
|
||||
resolveRegisteredWorktreePath,
|
||||
|
|
@ -432,4 +433,15 @@ export function registerFilesystemHandlers(store: Store): void {
|
|||
await discardChanges(worktreePath, filePath)
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
'git:remoteFileUrl',
|
||||
async (
|
||||
_event,
|
||||
args: { worktreePath: string; relativePath: string; line: number }
|
||||
): Promise<string | null> => {
|
||||
const worktreePath = await resolveRegisteredWorktreePath(args.worktreePath, store)
|
||||
return getRemoteFileUrl(worktreePath, args.relativePath, args.line)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
declare module 'hosted-git-info' {
|
||||
type HostedGitInfo = {
|
||||
browseFile(path: string, opts?: { committish?: string; fragment?: string }): string | undefined
|
||||
browse(path?: string, opts?: { committish?: string; fragment?: string }): string | undefined
|
||||
}
|
||||
|
||||
type HostedGitInfoStatic = {
|
||||
fromUrl(url: string): HostedGitInfo | undefined
|
||||
}
|
||||
|
||||
const hostedGitInfo: HostedGitInfoStatic
|
||||
export default hostedGitInfo
|
||||
}
|
||||
|
|
@ -172,6 +172,11 @@ type GitApi = {
|
|||
stage: (args: { worktreePath: string; filePath: string }) => Promise<void>
|
||||
unstage: (args: { worktreePath: string; filePath: string }) => Promise<void>
|
||||
discard: (args: { worktreePath: string; filePath: string }) => Promise<void>
|
||||
remoteFileUrl: (args: {
|
||||
worktreePath: string
|
||||
relativePath: string
|
||||
line: number
|
||||
}) => Promise<string | null>
|
||||
}
|
||||
|
||||
type Api = {
|
||||
|
|
|
|||
|
|
@ -283,7 +283,12 @@ const api = {
|
|||
unstage: (args: { worktreePath: string; filePath: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('git:unstage', args),
|
||||
discard: (args: { worktreePath: string; filePath: string }): Promise<void> =>
|
||||
ipcRenderer.invoke('git:discard', args)
|
||||
ipcRenderer.invoke('git:discard', args),
|
||||
remoteFileUrl: (args: {
|
||||
worktreePath: string
|
||||
relativePath: string
|
||||
line: number
|
||||
}): Promise<string | null> => ipcRenderer.invoke('git:remoteFileUrl', args)
|
||||
},
|
||||
|
||||
ui: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useRef, useCallback, useEffect, useState } from 'react'
|
||||
import Editor, { type OnMount } from '@monaco-editor/react'
|
||||
import type { editor } from 'monaco-editor'
|
||||
import { Copy } from 'lucide-react'
|
||||
import { Copy, ExternalLink } from 'lucide-react'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
|
@ -206,6 +206,23 @@ export default function MonacoEditor({
|
|||
<Copy className="w-3.5 h-3.5 mr-1.5" />
|
||||
Copy Rel. Path to Line
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={async () => {
|
||||
// Derive worktree root from the absolute and relative paths
|
||||
const worktreePath = filePath.slice(0, -(relativePath.length + 1))
|
||||
const url = await window.api.git.remoteFileUrl({
|
||||
worktreePath,
|
||||
relativePath,
|
||||
line: gutterMenuLine
|
||||
})
|
||||
if (url) {
|
||||
window.api.ui.writeClipboardText(url)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5 mr-1.5" />
|
||||
Copy Remote URL
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
|
|
|
|||
Loading…
Reference in New Issue