├── .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 |
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 |
--------------------------------------------------------------------------------