├── .editorconfig ├── .gitignore ├── .vscodeignore ├── changelog.md ├── license ├── package.json ├── readme.md ├── resources ├── logo-128x128.png ├── logo.afphoto └── logo.png ├── src ├── commands.ts ├── constants.ts ├── git.ts ├── index.ts ├── types.ts ├── url.ts └── utils.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.err 3 | *.log 4 | ._* 5 | .cache 6 | .fseventsd 7 | .DocumentRevisions* 8 | .DS_Store 9 | .TemporaryItems 10 | .Trashes 11 | Thumbs.db 12 | 13 | dist 14 | node_modules 15 | package-lock.json 16 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | resources/**/*.afdesign 2 | resources/**/*.afphoto 3 | resources/**/*.gif 4 | resources/**/*.mp4 5 | resources/logo.png 6 | 7 | src 8 | node_modules 9 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ### Version 2.3.1 2 | - Made parsing a remote git url a bit more permissive 3 | 4 | ### Version 2.3.0 5 | - New command: "Open in GitHub: Repository", just a convenience alias 6 | - New command: "Open in GitHub: Commits" 7 | 8 | ### Version 2.2.1 9 | - Fixed an issue with the detection of remote urls using subdomain 10 | 11 | ### Version 2.2.0 12 | - Calculating the relative path of a file more reliably 13 | - New setting: "openInGitHub.useLocalDomain", for inferring the remote domain automatically 14 | 15 | ### Version 2.1.3 16 | - Minor internal improvements 17 | 18 | ### Version 2.1.2 19 | - Updated some dependencies 20 | 21 | ### Version 2.1.1 22 | - Readme: deleted demos, for simplicity 23 | 24 | ### Version 2.1.0 25 | - New setting: `github.protocol` 26 | 27 | ### Version 2.0.0 28 | - Rewitten: more modern code, no third-party dependencies, 99% smaller bundle 29 | - New command: "Open in GitHub: Security" 30 | - New command: "Open in GitHub: Insights" 31 | - New command: "Open in GitHub: Tags" 32 | - New command: "Open in GitHub: Copy File Link" 33 | 34 | ### Version 1.3.0 35 | - New command: "Open in GitHub: Actions" 36 | - New command: "Open in GitHub: Projects" 37 | - New command: "Open in GitHub: Wiki" 38 | - New command: "Open in GitHub: Settings" 39 | - New command: "Open in GitHub: File Permalink" 40 | - New command: "Open in GitHub: Copy File Permalink" 41 | 42 | ### Version 1.2.4 43 | - Update .github/FUNDING.yml 44 | - Deleted repo-level github funding.yml 45 | - Ensuring custom domans are handled properly 46 | 47 | ### Version 1.2.3 48 | - Fixed remote support 49 | 50 | ### Version 1.2.2 51 | - Url: avoiding url encoding forward slashes 52 | 53 | ### Version 1.2.1 54 | - Fixed a couple of bugs 55 | 56 | ### Version 1.2.0 57 | - Improved support for monorepos -- Fixed usage under Windows 58 | - Added an “Open in GitHub: Releases” command 59 | 60 | ### Version 1.1.5 61 | - Fixed a regression 62 | 63 | ### Version 1.1.4 64 | - Setting `useLocalLine` to `false` by default 65 | - Simplified range URL when the selected range encompasses only one line 66 | - Properly escaping urls 67 | 68 | ### Version 1.1.3 69 | - Readme: using hi-res logo 70 | 71 | ### Version 1.1.2 72 | - Outputting modern code (es2017, faster) 73 | - Using "Debug Launcher" for debugging 74 | 75 | ### Version 1.1.1 76 | - Bundling with webpack 77 | 78 | ### Version 1.1.0 79 | - Added an `useLocalLine` setting 80 | 81 | ### Version 1.0.2 82 | - Selecting lines only if the current selection is not empty 83 | 84 | ### Version 1.0.1 85 | - Updated readme 86 | 87 | ### Version 1.0.0 88 | - Initial release 89 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-present Fabio Spampinato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-open-in-github", 3 | "displayName": "Open in GitHub", 4 | "publisher": "fabiospampinato", 5 | "repository": "github:fabiospampinato/vscode-open-in-github", 6 | "description": "Open the current project or file in github.com.", 7 | "icon": "resources/logo-128x128.png", 8 | "version": "2.3.1", 9 | "main": "dist/index.js", 10 | "contributes": { 11 | "configuration": { 12 | "type": "object", 13 | "title": "Open in GitHub - Configuration", 14 | "properties": { 15 | "openInGitHub.github.protocol": { 16 | "type": "string", 17 | "description": "Custom URL protocol", 18 | "default": "https" 19 | }, 20 | "openInGitHub.github.domain": { 21 | "type": "string", 22 | "description": "Custom GitHub domain", 23 | "default": "github.com" 24 | }, 25 | "openInGitHub.remote.name": { 26 | "type": "string", 27 | "description": "Name of the remote repository", 28 | "default": "origin" 29 | }, 30 | "openInGitHub.remote.branch": { 31 | "type": "string", 32 | "description": "Name of the remote branch", 33 | "default": "master" 34 | }, 35 | "openInGitHub.useLocalDomain": { 36 | "type": "boolean", 37 | "description": "Use the local domain instead of the fixed github domain", 38 | "default": true 39 | }, 40 | "openInGitHub.useLocalBranch": { 41 | "type": "boolean", 42 | "description": "Use the local branch instead of the fixed remote branch", 43 | "default": true 44 | }, 45 | "openInGitHub.useLocalRange": { 46 | "type": "boolean", 47 | "description": "Highlight the local selection range, if there's one", 48 | "default": true 49 | }, 50 | "openInGitHub.useLocalLine": { 51 | "type": "boolean", 52 | "description": "Highlight the local line if there's no selection range", 53 | "default": false 54 | } 55 | } 56 | }, 57 | "commands": [ 58 | { 59 | "command": "openInGitHub.openProject", 60 | "title": "Open in GitHub: Project" 61 | }, 62 | { 63 | "command": "openInGitHub.openRepository", 64 | "title": "Open in GitHub: Repository" 65 | }, 66 | { 67 | "command": "openInGitHub.openFile", 68 | "title": "Open in GitHub: File" 69 | }, 70 | { 71 | "command": "openInGitHub.openFileHistory", 72 | "title": "Open in GitHub: File History" 73 | }, 74 | { 75 | "command": "openInGitHub.openFileBlame", 76 | "title": "Open in GitHub: File Blame" 77 | }, 78 | { 79 | "command": "openInGitHub.openFilePermalink", 80 | "title": "Open in GitHub: File Permalink" 81 | }, 82 | { 83 | "command": "openInGitHub.openIssues", 84 | "title": "Open in GitHub: Issues" 85 | }, 86 | { 87 | "command": "openInGitHub.openPullRequests", 88 | "title": "Open in GitHub: Pull Requests" 89 | }, 90 | { 91 | "command": "openInGitHub.openReleases", 92 | "title": "Open in GitHub: Releases" 93 | }, 94 | { 95 | "command": "openInGitHub.openTags", 96 | "title": "Open in GitHub: Tags" 97 | }, 98 | { 99 | "command": "openInGitHub.openActions", 100 | "title": "Open in GitHub: Actions" 101 | }, 102 | { 103 | "command": "openInGitHub.openCommits", 104 | "title": "Open in GitHub: Commits" 105 | }, 106 | { 107 | "command": "openInGitHub.openProjects", 108 | "title": "Open in GitHub: Projects" 109 | }, 110 | { 111 | "command": "openInGitHub.openSecurity", 112 | "title": "Open in GitHub: Security" 113 | }, 114 | { 115 | "command": "openInGitHub.openInsights", 116 | "title": "Open in GitHub: Insights" 117 | }, 118 | { 119 | "command": "openInGitHub.openWiki", 120 | "title": "Open in GitHub: Wiki" 121 | }, 122 | { 123 | "command": "openInGitHub.openSettings", 124 | "title": "Open in GitHub: Settings" 125 | }, 126 | { 127 | "command": "openInGitHub.copyFileLink", 128 | "title": "Open in GitHub: Copy File Link" 129 | }, 130 | { 131 | "command": "openInGitHub.copyFilePermalink", 132 | "title": "Open in GitHub: Copy File Permalink" 133 | } 134 | ] 135 | }, 136 | "scripts": { 137 | "bundle:dev": "tsex bundle --external vscode --format cjs --platform node --no-declare", 138 | "bundle:prod": "tsex bundle --external vscode --format cjs --platform node --minify", 139 | "clean": "tsex clean", 140 | "compile": "tsex compile", 141 | "debug": "code --extensionDevelopmentPath $PWD --inspect-extensions 9222", 142 | "package": "vsce package", 143 | "prepublishOnly": "scex -bs clean bundle:prod", 144 | "vscode:prepublish": "scex -bs clean bundle:prod", 145 | "dev": "scex -bs bundle:dev debug", 146 | "prod": "scex -bs bundle:prod debug" 147 | }, 148 | "categories": [ 149 | "Other" 150 | ], 151 | "engines": { 152 | "vscode": "^1.87.0" 153 | }, 154 | "keywords": [ 155 | "vscode", 156 | "extension", 157 | "open", 158 | "in", 159 | "github" 160 | ], 161 | "dependencies": { 162 | "vscode-extras": "^1.6.1" 163 | }, 164 | "devDependencies": { 165 | "@types/node": "^18.19.23", 166 | "@types/vscode": "^1.87.0", 167 | "esbuild": "0.20.1", 168 | "scex": "^1.1.0", 169 | "tsex": "^3.2.0", 170 | "typescript": "^5.4.2" 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Open in GitHub 2 | 3 |

4 | Logo 5 |

6 | 7 | Open the current project or file in github.com. 8 | 9 | There are many other extensions for doing this, but they either didn't work well for me or they provided too few/many functionalities. 10 | 11 | ## Install 12 | 13 | Follow the instructions in the [Marketplace](https://marketplace.visualstudio.com/items?itemName=fabiospampinato.vscode-open-in-github), or run the following in the command palette: 14 | 15 | ```shell 16 | ext install fabiospampinato.vscode-open-in-github 17 | ``` 18 | 19 | ## Usage 20 | 21 | It adds 19 commands to the command palette: 22 | 23 | ```js 24 | 'Open in GitHub: Project' // Open the current project in GitHub 25 | 'Open in GitHub: Repository' // Open the current repository in GitHub 26 | 'Open in GitHub: File' // Open the current file in GitHub 27 | 'Open in GitHub: File Blame' // Open the current file's blame in GitHub 28 | 'Open in GitHub: File History' // Open the current file's history in GitHub 29 | 'Open in GitHub: File Permalink' // Open the current file's permalink in GitHub 30 | 'Open in GitHub: Issues' // Open the current project's issues in GitHub 31 | 'Open in GitHub: Pull Requests' // Open the current project's pull requests in GitHub 32 | 'Open in GitHub: Releases' // Open the current project's releases in GitHub 33 | 'Open in GitHub: Tags' // Open the current project's tags in GitHub 34 | 'Open in GitHub: Actions' // Open the current project's actions in GitHub 35 | 'Open in GitHub: Commits' // Open the current project's commits in GitHub 36 | 'Open in GitHub: Projects' // Open the current project's projects in GitHub 37 | 'Open in GitHub: Security' // Open the current project's security in GitHub 38 | 'Open in GitHub: Insights' // Open the current project's insights in GitHub 39 | 'Open in GitHub: Wiki' // Open the current project's wiki in GitHub 40 | 'Open in GitHub: Settings' // Open the current project's settings in GitHub 41 | 'Open in GitHub: Copy File Link' // Copy the current file's link to GitHub 42 | 'Open in GitHub: Copy File Permalink' // Copy the current file's permalink to GitHub 43 | ``` 44 | 45 | ## Settings 46 | 47 | ```js 48 | { 49 | "openInGitHub.github.protocol": "https", // Custom URL protocol 50 | "openInGitHub.github.domain": "github.com", // Custom GitHub domain 51 | "openInGitHub.remote.name": "origin", // Name of the remote repository 52 | "openInGitHub.remote.branch": "master", // Name of the remote branch 53 | "openInGitHub.useLocalDomain": true, // Use the local domain instead of the fixed github domain 54 | "openInGitHub.useLocalBranch": true, // Use the local branch instead of the fixed remote branch 55 | "openInGitHub.useLocalRange": true, // Highlight the local selection range, if there's one 56 | "openInGitHub.useLocalLine": false // Highlight the local line if there's no selection range 57 | } 58 | ``` 59 | 60 | ## License 61 | 62 | MIT © Fabio Spampinato 63 | -------------------------------------------------------------------------------- /resources/logo-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiospampinato/vscode-open-in-github/344243595f68cfbe1976754b29a831bc6e7ff85b/resources/logo-128x128.png -------------------------------------------------------------------------------- /resources/logo.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiospampinato/vscode-open-in-github/344243595f68cfbe1976754b29a831bc6e7ff85b/resources/logo.afphoto -------------------------------------------------------------------------------- /resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiospampinato/vscode-open-in-github/344243595f68cfbe1976754b29a831bc6e7ff85b/resources/logo.png -------------------------------------------------------------------------------- /src/commands.ts: -------------------------------------------------------------------------------- 1 | 2 | /* IMPORT */ 3 | 4 | import URL from './url'; 5 | 6 | /* MAIN */ 7 | 8 | const openProject = (): Promise => { 9 | 10 | return URL.open (); 11 | 12 | }; 13 | 14 | const openIssues = (): Promise => { 15 | 16 | return URL.open ( false, false, 'issues' ); 17 | 18 | }; 19 | 20 | const openPullRequests = (): Promise => { 21 | 22 | return URL.open ( false, false, 'pulls' ); 23 | 24 | }; 25 | 26 | const openActions = (): Promise => { 27 | 28 | return URL.open ( false, false, 'actions' ); 29 | 30 | }; 31 | 32 | const openCommits = (): Promise => { 33 | 34 | return URL.open ( false, false, 'commits' ); 35 | 36 | }; 37 | 38 | const openProjects = (): Promise => { 39 | 40 | return URL.open ( false, false, 'projects' ); 41 | 42 | }; 43 | 44 | const openSecurity = (): Promise => { 45 | 46 | return URL.open ( false, false, 'security' ); 47 | 48 | }; 49 | 50 | const openInsights = (): Promise => { 51 | 52 | return URL.open ( false, false, 'pulse' ); 53 | 54 | }; 55 | 56 | const openWiki = (): Promise => { 57 | 58 | return URL.open ( false, false, 'wiki' ); 59 | 60 | }; 61 | 62 | const openSettings = (): Promise => { 63 | 64 | return URL.open ( false, false, 'settings' ); 65 | 66 | }; 67 | 68 | const openReleases = (): Promise => { 69 | 70 | return URL.open ( false, false, 'releases' ); 71 | 72 | }; 73 | 74 | const openTags = (): Promise => { 75 | 76 | return URL.open ( false, false, 'tags' ); 77 | 78 | }; 79 | 80 | const openFile = (): Promise => { 81 | 82 | return URL.open ( true, false, 'blob' ); 83 | 84 | }; 85 | 86 | const openFileHistory = (): Promise => { 87 | 88 | return URL.open ( true, false, 'commits' ); 89 | 90 | }; 91 | 92 | const openFileBlame = (): Promise => { 93 | 94 | return URL.open ( true, false, 'blame' ); 95 | 96 | }; 97 | 98 | const openFilePermalink = (): Promise => { 99 | 100 | return URL.open ( true, true, 'blob' ); 101 | 102 | }; 103 | 104 | const copyFileLink = (): Promise => { 105 | 106 | return URL.copy ( true, false, 'blob' ); 107 | 108 | }; 109 | 110 | const copyFilePermalink = (): Promise => { 111 | 112 | return URL.copy ( true, true, 'blob' ); 113 | 114 | }; 115 | 116 | /* EXPORT */ 117 | 118 | export {openProject, openIssues, openPullRequests, openActions, openCommits, openProjects, openSecurity, openInsights, openWiki, openSettings, openReleases, openTags, openFile, openFileHistory, openFileBlame, openFilePermalink, copyFileLink, copyFilePermalink}; 119 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | 2 | /* MAIN */ 3 | 4 | const ERROR_NO_REPOSITORY = 1; 5 | const ERROR_NO_REMOTE = 2; 6 | const ERROR_NO_FILE = 3; 7 | 8 | /* EXPORT */ 9 | 10 | export {ERROR_NO_REPOSITORY, ERROR_NO_REMOTE, ERROR_NO_FILE}; 11 | -------------------------------------------------------------------------------- /src/git.ts: -------------------------------------------------------------------------------- 1 | 2 | /* IMPORT */ 3 | 4 | import {exec} from 'vscode-extras'; 5 | import {getOptions} from './utils'; 6 | import type {Remote} from './types'; 7 | 8 | /* MAIN */ 9 | 10 | const Git = { 11 | 12 | /* API */ 13 | 14 | exec: async ( cwd: string, args: string[] ): Promise => { 15 | 16 | const {stdout} = await exec ( 'git', args, { cwd, stdio: 'pipe' } ); 17 | const output = stdout.toString ().trim (); 18 | 19 | return output; 20 | 21 | }, 22 | 23 | getBranch: async ( cwd: string ): Promise => { 24 | 25 | const options = getOptions (); 26 | 27 | if ( !options.useLocalBranch ) return options.remote.branch; 28 | 29 | return Git.exec ( cwd, ['branch', '--show-current'] ); 30 | 31 | }, 32 | 33 | getCommit: async ( cwd: string ): Promise => { 34 | 35 | return Git.exec ( cwd, ['rev-parse', 'HEAD'] ); 36 | 37 | }, 38 | 39 | getRemotes: async ( cwd: string ): Promise => { 40 | 41 | const remoteRe = /^(\S+)\s+(\S+)(?:\s+\((push|fetch)\))?$/; 42 | const remotesLinesRaw = await Git.exec ( cwd, ['remote', '-v'] ); 43 | const remotesLines = remotesLinesRaw.split ( /\r?\n|\r/g ); 44 | const remotesMap: Record = {}; 45 | 46 | for ( const line of remotesLines ) { 47 | 48 | const match = remoteRe.exec ( line ); 49 | 50 | if ( !match ) continue; 51 | 52 | const name = match[1]; 53 | const ref = match[2]; 54 | const type = match[3] || 'fetch'; 55 | 56 | remotesMap[name] ||= { name, refs: {} }; 57 | 58 | if ( type !== 'fetch' && type !== 'push' ) continue; 59 | 60 | remotesMap[name].refs[type] = ref; 61 | 62 | } 63 | 64 | return Object.values ( remotesMap ); 65 | 66 | }, 67 | 68 | getRemoteUrl: async ( cwd: string ): Promise => { 69 | 70 | const options = getOptions (); 71 | const origin = options.remote.name; 72 | 73 | const remotes = await Git.getRemotes ( cwd ); 74 | const remotesValid = remotes.filter ( remote => remote.refs.fetch || remote.refs.push ); 75 | const remoteOrigin = remotesValid.find ( remote => remote.name === origin ); 76 | const remote = remoteOrigin || remotesValid[0]; 77 | 78 | if ( !remote ) return; 79 | 80 | const ref = remote.refs.fetch || remote.refs.push; 81 | 82 | if ( !ref ) return; 83 | 84 | const re = /(\w+(?:\.\w+)+)[:/]*([^/]+)\/(.*?)(?:\.git|\/)?$/; 85 | const match = re.exec ( ref ); 86 | 87 | if ( !match ) return; 88 | 89 | const protocol = options.github.protocol; 90 | const domain = options.useLocalDomain ? match[1] : options.github.domain; 91 | const username = match[2]; 92 | const reponame = match[3]; 93 | 94 | return `${protocol}://${domain}/${username}/${reponame}`; 95 | 96 | } 97 | 98 | }; 99 | 100 | /* EXPORT */ 101 | 102 | export default Git; 103 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | /* IMPORT */ 3 | 4 | import vscode from 'vscode'; 5 | import * as Commands from './commands'; 6 | 7 | /* MAIN */ 8 | 9 | const activate = (): void => { 10 | 11 | vscode.commands.registerCommand ( 'openInGitHub.openProject', Commands.openProject ); 12 | vscode.commands.registerCommand ( 'openInGitHub.openRepository', Commands.openProject ); 13 | vscode.commands.registerCommand ( 'openInGitHub.openFile', Commands.openFile ); 14 | vscode.commands.registerCommand ( 'openInGitHub.openFileHistory', Commands.openFileHistory ); 15 | vscode.commands.registerCommand ( 'openInGitHub.openFileBlame', Commands.openFileBlame ); 16 | vscode.commands.registerCommand ( 'openInGitHub.openFilePermalink', Commands.openFilePermalink ); 17 | vscode.commands.registerCommand ( 'openInGitHub.openIssues', Commands.openIssues ); 18 | vscode.commands.registerCommand ( 'openInGitHub.openPullRequests', Commands.openPullRequests ); 19 | vscode.commands.registerCommand ( 'openInGitHub.openReleases', Commands.openReleases ); 20 | vscode.commands.registerCommand ( 'openInGitHub.openTags', Commands.openTags ); 21 | vscode.commands.registerCommand ( 'openInGitHub.openActions', Commands.openActions ); 22 | vscode.commands.registerCommand ( 'openInGitHub.openCommits', Commands.openCommits ); 23 | vscode.commands.registerCommand ( 'openInGitHub.openProjects', Commands.openProjects ); 24 | vscode.commands.registerCommand ( 'openInGitHub.openSecurity', Commands.openSecurity ); 25 | vscode.commands.registerCommand ( 'openInGitHub.openInsights', Commands.openInsights ); 26 | vscode.commands.registerCommand ( 'openInGitHub.openWiki', Commands.openWiki ); 27 | vscode.commands.registerCommand ( 'openInGitHub.openSettings', Commands.openSettings ); 28 | vscode.commands.registerCommand ( 'openInGitHub.copyFileLink', Commands.copyFileLink ); 29 | vscode.commands.registerCommand ( 'openInGitHub.copyFilePermalink', Commands.copyFilePermalink ); 30 | 31 | }; 32 | 33 | /* EXPORT */ 34 | 35 | export {activate}; 36 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | /* MAIN */ 3 | 4 | type Options = { 5 | github: { 6 | protocol: string, 7 | domain: string 8 | }, 9 | remote: { 10 | name: string, 11 | branch: string 12 | }, 13 | useLocalDomain: boolean, 14 | useLocalBranch: boolean, 15 | useLocalRange: boolean, 16 | useLocalLine: boolean 17 | }; 18 | 19 | type Remote = { 20 | name: string; 21 | refs: { 22 | fetch?: string; 23 | push?: string; 24 | }; 25 | }; 26 | 27 | /* EXPORT */ 28 | 29 | export type {Options, Remote}; 30 | -------------------------------------------------------------------------------- /src/url.ts: -------------------------------------------------------------------------------- 1 | 2 | /* IMPORT */ 3 | 4 | import path from 'node:path'; 5 | import vscode from 'vscode'; 6 | import {alert, getActiveFilePath, getGitRootPath, openInExternal} from 'vscode-extras'; 7 | import {ERROR_NO_REPOSITORY, ERROR_NO_REMOTE, ERROR_NO_FILE} from './constants'; 8 | import {getOptions} from './utils'; 9 | import Git from './git'; 10 | 11 | /* MAIN */ 12 | 13 | const URL = { 14 | 15 | /* API */ 16 | 17 | get: async ( file: boolean = false, permalink: boolean = false, page?: string ): Promise => { 18 | 19 | const rootPath = await getGitRootPath (); 20 | 21 | if ( !rootPath ) return ERROR_NO_REPOSITORY; 22 | 23 | const repoUrl = await Git.getRemoteUrl ( rootPath ); 24 | 25 | if ( !repoUrl ) return ERROR_NO_REMOTE; 26 | 27 | let branch = ''; 28 | let commit = ''; 29 | let filePath = ''; 30 | let lines = ''; 31 | 32 | if ( file ) { 33 | 34 | const editorPath = getActiveFilePath (); 35 | 36 | if ( !editorPath ) return ERROR_NO_FILE; 37 | 38 | filePath = path.relative ( rootPath, editorPath ).replace( /\\+/g, '/' ); 39 | 40 | if ( filePath ) { 41 | 42 | branch = await Git.getBranch ( rootPath ); 43 | 44 | const options = getOptions (); 45 | 46 | if ( options.useLocalRange ) { 47 | 48 | const selections = vscode.window.activeTextEditor?.selections; 49 | 50 | if ( selections?.length === 1 ) { 51 | 52 | const selection = selections[0]; 53 | 54 | if ( !selection.isEmpty ) { 55 | 56 | if ( selection.start.line === selection.end.line ) { 57 | 58 | lines = `#L${selection.start.line + 1}`; 59 | 60 | } else { 61 | 62 | lines = `#L${selection.start.line + 1}-L${selection.end.line + 1}`; 63 | 64 | } 65 | 66 | } else if ( options.useLocalLine ) { 67 | 68 | lines = `#L${selection.start.line + 1}`; 69 | 70 | } 71 | 72 | } 73 | 74 | } 75 | 76 | if ( permalink ) { 77 | 78 | branch = ''; 79 | commit = await Git.getCommit ( rootPath ); 80 | 81 | } 82 | 83 | } 84 | 85 | } 86 | 87 | branch = encodeURIComponent ( branch ); 88 | filePath = encodeURIComponent ( filePath ).replace ( /%2F/g, '/' ); 89 | 90 | const url = [repoUrl, page, branch, commit, filePath, lines].filter ( Boolean ).join ( '/' ); 91 | 92 | return url; 93 | 94 | }, 95 | 96 | copy: async ( file: boolean = false, permalink: boolean = false, page?: string ): Promise => { 97 | 98 | const url = await URL.get ( file, permalink, page ); 99 | 100 | if ( url === ERROR_NO_REPOSITORY ) return alert.error ( 'You have to open a git project before being able to open it in GitHub' ); 101 | if ( url === ERROR_NO_REMOTE ) return alert.error ( 'Remote repository not found' ); 102 | if ( url === ERROR_NO_FILE ) return alert.error ( 'You have to open a repository file before being able to open it in GitHub' ); 103 | 104 | vscode.env.clipboard.writeText ( url ); 105 | 106 | alert.info ( 'Link copied to clipboard!' ); 107 | 108 | }, 109 | 110 | open: async ( file: boolean = false, permalink: boolean = false, page?: string ): Promise => { 111 | 112 | const url = await URL.get ( file, permalink, page ); 113 | 114 | if ( url === ERROR_NO_REPOSITORY ) return alert.error ( 'You have to open a git project before being able to open it in GitHub' ); 115 | if ( url === ERROR_NO_REMOTE ) return alert.error ( 'Remote repository not found' ); 116 | if ( url === ERROR_NO_FILE ) return alert.error ( 'You have to open a repository file before being able to open it in GitHub' ); 117 | 118 | openInExternal ( url ); 119 | 120 | } 121 | 122 | }; 123 | 124 | /* EXPORT */ 125 | 126 | export default URL; 127 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | /* IMPORT */ 3 | 4 | import {getConfig} from 'vscode-extras'; 5 | import type {Options} from './types'; 6 | 7 | /* MAIN */ 8 | 9 | const getOptions = (): Options => { 10 | 11 | const config = getConfig ( 'openInGitHub' ); 12 | const protocol = isString ( config?.github?.protocol ) ? config.github.protocol : 'https'; 13 | const domain = isString ( config?.github?.domain ) ? config.github.domain : 'github.com'; 14 | const name = isString ( config?.remote?.name ) ? config.remote.name : 'origin'; 15 | const branch = isString ( config?.remote?.branch ) ? config.remote.branch : 'master'; 16 | const useLocalDomain = isBoolean ( config?.useLocalDomain ) ? config.useLocalDomain : true; 17 | const useLocalBranch = isBoolean ( config?.useLocalBranch ) ? config.useLocalBranch : true; 18 | const useLocalRange = isBoolean ( config?.useLocalRange ) ? config.useLocalRange : true; 19 | const useLocalLine = isBoolean ( config?.useLocalLine ) ? config.useLocalLine : false; 20 | 21 | return { github: { protocol, domain }, remote: { name, branch }, useLocalDomain, useLocalBranch, useLocalRange, useLocalLine }; 22 | 23 | }; 24 | 25 | const isBoolean = ( value: unknown ): value is boolean => { 26 | 27 | return typeof value === 'boolean'; 28 | 29 | }; 30 | 31 | const isString = ( value: unknown ): value is string => { 32 | 33 | return typeof value === 'string'; 34 | 35 | }; 36 | 37 | /* EXPORT */ 38 | 39 | export {getOptions, isBoolean, isString}; 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsex/tsconfig.json", 3 | "compilerOptions": { 4 | "noPropertyAccessFromIndexSignature": false 5 | } 6 | } 7 | --------------------------------------------------------------------------------