diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 9fa0010264a..17f47ebca5b 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -240,7 +240,8 @@ const excludedWebExtensions = excludedCommonExtensions.concat([ ]); const marketplaceWebExtensions = [ - 'ms-vscode.references-view' + 'ms-vscode.references-view', + 'ms-vscode.github-browser' ]; interface IBuiltInExtension { @@ -309,7 +310,7 @@ export function packageLocalExtensionsStream(forWeb: boolean): Stream { export function packageMarketplaceExtensionsStream(forWeb: boolean): Stream { const marketplaceExtensionsDescriptions = ( builtInExtensions - .filter(({ name }) => (forWeb ? marketplaceWebExtensions.indexOf(name) >= 0 : true)) + .filter(({ name }) => (forWeb ? marketplaceWebExtensions.indexOf(name) >= 0 : true)) ); const marketplaceExtensionsStream = minifyExtensionResources( es.merge( diff --git a/extensions/github-browser/.gitignore b/extensions/github-browser/.gitignore deleted file mode 100644 index c19bd94aaa7..00000000000 --- a/extensions/github-browser/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -dist -out -node_modules diff --git a/extensions/github-browser/.vscodeignore b/extensions/github-browser/.vscodeignore deleted file mode 100644 index 32fe3f03697..00000000000 --- a/extensions/github-browser/.vscodeignore +++ /dev/null @@ -1,11 +0,0 @@ -.vscode/** -build/** -dist/** -out/** -src/** -typings/** -.gitignore -extension-browser.webpack.config.js -extension.webpack.config.js -tsconfig.json -yarn.lock diff --git a/extensions/github-browser/README.md b/extensions/github-browser/README.md deleted file mode 100644 index ef4d6f58e8b..00000000000 --- a/extensions/github-browser/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# GitHub FileSystem for Visual Studio Code - -**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. - -## Features - -This extension provides remote GitHub repository features for VS Code. diff --git a/extensions/github-browser/extension-browser.webpack.config.js b/extensions/github-browser/extension-browser.webpack.config.js deleted file mode 100644 index 55f3a268486..00000000000 --- a/extensions/github-browser/extension-browser.webpack.config.js +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; -const path = require('path'); -const withBrowserDefaults = require('../shared.webpack.config').browser; - -const config = withBrowserDefaults({ - context: __dirname, - node: false, - entry: { - extension: './src/extension.ts' - }, - resolve: { - alias: { - 'node-fetch': path.resolve(__dirname, 'node_modules/node-fetch/browser.js') - } - } -}); - -module.exports = config; diff --git a/extensions/github-browser/extension.webpack.config.js b/extensions/github-browser/extension.webpack.config.js deleted file mode 100644 index 45600607fc5..00000000000 --- a/extensions/github-browser/extension.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, - entry: { - extension: './src/extension.ts' - } -}); diff --git a/extensions/github-browser/package.json b/extensions/github-browser/package.json deleted file mode 100644 index 9938e25ed54..00000000000 --- a/extensions/github-browser/package.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "name": "github-browser", - "displayName": "%displayName%", - "description": "%description%", - "publisher": "vscode", - "version": "0.0.1", - "engines": { - "vscode": "^1.45.0" - }, - "enableProposedApi": true, - "private": true, - "categories": [ - "Other" - ], - "activationEvents": [ - "onFileSystem:codespace", - "onFileSystem:github", - "onCommand:githubBrowser.openRepository" - ], - "browser": "./dist/browser/extension.js", - "main": "./out/extension.js", - "contributes": { - "commands": [ - { - "command": "githubBrowser.openRepository", - "title": "Open GitHub Repository...", - "category": "GitHub Browser" - }, - { - "command": "githubBrowser.commit", - "title": "Commit", - "icon": "$(check)", - "category": "GitHub Browser" - }, - { - "command": "githubBrowser.discardChanges", - "title": "Discard Changes", - "icon": "$(discard)", - "category": "GitHub Browser" - }, - { - "command": "githubBrowser.openChanges", - "title": "Open Changes", - "icon": "$(git-compare)", - "category": "GitHub Browser" - }, - { - "command": "githubBrowser.openFile", - "title": "Open File", - "icon": "$(go-to-file)", - "category": "GitHub Browser" - } - ], - "menus": { - "commandPalette": [ - { - "command": "githubBrowser.openRepository", - "when": "config.githubBrowser.openRepository" - }, - { - "command": "githubBrowser.commit", - "when": "false" - }, - { - "command": "githubBrowser.discardChanges", - "when": "false" - }, - { - "command": "githubBrowser.openChanges", - "when": "false" - }, - { - "command": "githubBrowser.openFile", - "when": "false" - } - ], - "scm/title": [ - { - "command": "githubBrowser.commit", - "group": "navigation", - "when": "scmProvider == github" - } - ], - "scm/resourceState/context": [ - { - "command": "githubBrowser.openFile", - "when": "scmProvider == github && scmResourceGroup == github.changes", - "group": "inline@0" - }, - { - "command": "githubBrowser.discardChanges", - "when": "scmProvider == github && scmResourceGroup == github.changes", - "group": "inline@1" - }, - { - "command": "githubBrowser.openChanges", - "when": "scmProvider == github && scmResourceGroup == github.changes", - "group": "navigation@0" - }, - { - "command": "githubBrowser.openFile", - "when": "scmProvider == github && scmResourceGroup == github.changes", - "group": "navigation@1" - }, - { - "command": "githubBrowser.discardChanges", - "when": "scmProvider == github && scmResourceGroup == github.changes", - "group": "1_modification@0" - } - ] - }, - "resourceLabelFormatters": [ - { - "scheme": "github", - "authority": "HEAD", - "formatting": { - "label": "github.com${path}", - "separator": "/", - "workspaceSuffix": "GitHub" - } - }, - { - "scheme": "github", - "authority": "*", - "formatting": { - "label": "github.com${path} (${authority})", - "separator": "/", - "workspaceSuffix": "GitHub" - } - }, - { - "scheme": "codespace", - "authority": "HEAD", - "formatting": { - "label": "github.com${path}", - "separator": "/", - "workspaceSuffix": "GitHub" - } - }, - { - "scheme": "codespace", - "authority": "*", - "formatting": { - "label": "github.com${path} (${authority})", - "separator": "/", - "workspaceSuffix": "GitHub" - } - } - ] - }, - "scripts": { - "compile": "gulp compile-extension:github-browser", - "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", - "watch": "gulp watch-extension:github-browser", - "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose", - "vscode:prepublish": "npm run compile" - }, - "dependencies": { - "@octokit/graphql": "4.5.1", - "@octokit/rest": "18.0.0", - "fuzzysort": "1.1.4", - "node-fetch": "2.6.0", - "vscode-nls": "4.1.2" - }, - "devDependencies": { - "@types/node-fetch": "2.5.7" - } -} diff --git a/extensions/github-browser/package.nls.json b/extensions/github-browser/package.nls.json deleted file mode 100644 index 69f0f911776..00000000000 --- a/extensions/github-browser/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "GitHub Browser", - "description": "Remotely browse a GitHub repository" -} diff --git a/extensions/github-browser/src/changeStore.ts b/extensions/github-browser/src/changeStore.ts deleted file mode 100644 index f4bd624b9e7..00000000000 --- a/extensions/github-browser/src/changeStore.ts +++ /dev/null @@ -1,380 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; -import { commands, Event, EventEmitter, FileStat, FileType, Memento, TextDocumentShowOptions, Uri, ViewColumn } from 'vscode'; -import { getRootUri, getRelativePath, isChild } from './extension'; -import { sha1 } from './sha1'; - -const textDecoder = new TextDecoder(); - -interface CreateOperation { - type: 'created'; - size: number; - timestamp: number; - uri: T; - hash: string; - originalHash: string; -} - -interface ChangeOperation { - type: 'changed'; - size: number; - timestamp: number; - uri: T; - hash: string; - originalHash: string; -} - -interface DeleteOperation { - type: 'deleted'; - size: undefined; - timestamp: number; - uri: T; - hash: undefined; - originalHash: undefined; -} - -export type Operation = CreateOperation | ChangeOperation | DeleteOperation; -type StoredOperation = CreateOperation | ChangeOperation | DeleteOperation; - -const workingOperationsKeyPrefix = 'github.working.changes|'; -const workingFileKeyPrefix = 'github.working|'; - -function fromSerialized(operations: StoredOperation): Operation { - return { ...operations, uri: Uri.parse(operations.uri) }; -} - -export interface ChangeStoreEvent { - type: 'created' | 'changed' | 'deleted'; - rootUri: Uri; - uri: Uri; -} - -function toChangeStoreEvent(operation: Operation | StoredOperation, rootUri: Uri, uri?: Uri): ChangeStoreEvent { - return { - type: operation.type, - rootUri: rootUri, - uri: uri ?? (typeof operation.uri === 'string' ? Uri.parse(operation.uri) : operation.uri), - }; -} - -export interface IChangeStore { - onDidChange: Event; - - acceptAll(rootUri: Uri): Promise; - discard(uri: Uri): Promise; - discardAll(rootUri: Uri): Promise; - - hasChanges(rootUri: Uri): boolean; - - getChanges(rootUri: Uri): Operation[]; - getContent(uri: Uri): string | undefined; - - openChanges(uri: Uri, original: Uri): void; - openFile(uri: Uri): void; -} - -export interface IWritableChangeStore { - onDidChange: Event; - - hasChanges(rootUri: Uri): boolean; - - getContent(uri: Uri): string | undefined; - getStat(uri: Uri): FileStat | undefined; - updateDirectoryEntries(uri: Uri, entries: [string, FileType][]): [string, FileType][]; - - onFileChanged(uri: Uri, content: Uint8Array, originalContent: () => Uint8Array | Thenable): Promise; - onFileCreated(uri: Uri, content: Uint8Array): Promise; - onFileDeleted(uri: Uri): Promise; -} - -export class ChangeStore implements IChangeStore, IWritableChangeStore { - private _onDidChange = new EventEmitter(); - get onDidChange(): Event { - return this._onDidChange.event; - } - - constructor(private readonly memento: Memento) { } - - async acceptAll(rootUri: Uri): Promise { - const operations = this.getChanges(rootUri); - - await this.saveWorkingOperations(rootUri, undefined); - - const events: ChangeStoreEvent[] = []; - - for (const operation of operations) { - await this.discardWorkingContent(operation.uri); - events.push(toChangeStoreEvent(operation, rootUri)); - } - - for (const e of events) { - this._onDidChange.fire(e); - } - } - - async discard(uri: Uri): Promise { - const rootUri = getRootUri(uri); - if (rootUri === undefined) { - return; - } - - const key = uri.toString(); - - const operations = this.getWorkingOperations(rootUri); - const index = operations.findIndex(c => c.uri === key); - if (index === -1) { - return; - } - - const [operation] = operations.splice(index, 1); - await this.saveWorkingOperations(rootUri, operations); - await this.discardWorkingContent(uri); - - this._onDidChange.fire({ - type: operation.type === 'created' ? 'deleted' : operation.type === 'deleted' ? 'created' : 'changed', - rootUri: rootUri, - uri: uri, - }); - } - - async discardAll(rootUri: Uri): Promise { - const operations = this.getChanges(rootUri); - - await this.saveWorkingOperations(rootUri, undefined); - - const events: ChangeStoreEvent[] = []; - - for (const operation of operations) { - await this.discardWorkingContent(operation.uri); - events.push(toChangeStoreEvent(operation, rootUri)); - } - - for (const e of events) { - this._onDidChange.fire(e); - } - } - - getChanges(rootUri: Uri) { - return this.getWorkingOperations(rootUri).map(c => fromSerialized(c)); - } - - getContent(uri: Uri): string | undefined { - return this.memento.get(`${workingFileKeyPrefix}${uri.toString()}`); - } - - getStat(uri: Uri): FileStat | undefined { - const key = uri.toString(); - const operation = this.getChanges(getRootUri(uri)!).find(c => c.uri.toString() === key); - if (operation === undefined) { - return undefined; - } - - return { - type: FileType.File, - size: operation.size ?? 0, - ctime: 0, - mtime: operation.timestamp - }; - } - - hasChanges(rootUri: Uri): boolean { - return this.getWorkingOperations(rootUri).length !== 0; - } - - updateDirectoryEntries(uri: Uri, entries: [string, FileType][]): [string, FileType][] { - const rootUri = getRootUri(uri); - if (rootUri === undefined) { - return entries; - } - - const folderPath = getRelativePath(rootUri, uri); - - const operations = this.getChanges(rootUri); - for (const operation of operations) { - switch (operation.type) { - case 'changed': - continue; - - case 'created': { - const filePath = getRelativePath(rootUri, operation.uri); - if (isChild(folderPath, filePath)) { - entries.push([filePath, FileType.File]); - } - break; - } - - case 'deleted': { - const filePath = getRelativePath(rootUri, operation.uri); - if (isChild(folderPath, filePath)) { - const index = entries.findIndex(([path]) => path === filePath); - if (index !== -1) { - entries.splice(index, 1); - } - } - break; - } - } - } - - return entries; - } - - async onFileChanged(uri: Uri, content: Uint8Array, originalContent: () => Uint8Array | Thenable): Promise { - const rootUri = getRootUri(uri); - if (rootUri === undefined) { - return; - } - - const key = uri.toString(); - - const operations = this.getWorkingOperations(rootUri); - - const hash = await sha1(content); - - let operation = operations.find(c => c.uri === key); - if (operation === undefined) { - const originalHash = await sha1(await originalContent!()); - if (hash === originalHash) { - return; - } - - operation = { - type: 'changed', - size: content.byteLength, - timestamp: Date.now(), - uri: key, - hash: hash!, - originalHash: originalHash - } as ChangeOperation; - operations.push(operation); - - await this.saveWorkingOperations(rootUri, operations); - await this.saveWorkingContent(uri, textDecoder.decode(content)); - } else if (hash! === operation.originalHash) { - operations.splice(operations.indexOf(operation), 1); - - await this.saveWorkingOperations(rootUri, operations); - await this.discardWorkingContent(uri); - } else if (operation.hash !== hash) { - operation.hash = hash!; - operation.timestamp = Date.now(); - - await this.saveWorkingOperations(rootUri, operations); - await this.saveWorkingContent(uri, textDecoder.decode(content)); - } - - this._onDidChange.fire(toChangeStoreEvent(operation, rootUri, uri)); - } - - async onFileCreated(uri: Uri, content: Uint8Array): Promise { - const rootUri = getRootUri(uri); - if (rootUri === undefined) { - return; - } - - const key = uri.toString(); - - const operations = this.getWorkingOperations(rootUri); - - const hash = await sha1(content); - - let operation = operations.find(c => c.uri === key); - if (operation === undefined) { - operation = { - type: 'created', - size: content.byteLength, - timestamp: Date.now(), - uri: key, - hash: hash!, - originalHash: hash! - } as CreateOperation; - operations.push(operation); - - await this.saveWorkingOperations(rootUri, operations); - await this.saveWorkingContent(uri, textDecoder.decode(content)); - } else { - // Shouldn't happen, but if it does just update the contents - operation.hash = hash!; - operation.timestamp = Date.now(); - - await this.saveWorkingOperations(rootUri, operations); - await this.saveWorkingContent(uri, textDecoder.decode(content)); - } - - this._onDidChange.fire(toChangeStoreEvent(operation, rootUri, uri)); - } - - async onFileDeleted(uri: Uri): Promise { - const rootUri = getRootUri(uri); - if (rootUri === undefined) { - return; - } - - const key = uri.toString(); - - const operations = this.getWorkingOperations(rootUri); - - let operation = operations.find(c => c.uri === key); - if (operation !== undefined) { - operations.splice(operations.indexOf(operation), 1); - } - - const wasCreated = operation?.type === 'created'; - - operation = { - type: 'deleted', - timestamp: Date.now(), - uri: key, - } as DeleteOperation; - - // Only track the delete, if we weren't tracking the create - if (!wasCreated) { - operations.push(operation); - } - - await this.saveWorkingOperations(rootUri, operations); - await this.discardWorkingContent(uri); - - this._onDidChange.fire(toChangeStoreEvent(operation, rootUri, uri)); - } - - async openChanges(uri: Uri, original: Uri) { - const opts: TextDocumentShowOptions = { - preserveFocus: false, - preview: true, - viewColumn: ViewColumn.Active - }; - - await commands.executeCommand('vscode.diff', original, uri, `${uri.fsPath} (Working Tree)`, opts); - } - - async openFile(uri: Uri) { - const opts: TextDocumentShowOptions = { - preserveFocus: false, - preview: false, - viewColumn: ViewColumn.Active - }; - - await commands.executeCommand('vscode.open', uri, opts); - } - - private getWorkingOperations(rootUri: Uri): StoredOperation[] { - return this.memento.get(`${workingOperationsKeyPrefix}${rootUri.toString()}`, []); - } - - private async saveWorkingOperations(rootUri: Uri, operations: StoredOperation[] | undefined): Promise { - await this.memento.update(`${workingOperationsKeyPrefix}${rootUri.toString()}`, operations); - } - - private async saveWorkingContent(uri: Uri, content: string): Promise { - await this.memento.update(`${workingFileKeyPrefix}${uri.toString()}`, content); - } - - private async discardWorkingContent(uri: Uri): Promise { - await this.memento.update(`${workingFileKeyPrefix}${uri.toString()}`, undefined); - } -} diff --git a/extensions/github-browser/src/contextStore.ts b/extensions/github-browser/src/contextStore.ts deleted file mode 100644 index 80286445dfe..00000000000 --- a/extensions/github-browser/src/contextStore.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; -import { Event, EventEmitter, Memento, Uri, workspace } from 'vscode'; - -export interface WorkspaceFolderContext { - context: T; - name: string; - folderUri: Uri; -} - -export class ContextStore { - private _onDidChange = new EventEmitter(); - get onDidChange(): Event { - return this._onDidChange.event; - } - - constructor( - private readonly scheme: string, - private readonly originalScheme: string, - private readonly memento: Memento, - ) { } - - delete(uri: Uri) { - return this.set(uri, undefined); - } - - get(uri: Uri): T | undefined { - return this.memento.get(`${this.originalScheme}.context|${this.getOriginalResource(uri).toString()}`); - } - - getForWorkspace(): WorkspaceFolderContext[] { - const folders = workspace.workspaceFolders?.filter(f => f.uri.scheme === this.scheme || f.uri.scheme === this.originalScheme) ?? []; - return folders.map(f => ({ context: this.get(f.uri)!, name: f.name, folderUri: f.uri })).filter(c => c.context !== undefined); - } - - async set(uri: Uri, context: T | undefined) { - uri = this.getOriginalResource(uri); - await this.memento.update(`${this.originalScheme}.context|${uri.toString()}`, context); - this._onDidChange.fire(uri); - } - - getOriginalResource(uri: Uri): Uri { - return uri.with({ scheme: this.originalScheme }); - } - - getWorkspaceResource(uri: Uri): Uri { - return uri.with({ scheme: this.scheme }); - } -} diff --git a/extensions/github-browser/src/extension.ts b/extensions/github-browser/src/extension.ts deleted file mode 100644 index 893daf93c58..00000000000 --- a/extensions/github-browser/src/extension.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { commands, ExtensionContext, Uri, window, workspace } from 'vscode'; -import { ChangeStore } from './changeStore'; -import { ContextStore } from './contextStore'; -import { VirtualFS } from './fs'; -import { GitHubApiContext, GitHubApi } from './github/api'; -import { GitHubFS } from './github/fs'; -import { VirtualSCM } from './scm'; -import { StatusBar } from './statusbar'; - -const repositoryRegex = /^(?:(?:https:\/\/)?github.com\/)?([^\/]+)\/([^\/]+?)(?:\/|.git|$)/i; - -export async function activate(context: ExtensionContext) { - const contextStore = new ContextStore('codespace', GitHubFS.scheme, context.workspaceState); - const changeStore = new ChangeStore(context.workspaceState); - - const githubApi = new GitHubApi(contextStore); - const gitHubFS = new GitHubFS(githubApi); - const virtualFS = new VirtualFS('codespace', contextStore, changeStore, gitHubFS); - - context.subscriptions.push( - githubApi, - gitHubFS, - virtualFS, - new VirtualSCM(GitHubFS.scheme, githubApi, changeStore), - new StatusBar(contextStore, changeStore), - ); - - commands.registerCommand('githubBrowser.openRepository', async () => { - const value = await window.showInputBox({ - placeHolder: 'e.g. https://github.com/microsoft/vscode', - prompt: 'Enter a GitHub repository url', - validateInput: value => repositoryRegex.test(value) ? undefined : 'Invalid repository url' - }); - - if (value) { - const match = repositoryRegex.exec(value); - if (match) { - const [, owner, repo] = match; - - const uri = Uri.parse(`codespace://HEAD/${owner}/${repo}`); - openWorkspace(uri, repo, 'currentWindow'); - } - } - }); -} - -export function getRelativePath(rootUri: Uri, uri: Uri) { - return uri.path.substr(rootUri.path.length + 1); -} - -export function getRootUri(uri: Uri) { - return workspace.getWorkspaceFolder(uri)?.uri; -} - -export function isChild(folderPath: string, filePath: string) { - return isDescendent(folderPath, filePath) && filePath.substr(folderPath.length + (folderPath.endsWith('/') ? 0 : 1)).split('/').length === 1; -} - -export function isDescendent(folderPath: string, filePath: string) { - return folderPath.length === 0 || filePath.startsWith(folderPath.endsWith('/') ? folderPath : `${folderPath}/`); -} - -const shaRegex = /^[0-9a-f]{40}$/; -export function isSha(ref: string) { - return shaRegex.test(ref); -} - -function openWorkspace(uri: Uri, name: string, location: 'currentWindow' | 'newWindow' | 'addToCurrentWorkspace') { - if (location === 'addToCurrentWorkspace') { - const count = (workspace.workspaceFolders && workspace.workspaceFolders.length) || 0; - return workspace.updateWorkspaceFolders(count, 0, { uri: uri, name: name }); - } - - return commands.executeCommand('vscode.openFolder', uri, location === 'newWindow'); -} diff --git a/extensions/github-browser/src/fs.ts b/extensions/github-browser/src/fs.ts deleted file mode 100644 index 56af40f21ba..00000000000 --- a/extensions/github-browser/src/fs.ts +++ /dev/null @@ -1,216 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; -import { - CancellationToken, - Disposable, - Event, - EventEmitter, - FileChangeEvent, - FileChangeType, - FileSearchOptions, - FileSearchProvider, - FileSearchQuery, - FileStat, - FileSystemError, - FileSystemProvider, - FileType, - Progress, - TextSearchOptions, - TextSearchProvider, - TextSearchQuery, - TextSearchResult, - Uri, - workspace, -} from 'vscode'; -import { IWritableChangeStore } from './changeStore'; -import { ContextStore } from './contextStore'; -import { GitHubApiContext } from './github/api'; - -const emptyDisposable = { dispose: () => { /* noop */ } }; -const textEncoder = new TextEncoder(); - -export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSearchProvider, Disposable { - private _onDidChangeFile = new EventEmitter(); - get onDidChangeFile(): Event { - return this._onDidChangeFile.event; - } - - private readonly disposable: Disposable; - - constructor( - readonly scheme: string, - private readonly contextStore: ContextStore, - private readonly changeStore: IWritableChangeStore, - private readonly fs: FileSystemProvider & FileSearchProvider & TextSearchProvider - ) { - // TODO@eamodio listen for workspace folder changes - for (const context of contextStore.getForWorkspace()) { - // If we have a saved context, but no longer have any changes, reset the context - // We only do this on startup/reload to keep things consistent - if (!changeStore.hasChanges(context.folderUri)) { - console.log('Clear context', context.folderUri.toString()); - contextStore.delete(context.folderUri); - } - } - - this.disposable = Disposable.from( - workspace.registerFileSystemProvider(scheme, this, { isCaseSensitive: true }), - workspace.registerFileSearchProvider(scheme, this), - workspace.registerTextSearchProvider(scheme, this), - changeStore.onDidChange(e => { - switch (e.type) { - case 'created': - this._onDidChangeFile.fire([{ type: FileChangeType.Created, uri: e.uri }]); - break; - case 'changed': - this._onDidChangeFile.fire([{ type: FileChangeType.Changed, uri: e.uri }]); - break; - case 'deleted': - this._onDidChangeFile.fire([{ type: FileChangeType.Deleted, uri: e.uri }]); - break; - } - }), - ); - } - - dispose() { - this.disposable?.dispose(); - } - - private getOriginalResource(uri: Uri): Uri { - return this.contextStore.getOriginalResource(uri); - } - - private getWorkspaceResource(uri: Uri): Uri { - return this.contextStore.getWorkspaceResource(uri); - } - - //#region FileSystemProvider - - watch(): Disposable { - return emptyDisposable; - } - - async stat(uri: Uri): Promise { - let stat = this.changeStore.getStat(uri); - if (stat !== undefined) { - return stat; - } - - stat = await this.fs.stat(this.getOriginalResource(uri)); - return stat; - } - - async readDirectory(uri: Uri): Promise<[string, FileType][]> { - let entries = await this.fs.readDirectory(this.getOriginalResource(uri)); - entries = this.changeStore.updateDirectoryEntries(uri, entries); - return entries; - } - - createDirectory(_uri: Uri): void | Thenable { - // TODO@eamodio only support files for now - throw FileSystemError.NoPermissions(); - } - - async readFile(uri: Uri): Promise { - const content = this.changeStore.getContent(uri); - if (content !== undefined) { - return textEncoder.encode(content); - } - - const data = await this.fs.readFile(this.getOriginalResource(uri)); - return data; - } - - async writeFile(uri: Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): Promise { - let stat; - try { - stat = await this.stat(uri); - if (!options.overwrite) { - throw FileSystemError.FileExists(); - } - } catch (ex) { - if (ex instanceof FileSystemError && ex.code === 'FileNotFound') { - if (!options.create) { - throw FileSystemError.FileNotFound(); - } - } else { - throw ex; - } - } - - if (stat === undefined) { - await this.changeStore.onFileCreated(uri, content); - } else { - await this.changeStore.onFileChanged(uri, content, () => this.fs.readFile(this.getOriginalResource(uri))); - } - } - - async delete(uri: Uri, _options: { recursive: boolean }): Promise { - const stat = await this.stat(uri); - if (stat.type !== FileType.File) { - throw FileSystemError.NoPermissions(); - } - - await this.changeStore.onFileDeleted(uri); - } - - async rename(oldUri: Uri, newUri: Uri, options: { overwrite: boolean }): Promise { - const stat = await this.stat(oldUri); - // TODO@eamodio only support files for now - if (stat.type !== FileType.File) { - throw FileSystemError.NoPermissions(); - } - - const content = await this.readFile(oldUri); - await this.writeFile(newUri, content, { create: true, overwrite: options.overwrite }); - await this.delete(oldUri, { recursive: false }); - } - - async copy(source: Uri, destination: Uri, options: { overwrite: boolean }): Promise { - const stat = await this.stat(source); - // TODO@eamodio only support files for now - if (stat.type !== FileType.File) { - throw FileSystemError.NoPermissions(); - } - - const content = await this.readFile(source); - await this.writeFile(destination, content, { create: true, overwrite: options.overwrite }); - } - - //#endregion - - //#region FileSearchProvider - - provideFileSearchResults( - query: FileSearchQuery, - options: FileSearchOptions, - token: CancellationToken, - ) { - return this.fs.provideFileSearchResults(query, { ...options, folder: this.getOriginalResource(options.folder) }, token); - } - - //#endregion - - //#region TextSearchProvider - - provideTextSearchResults( - query: TextSearchQuery, - options: TextSearchOptions, - progress: Progress, - token: CancellationToken, - ) { - return this.fs.provideTextSearchResults( - query, - { ...options, folder: this.getOriginalResource(options.folder) }, - { report: (result: TextSearchResult) => progress.report({ ...result, uri: this.getWorkspaceResource(result.uri) }) }, - token - ); - } - - //#endregion -} diff --git a/extensions/github-browser/src/gate.ts b/extensions/github-browser/src/gate.ts deleted file mode 100644 index d49762dc748..00000000000 --- a/extensions/github-browser/src/gate.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const emptyStr = ''; - -function defaultResolver(...args: any[]): string { - if (args.length === 1) { - const arg0 = args[0]; - if (arg0 === undefined || arg0 === null) { - return emptyStr; - } - if (typeof arg0 === 'string') { - return arg0; - } - if (typeof arg0 === 'number' || typeof arg0 === 'boolean') { - return String(arg0); - } - - return JSON.stringify(arg0); - } - - return JSON.stringify(args); -} - -function iPromise(obj: T | Promise): obj is Promise { - return typeof (obj as Promise)?.then === 'function'; -} - -export function gate any>(resolver?: (...args: Parameters) => string) { - return (_target: any, key: string, descriptor: PropertyDescriptor) => { - let fn: Function | undefined; - if (typeof descriptor.value === 'function') { - fn = descriptor.value; - } else if (typeof descriptor.get === 'function') { - fn = descriptor.get; - } - if (fn === undefined || fn === null) { - throw new Error('Not supported'); - } - - const gateKey = `$gate$${key}`; - - descriptor.value = function (this: any, ...args: any[]) { - const prop = - args.length === 0 ? gateKey : `${gateKey}$${(resolver ?? defaultResolver)(...(args as Parameters))}`; - - if (!Object.prototype.hasOwnProperty.call(this, prop)) { - Object.defineProperty(this, prop, { - configurable: false, - enumerable: false, - writable: true, - value: undefined, - }); - } - - let promise = this[prop]; - if (promise === undefined) { - let result; - try { - result = fn!.apply(this, args); - if (result === undefined || fn === null || !iPromise(result)) { - return result; - } - - this[prop] = promise = result - .then((r: any) => { - this[prop] = undefined; - return r; - }) - .catch(ex => { - this[prop] = undefined; - throw ex; - }); - } catch (ex) { - this[prop] = undefined; - throw ex; - } - } - - return promise; - }; - }; -} diff --git a/extensions/github-browser/src/github/api.ts b/extensions/github-browser/src/github/api.ts deleted file mode 100644 index bb2bb65cb6d..00000000000 --- a/extensions/github-browser/src/github/api.ts +++ /dev/null @@ -1,504 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { authentication, AuthenticationSession, Disposable, Event, EventEmitter, Range, Uri } from 'vscode'; -import { graphql } from '@octokit/graphql'; -import { Octokit } from '@octokit/rest'; -import { ContextStore } from '../contextStore'; -import { fromGitHubUri } from './fs'; -import { isSha } from '../extension'; -import { Iterables } from '../iterables'; - -export interface GitHubApiContext { - requestRef: string; - - branch: string; - sha: string | undefined; - timestamp: number; -} - -interface CreateCommitOperation { - type: 'created'; - path: string; - content: string -} - -interface ChangeCommitOperation { - type: 'changed'; - path: string; - content: string -} - -interface DeleteCommitOperation { - type: 'deleted'; - path: string; - content: undefined -} - -export type CommitOperation = CreateCommitOperation | ChangeCommitOperation | DeleteCommitOperation; - -type ArrayElement> = T extends (infer U)[] ? U : never; -type GitCreateTreeParamsTree = ArrayElement[0]>['tree']>; - -function getGitHubRootUri(uri: Uri) { - const rootIndex = uri.path.indexOf('/', uri.path.indexOf('/', 1) + 1); - return uri.with({ - path: uri.path.substring(0, rootIndex === -1 ? undefined : rootIndex), - query: '' - }); -} - -export class GitHubApi implements Disposable { - private _onDidChangeContext = new EventEmitter(); - get onDidChangeContext(): Event { - return this._onDidChangeContext.event; - } - - private readonly disposable: Disposable; - - constructor(private readonly context: ContextStore) { - this.disposable = Disposable.from( - context.onDidChange(e => this._onDidChangeContext.fire(e)) - ); - } - - dispose() { - this.disposable.dispose(); - } - - private _session: AuthenticationSession | undefined; - async ensureAuthenticated() { - if (this._session === undefined) { - const providers = await authentication.getProviderIds(); - if (!providers.includes('github')) { - await new Promise(resolve => { - authentication.onDidChangeAuthenticationProviders(e => { - if (e.added.find(provider => provider.id === 'github')) { - resolve(); - } - }); - }); - } - - this._session = await authentication.getSession('github', ['repo'], { createIfNone: true }); - } - - return this._session; - } - - private _graphql: typeof graphql | undefined; - private async graphql() { - if (this._graphql === undefined) { - const session = await this.ensureAuthenticated(); - this._graphql = graphql.defaults({ - headers: { - Authorization: `Bearer ${session.accessToken}`, - } - }); - } - - return this._graphql; - } - - private _octokit: typeof Octokit | undefined; - private async octokit(options?: ConstructorParameters[0]) { - if (this._octokit === undefined) { - const session = await this.ensureAuthenticated(); - this._octokit = Octokit.defaults({ auth: `token ${session.accessToken}` }); - } - return new this._octokit(options); - } - - async commit(rootUri: Uri, message: string, operations: CommitOperation[]): Promise { - const { owner, repo } = fromGitHubUri(rootUri); - - try { - const context = await this.getContext(rootUri); - if (context.sha === undefined) { - throw new Error(`Cannot commit to Uri(${rootUri.toString(true)}); Invalid context sha`); - } - - const hasDeletes = operations.some(op => op.type === 'deleted'); - - const github = await this.octokit(); - const treeResp = await github.git.getTree({ - owner: owner, - repo: repo, - tree_sha: context.sha, - recursive: hasDeletes ? 'true' : undefined, - }); - - // 0100000000000000 (040000): Directory - // 1000000110100100 (100644): Regular non-executable file - // 1000000110110100 (100664): Regular non-executable group-writeable file - // 1000000111101101 (100755): Regular executable file - // 1010000000000000 (120000): Symbolic link - // 1110000000000000 (160000): Gitlink - let updatedTree: GitCreateTreeParamsTree[]; - - if (hasDeletes) { - updatedTree = treeResp.data.tree as GitCreateTreeParamsTree[]; - - for (const operation of operations) { - switch (operation.type) { - case 'created': - updatedTree.push({ path: operation.path, mode: '100644', type: 'blob', content: operation.content }); - break; - - case 'changed': { - const index = updatedTree.findIndex(item => item.path === operation.path); - if (index !== -1) { - const { path, mode, type } = updatedTree[index]; - updatedTree.splice(index, 1, { path: path, mode: mode, type: type, content: operation.content }); - } - break; - } - case 'deleted': { - const index = updatedTree.findIndex(item => item.path === operation.path); - if (index !== -1) { - updatedTree.splice(index, 1); - } - break; - } - } - } - } else { - updatedTree = []; - - for (const operation of operations) { - switch (operation.type) { - case 'created': - updatedTree.push({ path: operation.path, mode: '100644', type: 'blob', content: operation.content }); - break; - - case 'changed': - const item = treeResp.data.tree.find(item => item.path === operation.path) as GitCreateTreeParamsTree; - if (item !== undefined) { - const { path, mode, type } = item; - updatedTree.push({ path: path, mode: mode, type: type, content: operation.content }); - } - break; - } - } - } - - const updatedTreeResp = await github.git.createTree({ - owner: owner, - repo: repo, - base_tree: hasDeletes ? undefined : treeResp.data.sha, - tree: updatedTree - }); - - const resp = await github.git.createCommit({ - owner: owner, - repo: repo, - message: message, - tree: updatedTreeResp.data.sha, - parents: [context.sha] - }); - - this.updateContext(rootUri, { ...context, sha: resp.data.sha, timestamp: Date.now() }); - - // TODO@eamodio need to send a file change for any open files - - await github.git.updateRef({ - owner: owner, - repo: repo, - ref: `heads/${context.branch}`, - sha: resp.data.sha - }); - - return resp.data.sha; - } catch (ex) { - console.log(ex); - throw ex; - } - } - - async defaultBranchQuery(uri: Uri) { - const { owner, repo } = fromGitHubUri(uri); - - try { - const query = `query defaultBranch($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - defaultBranchRef { - name - } - } -}`; - - const rsp = await this.gqlQuery<{ - repository: { defaultBranchRef: { name: string; target: { oid: string } } | null | undefined }; - }>(query, { - owner: owner, - repo: repo, - }); - return rsp?.repository?.defaultBranchRef?.name ?? undefined; - } catch (ex) { - return undefined; - } - } - - async filesQuery(uri: Uri) { - const { owner, repo, ref } = fromGitHubUri(uri); - - try { - const context = await this.getContext(uri); - - const resp = await (await this.octokit()).git.getTree({ - owner: owner, - repo: repo, - recursive: '1', - tree_sha: context?.sha ?? ref, - }); - return Iterables.filterMap(resp.data.tree, p => p.type === 'blob' ? p.path : undefined); - } catch (ex) { - return []; - } - } - - async fsQuery(uri: Uri, innerQuery: string): Promise { - const { owner, repo, path, ref } = fromGitHubUri(uri); - - try { - const context = await this.getContext(uri); - - const query = `query fs($owner: String!, $repo: String!, $path: String) { - repository(owner: $owner, name: $repo) { - object(expression: $path) { - ${innerQuery} - } - } -}`; - - const rsp = await this.gqlQuery<{ - repository: { object: T | null | undefined }; - }>(query, { - owner: owner, - repo: repo, - path: `${context.sha ?? ref}:${path}`, - }); - return rsp?.repository?.object ?? undefined; - } catch (ex) { - return undefined; - } - } - - async latestCommitQuery(uri: Uri) { - const { owner, repo, ref } = fromGitHubUri(uri); - - try { - if (ref === 'HEAD') { - const query = `query latest($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - defaultBranchRef { - target { - oid - } - } - } -}`; - - const rsp = await this.gqlQuery<{ - repository: { defaultBranchRef: { name: string; target: { oid: string } } | null | undefined }; - }>(query, { - owner: owner, - repo: repo, - }); - return rsp?.repository?.defaultBranchRef?.target.oid ?? undefined; - } - - const query = `query latest($owner: String!, $repo: String!, $ref: String!) { - repository(owner: $owner, name: $repo) { - ref(qualifiedName: $ref) { - target { - oid - } - } - } -}`; - - const rsp = await this.gqlQuery<{ - repository: { ref: { target: { oid: string } } | null | undefined }; - }>(query, { - owner: owner, - repo: repo, - ref: ref ?? 'HEAD', - }); - return rsp?.repository?.ref?.target.oid ?? undefined; - } catch (ex) { - return undefined; - } - } - - async searchQuery( - query: string, - uri: Uri, - options: { maxResults?: number; context?: { before?: number; after?: number } }, - ): Promise { - const { owner, repo, ref } = fromGitHubUri(uri); - - // If we have a specific ref, don't try to search, because GitHub search only works against the default branch - if (ref !== 'HEAD') { - return { matches: [], limitHit: true }; - } - - try { - const resp = await (await this.octokit({ - request: { - headers: { - accept: 'application/vnd.github.v3.text-match+json', - }, - } - })).search.code({ - q: `${query} repo:${owner}/${repo}`, - }); - - // Since GitHub doesn't return ANY line numbers just fake it at the top of the file 😢 - const range = new Range(0, 0, 0, 0); - - const matches: SearchQueryMatch[] = []; - - let counter = 0; - let match: SearchQueryMatch; - for (const item of resp.data.items) { - for (const m of (item as typeof item & { text_matches: GitHubSearchTextMatch[] }).text_matches) { - counter++; - if (options.maxResults !== undefined && counter > options.maxResults) { - return { matches: matches, limitHit: true }; - } - - match = { - path: item.path, - ranges: [], - preview: m.fragment, - matches: [], - }; - - for (const lm of m.matches) { - let line = 0; - let shartChar = 0; - let endChar = 0; - for (let i = 0; i < lm.indices[1]; i++) { - if (i === lm.indices[0]) { - shartChar = endChar; - } - - if (m.fragment[i] === '\n') { - line++; - endChar = 0; - } else { - endChar++; - } - } - - match.ranges.push(range); - match.matches.push(new Range(line, shartChar, line, endChar)); - } - - matches.push(match); - } - } - - return { matches: matches, limitHit: false }; - } catch (ex) { - return { matches: [], limitHit: true }; - } - } - - private async gqlQuery(query: string, variables: { [key: string]: string | number }): Promise { - return (await this.graphql())(query, variables); - } - - private readonly pendingContextRequests = new Map>(); - async getContext(uri: Uri): Promise { - const rootUri = getGitHubRootUri(uri); - - let pending = this.pendingContextRequests.get(rootUri.toString()); - if (pending === undefined) { - pending = this.getContextCore(rootUri); - this.pendingContextRequests.set(rootUri.toString(), pending); - } - - try { - return await pending; - } finally { - this.pendingContextRequests.delete(rootUri.toString()); - } - } - - private readonly rootUriToContextMap = new Map(); - - private async getContextCore(rootUri: Uri): Promise { - const key = rootUri.toString(); - let context = this.rootUriToContextMap.get(key); - - // Check if we have a cached a context - if (context?.sha !== undefined) { - return context; - } - - // Check if we have a saved context - context = this.context.get(rootUri); - if (context?.sha !== undefined) { - this.rootUriToContextMap.set(key, context); - - return context; - } - - const { ref } = fromGitHubUri(rootUri); - - // If the requested ref looks like a sha, then use it - if (isSha(ref)) { - context = { requestRef: ref, branch: ref, sha: ref, timestamp: Date.now() }; - } else { - let branch; - if (ref === 'HEAD') { - branch = await this.defaultBranchQuery(rootUri); - if (branch === undefined) { - throw new Error(`Cannot get context for Uri(${rootUri.toString(true)}); unable to get default branch`); - } - } else { - branch = ref; - } - - // Query for the latest sha for the give ref - const sha = await this.latestCommitQuery(rootUri); - context = { requestRef: ref, branch: branch, sha: sha, timestamp: Date.now() }; - } - - this.updateContext(rootUri, context); - - return context; - } - - private updateContext(rootUri: Uri, context: GitHubApiContext) { - this.rootUriToContextMap.set(rootUri.toString(), context); - this.context.set(rootUri, context); - } -} - -interface GitHubSearchTextMatch { - object_url: string; - object_type: string; - property: string; - fragment: string; - matches: { - text: string; - indices: number[]; - }[]; -} - -interface SearchQueryMatch { - path: string; - ranges: Range[]; - preview: string; - matches: Range[]; -} - -interface SearchQueryResults { - matches: SearchQueryMatch[]; - limitHit: boolean; -} diff --git a/extensions/github-browser/src/github/fs.ts b/extensions/github-browser/src/github/fs.ts deleted file mode 100644 index d0af10751c0..00000000000 --- a/extensions/github-browser/src/github/fs.ts +++ /dev/null @@ -1,332 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; -import { - CancellationToken, - Disposable, - Event, - EventEmitter, - FileChangeEvent, - FileSearchOptions, - FileSearchProvider, - FileSearchQuery, - FileStat, - FileSystemError, - FileSystemProvider, - FileType, - Progress, - TextSearchComplete, - TextSearchOptions, - TextSearchProvider, - TextSearchQuery, - TextSearchResult, - Uri, - workspace, -} from 'vscode'; -import * as fuzzySort from 'fuzzysort'; -import fetch from 'node-fetch'; -import { GitHubApi } from './api'; -import { Iterables } from '../iterables'; -import { getRootUri } from '../extension'; - -const emptyDisposable = { dispose: () => { /* noop */ } }; -const replaceBackslashRegex = /(\/|\\)/g; -const textEncoder = new TextEncoder(); - -interface Fuzzysort extends Fuzzysort.Fuzzysort { - prepareSlow(target: string): Fuzzysort.Prepared; - cleanup(): void; -} - -export class GitHubFS implements FileSystemProvider, FileSearchProvider, TextSearchProvider, Disposable { - static scheme = 'github'; - - private _onDidChangeFile = new EventEmitter(); - get onDidChangeFile(): Event { - return this._onDidChangeFile.event; - } - - private readonly disposable: Disposable; - private fsCache = new Map>(); - - constructor(private readonly github: GitHubApi) { - this.disposable = Disposable.from( - workspace.registerFileSystemProvider(GitHubFS.scheme, this, { - isCaseSensitive: true, - isReadonly: true - }), - workspace.registerFileSearchProvider(GitHubFS.scheme, this), - workspace.registerTextSearchProvider(GitHubFS.scheme, this), - github.onDidChangeContext(e => this.fsCache.delete(e.toString())) - ); - } - - dispose() { - this.disposable?.dispose(); - } - - private getCache(uri: Uri) { - const rootUri = getRootUri(uri); - if (rootUri === undefined) { - return undefined; - } - - let cache = this.fsCache.get(rootUri.toString()); - if (cache === undefined) { - cache = new Map(); - this.fsCache.set(rootUri.toString(), cache); - } - return cache; - } - - //#region FileSystemProvider - - watch(): Disposable { - return emptyDisposable; - } - - async stat(uri: Uri): Promise { - if (uri.path === '' || uri.path.lastIndexOf('/') === 0) { - const context = await this.github.getContext(uri); - return { type: FileType.Directory, size: 0, ctime: 0, mtime: context?.timestamp }; - } - - const data = await this.fsQuery<{ - __typename: string; - byteSize: number | undefined; - }>( - uri, - `__typename - ...on Blob { - byteSize - }`, - this.getCache(uri), - ); - - if (data === undefined) { - throw FileSystemError.FileNotFound(); - } - - const context = await this.github.getContext(uri); - - return { - type: typenameToFileType(data.__typename), - size: data.byteSize ?? 0, - ctime: 0, - mtime: context?.timestamp, - }; - } - - async readDirectory(uri: Uri): Promise<[string, FileType][]> { - const data = await this.fsQuery<{ - entries: { name: string; type: string }[]; - }>( - uri, - `... on Tree { - entries { - name - type - } - }`, - this.getCache(uri), - ); - - return (data?.entries ?? []).map<[string, FileType]>(e => [ - e.name, - typenameToFileType(e.type), - ]); - } - - createDirectory(_uri: Uri): void | Thenable { - throw FileSystemError.NoPermissions(); - } - - async readFile(uri: Uri): Promise { - const data = await this.fsQuery<{ - oid: string; - isBinary: boolean; - text: string; - }>( - uri, - `... on Blob { - oid, - isBinary, - text - }`, - ); - - if (data?.isBinary) { - const { owner, repo, path } = fromGitHubUri(uri); - // e.g. https://raw.githubusercontent.com/eamodio/vscode-gitlens/HEAD/images/gitlens-icon.png - const downloadUri = uri.with({ - scheme: 'https', - authority: 'raw.githubusercontent.com', - path: `/${owner}/${repo}/HEAD/${path}`, - }); - - return downloadBinary(downloadUri); - } - - return textEncoder.encode(data?.text ?? ''); - } - - async writeFile(_uri: Uri, _content: Uint8Array, _options: { create: boolean, overwrite: boolean }): Promise { - throw FileSystemError.NoPermissions(); - } - - delete(_uri: Uri, _options: { recursive: boolean }): void | Thenable { - throw FileSystemError.NoPermissions(); - } - - rename(_oldUri: Uri, _newUri: Uri, _options: { overwrite: boolean }): void | Thenable { - throw FileSystemError.NoPermissions(); - } - - copy(_source: Uri, _destination: Uri, _options: { overwrite: boolean }): void | Thenable { - throw FileSystemError.NoPermissions(); - } - - //#endregion - - //#region FileSearchProvider - - private fileSearchCache = new Map(); - - async provideFileSearchResults( - query: FileSearchQuery, - options: FileSearchOptions, - token: CancellationToken, - ): Promise { - let searchable = this.fileSearchCache.get(options.folder.toString(true)); - if (searchable === undefined) { - const matches = await this.github.filesQuery(options.folder); - if (matches === undefined || token.isCancellationRequested) { - return []; - } - - searchable = [...Iterables.map(matches, m => (fuzzySort as Fuzzysort).prepareSlow(m))]; - this.fileSearchCache.set(options.folder.toString(true), searchable); - } - - if (options.maxResults === undefined || options.maxResults === 0 || options.maxResults >= searchable.length) { - const results = searchable.map(m => Uri.joinPath(options.folder, m.target)); - return results; - } - - const results = fuzzySort - .go(query.pattern.replace(replaceBackslashRegex, '/'), searchable, { - allowTypo: true, - limit: options.maxResults, - }) - .map(m => Uri.joinPath(options.folder, m.target)); - - (fuzzySort as Fuzzysort).cleanup(); - - return results; - } - - //#endregion - - //#region TextSearchProvider - - async provideTextSearchResults( - query: TextSearchQuery, - options: TextSearchOptions, - progress: Progress, - _token: CancellationToken, - ): Promise { - const results = await this.github.searchQuery( - query.pattern, - options.folder, - { maxResults: options.maxResults, context: { before: options.beforeContext, after: options.afterContext } }, - ); - if (results === undefined) { return { limitHit: true }; } - - let uri; - for (const m of results.matches) { - uri = Uri.joinPath(options.folder, m.path); - - progress.report({ - uri: uri, - ranges: m.ranges, - preview: { - text: m.preview, - matches: m.matches, - }, - }); - } - - return { limitHit: false }; - } - - //#endregion - - private async fsQuery(uri: Uri, query: string, cache?: Map): Promise { - const key = `${uri.toString()}:${getHashCode(query)}`; - - let data = cache?.get(key); - if (data !== undefined) { - return data as T; - } - - data = await this.github.fsQuery(uri, query); - cache?.set(key, data); - return data; - } -} - -async function downloadBinary(uri: Uri) { - const resp = await fetch(uri.toString()); - const array = new Uint8Array(await resp.arrayBuffer()); - return array; -} - -function typenameToFileType(typename: string | undefined | null) { - if (typename) { - typename = typename.toLocaleLowerCase(); - } - - switch (typename) { - case 'blob': - return FileType.File; - case 'tree': - return FileType.Directory; - default: - return FileType.Unknown; - } -} - -type RepoInfo = { owner: string; repo: string; path: string | undefined; ref: string }; -export function fromGitHubUri(uri: Uri): RepoInfo { - const [, owner, repo, ...rest] = uri.path.split('/'); - - let ref; - if (uri.authority) { - ref = uri.authority; - // The casing of HEAD is important for the GitHub api to work - if (/HEAD/i.test(ref)) { - ref = 'HEAD'; - } - } - return { owner: owner, repo: repo, path: rest.join('/'), ref: ref ?? 'HEAD' }; -} - -function getHashCode(s: string): number { - let hash = 0; - - if (s.length === 0) { - return hash; - } - - let char; - const len = s.length; - for (let i = 0; i < len; i++) { - char = s.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash |= 0; // Convert to 32bit integer - } - return hash; -} diff --git a/extensions/github-browser/src/iterables.ts b/extensions/github-browser/src/iterables.ts deleted file mode 100644 index 5679e2c81ec..00000000000 --- a/extensions/github-browser/src/iterables.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -export namespace Iterables { - export function* filterMap( - source: Iterable | IterableIterator, - predicateMapper: (item: T) => TMapped | undefined | null, - ): Iterable { - for (const item of source) { - const mapped = predicateMapper(item); - if (mapped !== undefined && mapped !== null) { - yield mapped; - } - } - } - - export function* map( - source: Iterable | IterableIterator, - mapper: (item: T) => TMapped, - ): Iterable { - for (const item of source) { - yield mapper(item); - } - } -} diff --git a/extensions/github-browser/src/scm.ts b/extensions/github-browser/src/scm.ts deleted file mode 100644 index 7a8e4292f22..00000000000 --- a/extensions/github-browser/src/scm.ts +++ /dev/null @@ -1,177 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; -import { CancellationToken, commands, Disposable, scm, SourceControl, SourceControlResourceGroup, SourceControlResourceState, Uri, window, workspace } from 'vscode'; -import * as nls from 'vscode-nls'; -import { IChangeStore } from './changeStore'; -import { GitHubApi, CommitOperation } from './github/api'; -import { getRelativePath } from './extension'; - -const localize = nls.loadMessageBundle(); - -interface ScmProvider { - sourceControl: SourceControl, - groups: SourceControlResourceGroup[] -} - -export class VirtualSCM implements Disposable { - private readonly providers: ScmProvider[] = []; - - private disposable: Disposable; - - constructor( - private readonly originalScheme: string, - private readonly github: GitHubApi, - private readonly changeStore: IChangeStore, - ) { - this.registerCommands(); - - // TODO@eamodio listen for workspace folder changes - for (const folder of workspace.workspaceFolders ?? []) { - this.createScmProvider(folder.uri, folder.name); - - for (const operation of changeStore.getChanges(folder.uri)) { - this.update(folder.uri, operation.uri); - } - } - - this.disposable = Disposable.from( - changeStore.onDidChange(e => this.update(e.rootUri, e.uri)), - ); - } - - dispose() { - this.disposable.dispose(); - } - - private registerCommands() { - commands.registerCommand('githubBrowser.commit', (sourceControl: SourceControl | undefined) => { - // TODO@eamodio remove this hack once I figure out why the args are missing - if (sourceControl === undefined && this.providers.length === 1) { - sourceControl = this.providers[0].sourceControl; - } - - if (sourceControl === undefined) { - return; - } - - this.commitChanges(sourceControl); - }); - - commands.registerCommand('githubBrowser.discardChanges', (resourceState: SourceControlResourceState) => - this.discardChanges(resourceState.resourceUri) - ); - - commands.registerCommand('githubBrowser.openChanges', (resourceState: SourceControlResourceState) => - this.openChanges(resourceState.resourceUri) - ); - - commands.registerCommand('githubBrowser.openFile', (resourceState: SourceControlResourceState) => - this.openFile(resourceState.resourceUri) - ); - } - - async commitChanges(sourceControl: SourceControl): Promise { - const operations = this.changeStore - .getChanges(sourceControl.rootUri!) - .map(operation => { - const path = getRelativePath(sourceControl.rootUri!, operation.uri); - switch (operation.type) { - case 'created': - return { type: operation.type, path: path, content: this.changeStore.getContent(operation.uri)! }; - case 'changed': - return { type: operation.type, path: path, content: this.changeStore.getContent(operation.uri)! }; - case 'deleted': - return { type: operation.type, path: path }; - } - }); - if (!operations.length) { - window.showInformationMessage(localize('no changes', "There are no changes to commit.")); - - return; - } - - const message = sourceControl.inputBox.value; - if (message) { - const sha = await this.github.commit(this.getOriginalResource(sourceControl.rootUri!), message, operations); - if (sha !== undefined) { - this.changeStore.acceptAll(sourceControl.rootUri!); - sourceControl.inputBox.value = ''; - } - } - } - - discardChanges(uri: Uri): Promise { - return this.changeStore.discard(uri); - } - - openChanges(uri: Uri) { - return this.changeStore.openChanges(uri, this.getOriginalResource(uri)); - } - - openFile(uri: Uri) { - return this.changeStore.openFile(uri); - } - - private update(rootUri: Uri, uri: Uri) { - const folder = workspace.getWorkspaceFolder(uri); - if (folder === undefined) { - return; - } - - const provider = this.createScmProvider(rootUri, folder.name); - const group = this.createChangesGroup(provider); - group.resourceStates = this.changeStore.getChanges(rootUri).map(op => { - const rs: SourceControlResourceState = { - decorations: { - strikeThrough: op.type === 'deleted' - }, - resourceUri: op.uri, - command: { - command: 'githubBrowser.openChanges', - title: 'Open Changes', - } - }; - rs.command!.arguments = [rs]; - return rs; - }); - } - - private createScmProvider(rootUri: Uri, name: string) { - let provider = this.providers.find(sc => sc.sourceControl.rootUri?.toString() === rootUri.toString()); - if (provider === undefined) { - const sourceControl = scm.createSourceControl('github', name, rootUri); - sourceControl.quickDiffProvider = { provideOriginalResource: uri => this.getOriginalResource(uri) }; - sourceControl.acceptInputCommand = { - command: 'githubBrowser.commit', - title: 'Commit', - arguments: [sourceControl] - }; - sourceControl.inputBox.placeholder = `Message (Ctrl+Enter to commit '${name}')`; - // sourceControl.inputBox.validateInput = value => value ? undefined : 'Invalid commit message'; - - provider = { sourceControl: sourceControl, groups: [] }; - this.createChangesGroup(provider); - this.providers.push(provider); - } - - return provider; - } - - private createChangesGroup(provider: ScmProvider) { - let group = provider.groups.find(g => g.id === 'github.changes'); - if (group === undefined) { - group = provider.sourceControl.createResourceGroup('github.changes', 'Changes'); - provider.groups.push(group); - } - - return group; - } - - private getOriginalResource(uri: Uri, _token?: CancellationToken): Uri { - return uri.with({ scheme: this.originalScheme }); - } -} diff --git a/extensions/github-browser/src/sha1.ts b/extensions/github-browser/src/sha1.ts deleted file mode 100644 index 0469b3c98a1..00000000000 --- a/extensions/github-browser/src/sha1.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const textDecoder = new TextDecoder(); -const textEncoder = new TextEncoder(); - -declare let WEBWORKER: boolean; - -export async function sha1(s: string | Uint8Array): Promise { - while (true) { - try { - if (WEBWORKER) { - const hash = await globalThis.crypto.subtle.digest({ name: 'sha-1' }, typeof s === 'string' ? textEncoder.encode(s) : s); - // Use encodeURIComponent to avoid issues with btoa and Latin-1 characters - return globalThis.btoa(encodeURIComponent(textDecoder.decode(hash))); - } else { - return (await import('crypto')).createHash('sha1').update(s).digest('base64'); - } - } catch (ex) { - if (ex instanceof ReferenceError) { - (global as any).WEBWORKER = false; - } - } - } -} diff --git a/extensions/github-browser/src/statusbar.ts b/extensions/github-browser/src/statusbar.ts deleted file mode 100644 index e5a049a8631..00000000000 --- a/extensions/github-browser/src/statusbar.ts +++ /dev/null @@ -1,99 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; -import { Disposable, StatusBarAlignment, StatusBarItem, Uri, window, workspace } from 'vscode'; -import { ChangeStoreEvent, IChangeStore } from './changeStore'; -import { GitHubApiContext } from './github/api'; -import { isSha } from './extension'; -import { ContextStore, WorkspaceFolderContext } from './contextStore'; - -export class StatusBar implements Disposable { - private readonly disposable: Disposable; - - private readonly items = new Map(); - - constructor( - private readonly contextStore: ContextStore, - private readonly changeStore: IChangeStore - ) { - this.disposable = Disposable.from( - contextStore.onDidChange(this.onContextsChanged, this), - changeStore.onDidChange(this.onChanged, this) - ); - - for (const context of this.contextStore.getForWorkspace()) { - this.createOrUpdateStatusBarItem(context); - } - } - - dispose() { - this.disposable?.dispose(); - this.items.forEach(i => i.dispose()); - } - - private createOrUpdateStatusBarItem(wc: WorkspaceFolderContext) { - let item = this.items.get(wc.folderUri.toString()); - if (item === undefined) { - item = window.createStatusBarItem({ - id: `githubBrowser.branch:${wc.folderUri.toString()}`, - name: `GitHub Browser: ${wc.name}`, - alignment: StatusBarAlignment.Left, - priority: 1000 - }); - } - - if (isSha(wc.context.branch)) { - item.text = `$(git-commit) ${wc.context.branch.substr(0, 8)}`; - item.tooltip = `${wc.name} \u2022 ${wc.context.branch.substr(0, 8)}`; - } else { - item.text = `$(git-branch) ${wc.context.branch}`; - item.tooltip = `${wc.name} \u2022 ${wc.context.branch}${wc.context.sha ? ` @ ${wc.context.sha?.substr(0, 8)}` : ''}`; - } - - const hasChanges = this.changeStore.hasChanges(wc.folderUri); - if (hasChanges) { - item.text += '*'; - } - - item.show(); - - this.items.set(wc.folderUri.toString(), item); - } - - private onContextsChanged(uri: Uri) { - const folder = workspace.getWorkspaceFolder(this.contextStore.getWorkspaceResource(uri)); - if (folder === undefined) { - return; - } - - const context = this.contextStore.get(uri); - if (context === undefined) { - return; - } - - this.createOrUpdateStatusBarItem({ - context: context, - name: folder.name, - folderUri: folder.uri, - }); - } - - private onChanged(e: ChangeStoreEvent) { - const item = this.items.get(e.rootUri.toString()); - if (item !== undefined) { - const hasChanges = this.changeStore.hasChanges(e.rootUri); - if (hasChanges) { - if (!item.text.endsWith('*')) { - item.text += '*'; - } - } else { - if (item.text.endsWith('*')) { - item.text = item.text.substr(0, item.text.length - 1); - } - } - } - } -} diff --git a/extensions/github-browser/src/typings/ref.d.ts b/extensions/github-browser/src/typings/ref.d.ts deleted file mode 100644 index 312efe5a30f..00000000000 --- a/extensions/github-browser/src/typings/ref.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/// -/// -/// diff --git a/extensions/github-browser/tsconfig.json b/extensions/github-browser/tsconfig.json deleted file mode 100644 index eb413a12602..00000000000 --- a/extensions/github-browser/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../shared.tsconfig.json", - "compilerOptions": { - "experimentalDecorators": true, - "lib": [ - "es2018", - "dom" - ], - "outDir": "./out" - }, - "include": [ - "src/**/*" - ] -} diff --git a/extensions/github-browser/yarn.lock b/extensions/github-browser/yarn.lock deleted file mode 100644 index 2c8f1ad9658..00000000000 --- a/extensions/github-browser/yarn.lock +++ /dev/null @@ -1,332 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@octokit/auth-token@^2.4.0": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.2.tgz#10d0ae979b100fa6b72fa0e8e63e27e6d0dbff8a" - integrity sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ== - dependencies: - "@octokit/types" "^5.0.0" - -"@octokit/core@^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.1.0.tgz#9c3c9b23f7504668cfa057f143ccbf0c645a0ac9" - integrity sha512-yPyQSmxIXLieEIRikk2w8AEtWkFdfG/LXcw1KvEtK3iP0ENZLW/WYQmdzOKqfSaLhooz4CJ9D+WY79C8ZliACw== - dependencies: - "@octokit/auth-token" "^2.4.0" - "@octokit/graphql" "^4.3.1" - "@octokit/request" "^5.4.0" - "@octokit/types" "^5.0.0" - before-after-hook "^2.1.0" - universal-user-agent "^5.0.0" - -"@octokit/endpoint@^6.0.1": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.3.tgz#dd09b599662d7e1b66374a177ab620d8cdf73487" - integrity sha512-Y900+r0gIz+cWp6ytnkibbD95ucEzDSKzlEnaWS52hbCDNcCJYO5mRmWW7HRAnDc7am+N/5Lnd8MppSaTYx1Yg== - dependencies: - "@octokit/types" "^5.0.0" - is-plain-object "^3.0.0" - universal-user-agent "^5.0.0" - -"@octokit/graphql@4.5.1", "@octokit/graphql@^4.3.1": - version "4.5.1" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.1.tgz#162aed1490320b88ce34775b3f6b8de945529fa9" - integrity sha512-qgMsROG9K2KxDs12CO3bySJaYoUu2aic90qpFrv7A8sEBzZ7UFGvdgPKiLw5gOPYEYbS0Xf8Tvf84tJutHPulQ== - dependencies: - "@octokit/request" "^5.3.0" - "@octokit/types" "^5.0.0" - universal-user-agent "^5.0.0" - -"@octokit/plugin-paginate-rest@^2.2.0": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.3.tgz#a6ad4377e7e7832fb4bdd9d421e600cb7640ac27" - integrity sha512-eKTs91wXnJH8Yicwa30jz6DF50kAh7vkcqCQ9D7/tvBAP5KKkg6I2nNof8Mp/65G0Arjsb4QcOJcIEQY+rK1Rg== - dependencies: - "@octokit/types" "^5.0.0" - -"@octokit/plugin-request-log@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" - integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw== - -"@octokit/plugin-rest-endpoint-methods@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.0.0.tgz#b02a2006dda8e908c3f8ab381dd5475ef5a810a8" - integrity sha512-emS6gysz4E9BNi9IrCl7Pm4kR+Az3MmVB0/DoDCmF4U48NbYG3weKyDlgkrz6Jbl4Mu4nDx8YWZwC4HjoTdcCA== - dependencies: - "@octokit/types" "^5.0.0" - deprecation "^2.3.1" - -"@octokit/request-error@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" - integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== - dependencies: - "@octokit/types" "^5.0.1" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request@^5.3.0", "@octokit/request@^5.4.0": - version "5.4.5" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.5.tgz#8df65bd812047521f7e9db6ff118c06ba84ac10b" - integrity sha512-atAs5GAGbZedvJXXdjtKljin+e2SltEs48B3naJjqWupYl2IUBbB/CJisyjbNHcKpHzb3E+OYEZ46G8eakXgQg== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.0.0" - "@octokit/types" "^5.0.0" - deprecation "^2.0.0" - is-plain-object "^3.0.0" - node-fetch "^2.3.0" - once "^1.4.0" - universal-user-agent "^5.0.0" - -"@octokit/rest@18.0.0": - version "18.0.0" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.0.tgz#7f401d9ce13530ad743dfd519ae62ce49bcc0358" - integrity sha512-4G/a42lry9NFGuuECnua1R1eoKkdBYJap97jYbWDNYBOUboWcM75GJ1VIcfvwDV/pW0lMPs7CEmhHoVrSV5shg== - dependencies: - "@octokit/core" "^3.0.0" - "@octokit/plugin-paginate-rest" "^2.2.0" - "@octokit/plugin-request-log" "^1.0.0" - "@octokit/plugin-rest-endpoint-methods" "4.0.0" - -"@octokit/types@^5.0.0", "@octokit/types@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.0.1.tgz#5459e9a5e9df8565dcc62c17a34491904d71971e" - integrity sha512-GorvORVwp244fGKEt3cgt/P+M0MGy4xEDbckw+K5ojEezxyMDgCaYPKVct+/eWQfZXOT7uq0xRpmrl/+hliabA== - dependencies: - "@types/node" ">= 8" - -"@types/node-fetch@2.5.7": - version "2.5.7" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" - integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - -"@types/node@*", "@types/node@>= 8": - version "14.0.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce" - integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -before-after-hook@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" - integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -deprecation@^2.0.0, deprecation@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" - integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -form-data@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" - integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -fuzzysort@1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-1.1.4.tgz#a0510206ed44532cbb52cf797bf5a3cb12acd4ba" - integrity sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ== - -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -is-plain-object@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" - integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -macos-release@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" - integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== - -mime-db@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -mime-types@^2.1.12: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-fetch@2.6.0, node-fetch@^2.3.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -os-name@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -semver@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -signal-exit@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -universal-user-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-5.0.0.tgz#a3182aa758069bf0e79952570ca757de3579c1d9" - integrity sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q== - dependencies: - os-name "^3.1.0" - -vscode-nls@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== - -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -windows-release@^3.1.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.1.tgz#cb4e80385f8550f709727287bf71035e209c4ace" - integrity sha512-Pngk/RDCaI/DkuHPlGTdIkDiTAnAkyMjoQMZqRsxydNl1qGXNIoZrB7RK8g53F2tEgQBMqQJHQdYZuQEEAu54A== - dependencies: - execa "^1.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/product.json b/product.json index 94b2c9862cc..308509f2a5b 100644 --- a/product.json +++ b/product.json @@ -25,7 +25,8 @@ "extensionAllowedProposedApi": [ "ms-vscode.vscode-js-profile-flame", "ms-vscode.vscode-js-profile-table", - "ms-vscode.references-view" + "ms-vscode.references-view", + "ms-vscode.github-browser" ], "builtInExtensions": [ { @@ -117,6 +118,21 @@ }, "publisherDisplayName": "Microsoft" } + }, + { + "name": "ms-vscode.github-browser", + "version": "0.0.1", + "repo": "https://github.com/Microsoft/vscode-github-browser", + "metadata": { + "id": "c1bcff4b-4ecb-466e-b8f6-b02788b5fb5a", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } } ] } diff --git a/resources/serverless/code-web.js b/resources/serverless/code-web.js index 9d9ef3cebcf..87117a7699f 100644 --- a/resources/serverless/code-web.js +++ b/resources/serverless/code-web.js @@ -275,7 +275,8 @@ async function handleRoot(req, res) { } const [owner, repo, ...branch] = gh.split('/', 3); - folderUri = { scheme: 'github', authority: branch.join('/') || 'HEAD', path: `/${owner}/${repo}` }; + const ref = branch.join('/'); + folderUri = { scheme: 'github', authority: `${owner}+${repo}${ref ? `+${ref}` : ''}`, path: '/' }; } else { let cs = qs.get('cs'); if (cs) { @@ -284,7 +285,8 @@ async function handleRoot(req, res) { } const [owner, repo, ...branch] = cs.split('/'); - folderUri = { scheme: 'codespace', authority: branch.join('/') || 'HEAD', path: `/${owner}/${repo}` }; + const ref = branch.join('/'); + folderUri = { scheme: 'codespace', authority: `${owner}+${repo}${ref ? `+${ref}` : ''}`, path: '/' }; } } } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 4e89d307994..3370a608b4b 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -25,7 +25,8 @@ if (isWeb) { nameShort: 'VSCode Web Dev', urlProtocol: 'code-oss', extensionAllowedProposedApi: [ - 'ms-vscode.references-view' + 'ms-vscode.references-view', + 'ms-vscode.github-browser' ], }); }