mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-30 17:23:29 -05:00
Merge from vscode 1b314ab317fbff7d799b21754326b7d849889ceb
This commit is contained in:
@@ -47,31 +47,17 @@ function fromSerialized(operations: StoredOperation): Operation {
|
||||
return { ...operations, uri: Uri.parse(operations.uri) };
|
||||
}
|
||||
|
||||
interface CreatedFileChangeStoreEvent {
|
||||
type: 'created';
|
||||
export interface ChangeStoreEvent {
|
||||
type: 'created' | 'changed' | 'deleted';
|
||||
rootUri: Uri;
|
||||
uri: Uri;
|
||||
}
|
||||
|
||||
interface ChangedFileChangeStoreEvent {
|
||||
type: 'changed';
|
||||
rootUri: Uri;
|
||||
uri: Uri;
|
||||
}
|
||||
|
||||
interface DeletedFileChangeStoreEvent {
|
||||
type: 'deleted';
|
||||
rootUri: Uri;
|
||||
uri: Uri;
|
||||
}
|
||||
|
||||
type ChangeStoreEvent = CreatedFileChangeStoreEvent | ChangedFileChangeStoreEvent | DeletedFileChangeStoreEvent;
|
||||
|
||||
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)
|
||||
uri: uri ?? (typeof operation.uri === 'string' ? Uri.parse(operation.uri) : operation.uri),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,6 +68,8 @@ export interface IChangeStore {
|
||||
discard(uri: Uri): Promise<void>;
|
||||
discardAll(rootUri: Uri): Promise<void>;
|
||||
|
||||
hasChanges(rootUri: Uri): boolean;
|
||||
|
||||
getChanges(rootUri: Uri): Operation[];
|
||||
getContent(uri: Uri): string | undefined;
|
||||
|
||||
@@ -116,9 +104,15 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore {
|
||||
|
||||
await this.saveWorkingOperations(rootUri, undefined);
|
||||
|
||||
const events: ChangeStoreEvent[] = [];
|
||||
|
||||
for (const operation of operations) {
|
||||
await this.discardWorkingContent(operation.uri);
|
||||
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri));
|
||||
events.push(toChangeStoreEvent(operation, rootUri));
|
||||
}
|
||||
|
||||
for (const e of events) {
|
||||
this._onDidChange.fire(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +137,7 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore {
|
||||
this._onDidChange.fire({
|
||||
type: operation.type === 'created' ? 'deleted' : operation.type === 'deleted' ? 'created' : 'changed',
|
||||
rootUri: rootUri,
|
||||
uri: uri
|
||||
uri: uri,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,9 +146,15 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore {
|
||||
|
||||
await this.saveWorkingOperations(rootUri, undefined);
|
||||
|
||||
const events: ChangeStoreEvent[] = [];
|
||||
|
||||
for (const operation of operations) {
|
||||
await this.discardWorkingContent(operation.uri);
|
||||
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri));
|
||||
events.push(toChangeStoreEvent(operation, rootUri));
|
||||
}
|
||||
|
||||
for (const e of events) {
|
||||
this._onDidChange.fire(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { Event, EventEmitter, Memento, Uri } from 'vscode';
|
||||
import { Event, EventEmitter, Memento, Uri, workspace } from 'vscode';
|
||||
|
||||
export const contextKeyPrefix = 'github.context|';
|
||||
export interface WorkspaceFolderContext<T> {
|
||||
context: T;
|
||||
name: string;
|
||||
folderUri: Uri;
|
||||
}
|
||||
|
||||
export class ContextStore<T> {
|
||||
private _onDidChange = new EventEmitter<Uri>();
|
||||
@@ -14,23 +18,36 @@ export class ContextStore<T> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
constructor(private readonly memento: Memento, private readonly scheme: string) { }
|
||||
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<T>(`${contextKeyPrefix}${uri.toString()}`);
|
||||
return this.memento.get<T>(`${this.originalScheme}.context|${this.getOriginalResource(uri).toString()}`);
|
||||
}
|
||||
|
||||
getForWorkspace(): WorkspaceFolderContext<T>[] {
|
||||
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) {
|
||||
if (uri.scheme !== this.scheme) {
|
||||
throw new Error(`Invalid context scheme: ${uri.scheme}`);
|
||||
}
|
||||
|
||||
await this.memento.update(`${contextKeyPrefix}${uri.toString()}`, context);
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,48 +3,50 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext, Uri, workspace } from 'vscode';
|
||||
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;
|
||||
const repositoryRegex = /^(?:(?:https:\/\/)?github.com\/)?([^\/]+)\/([^\/]+?)(?:\/|.git|$)/i;
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
const contextStore = new ContextStore<GitHubApiContext>(context.workspaceState, GitHubFS.scheme);
|
||||
export async function activate(context: ExtensionContext) {
|
||||
const contextStore = new ContextStore<GitHubApiContext>('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', GitHubFS.scheme, contextStore, changeStore, gitHubFS);
|
||||
const virtualFS = new VirtualFS('codespace', contextStore, changeStore, gitHubFS);
|
||||
|
||||
context.subscriptions.push(
|
||||
githubApi,
|
||||
gitHubFS,
|
||||
virtualFS,
|
||||
new VirtualSCM(GitHubFS.scheme, githubApi, changeStore)
|
||||
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'
|
||||
// });
|
||||
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;
|
||||
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');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
const uri = Uri.parse(`codespace://HEAD/${owner}/${repo}`);
|
||||
openWorkspace(uri, repo, 'currentWindow');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getRelativePath(rootUri: Uri, uri: Uri) {
|
||||
@@ -63,11 +65,16 @@ export function isDescendent(folderPath: string, filePath: string) {
|
||||
return folderPath.length === 0 || filePath.startsWith(folderPath.endsWith('/') ? folderPath : `${folderPath}/`);
|
||||
}
|
||||
|
||||
// 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 });
|
||||
// }
|
||||
const shaRegex = /^[0-9a-f]{40}$/;
|
||||
export function isSha(ref: string) {
|
||||
return shaRegex.test(ref);
|
||||
}
|
||||
|
||||
// return commands.executeCommand('vscode.openFolder', uri, location === 'newWindow');
|
||||
// }
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -43,26 +43,22 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe
|
||||
|
||||
constructor(
|
||||
readonly scheme: string,
|
||||
private readonly originalScheme: string,
|
||||
contextStore: ContextStore<GitHubApiContext>,
|
||||
private readonly contextStore: ContextStore<GitHubApiContext>,
|
||||
private readonly changeStore: IWritableChangeStore,
|
||||
private readonly fs: FileSystemProvider & FileSearchProvider & TextSearchProvider
|
||||
) {
|
||||
// TODO@eamodio listen for workspace folder changes
|
||||
for (const folder of workspace.workspaceFolders ?? []) {
|
||||
const uri = this.getOriginalResource(folder.uri);
|
||||
|
||||
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 (contextStore.get(uri) !== undefined && !changeStore.hasChanges(folder.uri)) {
|
||||
contextStore.delete(uri);
|
||||
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.registerFileSystemProvider(scheme, this, { isCaseSensitive: true }),
|
||||
workspace.registerFileSearchProvider(scheme, this),
|
||||
workspace.registerTextSearchProvider(scheme, this),
|
||||
changeStore.onDidChange(e => {
|
||||
@@ -86,11 +82,11 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe
|
||||
}
|
||||
|
||||
private getOriginalResource(uri: Uri): Uri {
|
||||
return uri.with({ scheme: this.originalScheme });
|
||||
return this.contextStore.getOriginalResource(uri);
|
||||
}
|
||||
|
||||
private getVirtualResource(uri: Uri): Uri {
|
||||
return uri.with({ scheme: this.scheme });
|
||||
private getWorkspaceResource(uri: Uri): Uri {
|
||||
return this.contextStore.getWorkspaceResource(uri);
|
||||
}
|
||||
|
||||
//#region FileSystemProvider
|
||||
@@ -211,7 +207,7 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe
|
||||
return this.fs.provideTextSearchResults(
|
||||
query,
|
||||
{ ...options, folder: this.getOriginalResource(options.folder) },
|
||||
{ report: (result: TextSearchResult) => progress.report({ ...result, uri: this.getVirtualResource(result.uri) }) },
|
||||
{ report: (result: TextSearchResult) => progress.report({ ...result, uri: this.getWorkspaceResource(result.uri) }) },
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
import { authentication, AuthenticationSession, Disposable, Event, EventEmitter, Range, Uri } from 'vscode';
|
||||
import { graphql } from '@octokit/graphql';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { fromGitHubUri } from './fs';
|
||||
import { ContextStore } from '../contextStore';
|
||||
import { fromGitHubUri } from './fs';
|
||||
import { isSha } from '../extension';
|
||||
import { Iterables } from '../iterables';
|
||||
|
||||
export const shaRegex = /^[0-9a-f]{40}$/;
|
||||
|
||||
export interface GitHubApiContext {
|
||||
sha: string;
|
||||
requestRef: string;
|
||||
|
||||
branch: string;
|
||||
sha: string | undefined;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
@@ -73,7 +75,7 @@ export class GitHubApi implements Disposable {
|
||||
if (!providers.includes('github')) {
|
||||
await new Promise(resolve => {
|
||||
authentication.onDidChangeAuthenticationProviders(e => {
|
||||
if (e.added.includes('github')) {
|
||||
if (e.added.find(provider => provider.id === 'github')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
@@ -110,19 +112,12 @@ export class GitHubApi implements Disposable {
|
||||
}
|
||||
|
||||
async commit(rootUri: Uri, message: string, operations: CommitOperation[]): Promise<string | undefined> {
|
||||
let { owner, repo, ref } = fromGitHubUri(rootUri);
|
||||
const { owner, repo } = fromGitHubUri(rootUri);
|
||||
|
||||
try {
|
||||
if (ref === undefined || ref === 'HEAD') {
|
||||
ref = await this.defaultBranchQuery(rootUri);
|
||||
if (ref === undefined) {
|
||||
throw new Error('Cannot commit — invalid ref');
|
||||
}
|
||||
}
|
||||
|
||||
const context = await this.getContext(rootUri);
|
||||
if (context.sha === undefined) {
|
||||
throw new Error('Cannot commit — invalid context');
|
||||
throw new Error(`Cannot commit to Uri(${rootUri.toString(true)}); Invalid context sha`);
|
||||
}
|
||||
|
||||
const hasDeletes = operations.some(op => op.type === 'deleted');
|
||||
@@ -204,14 +199,14 @@ export class GitHubApi implements Disposable {
|
||||
parents: [context.sha]
|
||||
});
|
||||
|
||||
this.updateContext(rootUri, { sha: resp.data.sha, timestamp: Date.now() });
|
||||
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/${ref}`,
|
||||
ref: `heads/${context.branch}`,
|
||||
sha: resp.data.sha
|
||||
});
|
||||
|
||||
@@ -256,7 +251,7 @@ export class GitHubApi implements Disposable {
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
recursive: '1',
|
||||
tree_sha: context?.sha ?? ref ?? 'HEAD',
|
||||
tree_sha: context?.sha ?? ref,
|
||||
});
|
||||
return Iterables.filterMap(resp.data.tree, p => p.type === 'blob' ? p.path : undefined);
|
||||
} catch (ex) {
|
||||
@@ -283,7 +278,7 @@ export class GitHubApi implements Disposable {
|
||||
}>(query, {
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
path: `${context.sha ?? ref ?? 'HEAD'}:${path}`,
|
||||
path: `${context.sha ?? ref}:${path}`,
|
||||
});
|
||||
return rsp?.repository?.object ?? undefined;
|
||||
} catch (ex) {
|
||||
@@ -295,7 +290,7 @@ export class GitHubApi implements Disposable {
|
||||
const { owner, repo, ref } = fromGitHubUri(uri);
|
||||
|
||||
try {
|
||||
if (ref === undefined || ref === 'HEAD') {
|
||||
if (ref === 'HEAD') {
|
||||
const query = `query latest($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
defaultBranchRef {
|
||||
@@ -322,6 +317,7 @@ export class GitHubApi implements Disposable {
|
||||
oid
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const rsp = await this.gqlQuery<{
|
||||
@@ -345,7 +341,7 @@ export class GitHubApi implements Disposable {
|
||||
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 === undefined) {
|
||||
if (ref !== 'HEAD') {
|
||||
return { matches: [], limitHit: true };
|
||||
}
|
||||
|
||||
@@ -436,29 +432,46 @@ export class GitHubApi implements Disposable {
|
||||
private readonly rootUriToContextMap = new Map<string, GitHubApiContext>();
|
||||
|
||||
private async getContextCore(rootUri: Uri): Promise<GitHubApiContext> {
|
||||
let context = this.rootUriToContextMap.get(rootUri.toString());
|
||||
if (context === undefined) {
|
||||
const { ref } = fromGitHubUri(rootUri);
|
||||
if (ref !== undefined && shaRegex.test(ref)) {
|
||||
context = { sha: ref, timestamp: Date.now() };
|
||||
} else {
|
||||
context = this.context.get(rootUri);
|
||||
if (context?.sha === undefined) {
|
||||
const sha = await this.latestCommitQuery(rootUri);
|
||||
if (sha !== undefined) {
|
||||
context = { sha: sha, timestamp: Date.now() };
|
||||
} else {
|
||||
context = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
const key = rootUri.toString();
|
||||
let context = this.rootUriToContextMap.get(key);
|
||||
|
||||
if (context !== undefined) {
|
||||
this.updateContext(rootUri, context);
|
||||
}
|
||||
// Check if we have a cached a context
|
||||
if (context?.sha !== undefined) {
|
||||
return context;
|
||||
}
|
||||
|
||||
return context ?? { sha: rootUri.authority, timestamp: Date.now() };
|
||||
// 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) {
|
||||
|
||||
@@ -299,7 +299,7 @@ function typenameToFileType(typename: string | undefined | null) {
|
||||
}
|
||||
}
|
||||
|
||||
type RepoInfo = { owner: string; repo: string; path: string | undefined; ref?: string };
|
||||
type RepoInfo = { owner: string; repo: string; path: string | undefined; ref: string };
|
||||
export function fromGitHubUri(uri: Uri): RepoInfo {
|
||||
const [, owner, repo, ...rest] = uri.path.split('/');
|
||||
|
||||
@@ -311,7 +311,7 @@ export function fromGitHubUri(uri: Uri): RepoInfo {
|
||||
ref = 'HEAD';
|
||||
}
|
||||
}
|
||||
return { owner: owner, repo: repo, path: rest.join('/'), ref: ref };
|
||||
return { owner: owner, repo: repo, path: rest.join('/'), ref: ref ?? 'HEAD' };
|
||||
}
|
||||
|
||||
function getHashCode(s: string): number {
|
||||
|
||||
@@ -12,8 +12,9 @@ export namespace Iterables {
|
||||
): Iterable<TMapped> {
|
||||
for (const item of source) {
|
||||
const mapped = predicateMapper(item);
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (mapped != null) { yield mapped; }
|
||||
if (mapped !== undefined && mapped !== null) {
|
||||
yield mapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,17 +32,15 @@ export class VirtualSCM implements Disposable {
|
||||
// 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)),
|
||||
);
|
||||
|
||||
for (const { uri } of workspace.workspaceFolders ?? []) {
|
||||
for (const operation of changeStore.getChanges(uri)) {
|
||||
this.update(uri, operation.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -50,7 +48,18 @@ export class VirtualSCM implements Disposable {
|
||||
}
|
||||
|
||||
private registerCommands() {
|
||||
commands.registerCommand('githubBrowser.commit', (...args: any[]) => this.commitChanges(args[0]));
|
||||
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)
|
||||
|
||||
99
extensions/github-browser/src/statusbar.ts
Normal file
99
extensions/github-browser/src/statusbar.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. 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<string, StatusBarItem>();
|
||||
|
||||
constructor(
|
||||
private readonly contextStore: ContextStore<GitHubApiContext>,
|
||||
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<GitHubApiContext>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user