├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── background-ff.js ├── background.js ├── icons ├── 128.png ├── 16.png ├── 32.png ├── 48.png └── 96.png ├── manifest-ff.json ├── manifest.json ├── options-ff.html ├── options.html └── options.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: ahnafm 2 | github: infinitepower18 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ahnaf Mahmud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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 FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub](https://img.shields.io/github/license/infinitepower18/CloneInVSCode) 2 | 3 | # Clone in Visual Studio Code 4 | 5 | A browser extension to clone any GitHub, GitLab or Bitbucket repository in Visual Studio Code with just one click. 6 | 7 | All you need is Visual Studio Code installed on your system for this extension to work. Then just click the extension on any GitHub, GitLab or Bitbucket repo and it will open VS Code where it will take you through the rest of the cloning process. 8 | 9 | The extension also supports other variations of VS Code such as VSCodium and Cursor. You can change which application the extension opens via the options page. 10 | 11 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/F1F1K06VY) 12 | 13 | ## Download 14 | 15 | ### Chrome, Vivaldi, Brave, Opera and Arc 16 | 17 | [![image](https://user-images.githubusercontent.com/44692189/184990816-0e709ef1-d0d7-4539-b168-ef1880a62295.png)](https://chromewebstore.google.com/detail/clone-in-vs-code/bafggjdhleamglhfhbilngjelbnfblof) 18 | 19 | ### Microsoft Edge 20 | 21 | [![image](https://user-images.githubusercontent.com/44692189/185233057-155578e0-a7cd-46e6-a09e-767a1125b1b4.png)](https://microsoftedge.microsoft.com/addons/detail/idolkdgdllilclecodkncimdbmmclmje) 22 | 23 | ### Mozilla Firefox 24 | 25 | [![image](https://user-images.githubusercontent.com/44692189/185073795-4624fbba-5e43-4f0f-8d41-99ede6fba054.png)](https://addons.mozilla.org/en-US/firefox/addon/clone-in-vs-code/) 26 | 27 | ### Safari 28 | 29 | [![image](https://ahnafmahmud.com/files/badges/MacAppStore.svg)](https://apps.apple.com/us/app/clone-in-vs-code/id1640113540) 30 | 31 | [Click here](https://github.com/infinitepower18/CloneInVSCode-Safari) for the source code of the Safari port. 32 | -------------------------------------------------------------------------------- /background-ff.js: -------------------------------------------------------------------------------- 1 | chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { 2 | if ( 3 | tab.url.match(/^https:\/\/github.com\/.*/) || 4 | tab.url.match(/^https:\/\/gitlab.com\/.*/) || 5 | tab.url.match(/^https:\/\/bitbucket.org\/.*/) 6 | ) { 7 | chrome.action.enable(tabId); 8 | } else { 9 | chrome.action.disable(tabId); 10 | } 11 | }); 12 | 13 | chrome.tabs.onCreated.addListener(function (tab) { 14 | if ( 15 | tab.url && 16 | (tab.url.match(/^https:\/\/github.com\/.*/) || 17 | tab.url.match(/^https:\/\/gitlab.com\/.*/) || 18 | tab.url.match(/^https:\/\/bitbucket.org\/.*/)) 19 | ) { 20 | chrome.action.enable(tabId); 21 | } else if (tab.url) { 22 | chrome.action.disable(tabId); 23 | } 24 | }); 25 | 26 | chrome.runtime.onStartup.addListener(() => { 27 | chrome.action.disable(); 28 | }); 29 | 30 | chrome.runtime.onInstalled.addListener(() => { 31 | chrome.action.disable(); 32 | }); 33 | 34 | chrome.action.onClicked.addListener((tab) => { 35 | chrome.storage.sync.get( 36 | { 37 | protocol: "HTTPS", 38 | application: "VS Code", 39 | urlScheme: "", 40 | }, 41 | function (items) { 42 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => { 43 | const url = new URL(tabs[0].url); 44 | const path = url.pathname.split("/"); 45 | if (items.application == "VS Code") { 46 | openApp = "vscode"; 47 | } else if (items.application == "VS Code Insiders") { 48 | openApp = "vscode-insiders"; 49 | } else if (items.application == "VSCodium") { 50 | openApp = "vscodium"; 51 | } else if (items.application == "VSCodium Insiders") { 52 | openApp = "vscodium-insiders"; 53 | } else if (items.application == "Cursor") { 54 | openApp = "cursor"; 55 | } else if (items.application == "Windsurf") { 56 | openApp = "windsurf"; 57 | } else if (items.application == "Other") { 58 | openApp = items.urlScheme; 59 | } 60 | if (path[1] !== undefined && path[2] !== undefined) { 61 | if (items.protocol == "HTTPS") { 62 | chrome.tabs.update({ 63 | url: 64 | openApp + 65 | "://vscode.git/clone?url=https://" + 66 | url.hostname + 67 | "/" + 68 | path[1] + 69 | "/" + 70 | path[2] + 71 | ".git", 72 | }); 73 | } else if (items.protocol == "SSH") { 74 | chrome.tabs.update({ 75 | url: 76 | openApp + 77 | "://vscode.git/clone?url=git@" + 78 | url.hostname + 79 | ":" + 80 | path[1] + 81 | "/" + 82 | path[2] + 83 | ".git", 84 | }); 85 | } 86 | } 87 | }); 88 | } 89 | ); 90 | }); 91 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | function ext() { 2 | chrome.action.disable(); 3 | 4 | chrome.declarativeContent.onPageChanged.removeRules(undefined, () => { 5 | let rule = { 6 | conditions: [ 7 | new chrome.declarativeContent.PageStateMatcher({ 8 | pageUrl: { hostEquals: "github.com" }, 9 | }), 10 | new chrome.declarativeContent.PageStateMatcher({ 11 | pageUrl: { hostEquals: "gitlab.com" }, 12 | }), 13 | new chrome.declarativeContent.PageStateMatcher({ 14 | pageUrl: { hostEquals: "bitbucket.org" }, 15 | }), 16 | ], 17 | actions: [new chrome.declarativeContent.ShowAction()], 18 | }; 19 | 20 | let rules = [rule]; 21 | chrome.declarativeContent.onPageChanged.addRules(rules); 22 | }); 23 | } 24 | 25 | chrome.runtime.onStartup.addListener(() => { 26 | ext(); 27 | }); 28 | 29 | chrome.runtime.onInstalled.addListener(() => { 30 | ext(); 31 | }); 32 | 33 | chrome.action.onClicked.addListener((tab) => { 34 | chrome.storage.sync.get( 35 | { 36 | protocol: "HTTPS", 37 | application: "VS Code", 38 | urlScheme: "", 39 | }, 40 | function (items) { 41 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => { 42 | const url = new URL(tabs[0].url); 43 | const path = url.pathname.split("/"); 44 | if (items.application == "VS Code") { 45 | openApp = "vscode"; 46 | } else if (items.application == "VS Code Insiders") { 47 | openApp = "vscode-insiders"; 48 | } else if (items.application == "VSCodium") { 49 | openApp = "vscodium"; 50 | } else if (items.application == "VSCodium Insiders") { 51 | openApp = "vscodium-insiders"; 52 | } else if (items.application == "Cursor") { 53 | openApp = "cursor"; 54 | } else if (items.application == "Windsurf") { 55 | openApp = "windsurf"; 56 | } else if (items.application == "Other") { 57 | openApp = items.urlScheme; 58 | } 59 | if (path[1] !== undefined && path[2] !== undefined) { 60 | if (items.protocol == "HTTPS") { 61 | chrome.tabs.update({ 62 | url: 63 | openApp + 64 | "://vscode.git/clone?url=https://" + 65 | url.hostname + 66 | "/" + 67 | path[1] + 68 | "/" + 69 | path[2] + 70 | ".git", 71 | }); 72 | } else if (items.protocol == "SSH") { 73 | chrome.tabs.update({ 74 | url: 75 | openApp + 76 | "://vscode.git/clone?url=git@" + 77 | url.hostname + 78 | ":" + 79 | path[1] + 80 | "/" + 81 | path[2] + 82 | ".git", 83 | }); 84 | } 85 | } 86 | }); 87 | } 88 | ); 89 | }); 90 | -------------------------------------------------------------------------------- /icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/clone-in-vscode/0f79d3fe0cab15b1631e389336f1122e8eef7f8e/icons/128.png -------------------------------------------------------------------------------- /icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/clone-in-vscode/0f79d3fe0cab15b1631e389336f1122e8eef7f8e/icons/16.png -------------------------------------------------------------------------------- /icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/clone-in-vscode/0f79d3fe0cab15b1631e389336f1122e8eef7f8e/icons/32.png -------------------------------------------------------------------------------- /icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/clone-in-vscode/0f79d3fe0cab15b1631e389336f1122e8eef7f8e/icons/48.png -------------------------------------------------------------------------------- /icons/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinitepower18/clone-in-vscode/0f79d3fe0cab15b1631e389336f1122e8eef7f8e/icons/96.png -------------------------------------------------------------------------------- /manifest-ff.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Clone in VS Code", 3 | "description": "Clone any GitHub, GitLab or Bitbucket repository in Visual Studio Code", 4 | "version": "1.4.0", 5 | "manifest_version": 3, 6 | "icons": { 7 | "16": "icons/16.png", 8 | "32": "icons/32.png", 9 | "48": "icons/48.png", 10 | "96": "icons/96.png", 11 | "128": "icons/128.png" 12 | }, 13 | "author": "Ahnaf Mahmud", 14 | "options_ui": { 15 | "page": "options.html", 16 | "open_in_tab": false 17 | }, 18 | "homepage_url": "https://github.com/infinitepower18/clone-in-vscode", 19 | "permissions": ["tabs", "storage"], 20 | "browser_specific_settings": { 21 | "gecko": { 22 | "id": "{323a4b8a-6075-4094-8052-b73b01ab027a}" 23 | } 24 | }, 25 | "action": { 26 | "default_icon": { 27 | "16": "icons/16.png", 28 | "32": "icons/32.png" 29 | } 30 | }, 31 | "background": { 32 | "scripts": ["background.js"] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Clone in VS Code", 3 | "description": "Clone any GitHub, GitLab or Bitbucket repository in Visual Studio Code", 4 | "version": "1.4.0", 5 | "manifest_version": 3, 6 | "icons": { 7 | "16": "icons/16.png", 8 | "32": "icons/32.png", 9 | "48": "icons/48.png", 10 | "96": "icons/96.png", 11 | "128": "icons/128.png" 12 | }, 13 | "action": {}, 14 | "options_ui": { 15 | "page": "options.html", 16 | "open_in_tab": false 17 | }, 18 | "author": "Ahnaf Mahmud", 19 | "homepage_url": "https://github.com/infinitepower18/clone-in-vscode", 20 | "permissions": ["declarativeContent", "storage"], 21 | "background": { 22 | "service_worker": "background.js" 23 | }, 24 | "host_permissions": [ 25 | "https://github.com/*", 26 | "https://gitlab.com/*", 27 | "https://bitbucket.org/*" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /options-ff.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Clone in VS Code Options 5 | 23 | 24 | 25 |
26 | Protocol: 27 | 31 |
32 |
33 | Open in: 34 | 43 |
44 |
45 | URL Scheme: 46 | 47 |
48 |
49 | 50 |
51 |

52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Clone in VS Code Options 5 | 23 | 24 | 25 |
26 | Protocol: 27 | 31 |
32 |
33 | Open in: 34 | 43 |
44 |
45 | URL Scheme: 46 | 47 |
48 |
49 | 50 |
51 |

52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | const applicationDropdown = document.getElementById("application"); 2 | const urlSchemeInput = document.getElementById("url-scheme"); 3 | const saveButton = document.getElementById("save"); 4 | 5 | function save_options() { 6 | var protocol = document.getElementById("protocol").value; 7 | var application = document.getElementById("application").value; 8 | var scheme = document.getElementById("url-scheme").value; 9 | chrome.storage.sync.set( 10 | { 11 | protocol: protocol, 12 | application: application, 13 | urlScheme: scheme, 14 | }, 15 | function () { 16 | var status = document.getElementById("status"); 17 | status.textContent = "Options saved."; 18 | setTimeout(function () { 19 | status.textContent = ""; 20 | }, 3000); 21 | } 22 | ); 23 | } 24 | 25 | function restore_options() { 26 | chrome.storage.sync.get( 27 | { 28 | protocol: "HTTPS", 29 | application: "VS Code", 30 | urlScheme: "", 31 | }, 32 | function (items) { 33 | document.getElementById("protocol").value = items.protocol; 34 | document.getElementById("application").value = items.application; 35 | document.getElementById("url-scheme").value = items.urlScheme; 36 | url_container_visibility(); 37 | checkSaveButton(); 38 | } 39 | ); 40 | } 41 | 42 | function url_container_visibility() { 43 | const urlContainer = document.getElementById("url-container"); 44 | 45 | if (applicationDropdown.value === "Other") { 46 | urlContainer.style.display = "block"; 47 | } else { 48 | urlContainer.style.display = "none"; 49 | urlSchemeInput.value = ""; 50 | } 51 | checkSaveButton(); 52 | } 53 | 54 | function checkSaveButton() { 55 | if (applicationDropdown.value === "Other" && urlSchemeInput.value.trim() === "") { 56 | saveButton.disabled = true; 57 | } else { 58 | saveButton.disabled = false; 59 | } 60 | } 61 | 62 | // Event listeners 63 | document.addEventListener("DOMContentLoaded", restore_options); 64 | document.getElementById("save").addEventListener("click", save_options); 65 | applicationDropdown.addEventListener("change", url_container_visibility); 66 | urlSchemeInput.addEventListener("input", checkSaveButton); 67 | --------------------------------------------------------------------------------