From 9745191a1a37bcbee442f9a1afb63efa47205a65 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 10 Jul 2017 12:28:39 +0200 Subject: [PATCH 01/67] wip: adopt ViewsViewlet in SCM --- .../scm/electron-browser/media/scmViewlet.css | 4 + .../parts/scm/electron-browser/scmMenus.ts | 30 ++ .../parts/scm/electron-browser/scmViewlet.ts | 354 +++++++++++------- src/vs/workbench/parts/views/browser/views.ts | 2 +- .../parts/views/browser/viewsRegistry.ts | 1 + 5 files changed, 249 insertions(+), 142 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css index 626f8cb689e38..44a844414e986 100644 --- a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css +++ b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css @@ -8,6 +8,10 @@ -webkit-mask-size: 19px; } +.scm-viewlet .scm-status { + height: 100%; +} + .scm-viewlet .monaco-list-row { padding: 0 12px 0 20px; line-height: 22px; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts index bfea5b8df9b91..1de131b047a66 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts @@ -174,3 +174,33 @@ export class SCMMenus implements IDisposable { this.disposables = dispose(this.disposables); } } + +export class SCMMenus2 implements IDisposable { + + getTitleActions(): IAction[] { + return []; + } + + getTitleSecondaryActions(): IAction[] { + return []; + } + + getResourceGroupActions(group: ISCMResourceGroup): IAction[] { + return []; + } + + getResourceGroupContextActions(group: ISCMResourceGroup): IAction[] { + return []; + } + + getResourceActions(resource: ISCMResource): IAction[] { + return []; + } + + getResourceContextActions(resource: ISCMResource): IAction[] { + return []; + } + + dispose(): void { + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 1d4064b4fb311..4879964022a21 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -14,7 +14,7 @@ import * as platform from 'vs/base/common/platform'; import { domEvent } from 'vs/base/browser/event'; import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Builder, Dimension } from 'vs/base/browser/builder'; -import { Viewlet } from 'vs/workbench/browser/viewlet'; +import { ComposedViewsViewlet, CollapsibleView, ICollapsibleViewOptions, IViewletViewOptions, IView } from 'vs/workbench/parts/views/browser/views'; import { append, $, toggleClass } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -34,18 +34,23 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMessageService } from 'vs/platform/message/common/message'; import { IListService } from 'vs/platform/list/browser/listService'; -import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions'; import { MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; -import { SCMMenus } from './scmMenus'; +import { SCMMenus, SCMMenus2 } from './scmMenus'; import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { IModelService } from 'vs/editor/common/services/modelService'; import { comparePaths } from 'vs/base/common/comparers'; import { isSCMResource } from './scmUtil'; import { attachInputBoxStyler, attachListStyler, attachBadgeStyler } from 'vs/platform/theme/common/styler'; import Severity from 'vs/base/common/severity'; +import { IExtensionService } from 'vs/platform/extensions/common/extensions'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ViewLocation, ViewsRegistry, IViewDescriptor } from 'vs/workbench/parts/views/browser/viewsRegistry'; +import { TreeView } from 'vs/workbench/parts/views/browser/treeView'; +import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; // TODO@Joao // Need to subclass MenuItemActionItem in order to respect @@ -91,7 +96,7 @@ class ResourceGroupRenderer implements IRenderer; - private menus: SCMMenus; - private providerChangeDisposable: IDisposable = EmptyDisposable; + private menus: SCMMenus2 = new SCMMenus2(); private disposables: IDisposable[] = []; constructor( - @ITelemetryService telemetryService: ITelemetryService, - @ISCMService private scmService: ISCMService, - @IInstantiationService private instantiationService: IInstantiationService, - @IContextViewService private contextViewService: IContextViewService, - @IContextKeyService private contextKeyService: IContextKeyService, - @IKeybindingService private keybindingService: IKeybindingService, - @IMessageService private messageService: IMessageService, - @IListService private listService: IListService, - @IContextMenuService private contextMenuService: IContextMenuService, + private provider: ISCMProvider, + options: IViewletViewOptions, + @IKeybindingService protected keybindingService: IKeybindingService, @IThemeService protected themeService: IThemeService, - @IMenuService private menuService: IMenuService, - @IModelService private modelService: IModelService, - @ICommandService private commandService: ICommandService, - @IEditorGroupService private groupService: IEditorGroupService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService + @IContextMenuService protected contextMenuService: IContextMenuService, + @IListService protected listService: IListService, + @ICommandService protected commandService: ICommandService, + @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, + @IEditorGroupService protected editorGroupService: IEditorGroupService, + @IInstantiationService protected instantiationService: IInstantiationService ) { - super(VIEWLET_ID, telemetryService, themeService); - - this.menus = this.instantiationService.createInstance(SCMMenus); - this.menus.onDidChangeTitle(this.updateTitleArea, this, this.disposables); - this.disposables.push(this.menus); + super({...options, sizing: ViewSizing.Flexible}, keybindingService, contextMenuService); } - private setActiveProvider(activeProvider: ISCMProvider | undefined): void { - this.providerChangeDisposable.dispose(); - this.activeProvider = activeProvider; + renderHeader(container: HTMLElement): void { + const title = append(container, $('div.title')); + title.textContent = this.name; - if (activeProvider) { - const disposables = [activeProvider.onDidChange(this.update, this)]; - - if (activeProvider.onDidChangeCommitTemplate) { - disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this)); - } - - this.providerChangeDisposable = combinedDisposable(disposables); - } else { - this.providerChangeDisposable = EmptyDisposable; - } - - this.updateInputBox(); - this.updateTitleArea(); - this.update(); + super.renderHeader(container); } - create(parent: Builder): TPromise { - super.create(parent); - parent.addClass('scm-viewlet'); - - const root = parent.getHTMLElement(); - this.inputBoxContainer = append(root, $('.scm-editor')); - - this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { - placeholder: localize('commitMessage', "Message (press {0} to commit)", platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'), - flexibleHeight: true - }); - this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService)); - this.disposables.push(this.inputBox); - - this.inputBox.value = this.scmService.input.value; - this.inputBox.onDidChange(value => this.scmService.input.value = value, null, this.disposables); - this.scmService.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); - this.disposables.push(this.inputBox.onDidHeightChange(() => this.layout())); - - chain(domEvent(this.inputBox.inputElement, 'keydown')) - .map(e => new StandardKeyboardEvent(e)) - .filter(e => e.equals(KeyMod.CtrlCmd | KeyCode.Enter) || e.equals(KeyMod.CtrlCmd | KeyCode.KEY_S)) - .on(this.onDidAcceptInput, this, this.disposables); - - this.listContainer = append(root, $('.scm-status.show-file-icons')); + renderBody(container: HTMLElement): void { + this.listContainer = append(container, $('.scm-status.show-file-icons')); const delegate = new Delegate(); const actionItemProvider = action => this.getActionItem(action); @@ -332,11 +299,168 @@ export class SCMViewlet extends Viewlet { this.list.onContextMenu(this.onListContextMenu, this, this.disposables); this.disposables.push(this.list); + this.provider.onDidChange(this.update, this, this.disposables); + } + + layoutBody(size: number): void { + this.list.layout(size); + } + + private update(): void { + const elements = this.provider.resources + .reduce<(ISCMResourceGroup | ISCMResource)[]>((r, g) => [...r, g, ...g.resources.sort(resourceSorter)], []); + + this.list.splice(0, this.list.length, elements); + } + + private open(e: ISCMResource): void { + if (!e.command) { + return; + } + + this.commandService.executeCommand(e.command.id, ...e.command.arguments) + .done(undefined, onUnexpectedError); + } + + private pin(): void { + const activeEditor = this.editorService.getActiveEditor(); + const activeEditorInput = this.editorService.getActiveEditorInput(); + this.editorGroupService.pinEditor(activeEditor.position, activeEditorInput); + } + + private onListContextMenu(e: IListContextMenuEvent): void { + const element = e.element; + let actions: IAction[]; + + if (isSCMResource(element)) { + actions = this.menus.getResourceContextActions(element); + } else { + actions = this.menus.getResourceGroupContextActions(element); + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => TPromise.as(actions), + getActionsContext: () => element, + actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources()) + }); + } + + private getSelectedResources(): ISCMResource[] { + return this.list.getSelectedElements() + .filter(r => isSCMResource(r)) as ISCMResource[]; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + super.dispose(); + } +} + +export class SCMViewlet extends ComposedViewsViewlet { + + private activeProvider: ISCMProvider | undefined; + private cachedDimension: Dimension; + private inputBoxContainer: HTMLElement; + private inputBox: InputBox; + private menus: SCMMenus; + private providerChangeDisposable: IDisposable = EmptyDisposable; + private disposables: IDisposable[] = []; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @ISCMService protected scmService: ISCMService, + @IInstantiationService instantiationService: IInstantiationService, + @IContextViewService protected contextViewService: IContextViewService, + @IContextKeyService contextKeyService: IContextKeyService, + @IKeybindingService protected keybindingService: IKeybindingService, + @IMessageService protected messageService: IMessageService, + @IListService protected listService: IListService, + @IContextMenuService contextMenuService: IContextMenuService, + @IThemeService protected themeService: IThemeService, + @ICommandService protected commandService: ICommandService, + @IEditorGroupService protected editorGroupService: IEditorGroupService, + @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @IExtensionService extensionService: IExtensionService + ) { + super(VIEWLET_ID, ViewLocation.SCM, `${VIEWLET_ID}.state`, false, + telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); + + this.menus = this.instantiationService.createInstance(SCMMenus); + this.menus.onDidChangeTitle(this.updateTitleArea, this, this.disposables); + this.disposables.push(this.menus); + } + + private setActiveProvider(activeProvider: ISCMProvider | undefined): void { + this.providerChangeDisposable.dispose(); + this.activeProvider = activeProvider; + + if (activeProvider) { + const disposables = []; + + // if (activeProvider.onDidChangeCommitTemplate) { + // disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this)); + // } + + const id = activeProvider.id; + ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]); + + disposables.push({ + dispose: () => { + ViewsRegistry.deregisterViews([id], ViewLocation.SCM); + } + }); + + this.providerChangeDisposable = combinedDisposable(disposables); + } else { + this.providerChangeDisposable = EmptyDisposable; + } + + // this.updateInputBox(); + // this.updateTitleArea(); + } + + async create(parent: Builder): TPromise { + await super.create(parent); + + parent.addClass('scm-viewlet'); + + // const root = parent.getHTMLElement(); + // this.inputBoxContainer = append(root, $('.scm-editor')); + + // this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { + // placeholder: localize('commitMessage', "Message (press {0} to commit)", platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'), + // flexibleHeight: true + // }); + // this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService)); + // this.disposables.push(this.inputBox); + + // this.inputBox.value = this.scmService.input.value; + // this.inputBox.onDidChange(value => this.scmService.input.value = value, null, this.disposables); + // this.scmService.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); + // this.disposables.push(this.inputBox.onDidHeightChange(() => this.layout())); + + // chain(domEvent(this.inputBox.inputElement, 'keydown')) + // .map(e => new StandardKeyboardEvent(e)) + // .filter(e => e.equals(KeyMod.CtrlCmd | KeyCode.Enter) || e.equals(KeyMod.CtrlCmd | KeyCode.KEY_S)) + // .on(this.onDidAcceptInput, this, this.disposables); + + this.setActiveProvider(this.scmService.activeProvider); this.scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); - this.themeService.onThemeChange(this.update, this, this.disposables); + // this.themeService.onThemeChange(this.update, this, this.disposables); - return TPromise.as(null); + // return TPromise.as(null); + } + + protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): IView { + if (viewDescriptor instanceof SourceControlViewDescriptor) { + return this.instantiationService.createInstance(SourceControlView, viewDescriptor.provider, options); + } + + return this.instantiationService.createInstance(viewDescriptor.ctor, options); } private onDidAcceptInput(): void { @@ -355,20 +479,6 @@ export class SCMViewlet extends Viewlet { .done(undefined, onUnexpectedError); } - private update(): void { - const provider = this.scmService.activeProvider; - - if (!provider) { - this.list.splice(0, this.list.length); - return; - } - - const elements = provider.resources - .reduce<(ISCMResourceGroup | ISCMResource)[]>((r, g) => [...r, g, ...g.resources.sort(resourceSorter)], []); - - this.list.splice(0, this.list.length, elements); - } - private updateInputBox(): void { if (!this.activeProvider) { return; @@ -381,21 +491,21 @@ export class SCMViewlet extends Viewlet { this.inputBox.value = this.activeProvider.commitTemplate; } - layout(dimension: Dimension = this.cachedDimension): void { - if (!dimension) { - return; - } + // layout(dimension: Dimension = this.cachedDimension): void { + // if (!dimension) { + // return; + // } - this.cachedDimension = dimension; - this.inputBox.layout(); + // this.cachedDimension = dimension; + // this.inputBox.layout(); - const editorHeight = this.inputBox.height; - const listHeight = dimension.height - (editorHeight + 12 /* margin */); - this.listContainer.style.height = `${listHeight}px`; - this.list.layout(listHeight); + // const editorHeight = this.inputBox.height; + // const listHeight = dimension.height - (editorHeight + 12 /* margin */); + // this.listContainer.style.height = `${listHeight}px`; + // this.list.layout(listHeight); - toggleClass(this.inputBoxContainer, 'scroll', editorHeight >= 134); - } + // toggleClass(this.inputBoxContainer, 'scroll', editorHeight >= 134); + // } getOptimalWidth(): number { return 400; @@ -403,22 +513,7 @@ export class SCMViewlet extends Viewlet { focus(): void { super.focus(); - this.inputBox.focus(); - } - - private open(e: ISCMResource): void { - if (!e.command) { - return; - } - - this.commandService.executeCommand(e.command.id, ...e.command.arguments) - .done(undefined, onUnexpectedError); - } - - private pin(): void { - const activeEditor = this.editorService.getActiveEditor(); - const activeEditorInput = this.editorService.getActiveEditorInput(); - this.groupService.pinEditor(activeEditor.position, activeEditorInput); + // this.inputBox.focus(); } getTitle(): string { @@ -448,29 +543,6 @@ export class SCMViewlet extends Viewlet { return new SCMMenuItemActionItem(action, this.keybindingService, this.messageService); } - private onListContextMenu(e: IListContextMenuEvent): void { - const element = e.element; - let actions: IAction[]; - - if (isSCMResource(element)) { - actions = this.menus.getResourceContextActions(element); - } else { - actions = this.menus.getResourceGroupContextActions(element); - } - - this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => TPromise.as(actions), - getActionsContext: () => element, - actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources()) - }); - } - - private getSelectedResources(): ISCMResource[] { - return this.list.getSelectedElements() - .filter(r => isSCMResource(r)) as ISCMResource[]; - } - dispose(): void { this.disposables = dispose(this.disposables); super.dispose(); diff --git a/src/vs/workbench/parts/views/browser/views.ts b/src/vs/workbench/parts/views/browser/views.ts index 07b381674c6e9..f16e2834a55ec 100644 --- a/src/vs/workbench/parts/views/browser/views.ts +++ b/src/vs/workbench/parts/views/browser/views.ts @@ -334,7 +334,7 @@ export class ComposedViewsViewlet extends Viewlet { @IThemeService themeService: IThemeService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IContextKeyService protected contextKeyService: IContextKeyService, - @IContextMenuService private contextMenuService: IContextMenuService, + @IContextMenuService protected contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { super(id, telemetryService, themeService); diff --git a/src/vs/workbench/parts/views/browser/viewsRegistry.ts b/src/vs/workbench/parts/views/browser/viewsRegistry.ts index c10d1922f8256..1a36523ab07b4 100644 --- a/src/vs/workbench/parts/views/browser/viewsRegistry.ts +++ b/src/vs/workbench/parts/views/browser/viewsRegistry.ts @@ -13,6 +13,7 @@ export class ViewLocation { static readonly Explorer = new ViewLocation('explorer'); static readonly Debug = new ViewLocation('debug'); static readonly Extensions = new ViewLocation('extensions'); + static readonly SCM = new ViewLocation('scm'); constructor(private _id: string) { } From 40923c70842a0508383f15c2118de535ae06ab03 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 10 Jul 2017 15:28:04 +0200 Subject: [PATCH 02/67] wip: scm menus --- .../parts/scm/electron-browser/scmMenus.ts | 162 +++--------------- .../parts/scm/electron-browser/scmViewlet.ts | 62 ++++--- .../services/scm/common/scmService.ts | 8 +- 3 files changed, 69 insertions(+), 163 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts index 1de131b047a66..84015f00e6f55 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts @@ -6,103 +6,48 @@ 'use strict'; import 'vs/css!./media/scmViewlet'; -import { localize } from 'vs/nls'; -import { TPromise } from 'vs/base/common/winjs.base'; import Event, { Emitter } from 'vs/base/common/event'; -import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; -import { ContextSubMenu } from 'vs/platform/contextview/browser/contextView'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; -import { ISCMService, ISCMProvider, ISCMResource, ISCMResourceGroup } from 'vs/workbench/services/scm/common/scm'; +import { ISCMProvider, ISCMResource, ISCMResourceGroup } from 'vs/workbench/services/scm/common/scm'; import { getSCMResourceContextKey } from './scmUtil'; -class SwitchProviderAction extends Action { - - get checked(): boolean { - return this.scmService.activeProvider === this.provider; - } - - constructor( - private provider: ISCMProvider, - @ISCMService private scmService: ISCMService - ) { - super('scm.switchProvider', provider.label, '', true); - } - - run(): TPromise { - this.scmService.activeProvider = this.provider; - return TPromise.as(null); - } -} - -class InstallAdditionalSCMProviders extends Action { - - constructor(private viewletService: IViewletService) { - super('scm.installAdditionalSCMProviders', localize('installAdditionalSCMProviders', "Install Additional SCM Providers..."), '', true); - } - - run(): TPromise { - return this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true).then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('category:"SCM Providers" @sort:installs'); - viewlet.focus(); - }); - } -} - export class SCMMenus implements IDisposable { - private disposables: IDisposable[] = []; - - private titleDisposable: IDisposable = EmptyDisposable; + private contextKeyService: IContextKeyService; + private titleMenu: IMenu; private titleActions: IAction[] = []; private titleSecondaryActions: IAction[] = []; private _onDidChangeTitle = new Emitter(); get onDidChangeTitle(): Event { return this._onDidChangeTitle.event; } + private disposables: IDisposable[] = []; + constructor( - @IContextKeyService private contextKeyService: IContextKeyService, - @ISCMService private scmService: ISCMService, - @IMenuService private menuService: IMenuService, - @IViewletService private viewletService: IViewletService + private provider: ISCMProvider, + @IContextKeyService contextKeyService: IContextKeyService, + @IMenuService private menuService: IMenuService ) { - this.setActiveProvider(this.scmService.activeProvider); - this.scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); + this.contextKeyService = contextKeyService.createScoped(); + const scmProviderKey = this.contextKeyService.createKey('scmProvider', void 0); + scmProviderKey.set(provider.id); + + this.titleMenu = this.menuService.createMenu(MenuId.SCMTitle, this.contextKeyService); + this.disposables.push(this.titleMenu); + + this.titleMenu.onDidChange(this.updateTitleActions, this, this.disposables); + this.updateTitleActions(); } - private setActiveProvider(activeProvider: ISCMProvider | undefined): void { - if (this.titleDisposable) { - this.titleDisposable.dispose(); - this.titleDisposable = EmptyDisposable; - } - - if (!activeProvider) { - return; - } - - const titleMenu = this.menuService.createMenu(MenuId.SCMTitle, this.contextKeyService); - const updateActions = () => { - this.titleActions = []; - this.titleSecondaryActions = []; - fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }); - this._onDidChangeTitle.fire(); - }; - - const listener = titleMenu.onDidChange(updateActions); - updateActions(); - - this.titleDisposable = toDisposable(() => { - listener.dispose(); - titleMenu.dispose(); - this.titleActions = []; - this.titleSecondaryActions = []; - }); + private updateTitleActions(): void { + this.titleActions = []; + this.titleSecondaryActions = []; + fillInActions(this.titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }); + this._onDidChangeTitle.fire(); } getTitleActions(): IAction[] { @@ -110,26 +55,7 @@ export class SCMMenus implements IDisposable { } getTitleSecondaryActions(): IAction[] { - const providerSwitchActions: IAction[] = this.scmService.providers - .map(p => new SwitchProviderAction(p, this.scmService)); - - let result = []; - - if (this.titleSecondaryActions.length > 0) { - result = result.concat(this.titleSecondaryActions); - } - if (providerSwitchActions.length > 0) { - providerSwitchActions.push(new Separator()); - } - providerSwitchActions.push(new InstallAdditionalSCMProviders(this.viewletService)); - - if (result.length > 0) { - result.push(new Separator()); - } - - result.push(new ContextSubMenu(localize('switch provider', "Switch SCM Provider..."), providerSwitchActions)); - - return result; + return this.titleSecondaryActions; } getResourceGroupActions(group: ISCMResourceGroup): IAction[] { @@ -148,13 +74,7 @@ export class SCMMenus implements IDisposable { return this.getActions(MenuId.SCMResourceContext, resource).secondary; } - private static readonly NoActions = { primary: [], secondary: [] }; - private getActions(menuId: MenuId, resource: ISCMResourceGroup | ISCMResource): { primary: IAction[]; secondary: IAction[]; } { - if (!this.scmService.activeProvider) { - return SCMMenus.NoActions; - } - const contextKeyService = this.contextKeyService.createScoped(); contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(resource)); @@ -173,34 +93,4 @@ export class SCMMenus implements IDisposable { dispose(): void { this.disposables = dispose(this.disposables); } -} - -export class SCMMenus2 implements IDisposable { - - getTitleActions(): IAction[] { - return []; - } - - getTitleSecondaryActions(): IAction[] { - return []; - } - - getResourceGroupActions(group: ISCMResourceGroup): IAction[] { - return []; - } - - getResourceGroupContextActions(group: ISCMResourceGroup): IAction[] { - return []; - } - - getResourceActions(resource: ISCMResource): IAction[] { - return []; - } - - getResourceContextActions(resource: ISCMResource): IAction[] { - return []; - } - - dispose(): void { - } } \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 4879964022a21..b50a46296bd6e 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -9,9 +9,8 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { chain } from 'vs/base/common/event'; +import { memoize } from 'vs/base/common/decorators'; import { onUnexpectedError } from 'vs/base/common/errors'; -import * as platform from 'vs/base/common/platform'; -import { domEvent } from 'vs/base/browser/event'; import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Builder, Dimension } from 'vs/base/browser/builder'; import { ComposedViewsViewlet, CollapsibleView, ICollapsibleViewOptions, IViewletViewOptions, IView } from 'vs/workbench/parts/views/browser/views'; @@ -35,9 +34,9 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMessageService } from 'vs/platform/message/common/message'; import { IListService } from 'vs/platform/list/browser/listService'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; -import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions'; +import { IAction, Action, IActionItem, ActionRunner } from 'vs/base/common/actions'; import { MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; -import { SCMMenus, SCMMenus2 } from './scmMenus'; +import { SCMMenus } from './scmMenus'; import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -49,8 +48,9 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ViewLocation, ViewsRegistry, IViewDescriptor } from 'vs/workbench/parts/views/browser/viewsRegistry'; -import { TreeView } from 'vs/workbench/parts/views/browser/treeView'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; +import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; // TODO@Joao // Need to subclass MenuItemActionItem in order to respect @@ -96,7 +96,7 @@ class ResourceGroupRenderer implements IRenderer { template.fileLabel.setFile(resource.sourceUri); template.actionBar.clear(); template.actionBar.context = resource; - template.actionBar.push(this.scmMenus.getResourceActions(resource)); + template.actionBar.push(this.scmMenus.getResourceActions(resource), { icon: true, label: false }); toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough); toggleClass(template.element, 'faded', resource.decorations.faded); @@ -242,7 +242,7 @@ class SourceControlView extends CollapsibleView { private listContainer: HTMLElement; private list: List; - private menus: SCMMenus2 = new SCMMenus2(); + private menus: SCMMenus; private disposables: IDisposable[] = []; constructor( @@ -258,6 +258,9 @@ class SourceControlView extends CollapsibleView { @IInstantiationService protected instantiationService: IInstantiationService ) { super({...options, sizing: ViewSizing.Flexible}, keybindingService, contextMenuService); + + this.menus = instantiationService.createInstance(SCMMenus, provider); + this.menus.onDidChangeTitle(this.updateActions, this, this.disposables); } renderHeader(container: HTMLElement): void { @@ -299,14 +302,22 @@ class SourceControlView extends CollapsibleView { this.list.onContextMenu(this.onListContextMenu, this, this.disposables); this.disposables.push(this.list); - this.provider.onDidChange(this.update, this, this.disposables); + this.provider.onDidChange(this.updateList, this, this.disposables); } layoutBody(size: number): void { this.list.layout(size); } - private update(): void { + getActions(): IAction[] { + return this.menus.getTitleActions(); + } + + getSecondaryActions(): IAction[] { + return this.menus.getTitleSecondaryActions(); + } + + private updateList(): void { const elements = this.provider.resources .reduce<(ISCMResourceGroup | ISCMResource)[]>((r, g) => [...r, g, ...g.resources.sort(resourceSorter)], []); @@ -357,13 +368,27 @@ class SourceControlView extends CollapsibleView { } } +class InstallAdditionalSCMProvidersAction extends Action { + + constructor(@IViewletService private viewletService: IViewletService) { + super('scm.installAdditionalSCMProviders', localize('installAdditionalSCMProviders', "Install Additional SCM Providers..."), '', true); + } + + run(): TPromise { + return this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true).then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search('category:"SCM Providers" @sort:installs'); + viewlet.focus(); + }); + } +} + export class SCMViewlet extends ComposedViewsViewlet { private activeProvider: ISCMProvider | undefined; private cachedDimension: Dimension; private inputBoxContainer: HTMLElement; private inputBox: InputBox; - private menus: SCMMenus; private providerChangeDisposable: IDisposable = EmptyDisposable; private disposables: IDisposable[] = []; @@ -387,10 +412,6 @@ export class SCMViewlet extends ComposedViewsViewlet { ) { super(VIEWLET_ID, ViewLocation.SCM, `${VIEWLET_ID}.state`, false, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); - - this.menus = this.instantiationService.createInstance(SCMMenus); - this.menus.onDidChangeTitle(this.updateTitleArea, this, this.disposables); - this.disposables.push(this.menus); } private setActiveProvider(activeProvider: ISCMProvider | undefined): void { @@ -527,12 +548,11 @@ export class SCMViewlet extends ComposedViewsViewlet { } } - getActions(): IAction[] { - return this.menus.getTitleActions(); - } - + @memoize getSecondaryActions(): IAction[] { - return this.menus.getTitleSecondaryActions(); + return [ + this.instantiationService.createInstance(InstallAdditionalSCMProvidersAction) + ]; } getActionItem(action: IAction): IActionItem { diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index f05e8810711ec..7e4aacead5ac3 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -8,7 +8,7 @@ import { IDisposable, toDisposable, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; import { memoize } from 'vs/base/common/decorators'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ISCMService, ISCMProvider, ISCMInput, DefaultSCMProviderIdStorageKey } from './scm'; @@ -36,7 +36,6 @@ export class SCMService implements ISCMService { private activeProviderDisposable: IDisposable = EmptyDisposable; private statusBarDisposable: IDisposable = EmptyDisposable; - private activeProviderContextKey: IContextKey; private _activeProvider: ISCMProvider | undefined; @@ -62,9 +61,7 @@ export class SCMService implements ISCMService { @IContextKeyService contextKeyService: IContextKeyService, @IStorageService private storageService: IStorageService, @IStatusbarService private statusbarService: IStatusbarService - ) { - this.activeProviderContextKey = contextKeyService.createKey('scmProvider', void 0); - } + ) {} private setActiveSCMProdiver(provider: ISCMProvider): void { this.activeProviderDisposable.dispose(); @@ -82,7 +79,6 @@ export class SCMService implements ISCMService { this.activeProviderDisposable = provider.onDidChange(() => this.onDidProviderChange(provider)); this.onDidProviderChange(provider); - this.activeProviderContextKey.set(provider ? provider.id : void 0); this._onDidChangeProvider.fire(provider); } From 3e6516db98855031a108929570795836096fb587 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 11 Jul 2017 10:19:41 +0200 Subject: [PATCH 03/67] wip: model registry --- extensions/git/src/main.ts | 10 +++-- extensions/git/src/model.ts | 5 +-- extensions/git/src/modelRegistry.ts | 60 +++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 extensions/git/src/modelRegistry.ts diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index d4eddb1ce803e..e1fd2ec463543 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -10,6 +10,7 @@ const localize = nls.config(process.env.VSCODE_NLS_CONFIG)(); import { ExtensionContext, workspace, window, Disposable, commands, Uri } from 'vscode'; import { findGit, Git, IGit } from './git'; import { Model } from './model'; +import { ModelRegistry } from './modelRegistry'; import { GitSCMProvider } from './scmProvider'; import { CommandCenter } from './commands'; import { StatusBarCommands } from './statusbar'; @@ -36,14 +37,17 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi const askpass = new Askpass(); const env = await askpass.getEnv(); const git = new Git({ gitPath: info.path, version: info.version, env }); + const modelRegistry = new ModelRegistry(); if (!workspaceRootPath || !enabled) { - const commandCenter = new CommandCenter(git, undefined, outputChannel, telemetryReporter); + const commandCenter = new CommandCenter(git, modelRegistry, outputChannel, telemetryReporter); disposables.push(commandCenter); return; } - const model = new Model(git, workspaceRootPath); + const workspaceRoot = Uri.file(workspaceRootPath); + const model = new Model(git, workspaceRoot); + modelRegistry.register(workspaceRoot, model); outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path)); @@ -51,7 +55,7 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi git.onOutput.addListener('log', onOutput); disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); - const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); + const commandCenter = new CommandCenter(git, modelRegistry, outputChannel, telemetryReporter); const statusBarCommands = new StatusBarCommands(model); const provider = new GitSCMProvider(model, commandCenter, statusBarCommands); const contentProvider = new GitContentProvider(model); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 7d852bc8a39f2..f1303722d17d2 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -363,7 +363,6 @@ export class Model implements Disposable { this._onDidChangeResources.fire(); } - private workspaceRoot: Uri; private onWorkspaceChange: Event; private isRepositoryHuge = false; private didWarnAboutLimit = false; @@ -372,10 +371,8 @@ export class Model implements Disposable { constructor( private _git: Git, - workspaceRootPath: string + private workspaceRoot: Uri ) { - this.workspaceRoot = Uri.file(workspaceRootPath); - const fsWatcher = workspace.createFileSystemWatcher('**'); this.onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); this.disposables.push(fsWatcher); diff --git a/extensions/git/src/modelRegistry.ts b/extensions/git/src/modelRegistry.ts new file mode 100644 index 0000000000000..862c6e1f76d05 --- /dev/null +++ b/extensions/git/src/modelRegistry.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Uri, window, QuickPickItem } from 'vscode'; +import { Model } from './model'; +import { memoize } from './decorators'; +import * as path from 'path'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + +class ModelPick implements QuickPickItem { + @memoize get label(): string { return path.basename(this.repositoryRoot.fsPath); } + @memoize get description(): string { return path.dirname(this.repositoryRoot.fsPath); } + constructor(protected repositoryRoot: Uri, public readonly model: Model) {} +} + +export class ModelRegistry { + + private models: Map = new Map(); + + register(uri: Uri, model): void { + this.models.set(uri, model); + } + + async pickModel(): Promise { + const picks = Array.from(this.models.entries(), ([uri, model]) => new ModelPick(uri, model)); + const placeHolder = localize('pick repo', "Choose a repository"); + const pick = await window.showQuickPick(picks, { placeHolder }); + + return pick && pick.model; + } + + async resolve(resource: Uri): Promise { + const resourcePath = resource.fsPath; + + for (let [repositoryRoot, model] of this.models) { + const repositoryRootPath = repositoryRoot.fsPath; + const relativePath = path.relative(repositoryRootPath, resourcePath); + + if (!/^\./.test(relativePath)) { + return model; + } + } + + const picks = Array.from(this.models.entries(), ([uri, model]) => new ModelPick(uri, model)); + const placeHolder = localize('pick repo', "Choose a repository"); + const pick = await window.showQuickPick(picks, { placeHolder }); + + if (pick) { + return pick.model; + } + + return undefined; + } +} From 408066b4cdd587c0b3f8de18d87e86c5d5a10597 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 24 Jul 2017 16:40:01 +0200 Subject: [PATCH 04/67] fix compile error --- src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index b50a46296bd6e..7ebe003211c68 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -13,7 +13,7 @@ import { memoize } from 'vs/base/common/decorators'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Builder, Dimension } from 'vs/base/browser/builder'; -import { ComposedViewsViewlet, CollapsibleView, ICollapsibleViewOptions, IViewletViewOptions, IView } from 'vs/workbench/parts/views/browser/views'; +import { ComposedViewsViewlet, CollapsibleView, ICollapsibleViewOptions, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views'; import { append, $, toggleClass } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -257,7 +257,7 @@ class SourceControlView extends CollapsibleView { @IEditorGroupService protected editorGroupService: IEditorGroupService, @IInstantiationService protected instantiationService: IInstantiationService ) { - super({...options, sizing: ViewSizing.Flexible}, keybindingService, contextMenuService); + super({ ...(options as IViewOptions), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService); this.menus = instantiationService.createInstance(SCMMenus, provider); this.menus.onDidChangeTitle(this.updateActions, this, this.disposables); @@ -370,7 +370,7 @@ class SourceControlView extends CollapsibleView { class InstallAdditionalSCMProvidersAction extends Action { - constructor(@IViewletService private viewletService: IViewletService) { + constructor( @IViewletService private viewletService: IViewletService) { super('scm.installAdditionalSCMProviders', localize('installAdditionalSCMProviders', "Install Additional SCM Providers..."), '', true); } From 1c24f825b56eadcd45f3336c8297a5d12846fb0b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 24 Jul 2017 16:40:09 +0200 Subject: [PATCH 05/67] git: model commands --- extensions/git/src/commands.ts | 351 +++++++++++++++++---------------- 1 file changed, 186 insertions(+), 165 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 31f4fcf408a11..af83d3056725b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -8,6 +8,7 @@ import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; import { Model, Resource, Status, CommitOptions, WorkingTreeGroup, IndexGroup, MergeGroup } from './model'; +import { ModelRegistry } from './modelRegistry'; import { toGitUri, fromGitUri } from './uri'; import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange } from './staging'; import * as path from 'path'; @@ -119,46 +120,52 @@ function command(commandId: string, skipModelCheck = false, requiresDiffInformat }; } +function globalCommand(commandId: string): Function { + return command(commandId, true, false); +} + +function modelCommand(commandId: string): Function { + return command(commandId); +} + +function diffCommand(commandId: string): Function { + return command(commandId, false, true); +} + export class CommandCenter { - private model: Model; private disposables: Disposable[]; constructor( private git: Git, - model: Model | undefined, + private modelRegistry: ModelRegistry, private outputChannel: OutputChannel, private telemetryReporter: TelemetryReporter ) { - if (model) { - this.model = model; - } - - this.disposables = Commands - .map(({ commandId, key, method, skipModelCheck, requiresDiffInformation }) => { - const command = this.createCommand(commandId, key, method, skipModelCheck); + this.disposables = Commands.map(({ commandId, key, method, skipModelCheck, requiresDiffInformation }) => { + const command = this.createCommand(commandId, key, method, skipModelCheck); - if (requiresDiffInformation) { - return commands.registerDiffInformationCommand(commandId, command); - } else { - return commands.registerCommand(commandId, command); - } - }); + if (requiresDiffInformation) { + return commands.registerDiffInformationCommand(commandId, command); + } else { + return commands.registerCommand(commandId, command); + } + }); } - @command('git.refresh') - async refresh(): Promise { - await this.model.status(); + @modelCommand('git.refresh') + async refresh(model: Model): Promise { + await model.status(); } - @command('git.openResource') - async openResource(resource: Resource): Promise { - await this._openResource(resource); + @modelCommand('git.openResource') + async openResource(model: Model, resource: Resource): Promise { + await this._openResource(model, resource); } - private async _openResource(resource: Resource): Promise { + private async _openResource(model: Model, resource: Resource): Promise { const left = this.getLeftResource(resource); - const right = this.getRightResource(resource); + const right = this.getRightResource(model, resource); const title = this.getTitle(resource); if (!right) { @@ -197,7 +204,7 @@ export class CommandCenter { } } - private getRightResource(resource: Resource): Uri | undefined { + private getRightResource(model: Model, resource: Resource): Uri | undefined { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: @@ -213,7 +220,7 @@ export class CommandCenter { case Status.UNTRACKED: case Status.IGNORED: const uriString = resource.resourceUri.toString(); - const [indexStatus] = this.model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString); + const [indexStatus] = model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString); if (indexStatus && indexStatus.renameResourceUri) { return indexStatus.renameResourceUri; @@ -241,7 +248,7 @@ export class CommandCenter { return ''; } - @command('git.clone', true) + @globalCommand('git.clone') async clone(): Promise { const url = await window.showInputBox({ prompt: localize('repourl', "Repository URL"), @@ -291,13 +298,14 @@ export class CommandCenter { } } - @command('git.init') + @globalCommand('git.init') async init(): Promise { - await this.model.init(); + // TODO@joao + // await model.init(); } - @command('git.openFile') - async openFile(arg?: Resource | Uri): Promise { + @modelCommand('git.openFile') + async openFile(model: Model, arg?: Resource | Uri): Promise { let uri: Uri | undefined; if (arg instanceof Uri) { @@ -311,7 +319,7 @@ export class CommandCenter { if (!(resource instanceof Resource)) { // can happen when called from a keybinding - resource = this.getSCMResource(); + resource = this.getSCMResource(model); } if (resource) { @@ -335,16 +343,16 @@ export class CommandCenter { } } - @command('git.openHEADFile') - async openHEADFile(arg?: Resource | Uri): Promise { + @modelCommand('git.openHEADFile') + async openHEADFile(model: Model, arg?: Resource | Uri): Promise { let resource: Resource | undefined = undefined; if (arg instanceof Resource) { resource = arg; } else if (arg instanceof Uri) { - resource = this.getSCMResource(arg); + resource = this.getSCMResource(model, arg); } else { - resource = this.getSCMResource(); + resource = this.getSCMResource(model); } if (!resource) { @@ -361,29 +369,29 @@ export class CommandCenter { return await commands.executeCommand('vscode.open', HEAD); } - @command('git.openChange') - async openChange(arg?: Resource | Uri): Promise { + @modelCommand('git.openChange') + async openChange(model: Model, arg?: Resource | Uri): Promise { let resource: Resource | undefined = undefined; if (arg instanceof Resource) { resource = arg; } else if (arg instanceof Uri) { - resource = this.getSCMResource(arg); + resource = this.getSCMResource(model, arg); } else { - resource = this.getSCMResource(); + resource = this.getSCMResource(model); } if (!resource) { return; } - return await this._openResource(resource); + return await this._openResource(model, resource); } - @command('git.stage') - async stage(...resourceStates: SourceControlResourceState[]): Promise { + @modelCommand('git.stage') + async stage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { - const resource = this.getSCMResource(); + const resource = this.getSCMResource(model); if (!resource) { return; @@ -399,16 +407,16 @@ export class CommandCenter { return; } - return await this.model.add(...resources); + return await model.add(...resources); } - @command('git.stageAll') - async stageAll(): Promise { - return await this.model.add(); + @modelCommand('git.stageAll') + async stageAll(model: Model): Promise { + return await model.add(); } - @command('git.stageSelectedRanges', false, true) - async stageSelectedRanges(diffs: LineChange[]): Promise { + @diffCommand('git.stageSelectedRanges') + async stageSelectedRanges(model: Model, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -435,11 +443,11 @@ export class CommandCenter { const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs); - await this.model.stage(modifiedUri, result); + await model.stage(modifiedUri, result); } - @command('git.revertSelectedRanges', false, true) - async revertSelectedRanges(diffs: LineChange[]): Promise { + @diffCommand('git.revertSelectedRanges') + async revertSelectedRanges(model: Model, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -483,10 +491,10 @@ export class CommandCenter { workspace.applyEdit(edit); } - @command('git.unstage') - async unstage(...resourceStates: SourceControlResourceState[]): Promise { + @modelCommand('git.unstage') + async unstage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { - const resource = this.getSCMResource(); + const resource = this.getSCMResource(model); if (!resource) { return; @@ -502,16 +510,16 @@ export class CommandCenter { return; } - return await this.model.revertFiles(...resources); + return await model.revertFiles(...resources); } - @command('git.unstageAll') - async unstageAll(): Promise { - return await this.model.revertFiles(); + @modelCommand('git.unstageAll') + async unstageAll(model: Model): Promise { + return await model.revertFiles(); } - @command('git.unstageSelectedRanges', false, true) - async unstageSelectedRanges(diffs: LineChange[]): Promise { + @diffCommand('git.unstageSelectedRanges') + async unstageSelectedRanges(model: Model, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -545,13 +553,13 @@ export class CommandCenter { const invertedDiffs = selectedDiffs.map(invertLineChange); const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs); - await this.model.stage(modifiedUri, result); + await model.stage(modifiedUri, result); } - @command('git.clean') - async clean(...resourceStates: SourceControlResourceState[]): Promise { + @modelCommand('git.clean') + async clean(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { - const resource = this.getSCMResource(); + const resource = this.getSCMResource(model); if (!resource) { return; @@ -578,11 +586,11 @@ export class CommandCenter { return; } - await this.model.clean(...resources); + await model.clean(...resources); } - @command('git.cleanAll') - async cleanAll(): Promise { + @modelCommand('git.cleanAll') + async cleanAll(model: Model): Promise { const message = localize('confirm discard all', "Are you sure you want to discard ALL changes? This is IRREVERSIBLE!"); const yes = localize('discardAll', "Discard ALL Changes"); const pick = await window.showWarningMessage(message, { modal: true }, yes); @@ -591,17 +599,18 @@ export class CommandCenter { return; } - await this.model.clean(...this.model.workingTreeGroup.resources); + await model.clean(...model.workingTreeGroup.resources); } private async smartCommit( + model: Model, getCommitMessage: () => Promise, opts?: CommitOptions ): Promise { const config = workspace.getConfiguration('git'); const enableSmartCommit = config.get('enableSmartCommit') === true; - const noStagedChanges = this.model.indexGroup.resources.length === 0; - const noUnstagedChanges = this.model.workingTreeGroup.resources.length === 0; + const noStagedChanges = model.indexGroup.resources.length === 0; + const noUnstagedChanges = model.workingTreeGroup.resources.length === 0; // no changes, and the user has not configured to commit all in this case if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit) { @@ -640,12 +649,12 @@ export class CommandCenter { return false; } - await this.model.commit(message, opts); + await model.commit(message, opts); return true; } - private async commitWithAnyInput(opts?: CommitOptions): Promise { + private async commitWithAnyInput(model: Model, opts?: CommitOptions): Promise { const message = scm.inputBox.value; const getCommitMessage = async () => { if (message) { @@ -659,68 +668,68 @@ export class CommandCenter { }); }; - const didCommit = await this.smartCommit(getCommitMessage, opts); + const didCommit = await this.smartCommit(model, getCommitMessage, opts); if (message && didCommit) { - scm.inputBox.value = await this.model.getCommitTemplate(); + scm.inputBox.value = await model.getCommitTemplate(); } } - @command('git.commit') - async commit(): Promise { - await this.commitWithAnyInput(); + @modelCommand('git.commit') + async commit(model: Model): Promise { + await this.commitWithAnyInput(model); } - @command('git.commitWithInput') - async commitWithInput(): Promise { + @modelCommand('git.commitWithInput') + async commitWithInput(model: Model): Promise { if (!scm.inputBox.value) { return; } - const didCommit = await this.smartCommit(async () => scm.inputBox.value); + const didCommit = await this.smartCommit(model, async () => scm.inputBox.value); if (didCommit) { - scm.inputBox.value = await this.model.getCommitTemplate(); + scm.inputBox.value = await model.getCommitTemplate(); } } - @command('git.commitStaged') - async commitStaged(): Promise { - await this.commitWithAnyInput({ all: false }); + @modelCommand('git.commitStaged') + async commitStaged(model: Model): Promise { + await this.commitWithAnyInput(model, { all: false }); } - @command('git.commitStagedSigned') - async commitStagedSigned(): Promise { - await this.commitWithAnyInput({ all: false, signoff: true }); + @modelCommand('git.commitStagedSigned') + async commitStagedSigned(model: Model): Promise { + await this.commitWithAnyInput(model, { all: false, signoff: true }); } - @command('git.commitAll') - async commitAll(): Promise { - await this.commitWithAnyInput({ all: true }); + @modelCommand('git.commitAll') + async commitAll(model: Model): Promise { + await this.commitWithAnyInput(model, { all: true }); } - @command('git.commitAllSigned') - async commitAllSigned(): Promise { - await this.commitWithAnyInput({ all: true, signoff: true }); + @modelCommand('git.commitAllSigned') + async commitAllSigned(model: Model): Promise { + await this.commitWithAnyInput(model, { all: true, signoff: true }); } - @command('git.undoCommit') - async undoCommit(): Promise { - const HEAD = this.model.HEAD; + @modelCommand('git.undoCommit') + async undoCommit(model: Model): Promise { + const HEAD = model.HEAD; if (!HEAD || !HEAD.commit) { return; } - const commit = await this.model.getCommit('HEAD'); - await this.model.reset('HEAD~'); + const commit = await model.getCommit('HEAD'); + await model.reset('HEAD~'); scm.inputBox.value = commit.message; } - @command('git.checkout') - async checkout(treeish: string): Promise { + @modelCommand('git.checkout') + async checkout(model: Model, treeish: string): Promise { if (typeof treeish === 'string') { - return await this.model.checkout(treeish); + return await model.checkout(treeish); } const config = workspace.getConfiguration('git'); @@ -730,13 +739,13 @@ export class CommandCenter { const createBranch = new CreateBranchItem(); - const heads = this.model.refs.filter(ref => ref.type === RefType.Head) + const heads = model.refs.filter(ref => ref.type === RefType.Head) .map(ref => new CheckoutItem(ref)); - const tags = (includeTags ? this.model.refs.filter(ref => ref.type === RefType.Tag) : []) + const tags = (includeTags ? model.refs.filter(ref => ref.type === RefType.Tag) : []) .map(ref => new CheckoutTagItem(ref)); - const remoteHeads = (includeRemotes ? this.model.refs.filter(ref => ref.type === RefType.RemoteHead) : []) + const remoteHeads = (includeRemotes ? model.refs.filter(ref => ref.type === RefType.RemoteHead) : []) .map(ref => new CheckoutRemoteHeadItem(ref)); const picks = [createBranch, ...heads, ...tags, ...remoteHeads]; @@ -747,11 +756,11 @@ export class CommandCenter { return; } - await choice.run(this.model); + await choice.run(model); } - @command('git.branch') - async branch(): Promise { + @modelCommand('git.branch') + async branch(model: Model): Promise { const result = await window.showInputBox({ placeHolder: localize('branch name', "Branch name"), prompt: localize('provide branch name', "Please provide a branch name"), @@ -763,17 +772,17 @@ export class CommandCenter { } const name = result.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-'); - await this.model.branch(name); + await model.branch(name); } - @command('git.deleteBranch') - async deleteBranch(name: string, force?: boolean): Promise { + @modelCommand('git.deleteBranch') + async deleteBranch(model: Model, name: string, force?: boolean): Promise { let run: (force?: boolean) => Promise; if (typeof name === 'string') { - run = force => this.model.deleteBranch(name, force); + run = force => model.deleteBranch(name, force); } else { - const currentHead = this.model.HEAD && this.model.HEAD.name; - const heads = this.model.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead) + const currentHead = model.HEAD && model.HEAD.name; + const heads = model.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead) .map(ref => new BranchDeleteItem(ref)); const placeHolder = localize('select branch to delete', 'Select a branch to delete'); @@ -783,7 +792,7 @@ export class CommandCenter { return; } name = choice.branchName; - run = force => choice.run(this.model, force); + run = force => choice.run(model, force); } try { @@ -803,17 +812,17 @@ export class CommandCenter { } } - @command('git.merge') - async merge(): Promise { + @modelCommand('git.merge') + async merge(model: Model): Promise { const config = workspace.getConfiguration('git'); const checkoutType = config.get('checkoutType') || 'all'; const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; - const heads = this.model.refs.filter(ref => ref.type === RefType.Head) + const heads = model.refs.filter(ref => ref.type === RefType.Head) .filter(ref => ref.name || ref.commit) .map(ref => new MergeItem(ref as Branch)); - const remoteHeads = (includeRemotes ? this.model.refs.filter(ref => ref.type === RefType.RemoteHead) : []) + const remoteHeads = (includeRemotes ? model.refs.filter(ref => ref.type === RefType.RemoteHead) : []) .filter(ref => ref.name || ref.commit) .map(ref => new MergeItem(ref as Branch)); @@ -826,7 +835,7 @@ export class CommandCenter { } try { - await choice.run(this.model); + await choice.run(model); } catch (err) { if (err.gitErrorCode !== GitErrorCodes.Conflict) { throw err; @@ -837,9 +846,9 @@ export class CommandCenter { } } - @command('git.pullFrom') - async pullFrom(): Promise { - const remotes = this.model.remotes; + @modelCommand('git.pullFrom') + async pullFrom(model: Model): Promise { + const remotes = model.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); @@ -864,60 +873,60 @@ export class CommandCenter { return; } - this.model.pull(false, pick.label, branchName); + model.pull(false, pick.label, branchName); } - @command('git.pull') - async pull(): Promise { - const remotes = this.model.remotes; + @modelCommand('git.pull') + async pull(model: Model): Promise { + const remotes = model.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); return; } - await this.model.pull(); + await model.pull(); } - @command('git.pullRebase') - async pullRebase(): Promise { - const remotes = this.model.remotes; + @modelCommand('git.pullRebase') + async pullRebase(model: Model): Promise { + const remotes = model.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); return; } - await this.model.pullWithRebase(); + await model.pullWithRebase(); } - @command('git.push') - async push(): Promise { - const remotes = this.model.remotes; + @modelCommand('git.push') + async push(model: Model): Promise { + const remotes = model.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to.")); return; } - await this.model.push(); + await model.push(); } - @command('git.pushTo') - async pushTo(): Promise { - const remotes = this.model.remotes; + @modelCommand('git.pushTo') + async pushTo(model: Model): Promise { + const remotes = model.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to.")); return; } - if (!this.model.HEAD || !this.model.HEAD.name) { + if (!model.HEAD || !model.HEAD.name) { window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote.")); return; } - const branchName = this.model.HEAD.name; + const branchName = model.HEAD.name; const picks = remotes.map(r => ({ label: r.name, description: r.url })); const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); const pick = await window.showQuickPick(picks, { placeHolder }); @@ -926,12 +935,12 @@ export class CommandCenter { return; } - this.model.pushTo(pick.label, branchName); + model.pushTo(pick.label, branchName); } - @command('git.sync') - async sync(): Promise { - const HEAD = this.model.HEAD; + @modelCommand('git.sync') + async sync(model: Model): Promise { + const HEAD = model.HEAD; if (!HEAD || !HEAD.upstream) { return; @@ -953,20 +962,20 @@ export class CommandCenter { } } - await this.model.sync(); + await model.sync(); } - @command('git.publish') - async publish(): Promise { - const remotes = this.model.remotes; + @modelCommand('git.publish') + async publish(model: Model): Promise { + const remotes = model.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to publish', "Your repository has no remotes configured to publish to.")); return; } - const branchName = this.model.HEAD && this.model.HEAD.name || ''; - const picks = this.model.remotes.map(r => r.name); + const branchName = model.HEAD && model.HEAD.name || ''; + const picks = model.remotes.map(r => r.name); const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); const choice = await window.showQuickPick(picks, { placeHolder }); @@ -974,16 +983,16 @@ export class CommandCenter { return; } - await this.model.pushTo(choice, branchName, true); + await model.pushTo(choice, branchName, true); } - @command('git.showOutput') + @globalCommand('git.showOutput') showOutput(): void { this.outputChannel.show(); } - @command('git.ignore') - async ignore(...resourceStates: SourceControlResourceState[]): Promise { + @modelCommand('git.ignore') + async ignore(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const uri = window.activeTextEditor && window.activeTextEditor.document.uri; @@ -991,7 +1000,7 @@ export class CommandCenter { return; } - return await this.model.ignore([uri]); + return await model.ignore([uri]); } const uris = resourceStates @@ -1002,20 +1011,32 @@ export class CommandCenter { return; } - await this.model.ignore(uris); + await model.ignore(uris); } private createCommand(id: string, key: string, method: Function, skipModelCheck: boolean): (...args: any[]) => any { const result = (...args) => { - if (!skipModelCheck && !this.model) { - window.showInformationMessage(localize('disabled', "Git is either disabled or not supported in this workspace")); - return; + // if (!skipModelCheck && !this.model) { + // window.showInformationMessage(localize('disabled', "Git is either disabled or not supported in this workspace")); + // return; + // } + + let result: Promise; + + if (skipModelCheck) { + result = Promise.resolve(method.apply(this, args)); + } else { + result = this.modelRegistry.pickModel().then(model => { + if (!model) { + return Promise.reject(localize('modelnotfound', "Git model not found")); + } + + return Promise.resolve(method.apply(this, [model, ...args])); + }); } this.telemetryReporter.sendTelemetryEvent('git.command', { command: id }); - const result = Promise.resolve(method.apply(this, args)); - return result.catch(async err => { let message: string; @@ -1062,7 +1083,7 @@ export class CommandCenter { return result; } - private getSCMResource(uri?: Uri): Resource | undefined { + private getSCMResource(model: Model, uri?: Uri): Resource | undefined { uri = uri ? uri : window.activeTextEditor && window.activeTextEditor.document.uri; if (!uri) { @@ -1077,8 +1098,8 @@ export class CommandCenter { if (uri.scheme === 'file') { const uriString = uri.toString(); - return this.model.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0] - || this.model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]; + return model.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0] + || model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]; } } From f61fe8ad0449c9f547667903063ed7391c5a29c9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 24 Jul 2017 16:58:22 +0200 Subject: [PATCH 06/67] scm: fix contents not visible --- src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 7ebe003211c68..ad5845e3854c9 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -303,6 +303,7 @@ class SourceControlView extends CollapsibleView { this.disposables.push(this.list); this.provider.onDidChange(this.updateList, this, this.disposables); + this.updateList(); } layoutBody(size: number): void { From 405ed721420596af539ffdc2ac7dc2fb8994cca4 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 14 Aug 2017 15:30:23 +0200 Subject: [PATCH 07/67] different command syntax --- extensions/git/src/commands.ts | 114 +++++++++++++++------------------ 1 file changed, 53 insertions(+), 61 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index c90f3daf70255..7a264c1dce735 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -100,38 +100,30 @@ class CreateBranchItem implements QuickPickItem { } } +interface CommandOptions { + model?: boolean; + diff?: boolean; +} + interface Command { commandId: string; key: string; method: Function; - skipModelCheck: boolean; - requiresDiffInformation: boolean; + options: CommandOptions; } const Commands: Command[] = []; -function command(commandId: string, skipModelCheck = false, requiresDiffInformation = false): Function { +function command(commandId: string, options: CommandOptions = {}): Function { return (target: any, key: string, descriptor: any) => { if (!(typeof descriptor.value === 'function')) { throw new Error('not supported'); } - Commands.push({ commandId, key, method: descriptor.value, skipModelCheck, requiresDiffInformation }); + Commands.push({ commandId, key, method: descriptor.value, options }); }; } -function globalCommand(commandId: string): Function { - return command(commandId, true, false); -} - -function modelCommand(commandId: string): Function { - return command(commandId); -} - -function diffCommand(commandId: string): Function { - return command(commandId, false, true); -} - export class CommandCenter { private disposables: Disposable[]; @@ -142,10 +134,10 @@ export class CommandCenter { private outputChannel: OutputChannel, private telemetryReporter: TelemetryReporter ) { - this.disposables = Commands.map(({ commandId, key, method, skipModelCheck, requiresDiffInformation }) => { - const command = this.createCommand(commandId, key, method, skipModelCheck); + this.disposables = Commands.map(({ commandId, key, method, options }) => { + const command = this.createCommand(commandId, key, method, options); - if (requiresDiffInformation) { + if (options.diff) { return commands.registerDiffInformationCommand(commandId, command); } else { return commands.registerCommand(commandId, command); @@ -153,12 +145,12 @@ export class CommandCenter { }); } - @modelCommand('git.refresh') + @command('git.refresh', { model: true }) async refresh(model: Model): Promise { await model.status(); } - @modelCommand('git.openResource') + @command('git.openResource', { model: true }) async openResource(model: Model, resource: Resource): Promise { await this._openResource(model, resource); } @@ -258,7 +250,7 @@ export class CommandCenter { return ''; } - @globalCommand('git.clone') + @command('git.clone') async clone(): Promise { const url = await window.showInputBox({ prompt: localize('repourl', "Repository URL"), @@ -308,13 +300,13 @@ export class CommandCenter { } } - @globalCommand('git.init') + @command('git.init') async init(): Promise { // TODO@joao // await model.init(); } - @modelCommand('git.openFile') + @command('git.openFile', { model: true }) async openFile(model: Model, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let uris: Uri[] | undefined; @@ -364,7 +356,7 @@ export class CommandCenter { } } - @modelCommand('git.openHEADFile') + @command('git.openHEADFile', { model: true }) async openHEADFile(model: Model, arg?: Resource | Uri): Promise { let resource: Resource | undefined = undefined; @@ -390,7 +382,7 @@ export class CommandCenter { return await commands.executeCommand('vscode.open', HEAD); } - @modelCommand('git.openChange') + @command('git.openChange', { model: true }) async openChange(model: Model, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let resources: Resource[] | undefined = undefined; @@ -423,7 +415,7 @@ export class CommandCenter { } } - @modelCommand('git.stage') + @command('git.stage', { model: true }) async stage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(model); @@ -445,13 +437,13 @@ export class CommandCenter { return await model.add(...resources); } - @modelCommand('git.stageAll') + @command('git.stageAll', { model: true }) async stageAll(model: Model): Promise { return await model.add(); } // TODO@Joao does this command really receive a model? - @diffCommand('git.stageSelectedRanges') + @command('git.stageSelectedRanges', { model: true, diff: true }) async stageSelectedRanges(model: Model, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; @@ -483,7 +475,7 @@ export class CommandCenter { } // TODO@Joao does this command really receive a model? - @diffCommand('git.revertSelectedRanges') + @command('git.revertSelectedRanges', { model: true, diff: true }) async revertSelectedRanges(model: Model, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; @@ -528,7 +520,7 @@ export class CommandCenter { workspace.applyEdit(edit); } - @modelCommand('git.unstage') + @command('git.unstage', { model: true }) async unstage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(model); @@ -550,13 +542,13 @@ export class CommandCenter { return await model.revertFiles(...resources); } - @modelCommand('git.unstageAll') + @command('git.unstageAll', { model: true }) async unstageAll(model: Model): Promise { return await model.revertFiles(); } // TODO@Joao does this command really receive a model? - @diffCommand('git.unstageSelectedRanges') + @command('git.unstageSelectedRanges', { model: true, diff: true }) async unstageSelectedRanges(model: Model, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; @@ -594,7 +586,7 @@ export class CommandCenter { await model.stage(modifiedUri, result); } - @modelCommand('git.clean') + @command('git.clean', { model: true }) async clean(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(model); @@ -627,7 +619,7 @@ export class CommandCenter { await model.clean(...resources); } - @modelCommand('git.cleanAll') + @command('git.cleanAll', { model: true }) async cleanAll(model: Model): Promise { const message = localize('confirm discard all', "Are you sure you want to discard ALL changes? This is IRREVERSIBLE!"); const yes = localize('discardAll', "Discard ALL Changes"); @@ -717,12 +709,12 @@ export class CommandCenter { } } - @modelCommand('git.commit') + @command('git.commit', { model: true }) async commit(model: Model): Promise { await this.commitWithAnyInput(model); } - @modelCommand('git.commitWithInput') + @command('git.commitWithInput', { model: true }) async commitWithInput(model: Model): Promise { if (!scm.inputBox.value) { return; @@ -735,37 +727,37 @@ export class CommandCenter { } } - @modelCommand('git.commitStaged') + @command('git.commitStaged', { model: true }) async commitStaged(model: Model): Promise { await this.commitWithAnyInput(model, { all: false }); } - @modelCommand('git.commitStagedSigned') + @command('git.commitStagedSigned', { model: true }) async commitStagedSigned(model: Model): Promise { await this.commitWithAnyInput(model, { all: false, signoff: true }); } - @modelCommand('git.commitStagedAmend') + @command('git.commitStagedAmend', { model: true }) async commitStagedAmend(model: Model): Promise { await this.commitWithAnyInput(model, { all: false, amend: true }); } - @modelCommand('git.commitAll') + @command('git.commitAll', { model: true }) async commitAll(model: Model): Promise { await this.commitWithAnyInput(model, { all: true }); } - @modelCommand('git.commitAllSigned') + @command('git.commitAllSigned', { model: true }) async commitAllSigned(model: Model): Promise { await this.commitWithAnyInput(model, { all: true, signoff: true }); } - @modelCommand('git.commitAllAmend') + @command('git.commitAllAmend', { model: true }) async commitAllAmend(model: Model): Promise { await this.commitWithAnyInput(model, { all: true, amend: true }); } - @modelCommand('git.undoCommit') + @command('git.undoCommit', { model: true }) async undoCommit(model: Model): Promise { const HEAD = model.HEAD; @@ -778,7 +770,7 @@ export class CommandCenter { scm.inputBox.value = commit.message; } - @modelCommand('git.checkout') + @command('git.checkout', { model: true }) async checkout(model: Model, treeish: string): Promise { if (typeof treeish === 'string') { return await model.checkout(treeish); @@ -811,7 +803,7 @@ export class CommandCenter { await choice.run(model); } - @modelCommand('git.branch') + @command('git.branch', { model: true }) async branch(model: Model): Promise { const result = await window.showInputBox({ placeHolder: localize('branch name', "Branch name"), @@ -827,7 +819,7 @@ export class CommandCenter { await model.branch(name); } - @modelCommand('git.deleteBranch') + @command('git.deleteBranch', { model: true }) async deleteBranch(model: Model, name: string, force?: boolean): Promise { let run: (force?: boolean) => Promise; if (typeof name === 'string') { @@ -864,7 +856,7 @@ export class CommandCenter { } } - @modelCommand('git.merge') + @command('git.merge', { model: true }) async merge(model: Model): Promise { const config = workspace.getConfiguration('git'); const checkoutType = config.get('checkoutType') || 'all'; @@ -898,7 +890,7 @@ export class CommandCenter { } } - @modelCommand('git.createTag') + @command('git.createTag', { model: true }) async createTag(model: Model): Promise { const inputTagName = await window.showInputBox({ placeHolder: localize('tag name', "Tag name"), @@ -921,7 +913,7 @@ export class CommandCenter { await model.tag(name, message); } - @modelCommand('git.pullFrom') + @command('git.pullFrom', { model: true }) async pullFrom(model: Model): Promise { const remotes = model.remotes; @@ -951,7 +943,7 @@ export class CommandCenter { model.pull(false, pick.label, branchName); } - @modelCommand('git.pull') + @command('git.pull', { model: true }) async pull(model: Model): Promise { const remotes = model.remotes; @@ -963,7 +955,7 @@ export class CommandCenter { await model.pull(); } - @modelCommand('git.pullRebase') + @command('git.pullRebase', { model: true }) async pullRebase(model: Model): Promise { const remotes = model.remotes; @@ -975,7 +967,7 @@ export class CommandCenter { await model.pullWithRebase(); } - @modelCommand('git.push') + @command('git.push', { model: true }) async push(model: Model): Promise { const remotes = model.remotes; @@ -987,7 +979,7 @@ export class CommandCenter { await model.push(); } - @modelCommand('git.pushWithTags') + @command('git.pushWithTags', { model: true }) async pushWithTags(model: Model): Promise { const remotes = model.remotes; @@ -1001,7 +993,7 @@ export class CommandCenter { window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags.")); } - @modelCommand('git.pushTo') + @command('git.pushTo', { model: true }) async pushTo(model: Model): Promise { const remotes = model.remotes; @@ -1027,7 +1019,7 @@ export class CommandCenter { model.pushTo(pick.label, branchName); } - @modelCommand('git.sync') + @command('git.sync', { model: true }) async sync(model: Model): Promise { const HEAD = model.HEAD; @@ -1054,7 +1046,7 @@ export class CommandCenter { await model.sync(); } - @modelCommand('git.publish') + @command('git.publish', { model: true }) async publish(model: Model): Promise { const remotes = model.remotes; @@ -1075,12 +1067,12 @@ export class CommandCenter { await model.pushTo(choice, branchName, true); } - @globalCommand('git.showOutput') + @command('git.showOutput') showOutput(): void { this.outputChannel.show(); } - @modelCommand('git.ignore') + @command('git.ignore', { model: true }) async ignore(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const uri = window.activeTextEditor && window.activeTextEditor.document.uri; @@ -1103,7 +1095,7 @@ export class CommandCenter { await model.ignore(uris); } - private createCommand(id: string, key: string, method: Function, skipModelCheck: boolean): (...args: any[]) => any { + private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any { const result = (...args) => { // if (!skipModelCheck && !this.model) { // window.showInformationMessage(localize('disabled', "Git is either disabled or not supported in this workspace")); @@ -1112,7 +1104,7 @@ export class CommandCenter { let result: Promise; - if (skipModelCheck) { + if (!options.model) { result = Promise.resolve(method.apply(this, args)); } else { result = this.modelRegistry.pickModel().then(model => { From b8b36a0f18e8b810fdfb323e80500be9e0caf13c Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 14 Aug 2017 16:44:58 +0200 Subject: [PATCH 08/67] remove unused property --- extensions/git/src/main.ts | 2 +- extensions/git/src/modelRegistry.ts | 2 +- extensions/git/src/scmProvider.ts | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index e1fd2ec463543..2db3c8df3244b 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -57,7 +57,7 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi const commandCenter = new CommandCenter(git, modelRegistry, outputChannel, telemetryReporter); const statusBarCommands = new StatusBarCommands(model); - const provider = new GitSCMProvider(model, commandCenter, statusBarCommands); + const provider = new GitSCMProvider(model, statusBarCommands); const contentProvider = new GitContentProvider(model); const autoFetcher = new AutoFetcher(model); diff --git a/extensions/git/src/modelRegistry.ts b/extensions/git/src/modelRegistry.ts index 862c6e1f76d05..470619d3cab33 100644 --- a/extensions/git/src/modelRegistry.ts +++ b/extensions/git/src/modelRegistry.ts @@ -16,7 +16,7 @@ const localize = nls.loadMessageBundle(); class ModelPick implements QuickPickItem { @memoize get label(): string { return path.basename(this.repositoryRoot.fsPath); } @memoize get description(): string { return path.dirname(this.repositoryRoot.fsPath); } - constructor(protected repositoryRoot: Uri, public readonly model: Model) {} + constructor(protected repositoryRoot: Uri, public readonly model: Model) { } } export class ModelRegistry { diff --git a/extensions/git/src/scmProvider.ts b/extensions/git/src/scmProvider.ts index f942fcc8a5e95..80a413b65d2e8 100644 --- a/extensions/git/src/scmProvider.ts +++ b/extensions/git/src/scmProvider.ts @@ -8,7 +8,6 @@ import { scm, Uri, Disposable, SourceControl, SourceControlResourceGroup, Event, workspace, commands } from 'vscode'; import { Model, State, Status } from './model'; import { StatusBarCommands } from './statusbar'; -import { CommandCenter } from './commands'; import { mapEvent } from './util'; import { toGitUri } from './uri'; import * as nls from 'vscode-nls'; @@ -60,7 +59,6 @@ export class GitSCMProvider { constructor( private model: Model, - private commandCenter: CommandCenter, private statusBarCommands: StatusBarCommands ) { this._sourceControl = scm.createSourceControl('git', 'Git'); From b9eb8baea5c47ad06b932567057ee31c36af6a82 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 09:35:24 +0200 Subject: [PATCH 09/67] wip: add across multiple repositories --- extensions/git/src/commands.ts | 51 ++++++++++++++++++++++------- extensions/git/src/modelRegistry.ts | 12 ++++++- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 7a264c1dce735..a674df932863f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -145,6 +145,21 @@ export class CommandCenter { }); } + private groupByModel(resources: Resource[]): [Model | undefined, Resource[]][] { + return resources.reduce((result, resource) => { + const model = this.modelRegistry.getModel(resource.resourceUri); + const pair = result.filter(p => p[0] === model)[0]; + + if (pair) { + pair[1].push(resource); + } else { + result.push([model, [resource]]); + } + + return result; + }, [] as [Model | undefined, Resource[]][]); + } + @command('git.refresh', { model: true }) async refresh(model: Model): Promise { await model.status(); @@ -321,7 +336,7 @@ export class CommandCenter { if (!(resource instanceof Resource)) { // can happen when called from a keybinding - resource = this.getSCMResource(model); + resource = this.getSCMResource(); } if (resource) { @@ -363,9 +378,9 @@ export class CommandCenter { if (arg instanceof Resource) { resource = arg; } else if (arg instanceof Uri) { - resource = this.getSCMResource(model, arg); + resource = this.getSCMResource(arg); } else { - resource = this.getSCMResource(model); + resource = this.getSCMResource(); } if (!resource) { @@ -387,7 +402,7 @@ export class CommandCenter { let resources: Resource[] | undefined = undefined; if (arg instanceof Uri) { - const resource = this.getSCMResource(model, arg); + const resource = this.getSCMResource(arg); if (resource !== undefined) { resources = [resource]; } @@ -397,7 +412,7 @@ export class CommandCenter { if (arg instanceof Resource) { resource = arg; } else { - resource = this.getSCMResource(model); + resource = this.getSCMResource(); } if (resource) { @@ -415,10 +430,10 @@ export class CommandCenter { } } - @command('git.stage', { model: true }) - async stage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { + @command('git.stage') + async stage(...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { - const resource = this.getSCMResource(model); + const resource = this.getSCMResource(); if (!resource) { return; @@ -434,7 +449,13 @@ export class CommandCenter { return; } - return await model.add(...resources); + await Promise.all(this.groupByModel(resources).map(async ([model, resources]) => { + if (!model) { + return; + } + + await model.add(...resources); + })); } @command('git.stageAll', { model: true }) @@ -523,7 +544,7 @@ export class CommandCenter { @command('git.unstage', { model: true }) async unstage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { - const resource = this.getSCMResource(model); + const resource = this.getSCMResource(); if (!resource) { return; @@ -589,7 +610,7 @@ export class CommandCenter { @command('git.clean', { model: true }) async clean(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { - const resource = this.getSCMResource(model); + const resource = this.getSCMResource(); if (!resource) { return; @@ -1164,7 +1185,8 @@ export class CommandCenter { return result; } - private getSCMResource(model: Model, uri?: Uri): Resource | undefined { + // TODO@Joao: possibly remove? do we really need to return resources? + private getSCMResource(uri?: Uri): Resource | undefined { uri = uri ? uri : window.activeTextEditor && window.activeTextEditor.document.uri; if (!uri) { @@ -1178,6 +1200,11 @@ export class CommandCenter { if (uri.scheme === 'file') { const uriString = uri.toString(); + const model = this.modelRegistry.getModel(uri); + + if (!model) { + return undefined; + } return model.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0] || model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]; diff --git a/extensions/git/src/modelRegistry.ts b/extensions/git/src/modelRegistry.ts index 470619d3cab33..0ff90cf3ba0e5 100644 --- a/extensions/git/src/modelRegistry.ts +++ b/extensions/git/src/modelRegistry.ts @@ -35,7 +35,7 @@ export class ModelRegistry { return pick && pick.model; } - async resolve(resource: Uri): Promise { + getModel(resource: Uri): Model | undefined { const resourcePath = resource.fsPath; for (let [repositoryRoot, model] of this.models) { @@ -47,6 +47,16 @@ export class ModelRegistry { } } + return undefined; + } + + async resolve(resource: Uri): Promise { + const model = this.getModel(resource); + + if (model) { + return model; + } + const picks = Array.from(this.models.entries(), ([uri, model]) => new ModelPick(uri, model)); const placeHolder = localize('pick repo', "Choose a repository"); const pick = await window.showQuickPick(picks, { placeHolder }); From acb83d00c0c761a821480ccefc2f201f171b3915 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 11:53:35 +0200 Subject: [PATCH 10/67] remove Resource from git model method interfaces --- extensions/git/src/commands.ts | 30 ++++++++++++++++++------------ extensions/git/src/model.ts | 25 ++++++++++++++++--------- extensions/git/src/util.ts | 15 +++++++++++++++ 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a674df932863f..9b6f5e1541156 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -145,9 +145,9 @@ export class CommandCenter { }); } - private groupByModel(resources: Resource[]): [Model | undefined, Resource[]][] { + private groupByModel(resources: Uri[]): [Model | undefined, Uri[]][] { return resources.reduce((result, resource) => { - const model = this.modelRegistry.getModel(resource.resourceUri); + const model = this.modelRegistry.getModel(resource); const pair = result.filter(p => p[0] === model)[0]; if (pair) { @@ -157,7 +157,7 @@ export class CommandCenter { } return result; - }, [] as [Model | undefined, Resource[]][]); + }, [] as [Model | undefined, Uri[]][]); } @command('git.refresh', { model: true }) @@ -442,16 +442,19 @@ export class CommandCenter { resourceStates = [resource]; } - const resources = resourceStates + const scmResources = resourceStates .filter(s => s instanceof Resource && (s.resourceGroup instanceof WorkingTreeGroup || s.resourceGroup instanceof MergeGroup)) as Resource[]; - if (!resources.length) { + if (!scmResources.length) { return; } - await Promise.all(this.groupByModel(resources).map(async ([model, resources]) => { + const resources = scmResources.map(r => r.resourceUri); + const resourcesByModel = this.groupByModel(resources); + + await Promise.all(resourcesByModel.map(async ([model, resources]) => { if (!model) { - return; + return; // TODO@joao } await model.add(...resources); @@ -553,13 +556,15 @@ export class CommandCenter { resourceStates = [resource]; } - const resources = resourceStates + const scmResources = resourceStates .filter(s => s instanceof Resource && s.resourceGroup instanceof IndexGroup) as Resource[]; - if (!resources.length) { + if (!scmResources.length) { return; } + const resources = scmResources.map(r => r.resourceUri); + return await model.revertFiles(...resources); } @@ -620,14 +625,15 @@ export class CommandCenter { } const resources = resourceStates - .filter(s => s instanceof Resource && s.resourceGroup instanceof WorkingTreeGroup) as Resource[]; + .filter(s => s instanceof Resource && s.resourceGroup instanceof WorkingTreeGroup) + .map(r => r.resourceUri); if (!resources.length) { return; } const message = resources.length === 1 - ? localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath)) + ? localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].fsPath)) : localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", resources.length); const yes = localize('discard', "Discard Changes"); @@ -650,7 +656,7 @@ export class CommandCenter { return; } - await model.clean(...model.workingTreeGroup.resources); + await model.clean(...model.workingTreeGroup.resources.map(r => r.resourceUri)); } private async smartCommit( diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 6ea442449c4c6..8674145fa570a 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -7,7 +7,7 @@ import { Uri, Command, EventEmitter, Event, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; import { Git, Repository, Ref, Branch, Remote, Commit, GitErrorCodes } from './git'; -import { anyEvent, eventToPromise, filterEvent, EmptyDisposable, combinedDisposable, dispose } from './util'; +import { anyEvent, eventToPromise, filterEvent, EmptyDisposable, combinedDisposable, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; import * as path from 'path'; import * as nls from 'vscode-nls'; @@ -397,8 +397,8 @@ export class Model implements Disposable { await this.run(Operation.Status); } - async add(...resources: Resource[]): Promise { - await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.resourceUri.fsPath))); + async add(...resources: Uri[]): Promise { + await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath))); } async stage(uri: Uri, contents: string): Promise { @@ -406,8 +406,8 @@ export class Model implements Disposable { await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents)); } - async revertFiles(...resources: Resource[]): Promise { - await this.run(Operation.RevertFiles, () => this.repository.revertFiles('HEAD', resources.map(r => r.resourceUri.fsPath))); + async revertFiles(...resources: Uri[]): Promise { + await this.run(Operation.RevertFiles, () => this.repository.revertFiles('HEAD', resources.map(r => r.fsPath))); } async commit(message: string, opts: CommitOptions = Object.create(null)): Promise { @@ -420,20 +420,27 @@ export class Model implements Disposable { }); } - async clean(...resources: Resource[]): Promise { + async clean(...resources: Uri[]): Promise { await this.run(Operation.Clean, async () => { const toClean: string[] = []; const toCheckout: string[] = []; resources.forEach(r => { - switch (r.type) { + const raw = r.toString(); + const scmResource = find(this.workingTreeGroup.resources, sr => sr.resourceUri.toString() === raw); + + if (!scmResource) { + return; + } + + switch (scmResource.type) { case Status.UNTRACKED: case Status.IGNORED: - toClean.push(r.resourceUri.fsPath); + toClean.push(r.fsPath); break; default: - toCheckout.push(r.resourceUri.fsPath); + toCheckout.push(r.fsPath); break; } }); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 668dcf56fdc7b..553e677e47a28 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -162,4 +162,19 @@ export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { seen[key] = true; return true; }; +} + +export function find(array: T[], fn: (t: T) => boolean): T | undefined { + let result: T | undefined = undefined; + + array.some(e => { + if (fn(e)) { + result = e; + return true; + } + + return false; + }); + + return result; } \ No newline at end of file From 16e8189650b0682f38c0165a2ee494902a188154 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 15:17:15 +0200 Subject: [PATCH 11/67] model -> repository --- extensions/git/src/autofetch.ts | 4 +- extensions/git/src/commands.ts | 100 +++++++++--------- extensions/git/src/contentProvider.ts | 4 +- extensions/git/src/main.ts | 16 +-- extensions/git/src/modelRegistry.ts | 12 +-- .../git/src/{model.ts => repository.ts} | 6 +- extensions/git/src/scmProvider.ts | 4 +- extensions/git/src/statusbar.ts | 8 +- 8 files changed, 77 insertions(+), 77 deletions(-) rename extensions/git/src/{model.ts => repository.ts} (99%) diff --git a/extensions/git/src/autofetch.ts b/extensions/git/src/autofetch.ts index a663338a6a9ef..3e119e9fec7cd 100644 --- a/extensions/git/src/autofetch.ts +++ b/extensions/git/src/autofetch.ts @@ -7,7 +7,7 @@ import { workspace, Disposable } from 'vscode'; import { GitErrorCodes } from './git'; -import { Model } from './model'; +import { Repository } from './repository'; import { throttle } from './decorators'; export class AutoFetcher { @@ -16,7 +16,7 @@ export class AutoFetcher { private disposables: Disposable[] = []; private timer: NodeJS.Timer; - constructor(private model: Model) { + constructor(private model: Repository) { workspace.onDidChangeConfiguration(this.onConfiguration, this, this.disposables); this.onConfiguration(); } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index db910fc64c950..f78fc8db31aee 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -7,7 +7,7 @@ import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; -import { Model, Resource, Status, CommitOptions, WorkingTreeGroup, IndexGroup, MergeGroup } from './model'; +import { Repository, Resource, Status, CommitOptions, WorkingTreeGroup, IndexGroup, MergeGroup } from './repository'; import { ModelRegistry } from './modelRegistry'; import { toGitUri, fromGitUri } from './uri'; import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange } from './staging'; @@ -27,7 +27,7 @@ class CheckoutItem implements QuickPickItem { constructor(protected ref: Ref) { } - async run(model: Model): Promise { + async run(model: Repository): Promise { const ref = this.treeish; if (!ref) { @@ -70,7 +70,7 @@ class BranchDeleteItem implements QuickPickItem { constructor(private ref: Ref) { } - async run(model: Model, force?: boolean): Promise { + async run(model: Repository, force?: boolean): Promise { if (!this.branchName) { return; } @@ -85,7 +85,7 @@ class MergeItem implements QuickPickItem { constructor(protected ref: Ref) { } - async run(model: Model): Promise { + async run(model: Repository): Promise { await model.merge(this.ref.name! || this.ref.commit!); } } @@ -95,7 +95,7 @@ class CreateBranchItem implements QuickPickItem { get label(): string { return localize('create branch', '$(plus) Create new branch'); } get description(): string { return ''; } - async run(model: Model): Promise { + async run(model: Repository): Promise { await commands.executeCommand('git.branch'); } } @@ -145,7 +145,7 @@ export class CommandCenter { }); } - private groupByModel(resources: Uri[]): [Model | undefined, Uri[]][] { + private groupByModel(resources: Uri[]): [Repository | undefined, Uri[]][] { return resources.reduce((result, resource) => { const model = this.modelRegistry.getModel(resource); const pair = result.filter(p => p[0] === model)[0]; @@ -157,20 +157,20 @@ export class CommandCenter { } return result; - }, [] as [Model | undefined, Uri[]][]); + }, [] as [Repository | undefined, Uri[]][]); } @command('git.refresh', { model: true }) - async refresh(model: Model): Promise { + async refresh(model: Repository): Promise { await model.status(); } @command('git.openResource', { model: true }) - async openResource(model: Model, resource: Resource): Promise { + async openResource(model: Repository, resource: Resource): Promise { await this._openResource(model, resource); } - private async _openResource(model: Model, resource: Resource, preview?: boolean): Promise { + private async _openResource(model: Repository, resource: Resource, preview?: boolean): Promise { const left = this.getLeftResource(resource); const right = this.getRightResource(model, resource); const title = this.getTitle(resource); @@ -216,7 +216,7 @@ export class CommandCenter { } } - private getRightResource(model: Model, resource: Resource): Uri | undefined { + private getRightResource(model: Repository, resource: Resource): Uri | undefined { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: @@ -322,7 +322,7 @@ export class CommandCenter { } @command('git.openFile', { model: true }) - async openFile(model: Model, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { + async openFile(model: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let uris: Uri[] | undefined; if (arg instanceof Uri) { @@ -372,7 +372,7 @@ export class CommandCenter { } @command('git.openHEADFile', { model: true }) - async openHEADFile(model: Model, arg?: Resource | Uri): Promise { + async openHEADFile(model: Repository, arg?: Resource | Uri): Promise { let resource: Resource | undefined = undefined; if (arg instanceof Resource) { @@ -398,7 +398,7 @@ export class CommandCenter { } @command('git.openChange', { model: true }) - async openChange(model: Model, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { + async openChange(model: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let resources: Resource[] | undefined = undefined; if (arg instanceof Uri) { @@ -462,13 +462,13 @@ export class CommandCenter { } @command('git.stageAll', { model: true }) - async stageAll(model: Model): Promise { + async stageAll(model: Repository): Promise { return await model.add(); } // TODO@Joao does this command really receive a model? @command('git.stageSelectedRanges', { model: true, diff: true }) - async stageSelectedRanges(model: Model, diffs: LineChange[]): Promise { + async stageSelectedRanges(model: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -500,7 +500,7 @@ export class CommandCenter { // TODO@Joao does this command really receive a model? @command('git.revertSelectedRanges', { model: true, diff: true }) - async revertSelectedRanges(model: Model, diffs: LineChange[]): Promise { + async revertSelectedRanges(model: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -545,7 +545,7 @@ export class CommandCenter { } @command('git.unstage', { model: true }) - async unstage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { + async unstage(model: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(); @@ -569,13 +569,13 @@ export class CommandCenter { } @command('git.unstageAll', { model: true }) - async unstageAll(model: Model): Promise { + async unstageAll(model: Repository): Promise { return await model.revertFiles(); } // TODO@Joao does this command really receive a model? @command('git.unstageSelectedRanges', { model: true, diff: true }) - async unstageSelectedRanges(model: Model, diffs: LineChange[]): Promise { + async unstageSelectedRanges(model: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -613,7 +613,7 @@ export class CommandCenter { } @command('git.clean', { model: true }) - async clean(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { + async clean(model: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(); @@ -660,7 +660,7 @@ export class CommandCenter { } @command('git.cleanAll', { model: true }) - async cleanAll(model: Model): Promise { + async cleanAll(model: Repository): Promise { const config = workspace.getConfiguration('git'); let scope = config.get('discardAllScope') || 'prompt'; let resources = model.workingTreeGroup.resources; @@ -712,7 +712,7 @@ export class CommandCenter { } private async smartCommit( - model: Model, + model: Repository, getCommitMessage: () => Promise, opts?: CommitOptions ): Promise { @@ -767,7 +767,7 @@ export class CommandCenter { return true; } - private async commitWithAnyInput(model: Model, opts?: CommitOptions): Promise { + private async commitWithAnyInput(model: Repository, opts?: CommitOptions): Promise { const message = scm.inputBox.value; const getCommitMessage = async () => { if (message) { @@ -789,12 +789,12 @@ export class CommandCenter { } @command('git.commit', { model: true }) - async commit(model: Model): Promise { + async commit(model: Repository): Promise { await this.commitWithAnyInput(model); } @command('git.commitWithInput', { model: true }) - async commitWithInput(model: Model): Promise { + async commitWithInput(model: Repository): Promise { if (!scm.inputBox.value) { return; } @@ -807,37 +807,37 @@ export class CommandCenter { } @command('git.commitStaged', { model: true }) - async commitStaged(model: Model): Promise { + async commitStaged(model: Repository): Promise { await this.commitWithAnyInput(model, { all: false }); } @command('git.commitStagedSigned', { model: true }) - async commitStagedSigned(model: Model): Promise { + async commitStagedSigned(model: Repository): Promise { await this.commitWithAnyInput(model, { all: false, signoff: true }); } @command('git.commitStagedAmend', { model: true }) - async commitStagedAmend(model: Model): Promise { + async commitStagedAmend(model: Repository): Promise { await this.commitWithAnyInput(model, { all: false, amend: true }); } @command('git.commitAll', { model: true }) - async commitAll(model: Model): Promise { + async commitAll(model: Repository): Promise { await this.commitWithAnyInput(model, { all: true }); } @command('git.commitAllSigned', { model: true }) - async commitAllSigned(model: Model): Promise { + async commitAllSigned(model: Repository): Promise { await this.commitWithAnyInput(model, { all: true, signoff: true }); } @command('git.commitAllAmend', { model: true }) - async commitAllAmend(model: Model): Promise { + async commitAllAmend(model: Repository): Promise { await this.commitWithAnyInput(model, { all: true, amend: true }); } @command('git.undoCommit', { model: true }) - async undoCommit(model: Model): Promise { + async undoCommit(model: Repository): Promise { const HEAD = model.HEAD; if (!HEAD || !HEAD.commit) { @@ -850,7 +850,7 @@ export class CommandCenter { } @command('git.checkout', { model: true }) - async checkout(model: Model, treeish: string): Promise { + async checkout(model: Repository, treeish: string): Promise { if (typeof treeish === 'string') { return await model.checkout(treeish); } @@ -883,7 +883,7 @@ export class CommandCenter { } @command('git.branch', { model: true }) - async branch(model: Model): Promise { + async branch(model: Repository): Promise { const result = await window.showInputBox({ placeHolder: localize('branch name', "Branch name"), prompt: localize('provide branch name', "Please provide a branch name"), @@ -899,7 +899,7 @@ export class CommandCenter { } @command('git.deleteBranch', { model: true }) - async deleteBranch(model: Model, name: string, force?: boolean): Promise { + async deleteBranch(model: Repository, name: string, force?: boolean): Promise { let run: (force?: boolean) => Promise; if (typeof name === 'string') { run = force => model.deleteBranch(name, force); @@ -936,7 +936,7 @@ export class CommandCenter { } @command('git.merge', { model: true }) - async merge(model: Model): Promise { + async merge(model: Repository): Promise { const config = workspace.getConfiguration('git'); const checkoutType = config.get('checkoutType') || 'all'; const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; @@ -970,7 +970,7 @@ export class CommandCenter { } @command('git.createTag', { model: true }) - async createTag(model: Model): Promise { + async createTag(model: Repository): Promise { const inputTagName = await window.showInputBox({ placeHolder: localize('tag name', "Tag name"), prompt: localize('provide tag name', "Please provide a tag name"), @@ -993,7 +993,7 @@ export class CommandCenter { } @command('git.pullFrom', { model: true }) - async pullFrom(model: Model): Promise { + async pullFrom(model: Repository): Promise { const remotes = model.remotes; if (remotes.length === 0) { @@ -1023,7 +1023,7 @@ export class CommandCenter { } @command('git.pull', { model: true }) - async pull(model: Model): Promise { + async pull(model: Repository): Promise { const remotes = model.remotes; if (remotes.length === 0) { @@ -1035,7 +1035,7 @@ export class CommandCenter { } @command('git.pullRebase', { model: true }) - async pullRebase(model: Model): Promise { + async pullRebase(model: Repository): Promise { const remotes = model.remotes; if (remotes.length === 0) { @@ -1047,7 +1047,7 @@ export class CommandCenter { } @command('git.push', { model: true }) - async push(model: Model): Promise { + async push(model: Repository): Promise { const remotes = model.remotes; if (remotes.length === 0) { @@ -1059,7 +1059,7 @@ export class CommandCenter { } @command('git.pushWithTags', { model: true }) - async pushWithTags(model: Model): Promise { + async pushWithTags(model: Repository): Promise { const remotes = model.remotes; if (remotes.length === 0) { @@ -1073,7 +1073,7 @@ export class CommandCenter { } @command('git.pushTo', { model: true }) - async pushTo(model: Model): Promise { + async pushTo(model: Repository): Promise { const remotes = model.remotes; if (remotes.length === 0) { @@ -1099,7 +1099,7 @@ export class CommandCenter { } @command('git.sync', { model: true }) - async sync(model: Model): Promise { + async sync(model: Repository): Promise { const HEAD = model.HEAD; if (!HEAD || !HEAD.upstream) { @@ -1126,7 +1126,7 @@ export class CommandCenter { } @command('git.publish', { model: true }) - async publish(model: Model): Promise { + async publish(model: Repository): Promise { const remotes = model.remotes; if (remotes.length === 0) { @@ -1152,7 +1152,7 @@ export class CommandCenter { } @command('git.ignore', { model: true }) - async ignore(model: Model, ...resourceStates: SourceControlResourceState[]): Promise { + async ignore(model: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const uri = window.activeTextEditor && window.activeTextEditor.document.uri; @@ -1175,7 +1175,7 @@ export class CommandCenter { } @command('git.stash', { model: true }) - async stash(model: Model): Promise { + async stash(model: Repository): Promise { if (model.workingTreeGroup.resources.length === 0) { window.showInformationMessage(localize('no changes stash', "There are no changes to stash.")); return; @@ -1194,7 +1194,7 @@ export class CommandCenter { } @command('git.stashPop', { model: true }) - async stashPop(model: Model): Promise { + async stashPop(model: Repository): Promise { const stashes = await model.getStashes(); if (stashes.length === 0) { @@ -1214,7 +1214,7 @@ export class CommandCenter { } @command('git.stashPopLatest', { model: true }) - async stashPopLatest(model: Model): Promise { + async stashPopLatest(model: Repository): Promise { const stashes = await model.getStashes(); if (stashes.length === 0) { diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index d99d31fd572b8..1023b422c96a8 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -8,7 +8,7 @@ import { workspace, Uri, Disposable, Event, EventEmitter, window } from 'vscode'; import { debounce } from './decorators'; import { fromGitUri } from './uri'; -import { Model } from './model'; +import { Repository } from './repository'; interface CacheRow { uri: Uri; @@ -30,7 +30,7 @@ export class GitContentProvider { private cache: Cache = Object.create(null); private disposables: Disposable[] = []; - constructor(private model: Model) { + constructor(private model: Repository) { this.disposables.push( model.onDidChangeRepository(this.eventuallyFireChangeEvents, this), workspace.registerTextDocumentContentProvider('git', this) diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 2db3c8df3244b..981ef95417de7 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -9,7 +9,7 @@ import * as nls from 'vscode-nls'; const localize = nls.config(process.env.VSCODE_NLS_CONFIG)(); import { ExtensionContext, workspace, window, Disposable, commands, Uri } from 'vscode'; import { findGit, Git, IGit } from './git'; -import { Model } from './model'; +import { Repository } from './repository'; import { ModelRegistry } from './modelRegistry'; import { GitSCMProvider } from './scmProvider'; import { CommandCenter } from './commands'; @@ -46,8 +46,8 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi } const workspaceRoot = Uri.file(workspaceRootPath); - const model = new Model(git, workspaceRoot); - modelRegistry.register(workspaceRoot, model); + const repository = new Repository(git, workspaceRoot); + modelRegistry.register(workspaceRoot, repository); outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path)); @@ -56,17 +56,17 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); const commandCenter = new CommandCenter(git, modelRegistry, outputChannel, telemetryReporter); - const statusBarCommands = new StatusBarCommands(model); - const provider = new GitSCMProvider(model, statusBarCommands); - const contentProvider = new GitContentProvider(model); - const autoFetcher = new AutoFetcher(model); + const statusBarCommands = new StatusBarCommands(repository); + const provider = new GitSCMProvider(repository, statusBarCommands); + const contentProvider = new GitContentProvider(repository); + const autoFetcher = new AutoFetcher(repository); disposables.push( commandCenter, provider, contentProvider, autoFetcher, - model + repository ); await checkGitVersion(info); diff --git a/extensions/git/src/modelRegistry.ts b/extensions/git/src/modelRegistry.ts index 0ff90cf3ba0e5..703d321414679 100644 --- a/extensions/git/src/modelRegistry.ts +++ b/extensions/git/src/modelRegistry.ts @@ -6,7 +6,7 @@ 'use strict'; import { Uri, window, QuickPickItem } from 'vscode'; -import { Model } from './model'; +import { Repository } from './repository'; import { memoize } from './decorators'; import * as path from 'path'; import * as nls from 'vscode-nls'; @@ -16,18 +16,18 @@ const localize = nls.loadMessageBundle(); class ModelPick implements QuickPickItem { @memoize get label(): string { return path.basename(this.repositoryRoot.fsPath); } @memoize get description(): string { return path.dirname(this.repositoryRoot.fsPath); } - constructor(protected repositoryRoot: Uri, public readonly model: Model) { } + constructor(protected repositoryRoot: Uri, public readonly model: Repository) { } } export class ModelRegistry { - private models: Map = new Map(); + private models: Map = new Map(); register(uri: Uri, model): void { this.models.set(uri, model); } - async pickModel(): Promise { + async pickModel(): Promise { const picks = Array.from(this.models.entries(), ([uri, model]) => new ModelPick(uri, model)); const placeHolder = localize('pick repo', "Choose a repository"); const pick = await window.showQuickPick(picks, { placeHolder }); @@ -35,7 +35,7 @@ export class ModelRegistry { return pick && pick.model; } - getModel(resource: Uri): Model | undefined { + getModel(resource: Uri): Repository | undefined { const resourcePath = resource.fsPath; for (let [repositoryRoot, model] of this.models) { @@ -50,7 +50,7 @@ export class ModelRegistry { return undefined; } - async resolve(resource: Uri): Promise { + async resolve(resource: Uri): Promise { const model = this.getModel(resource); if (model) { diff --git a/extensions/git/src/model.ts b/extensions/git/src/repository.ts similarity index 99% rename from extensions/git/src/model.ts rename to extensions/git/src/repository.ts index 0c0ba8bd24ba1..cbf9371019062 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/repository.ts @@ -6,7 +6,7 @@ 'use strict'; import { Uri, Command, EventEmitter, Event, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; -import { Git, Repository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git'; +import { Git, Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git'; import { anyEvent, eventToPromise, filterEvent, EmptyDisposable, combinedDisposable, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; import * as path from 'path'; @@ -295,7 +295,7 @@ export interface CommitOptions { signCommit?: boolean; } -export class Model implements Disposable { +export class Repository implements Disposable { private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; @@ -349,7 +349,7 @@ export class Model implements Disposable { private _operations = new OperationsImpl(); get operations(): Operations { return this._operations; } - private repository: Repository; + private repository: BaseRepository; private _state = State.Uninitialized; get state(): State { return this._state; } diff --git a/extensions/git/src/scmProvider.ts b/extensions/git/src/scmProvider.ts index 80a413b65d2e8..2b8ccc8465b8e 100644 --- a/extensions/git/src/scmProvider.ts +++ b/extensions/git/src/scmProvider.ts @@ -6,7 +6,7 @@ 'use strict'; import { scm, Uri, Disposable, SourceControl, SourceControlResourceGroup, Event, workspace, commands } from 'vscode'; -import { Model, State, Status } from './model'; +import { Repository, State, Status } from './repository'; import { StatusBarCommands } from './statusbar'; import { mapEvent } from './util'; import { toGitUri } from './uri'; @@ -58,7 +58,7 @@ export class GitSCMProvider { private workingTreeGroup: SourceControlResourceGroup; constructor( - private model: Model, + private model: Repository, private statusBarCommands: StatusBarCommands ) { this._sourceControl = scm.createSourceControl('git', 'Git'); diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index 9fe437ccec562..bf88320a8d6ea 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -7,7 +7,7 @@ import { Disposable, Command, EventEmitter, Event } from 'vscode'; import { RefType, Branch } from './git'; -import { Model, Operation } from './model'; +import { Repository, Operation } from './repository'; import { anyEvent, dispose } from './util'; import * as nls from 'vscode-nls'; @@ -19,7 +19,7 @@ class CheckoutStatusBar { get onDidChange(): Event { return this._onDidChange.event; } private disposables: Disposable[] = []; - constructor(private model: Model) { + constructor(private model: Repository) { model.onDidChange(this._onDidChange.fire, this._onDidChange, this.disposables); } @@ -76,7 +76,7 @@ class SyncStatusBar { this._onDidChange.fire(); } - constructor(private model: Model) { + constructor(private model: Repository) { model.onDidChange(this.onModelChange, this, this.disposables); model.onDidChangeOperations(this.onOperationsChange, this, this.disposables); this._onDidChange.fire(); @@ -149,7 +149,7 @@ export class StatusBarCommands { private checkoutStatusBar: CheckoutStatusBar; private disposables: Disposable[] = []; - constructor(model: Model) { + constructor(model: Repository) { this.syncStatusBar = new SyncStatusBar(model); this.checkoutStatusBar = new CheckoutStatusBar(model); } From 303dd1174773842534fbc4d061e7848e2d0ed248 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 15:55:00 +0200 Subject: [PATCH 12/67] git multirepo stage --- extensions/git/src/commands.ts | 43 +++-------- extensions/git/src/main.ts | 10 +-- extensions/git/src/model.ts | 114 ++++++++++++++++++++++++++++ extensions/git/src/modelRegistry.ts | 70 ----------------- extensions/git/src/repository.ts | 6 +- extensions/git/src/util.ts | 16 +++- 6 files changed, 148 insertions(+), 111 deletions(-) create mode 100644 extensions/git/src/model.ts delete mode 100644 extensions/git/src/modelRegistry.ts diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index f78fc8db31aee..5b4d22f775ba6 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -8,7 +8,7 @@ import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; import { Repository, Resource, Status, CommitOptions, WorkingTreeGroup, IndexGroup, MergeGroup } from './repository'; -import { ModelRegistry } from './modelRegistry'; +import { Model } from './model'; import { toGitUri, fromGitUri } from './uri'; import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange } from './staging'; import * as path from 'path'; @@ -130,7 +130,7 @@ export class CommandCenter { constructor( private git: Git, - private modelRegistry: ModelRegistry, + private model: Model, private outputChannel: OutputChannel, private telemetryReporter: TelemetryReporter ) { @@ -145,21 +145,6 @@ export class CommandCenter { }); } - private groupByModel(resources: Uri[]): [Repository | undefined, Uri[]][] { - return resources.reduce((result, resource) => { - const model = this.modelRegistry.getModel(resource); - const pair = result.filter(p => p[0] === model)[0]; - - if (pair) { - pair[1].push(resource); - } else { - result.push([model, [resource]]); - } - - return result; - }, [] as [Repository | undefined, Uri[]][]); - } - @command('git.refresh', { model: true }) async refresh(model: Repository): Promise { await model.status(); @@ -450,15 +435,7 @@ export class CommandCenter { } const resources = scmResources.map(r => r.resourceUri); - const resourcesByModel = this.groupByModel(resources); - - await Promise.all(resourcesByModel.map(async ([model, resources]) => { - if (!model) { - return; // TODO@joao - } - - await model.add(...resources); - })); + await this.model.add(...resources); } @command('git.stageAll', { model: true }) @@ -1237,12 +1214,12 @@ export class CommandCenter { if (!options.model) { result = Promise.resolve(method.apply(this, args)); } else { - result = this.modelRegistry.pickModel().then(model => { - if (!model) { + result = this.model.pickRepository().then(repository => { + if (!repository) { return Promise.reject(localize('modelnotfound', "Git model not found")); } - return Promise.resolve(method.apply(this, [model, ...args])); + return Promise.resolve(method.apply(this, [repository, ...args])); }); } @@ -1309,14 +1286,14 @@ export class CommandCenter { if (uri.scheme === 'file') { const uriString = uri.toString(); - const model = this.modelRegistry.getModel(uri); + const repository = this.model.getRepository(uri); - if (!model) { + if (!repository) { return undefined; } - return model.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0] - || model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]; + return repository.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0] + || repository.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]; } } diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 981ef95417de7..046f7c7d2050a 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -10,7 +10,7 @@ const localize = nls.config(process.env.VSCODE_NLS_CONFIG)(); import { ExtensionContext, workspace, window, Disposable, commands, Uri } from 'vscode'; import { findGit, Git, IGit } from './git'; import { Repository } from './repository'; -import { ModelRegistry } from './modelRegistry'; +import { Model } from './model'; import { GitSCMProvider } from './scmProvider'; import { CommandCenter } from './commands'; import { StatusBarCommands } from './statusbar'; @@ -37,17 +37,17 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi const askpass = new Askpass(); const env = await askpass.getEnv(); const git = new Git({ gitPath: info.path, version: info.version, env }); - const modelRegistry = new ModelRegistry(); + const model = new Model(); if (!workspaceRootPath || !enabled) { - const commandCenter = new CommandCenter(git, modelRegistry, outputChannel, telemetryReporter); + const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); disposables.push(commandCenter); return; } const workspaceRoot = Uri.file(workspaceRootPath); const repository = new Repository(git, workspaceRoot); - modelRegistry.register(workspaceRoot, repository); + model.register(workspaceRoot, repository); outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path)); @@ -55,7 +55,7 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi git.onOutput.addListener('log', onOutput); disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); - const commandCenter = new CommandCenter(git, modelRegistry, outputChannel, telemetryReporter); + const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); const statusBarCommands = new StatusBarCommands(repository); const provider = new GitSCMProvider(repository, statusBarCommands); const contentProvider = new GitContentProvider(repository); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts new file mode 100644 index 0000000000000..6841aba28d755 --- /dev/null +++ b/extensions/git/src/model.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Uri, window, QuickPickItem, Disposable } from 'vscode'; +import { GitErrorCodes } from './git'; +import { Repository, IRepository, State } from './repository'; +import { memoize } from './decorators'; +import { toDisposable, filterEvent, once } from './util'; +import * as path from 'path'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + +class RepositoryPick implements QuickPickItem { + @memoize get label(): string { return path.basename(this.repositoryRoot.fsPath); } + @memoize get description(): string { return path.dirname(this.repositoryRoot.fsPath); } + constructor(protected repositoryRoot: Uri, public readonly repository: Repository) { } +} + +export class Model implements IRepository { + + private repositories: Map = new Map(); + + register(uri: Uri, repository: Repository): Disposable { + if (this.repositories.has(uri)) { + // TODO@Joao: what should happen? + throw new Error('Cant register repository with the same URI'); + } + + this.repositories.set(uri, repository); + + const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.NotAGitRepository); + const listener = onDidDisappearRepository(() => disposable.dispose()); + + const disposable = toDisposable(once(() => { + this.repositories.delete(uri); + listener.dispose(); + })); + + return disposable; + } + + async pickRepository(): Promise { + const picks = Array.from(this.repositories.entries(), ([uri, model]) => new RepositoryPick(uri, model)); + const placeHolder = localize('pick repo', "Choose a repository"); + const pick = await window.showQuickPick(picks, { placeHolder }); + + return pick && pick.repository; + } + + getRepository(resource: Uri): Repository | undefined { + const resourcePath = resource.fsPath; + + for (let [repositoryRoot, model] of this.repositories) { + const repositoryRootPath = repositoryRoot.fsPath; + const relativePath = path.relative(repositoryRootPath, resourcePath); + + if (!/^\./.test(relativePath)) { + return model; + } + } + + return undefined; + } + + private async runByRepository(resources: Uri[], fn: (repository: Repository, resources: Uri[]) => Promise): Promise { + const groups = resources.reduce((result, resource) => { + const repository = this.getRepository(resource); + + // TODO@Joao: what should happen? + if (!repository) { + console.warn('Could not find git repository for ', resource); + return result; + } + + const tuple = result.filter(p => p[0] === repository)[0]; + + if (tuple) { + tuple.resources.push(resource); + } else { + result.push({ repository, resources: [resource] }); + } + + return result; + }, [] as { repository: Repository, resources: Uri[] }[]); + + const promises = groups + .map(({ repository, resources }) => this.run(repository, () => fn(repository as Repository, resources))); + + return Promise.all(promises); + } + + private async run(repository: Repository, fn: () => Promise): Promise { + try { + return fn(); + } catch (err) { + if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { + // do something about it + } + + throw err; + } + } + + // IRepository + + async add(...resources: Uri[]): Promise { + await this.runByRepository(resources, async (repository, resources) => repository.add(...resources)); + } +} \ No newline at end of file diff --git a/extensions/git/src/modelRegistry.ts b/extensions/git/src/modelRegistry.ts deleted file mode 100644 index 703d321414679..0000000000000 --- a/extensions/git/src/modelRegistry.ts +++ /dev/null @@ -1,70 +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 { Uri, window, QuickPickItem } from 'vscode'; -import { Repository } from './repository'; -import { memoize } from './decorators'; -import * as path from 'path'; -import * as nls from 'vscode-nls'; - -const localize = nls.loadMessageBundle(); - -class ModelPick implements QuickPickItem { - @memoize get label(): string { return path.basename(this.repositoryRoot.fsPath); } - @memoize get description(): string { return path.dirname(this.repositoryRoot.fsPath); } - constructor(protected repositoryRoot: Uri, public readonly model: Repository) { } -} - -export class ModelRegistry { - - private models: Map = new Map(); - - register(uri: Uri, model): void { - this.models.set(uri, model); - } - - async pickModel(): Promise { - const picks = Array.from(this.models.entries(), ([uri, model]) => new ModelPick(uri, model)); - const placeHolder = localize('pick repo', "Choose a repository"); - const pick = await window.showQuickPick(picks, { placeHolder }); - - return pick && pick.model; - } - - getModel(resource: Uri): Repository | undefined { - const resourcePath = resource.fsPath; - - for (let [repositoryRoot, model] of this.models) { - const repositoryRootPath = repositoryRoot.fsPath; - const relativePath = path.relative(repositoryRootPath, resourcePath); - - if (!/^\./.test(relativePath)) { - return model; - } - } - - return undefined; - } - - async resolve(resource: Uri): Promise { - const model = this.getModel(resource); - - if (model) { - return model; - } - - const picks = Array.from(this.models.entries(), ([uri, model]) => new ModelPick(uri, model)); - const placeHolder = localize('pick repo', "Choose a repository"); - const pick = await window.showQuickPick(picks, { placeHolder }); - - if (pick) { - return pick.model; - } - - return undefined; - } -} diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index cbf9371019062..6355cbea9b16c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -295,7 +295,11 @@ export interface CommitOptions { signCommit?: boolean; } -export class Repository implements Disposable { +export interface IRepository { + add(...resources: Uri[]): Promise; +} + +export class Repository implements IRepository, Disposable { private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 553e677e47a28..8d5c9b202517f 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -56,7 +56,7 @@ export function done(promise: Promise): Promise { return promise.then(() => void 0); } -export function once(event: Event): Event { +export function onceEvent(event: Event): Event { return (listener, thisArgs = null, disposables?) => { const result = event(e => { result.dispose(); @@ -68,7 +68,19 @@ export function once(event: Event): Event { } export function eventToPromise(event: Event): Promise { - return new Promise(c => once(event)(c)); + return new Promise(c => onceEvent(event)(c)); +} + +export function once(fn: (...args: any[]) => any): (...args: any[]) => any { + let didRun = false; + + return (...args) => { + if (didRun) { + return; + } + + return fn(...args); + }; } // TODO@Joao: replace with Object.assign From 8c747ed66e543a383a1ca08a69eac2bd1569545b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 15:56:11 +0200 Subject: [PATCH 13/67] git model remove run --- extensions/git/src/model.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 6841aba28d755..124de5ec21038 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -6,7 +6,6 @@ 'use strict'; import { Uri, window, QuickPickItem, Disposable } from 'vscode'; -import { GitErrorCodes } from './git'; import { Repository, IRepository, State } from './repository'; import { memoize } from './decorators'; import { toDisposable, filterEvent, once } from './util'; @@ -89,23 +88,11 @@ export class Model implements IRepository { }, [] as { repository: Repository, resources: Uri[] }[]); const promises = groups - .map(({ repository, resources }) => this.run(repository, () => fn(repository as Repository, resources))); + .map(({ repository, resources }) => fn(repository as Repository, resources)); return Promise.all(promises); } - private async run(repository: Repository, fn: () => Promise): Promise { - try { - return fn(); - } catch (err) { - if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { - // do something about it - } - - throw err; - } - } - // IRepository async add(...resources: Uri[]): Promise { From 76cfa5d7967b43d8deab124d59173c2092499a19 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 16:14:41 +0200 Subject: [PATCH 14/67] git: remove splats --- extensions/git/src/commands.ts | 12 ++++++------ extensions/git/src/model.ts | 4 ++-- extensions/git/src/repository.ts | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 5b4d22f775ba6..6e8c597abb7ce 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -435,12 +435,12 @@ export class CommandCenter { } const resources = scmResources.map(r => r.resourceUri); - await this.model.add(...resources); + await this.model.add(resources); } @command('git.stageAll', { model: true }) async stageAll(model: Repository): Promise { - return await model.add(); + await model.add([]); } // TODO@Joao does this command really receive a model? @@ -542,12 +542,12 @@ export class CommandCenter { const resources = scmResources.map(r => r.resourceUri); - return await model.revertFiles(...resources); + return await model.revertFiles(resources); } @command('git.unstageAll', { model: true }) async unstageAll(model: Repository): Promise { - return await model.revertFiles(); + return await model.revertFiles([]); } // TODO@Joao does this command really receive a model? @@ -633,7 +633,7 @@ export class CommandCenter { return; } - await model.clean(...resources.map(r => r.resourceUri)); + await model.clean(resources.map(r => r.resourceUri)); } @command('git.cleanAll', { model: true }) @@ -685,7 +685,7 @@ export class CommandCenter { return; } - await model.clean(...resources.map(r => r.resourceUri)); + await model.clean(resources.map(r => r.resourceUri)); } private async smartCommit( diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 124de5ec21038..db8e5696a6ba2 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -95,7 +95,7 @@ export class Model implements IRepository { // IRepository - async add(...resources: Uri[]): Promise { - await this.runByRepository(resources, async (repository, resources) => repository.add(...resources)); + async add(resources: Uri[]): Promise { + await this.runByRepository(resources, async (repository, resources) => repository.add(resources)); } } \ No newline at end of file diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6355cbea9b16c..9498174e9fd7f 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -296,7 +296,7 @@ export interface CommitOptions { } export interface IRepository { - add(...resources: Uri[]): Promise; + add(resources: Uri[]): Promise; } export class Repository implements IRepository, Disposable { @@ -402,7 +402,7 @@ export class Repository implements IRepository, Disposable { await this.run(Operation.Status); } - async add(...resources: Uri[]): Promise { + async add(resources: Uri[]): Promise { await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath))); } @@ -411,7 +411,7 @@ export class Repository implements IRepository, Disposable { await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents)); } - async revertFiles(...resources: Uri[]): Promise { + async revertFiles(resources: Uri[]): Promise { await this.run(Operation.RevertFiles, () => this.repository.revertFiles('HEAD', resources.map(r => r.fsPath))); } @@ -425,7 +425,7 @@ export class Repository implements IRepository, Disposable { }); } - async clean(...resources: Uri[]): Promise { + async clean(resources: Uri[]): Promise { await this.run(Operation.Clean, async () => { const toClean: string[] = []; const toCheckout: string[] = []; From b95a585df97351c23a03955a3caad4715b758ef8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 16:19:44 +0200 Subject: [PATCH 15/67] git: multi repo stage --- extensions/git/src/model.ts | 13 +++++++++++-- extensions/git/src/repository.ts | 5 +++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index db8e5696a6ba2..12e374430c99c 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -66,7 +66,12 @@ export class Model implements IRepository { return undefined; } - private async runByRepository(resources: Uri[], fn: (repository: Repository, resources: Uri[]) => Promise): Promise { + private runByRepository(resources: Uri, fn: (repository: Repository, resources: Uri) => Promise): Promise; + private runByRepository(resources: Uri[], fn: (repository: Repository, resources: Uri[]) => Promise): Promise; + private async runByRepository(arg: Uri | Uri[], fn: (repository: Repository, resources: any) => Promise): Promise { + const resources = arg instanceof Uri ? [arg] : arg; + const isSingleResource = arg instanceof Uri; + const groups = resources.reduce((result, resource) => { const repository = this.getRepository(resource); @@ -88,7 +93,7 @@ export class Model implements IRepository { }, [] as { repository: Repository, resources: Uri[] }[]); const promises = groups - .map(({ repository, resources }) => fn(repository as Repository, resources)); + .map(({ repository, resources }) => fn(repository as Repository, isSingleResource ? resources[0] : resources)); return Promise.all(promises); } @@ -98,4 +103,8 @@ export class Model implements IRepository { async add(resources: Uri[]): Promise { await this.runByRepository(resources, async (repository, resources) => repository.add(resources)); } + + async stage(resource: Uri, contents: string): Promise { + await this.runByRepository(resource, async (repository, uri) => repository.stage(uri, contents)); + } } \ No newline at end of file diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 9498174e9fd7f..83daf1b60dbd5 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -297,6 +297,7 @@ export interface CommitOptions { export interface IRepository { add(resources: Uri[]): Promise; + stage(resource: Uri, contents: string): Promise; } export class Repository implements IRepository, Disposable { @@ -406,8 +407,8 @@ export class Repository implements IRepository, Disposable { await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath))); } - async stage(uri: Uri, contents: string): Promise { - const relativePath = path.relative(this.repository.root, uri.fsPath).replace(/\\/g, '/'); + async stage(resource: Uri, contents: string): Promise { + const relativePath = path.relative(this.repository.root, resource.fsPath).replace(/\\/g, '/'); await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents)); } From 3f31510686447d2e49214b444686c62a6b325ced Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 16:20:58 +0200 Subject: [PATCH 16/67] git: multirepo revert --- extensions/git/src/commands.ts | 4 ++-- extensions/git/src/git.ts | 2 +- extensions/git/src/model.ts | 4 ++++ extensions/git/src/repository.ts | 5 +++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6e8c597abb7ce..76b79736b3d93 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -542,12 +542,12 @@ export class CommandCenter { const resources = scmResources.map(r => r.resourceUri); - return await model.revertFiles(resources); + return await model.revert(resources); } @command('git.unstageAll', { model: true }) async unstageAll(model: Repository): Promise { - return await model.revertFiles([]); + return await model.revert([]); } // TODO@Joao does this command really receive a model? diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 51b8d18160624..92a71b1697a25 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -737,7 +737,7 @@ export class Repository { await this.run(args); } - async revertFiles(treeish: string, paths: string[]): Promise { + async revert(treeish: string, paths: string[]): Promise { const result = await this.run(['branch']); let args: string[]; diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 12e374430c99c..10ecf18be8818 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -107,4 +107,8 @@ export class Model implements IRepository { async stage(resource: Uri, contents: string): Promise { await this.runByRepository(resource, async (repository, uri) => repository.stage(uri, contents)); } + + async revert(resources: Uri[]): Promise { + await this.runByRepository(resources, async (repository, resources) => repository.revert(resources)); + } } \ No newline at end of file diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 83daf1b60dbd5..fdedcb191d289 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -298,6 +298,7 @@ export interface CommitOptions { export interface IRepository { add(resources: Uri[]): Promise; stage(resource: Uri, contents: string): Promise; + revert(resources: Uri[]): Promise; } export class Repository implements IRepository, Disposable { @@ -412,8 +413,8 @@ export class Repository implements IRepository, Disposable { await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents)); } - async revertFiles(resources: Uri[]): Promise { - await this.run(Operation.RevertFiles, () => this.repository.revertFiles('HEAD', resources.map(r => r.fsPath))); + async revert(resources: Uri[]): Promise { + await this.run(Operation.RevertFiles, () => this.repository.revert('HEAD', resources.map(r => r.fsPath))); } async commit(message: string, opts: CommitOptions = Object.create(null)): Promise { From 56d1dadf0545b3d8ee90b752eb9a5e3440229672 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 16:21:46 +0200 Subject: [PATCH 17/67] git: multirepo clean --- extensions/git/src/model.ts | 4 ++++ extensions/git/src/repository.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 10ecf18be8818..5f7f4f728f345 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -111,4 +111,8 @@ export class Model implements IRepository { async revert(resources: Uri[]): Promise { await this.runByRepository(resources, async (repository, resources) => repository.revert(resources)); } + + async clean(resources: Uri[]): Promise { + await this.runByRepository(resources, async (repository, resources) => repository.clean(resources)); + } } \ No newline at end of file diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index fdedcb191d289..0e89c9e81491c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -299,6 +299,7 @@ export interface IRepository { add(resources: Uri[]): Promise; stage(resource: Uri, contents: string): Promise; revert(resources: Uri[]): Promise; + clean(resources: Uri[]): Promise; } export class Repository implements IRepository, Disposable { From fe8ee2e7b856eeff12e69e6d9762605c3a6449f0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 18:03:47 +0200 Subject: [PATCH 18/67] git: repository == scm provider --- extensions/git/package.json | 170 ++++++++--------- extensions/git/src/autofetch.ts | 4 +- extensions/git/src/commands.ts | 256 +++++++++++++------------- extensions/git/src/contentProvider.ts | 8 +- extensions/git/src/main.ts | 5 - extensions/git/src/repository.ts | 183 ++++++++++-------- extensions/git/src/scmProvider.ts | 120 ------------ extensions/git/src/statusbar.ts | 32 ++-- 8 files changed, 341 insertions(+), 437 deletions(-) delete mode 100644 extensions/git/src/scmProvider.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index 3173dfddf528c..491e5ca439080 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -276,393 +276,393 @@ }, { "command": "git.init", - "when": "config.git.enabled && scmProvider == git && gitState == norepo" + "when": "config.git.enabled" }, { "command": "git.refresh", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.openFile", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.openHEADFile", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.openChange", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.stage", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.stageAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.stageSelectedRanges", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.revertSelectedRanges", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.unstage", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.unstageAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.unstageSelectedRanges", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.clean", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.cleanAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.commit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.commitStaged", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.commitStagedSigned", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.commitStagedAmend", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.commitAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.commitAllSigned", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.commitAllAmend", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.undoCommit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.checkout", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.branch", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.deleteBranch", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.pull", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.pullFrom", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.pullRebase", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.pullFrom", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.createTag", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.push", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.pushTo", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.pushWithTags", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.sync", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.publish", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.showOutput", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.stash", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.stashPop", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" }, { "command": "git.stashPopLatest", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled" } ], "scm/title": [ { "command": "git.init", "group": "navigation", - "when": "config.git.enabled && scmProvider == git && gitState == norepo" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.commit", "group": "navigation", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.refresh", "group": "navigation", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.sync", "group": "1_sync", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.pull", "group": "1_sync", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.pullRebase", "group": "1_sync", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.pullFrom", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.push", "group": "1_sync", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.pushTo", "group": "1_sync", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.publish", "group": "2_publish", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.commitStaged", "group": "3_commit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.commitStagedSigned", "group": "3_commit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.commitStagedAmend", "group": "3_commit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.commitAll", "group": "3_commit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.commitAllSigned", "group": "3_commit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.commitAllAmend", "group": "3_commit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.undoCommit", "group": "3_commit", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.unstageAll", "group": "4_stage", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.cleanAll", "group": "4_stage", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.showOutput", "group": "6_output", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.stash", "group": "5_stash", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.stashPop", "group": "5_stash", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" }, { "command": "git.stashPopLatest", "group": "5_stash", - "when": "config.git.enabled && scmProvider == git && gitState == idle" + "when": "config.git.enabled && scmProvider == git" } ], "scm/resourceGroup/context": [ { "command": "git.stageAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == merge", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == merge", "group": "1_modification" }, { "command": "git.stageAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == merge", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == merge", "group": "inline" }, { "command": "git.unstageAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == index", "group": "1_modification" }, { "command": "git.unstageAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == index", "group": "inline" }, { "command": "git.cleanAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.stageAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.cleanAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "inline" }, { "command": "git.stageAll", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "inline" } ], "scm/resourceState/context": [ { "command": "git.stage", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == merge", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == merge", "group": "1_modification" }, { "command": "git.stage", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == merge", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == merge", "group": "inline" }, { "command": "git.openChange", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == index", "group": "navigation" }, { "command": "git.openFile", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == index", "group": "navigation" }, { "command": "git.openHEADFile", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == index", "group": "navigation" }, { "command": "git.unstage", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == index", "group": "1_modification" }, { "command": "git.unstage", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == index", "group": "inline" }, { "command": "git.openChange", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "navigation" }, { "command": "git.openHEADFile", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "navigation" }, { "command": "git.openFile", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "navigation" }, { "command": "git.stage", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.clean", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.clean", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "inline" }, { "command": "git.stage", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "inline" }, { "command": "git.ignore", - "when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree", + "when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification@3" } ], diff --git a/extensions/git/src/autofetch.ts b/extensions/git/src/autofetch.ts index 3e119e9fec7cd..930fc2f05f327 100644 --- a/extensions/git/src/autofetch.ts +++ b/extensions/git/src/autofetch.ts @@ -16,7 +16,7 @@ export class AutoFetcher { private disposables: Disposable[] = []; private timer: NodeJS.Timer; - constructor(private model: Repository) { + constructor(private repository: Repository) { workspace.onDidChangeConfiguration(this.onConfiguration, this, this.disposables); this.onConfiguration(); } @@ -47,7 +47,7 @@ export class AutoFetcher { @throttle private async fetch(): Promise { try { - await this.model.fetch(); + await this.repository.fetch(); } catch (err) { if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) { this.disable(); diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 76b79736b3d93..29a036a726a59 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -7,7 +7,7 @@ import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; -import { Repository, Resource, Status, CommitOptions, WorkingTreeGroup, IndexGroup, MergeGroup } from './repository'; +import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository'; import { Model } from './model'; import { toGitUri, fromGitUri } from './uri'; import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange } from './staging'; @@ -27,14 +27,14 @@ class CheckoutItem implements QuickPickItem { constructor(protected ref: Ref) { } - async run(model: Repository): Promise { + async run(repository: Repository): Promise { const ref = this.treeish; if (!ref) { return; } - await model.checkout(ref); + await repository.checkout(ref); } } @@ -70,11 +70,11 @@ class BranchDeleteItem implements QuickPickItem { constructor(private ref: Ref) { } - async run(model: Repository, force?: boolean): Promise { + async run(repository: Repository, force?: boolean): Promise { if (!this.branchName) { return; } - await model.deleteBranch(this.branchName, force); + await repository.deleteBranch(this.branchName, force); } } @@ -85,8 +85,8 @@ class MergeItem implements QuickPickItem { constructor(protected ref: Ref) { } - async run(model: Repository): Promise { - await model.merge(this.ref.name! || this.ref.commit!); + async run(repository: Repository): Promise { + await repository.merge(this.ref.name! || this.ref.commit!); } } @@ -95,7 +95,7 @@ class CreateBranchItem implements QuickPickItem { get label(): string { return localize('create branch', '$(plus) Create new branch'); } get description(): string { return ''; } - async run(model: Repository): Promise { + async run(repository: Repository): Promise { await commands.executeCommand('git.branch'); } } @@ -146,18 +146,18 @@ export class CommandCenter { } @command('git.refresh', { model: true }) - async refresh(model: Repository): Promise { - await model.status(); + async refresh(repository: Repository): Promise { + await repository.status(); } @command('git.openResource', { model: true }) - async openResource(model: Repository, resource: Resource): Promise { - await this._openResource(model, resource); + async openResource(repository: Repository, resource: Resource): Promise { + await this._openResource(repository, resource); } - private async _openResource(model: Repository, resource: Resource, preview?: boolean): Promise { + private async _openResource(repository: Repository, resource: Resource, preview?: boolean): Promise { const left = this.getLeftResource(resource); - const right = this.getRightResource(model, resource); + const right = this.getRightResource(repository, resource); const title = this.getTitle(resource); if (!right) { @@ -201,7 +201,7 @@ export class CommandCenter { } } - private getRightResource(model: Repository, resource: Resource): Uri | undefined { + private getRightResource(repository: Repository, resource: Resource): Uri | undefined { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: @@ -218,7 +218,7 @@ export class CommandCenter { case Status.UNTRACKED: case Status.IGNORED: const uriString = resource.resourceUri.toString(); - const [indexStatus] = model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString); + const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); if (indexStatus && indexStatus.renameResourceUri) { return indexStatus.renameResourceUri; @@ -307,7 +307,7 @@ export class CommandCenter { } @command('git.openFile', { model: true }) - async openFile(model: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { + async openFile(repository: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let uris: Uri[] | undefined; if (arg instanceof Uri) { @@ -357,7 +357,7 @@ export class CommandCenter { } @command('git.openHEADFile', { model: true }) - async openHEADFile(model: Repository, arg?: Resource | Uri): Promise { + async openHEADFile(repository: Repository, arg?: Resource | Uri): Promise { let resource: Resource | undefined = undefined; if (arg instanceof Resource) { @@ -383,7 +383,7 @@ export class CommandCenter { } @command('git.openChange', { model: true }) - async openChange(model: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { + async openChange(repository: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let resources: Resource[] | undefined = undefined; if (arg instanceof Uri) { @@ -411,7 +411,7 @@ export class CommandCenter { const preview = resources.length === 1 ? undefined : false; for (const resource of resources) { - await this._openResource(model, resource, preview); + await this._openResource(repository, resource, preview); } } @@ -428,7 +428,7 @@ export class CommandCenter { } const scmResources = resourceStates - .filter(s => s instanceof Resource && (s.resourceGroup instanceof WorkingTreeGroup || s.resourceGroup instanceof MergeGroup)) as Resource[]; + .filter(s => s instanceof Resource && (s.resourceGroupType === ResourceGroupType.WorkingTree || s.resourceGroupType === ResourceGroupType.Merge)) as Resource[]; if (!scmResources.length) { return; @@ -439,13 +439,13 @@ export class CommandCenter { } @command('git.stageAll', { model: true }) - async stageAll(model: Repository): Promise { - await model.add([]); + async stageAll(repository: Repository): Promise { + await repository.add([]); } // TODO@Joao does this command really receive a model? @command('git.stageSelectedRanges', { model: true, diff: true }) - async stageSelectedRanges(model: Repository, diffs: LineChange[]): Promise { + async stageSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -472,12 +472,12 @@ export class CommandCenter { const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs); - await model.stage(modifiedUri, result); + await repository.stage(modifiedUri, result); } // TODO@Joao does this command really receive a model? @command('git.revertSelectedRanges', { model: true, diff: true }) - async revertSelectedRanges(model: Repository, diffs: LineChange[]): Promise { + async revertSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -522,7 +522,7 @@ export class CommandCenter { } @command('git.unstage', { model: true }) - async unstage(model: Repository, ...resourceStates: SourceControlResourceState[]): Promise { + async unstage(repository: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(); @@ -534,7 +534,7 @@ export class CommandCenter { } const scmResources = resourceStates - .filter(s => s instanceof Resource && s.resourceGroup instanceof IndexGroup) as Resource[]; + .filter(s => s instanceof Resource && s.resourceGroupType === ResourceGroupType.Index) as Resource[]; if (!scmResources.length) { return; @@ -542,17 +542,17 @@ export class CommandCenter { const resources = scmResources.map(r => r.resourceUri); - return await model.revert(resources); + return await repository.revert(resources); } @command('git.unstageAll', { model: true }) - async unstageAll(model: Repository): Promise { - return await model.revert([]); + async unstageAll(repository: Repository): Promise { + return await repository.revert([]); } // TODO@Joao does this command really receive a model? @command('git.unstageSelectedRanges', { model: true, diff: true }) - async unstageSelectedRanges(model: Repository, diffs: LineChange[]): Promise { + async unstageSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -586,11 +586,11 @@ export class CommandCenter { const invertedDiffs = selectedDiffs.map(invertLineChange); const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs); - await model.stage(modifiedUri, result); + await repository.stage(modifiedUri, result); } @command('git.clean', { model: true }) - async clean(model: Repository, ...resourceStates: SourceControlResourceState[]): Promise { + async clean(repository: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(); @@ -602,7 +602,7 @@ export class CommandCenter { } const resources = resourceStates - .filter(s => s instanceof Resource && s.resourceGroup instanceof WorkingTreeGroup) as Resource[]; + .filter(s => s instanceof Resource && s.resourceGroupType === ResourceGroupType.WorkingTree) as Resource[]; if (!resources.length) { return; @@ -633,14 +633,14 @@ export class CommandCenter { return; } - await model.clean(resources.map(r => r.resourceUri)); + await repository.clean(resources.map(r => r.resourceUri)); } @command('git.cleanAll', { model: true }) - async cleanAll(model: Repository): Promise { + async cleanAll(repository: Repository): Promise { const config = workspace.getConfiguration('git'); let scope = config.get('discardAllScope') || 'prompt'; - let resources = model.workingTreeGroup.resources; + let resources = repository.workingTreeGroup.resourceStates; if (resources.length === 0) { return; @@ -685,19 +685,19 @@ export class CommandCenter { return; } - await model.clean(resources.map(r => r.resourceUri)); + await repository.clean(resources.map(r => r.resourceUri)); } private async smartCommit( - model: Repository, + repository: Repository, getCommitMessage: () => Promise, opts?: CommitOptions ): Promise { const config = workspace.getConfiguration('git'); const enableSmartCommit = config.get('enableSmartCommit') === true; const enableCommitSigning = config.get('enableCommitSigning') === true; - const noStagedChanges = model.indexGroup.resources.length === 0; - const noUnstagedChanges = model.workingTreeGroup.resources.length === 0; + const noStagedChanges = repository.indexGroup.resourceStates.length === 0; + const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; // no changes, and the user has not configured to commit all in this case if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit) { @@ -739,12 +739,12 @@ export class CommandCenter { return false; } - await model.commit(message, opts); + await repository.commit(message, opts); return true; } - private async commitWithAnyInput(model: Repository, opts?: CommitOptions): Promise { + private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise { const message = scm.inputBox.value; const getCommitMessage = async () => { if (message) { @@ -758,78 +758,78 @@ export class CommandCenter { }); }; - const didCommit = await this.smartCommit(model, getCommitMessage, opts); + const didCommit = await this.smartCommit(repository, getCommitMessage, opts); if (message && didCommit) { - scm.inputBox.value = await model.getCommitTemplate(); + scm.inputBox.value = await repository.getCommitTemplate(); } } @command('git.commit', { model: true }) - async commit(model: Repository): Promise { - await this.commitWithAnyInput(model); + async commit(repository: Repository): Promise { + await this.commitWithAnyInput(repository); } @command('git.commitWithInput', { model: true }) - async commitWithInput(model: Repository): Promise { + async commitWithInput(repository: Repository): Promise { if (!scm.inputBox.value) { return; } - const didCommit = await this.smartCommit(model, async () => scm.inputBox.value); + const didCommit = await this.smartCommit(repository, async () => scm.inputBox.value); if (didCommit) { - scm.inputBox.value = await model.getCommitTemplate(); + scm.inputBox.value = await repository.getCommitTemplate(); } } @command('git.commitStaged', { model: true }) - async commitStaged(model: Repository): Promise { - await this.commitWithAnyInput(model, { all: false }); + async commitStaged(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: false }); } @command('git.commitStagedSigned', { model: true }) - async commitStagedSigned(model: Repository): Promise { - await this.commitWithAnyInput(model, { all: false, signoff: true }); + async commitStagedSigned(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: false, signoff: true }); } @command('git.commitStagedAmend', { model: true }) - async commitStagedAmend(model: Repository): Promise { - await this.commitWithAnyInput(model, { all: false, amend: true }); + async commitStagedAmend(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: false, amend: true }); } @command('git.commitAll', { model: true }) - async commitAll(model: Repository): Promise { - await this.commitWithAnyInput(model, { all: true }); + async commitAll(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: true }); } @command('git.commitAllSigned', { model: true }) - async commitAllSigned(model: Repository): Promise { - await this.commitWithAnyInput(model, { all: true, signoff: true }); + async commitAllSigned(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: true, signoff: true }); } @command('git.commitAllAmend', { model: true }) - async commitAllAmend(model: Repository): Promise { - await this.commitWithAnyInput(model, { all: true, amend: true }); + async commitAllAmend(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: true, amend: true }); } @command('git.undoCommit', { model: true }) - async undoCommit(model: Repository): Promise { - const HEAD = model.HEAD; + async undoCommit(repository: Repository): Promise { + const HEAD = repository.HEAD; if (!HEAD || !HEAD.commit) { return; } - const commit = await model.getCommit('HEAD'); - await model.reset('HEAD~'); + const commit = await repository.getCommit('HEAD'); + await repository.reset('HEAD~'); scm.inputBox.value = commit.message; } @command('git.checkout', { model: true }) - async checkout(model: Repository, treeish: string): Promise { + async checkout(repository: Repository, treeish: string): Promise { if (typeof treeish === 'string') { - return await model.checkout(treeish); + return await repository.checkout(treeish); } const config = workspace.getConfiguration('git'); @@ -839,13 +839,13 @@ export class CommandCenter { const createBranch = new CreateBranchItem(); - const heads = model.refs.filter(ref => ref.type === RefType.Head) + const heads = repository.refs.filter(ref => ref.type === RefType.Head) .map(ref => new CheckoutItem(ref)); - const tags = (includeTags ? model.refs.filter(ref => ref.type === RefType.Tag) : []) + const tags = (includeTags ? repository.refs.filter(ref => ref.type === RefType.Tag) : []) .map(ref => new CheckoutTagItem(ref)); - const remoteHeads = (includeRemotes ? model.refs.filter(ref => ref.type === RefType.RemoteHead) : []) + const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) .map(ref => new CheckoutRemoteHeadItem(ref)); const picks = [createBranch, ...heads, ...tags, ...remoteHeads]; @@ -856,11 +856,11 @@ export class CommandCenter { return; } - await choice.run(model); + await choice.run(repository); } @command('git.branch', { model: true }) - async branch(model: Repository): Promise { + async branch(repository: Repository): Promise { const result = await window.showInputBox({ placeHolder: localize('branch name', "Branch name"), prompt: localize('provide branch name', "Please provide a branch name"), @@ -872,17 +872,17 @@ export class CommandCenter { } const name = result.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-'); - await model.branch(name); + await repository.branch(name); } @command('git.deleteBranch', { model: true }) - async deleteBranch(model: Repository, name: string, force?: boolean): Promise { + async deleteBranch(repository: Repository, name: string, force?: boolean): Promise { let run: (force?: boolean) => Promise; if (typeof name === 'string') { - run = force => model.deleteBranch(name, force); + run = force => repository.deleteBranch(name, force); } else { - const currentHead = model.HEAD && model.HEAD.name; - const heads = model.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead) + const currentHead = repository.HEAD && repository.HEAD.name; + const heads = repository.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead) .map(ref => new BranchDeleteItem(ref)); const placeHolder = localize('select branch to delete', 'Select a branch to delete'); @@ -892,7 +892,7 @@ export class CommandCenter { return; } name = choice.branchName; - run = force => choice.run(model, force); + run = force => choice.run(repository, force); } try { @@ -913,16 +913,16 @@ export class CommandCenter { } @command('git.merge', { model: true }) - async merge(model: Repository): Promise { + async merge(repository: Repository): Promise { const config = workspace.getConfiguration('git'); const checkoutType = config.get('checkoutType') || 'all'; const includeRemotes = checkoutType === 'all' || checkoutType === 'remote'; - const heads = model.refs.filter(ref => ref.type === RefType.Head) + const heads = repository.refs.filter(ref => ref.type === RefType.Head) .filter(ref => ref.name || ref.commit) .map(ref => new MergeItem(ref as Branch)); - const remoteHeads = (includeRemotes ? model.refs.filter(ref => ref.type === RefType.RemoteHead) : []) + const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) .filter(ref => ref.name || ref.commit) .map(ref => new MergeItem(ref as Branch)); @@ -935,7 +935,7 @@ export class CommandCenter { } try { - await choice.run(model); + await choice.run(repository); } catch (err) { if (err.gitErrorCode !== GitErrorCodes.Conflict) { throw err; @@ -947,7 +947,7 @@ export class CommandCenter { } @command('git.createTag', { model: true }) - async createTag(model: Repository): Promise { + async createTag(repository: Repository): Promise { const inputTagName = await window.showInputBox({ placeHolder: localize('tag name', "Tag name"), prompt: localize('provide tag name', "Please provide a tag name"), @@ -966,12 +966,12 @@ export class CommandCenter { const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-'); const message = inputMessage || name; - await model.tag(name, message); + await repository.tag(name, message); } @command('git.pullFrom', { model: true }) - async pullFrom(model: Repository): Promise { - const remotes = model.remotes; + async pullFrom(repository: Repository): Promise { + const remotes = repository.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); @@ -996,74 +996,74 @@ export class CommandCenter { return; } - model.pull(false, pick.label, branchName); + repository.pull(false, pick.label, branchName); } @command('git.pull', { model: true }) - async pull(model: Repository): Promise { - const remotes = model.remotes; + async pull(repository: Repository): Promise { + const remotes = repository.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); return; } - await model.pull(); + await repository.pull(); } @command('git.pullRebase', { model: true }) - async pullRebase(model: Repository): Promise { - const remotes = model.remotes; + async pullRebase(repository: Repository): Promise { + const remotes = repository.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); return; } - await model.pullWithRebase(); + await repository.pullWithRebase(); } @command('git.push', { model: true }) - async push(model: Repository): Promise { - const remotes = model.remotes; + async push(repository: Repository): Promise { + const remotes = repository.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to.")); return; } - await model.push(); + await repository.push(); } @command('git.pushWithTags', { model: true }) - async pushWithTags(model: Repository): Promise { - const remotes = model.remotes; + async pushWithTags(repository: Repository): Promise { + const remotes = repository.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to.")); return; } - await model.pushTags(); + await repository.pushTags(); window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags.")); } @command('git.pushTo', { model: true }) - async pushTo(model: Repository): Promise { - const remotes = model.remotes; + async pushTo(repository: Repository): Promise { + const remotes = repository.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to.")); return; } - if (!model.HEAD || !model.HEAD.name) { + if (!repository.HEAD || !repository.HEAD.name) { window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote.")); return; } - const branchName = model.HEAD.name; + const branchName = repository.HEAD.name; const picks = remotes.map(r => ({ label: r.name, description: r.url })); const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); const pick = await window.showQuickPick(picks, { placeHolder }); @@ -1072,12 +1072,12 @@ export class CommandCenter { return; } - model.pushTo(pick.label, branchName); + repository.pushTo(pick.label, branchName); } @command('git.sync', { model: true }) - async sync(model: Repository): Promise { - const HEAD = model.HEAD; + async sync(repository: Repository): Promise { + const HEAD = repository.HEAD; if (!HEAD || !HEAD.upstream) { return; @@ -1099,20 +1099,20 @@ export class CommandCenter { } } - await model.sync(); + await repository.sync(); } @command('git.publish', { model: true }) - async publish(model: Repository): Promise { - const remotes = model.remotes; + async publish(repository: Repository): Promise { + const remotes = repository.remotes; if (remotes.length === 0) { window.showWarningMessage(localize('no remotes to publish', "Your repository has no remotes configured to publish to.")); return; } - const branchName = model.HEAD && model.HEAD.name || ''; - const picks = model.remotes.map(r => r.name); + const branchName = repository.HEAD && repository.HEAD.name || ''; + const picks = repository.remotes.map(r => r.name); const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); const choice = await window.showQuickPick(picks, { placeHolder }); @@ -1120,7 +1120,7 @@ export class CommandCenter { return; } - await model.pushTo(choice, branchName, true); + await repository.pushTo(choice, branchName, true); } @command('git.showOutput') @@ -1129,7 +1129,7 @@ export class CommandCenter { } @command('git.ignore', { model: true }) - async ignore(model: Repository, ...resourceStates: SourceControlResourceState[]): Promise { + async ignore(repository: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const uri = window.activeTextEditor && window.activeTextEditor.document.uri; @@ -1137,7 +1137,7 @@ export class CommandCenter { return; } - return await model.ignore([uri]); + return await repository.ignore([uri]); } const uris = resourceStates @@ -1148,12 +1148,12 @@ export class CommandCenter { return; } - await model.ignore(uris); + await repository.ignore(uris); } @command('git.stash', { model: true }) - async stash(model: Repository): Promise { - if (model.workingTreeGroup.resources.length === 0) { + async stash(repository: Repository): Promise { + if (repository.workingTreeGroup.resourceStates.length === 0) { window.showInformationMessage(localize('no changes stash', "There are no changes to stash.")); return; } @@ -1167,12 +1167,12 @@ export class CommandCenter { return; } - await model.createStash(message); + await repository.createStash(message); } @command('git.stashPop', { model: true }) - async stashPop(model: Repository): Promise { - const stashes = await model.getStashes(); + async stashPop(repository: Repository): Promise { + const stashes = await repository.getStashes(); if (stashes.length === 0) { window.showInformationMessage(localize('no stashes', "There are no stashes to restore.")); @@ -1187,19 +1187,19 @@ export class CommandCenter { return; } - await model.popStash(choice.id); + await repository.popStash(choice.id); } @command('git.stashPopLatest', { model: true }) - async stashPopLatest(model: Repository): Promise { - const stashes = await model.getStashes(); + async stashPopLatest(repository: Repository): Promise { + const stashes = await repository.getStashes(); if (stashes.length === 0) { window.showInformationMessage(localize('no stashes', "There are no stashes to restore.")); return; } - await model.popStash(); + await repository.popStash(); } private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any { @@ -1292,8 +1292,8 @@ export class CommandCenter { return undefined; } - return repository.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0] - || repository.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]; + return repository.workingTreeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0] + || repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]; } } diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index 1023b422c96a8..e8f92a82ccdf0 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -30,9 +30,9 @@ export class GitContentProvider { private cache: Cache = Object.create(null); private disposables: Disposable[] = []; - constructor(private model: Repository) { + constructor(private repository: Repository) { this.disposables.push( - model.onDidChangeRepository(this.eventuallyFireChangeEvents, this), + repository.onDidChangeRepository(this.eventuallyFireChangeEvents, this), workspace.registerTextDocumentContentProvider('git', this) ); @@ -61,12 +61,12 @@ export class GitContentProvider { if (ref === '~') { const fileUri = Uri.file(path); const uriString = fileUri.toString(); - const [indexStatus] = this.model.indexGroup.resources.filter(r => r.original.toString() === uriString); + const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.original.toString() === uriString); ref = indexStatus ? '' : 'HEAD'; } try { - return await this.model.show(ref, path); + return await this.repository.show(ref, path); } catch (err) { return ''; } diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 046f7c7d2050a..aa0cd238b38d7 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -11,9 +11,7 @@ import { ExtensionContext, workspace, window, Disposable, commands, Uri } from ' import { findGit, Git, IGit } from './git'; import { Repository } from './repository'; import { Model } from './model'; -import { GitSCMProvider } from './scmProvider'; import { CommandCenter } from './commands'; -import { StatusBarCommands } from './statusbar'; import { GitContentProvider } from './contentProvider'; import { AutoFetcher } from './autofetch'; import { Askpass } from './askpass'; @@ -56,14 +54,11 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); - const statusBarCommands = new StatusBarCommands(repository); - const provider = new GitSCMProvider(repository, statusBarCommands); const contentProvider = new GitContentProvider(repository); const autoFetcher = new AutoFetcher(repository); disposables.push( commandCenter, - provider, contentProvider, autoFetcher, repository diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 0e89c9e81491c..b4b7d594d5a49 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -5,10 +5,11 @@ 'use strict'; -import { Uri, Command, EventEmitter, Event, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; +import { Uri, Command, EventEmitter, Event, scm, commands, SourceControl, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; import { Git, Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git'; import { anyEvent, eventToPromise, filterEvent, EmptyDisposable, combinedDisposable, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; +import { toGitUri } from './uri'; import * as path from 'path'; import * as nls from 'vscode-nls'; import * as fs from 'fs'; @@ -49,6 +50,12 @@ export enum Status { BOTH_MODIFIED } +export enum ResourceGroupType { + Merge, + Index, + WorkingTree +} + export class Resource implements SourceControlResourceState { @memoize @@ -69,7 +76,7 @@ export class Resource implements SourceControlResourceState { }; } - get resourceGroup(): ResourceGroup { return this._resourceGroup; } + get resourceGroupType(): ResourceGroupType { return this._resourceGroupType; } get type(): Status { return this._type; } get original(): Uri { return this._resourceUri; } get renameResourceUri(): Uri | undefined { return this._renameResourceUri; } @@ -149,52 +156,13 @@ export class Resource implements SourceControlResourceState { constructor( private workspaceRoot: Uri, - private _resourceGroup: ResourceGroup, + private _resourceGroupType: ResourceGroupType, private _resourceUri: Uri, private _type: Status, private _renameResourceUri?: Uri ) { } } -export abstract class ResourceGroup { - - get id(): string { return this._id; } - get contextKey(): string { return this._id; } - get label(): string { return this._label; } - get resources(): Resource[] { return this._resources; } - - constructor(private _id: string, private _label: string, private _resources: Resource[]) { - - } -} - -export class MergeGroup extends ResourceGroup { - - static readonly ID = 'merge'; - - constructor(resources: Resource[] = []) { - super(MergeGroup.ID, localize('merge changes', "Merge Changes"), resources); - } -} - -export class IndexGroup extends ResourceGroup { - - static readonly ID = 'index'; - - constructor(resources: Resource[] = []) { - super(IndexGroup.ID, localize('staged changes', "Staged Changes"), resources); - } -} - -export class WorkingTreeGroup extends ResourceGroup { - - static readonly ID = 'workingTree'; - - constructor(resources: Resource[] = []) { - super(WorkingTreeGroup.ID, localize('changes', "Changes"), resources); - } -} - export enum Operation { Status = 1 << 0, Add = 1 << 1, @@ -302,6 +270,10 @@ export interface IRepository { clean(resources: Uri[]): Promise; } +export interface GitResourceGroup extends SourceControlResourceGroup { + resourceStates: Resource[]; +} + export class Repository implements IRepository, Disposable { private _onDidChangeRepository = new EventEmitter(); @@ -310,12 +282,9 @@ export class Repository implements IRepository, Disposable { private _onDidChangeState = new EventEmitter(); readonly onDidChangeState: Event = this._onDidChangeState.event; - private _onDidChangeResources = new EventEmitter(); - readonly onDidChangeResources: Event = this._onDidChangeResources.event; - @memoize get onDidChange(): Event { - return anyEvent(this.onDidChangeState, this.onDidChangeResources); + return anyEvent(this.onDidChangeState); } private _onRunOperation = new EventEmitter(); @@ -329,14 +298,17 @@ export class Repository implements IRepository, Disposable { return anyEvent(this.onRunOperation as Event, this.onDidRunOperation as Event); } - private _mergeGroup = new MergeGroup([]); - get mergeGroup(): MergeGroup { return this._mergeGroup; } + private _sourceControl: SourceControl; + get sourceControl(): SourceControl { return this._sourceControl; } + + private _mergeGroup: SourceControlResourceGroup; + get mergeGroup(): GitResourceGroup { return this._mergeGroup as GitResourceGroup; } - private _indexGroup = new IndexGroup([]); - get indexGroup(): IndexGroup { return this._indexGroup; } + private _indexGroup: SourceControlResourceGroup; + get indexGroup(): GitResourceGroup { return this._indexGroup as GitResourceGroup; } - private _workingTreeGroup = new WorkingTreeGroup([]); - get workingTreeGroup(): WorkingTreeGroup { return this._workingTreeGroup; } + private _workingTreeGroup: SourceControlResourceGroup; + get workingTreeGroup(): GitResourceGroup { return this._workingTreeGroup as GitResourceGroup; } private _HEAD: Branch | undefined; get HEAD(): Branch | undefined { @@ -367,10 +339,11 @@ export class Repository implements IRepository, Disposable { this._HEAD = undefined; this._refs = []; this._remotes = []; - this._mergeGroup = new MergeGroup(); - this._indexGroup = new IndexGroup(); - this._workingTreeGroup = new WorkingTreeGroup(); - this._onDidChangeResources.fire(); + this.mergeGroup.resourceStates = []; + this.indexGroup.resourceStates = []; + this.workingTreeGroup.resourceStates = []; + this._sourceControl.count = 0; + commands.executeCommand('setContext', 'gitState', ''); } private onWorkspaceChange: Event; @@ -387,9 +360,43 @@ export class Repository implements IRepository, Disposable { this.onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); this.disposables.push(fsWatcher); + this._sourceControl = scm.createSourceControl('git', 'Git'); + this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") }; + this._sourceControl.quickDiffProvider = this; + this.disposables.push(this._sourceControl); + + this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes")); + this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes")); + this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes")); + + this.mergeGroup.hideWhenEmpty = true; + this.indexGroup.hideWhenEmpty = true; + + this.disposables.push(this.mergeGroup); + this.disposables.push(this.indexGroup); + this.disposables.push(this.workingTreeGroup); + + this.updateCommitTemplate(); this.status(); } + // TODO@Joao reorganize this + provideOriginalResource(uri: Uri): Uri | undefined { + if (uri.scheme !== 'file') { + return; + } + + return toGitUri(uri, '', true); + } + + private async updateCommitTemplate(): Promise { + try { + this._sourceControl.commitTemplate = await this.repository.getCommitTemplate(); + } catch (e) { + // noop + } + } + @throttle async init(): Promise { if (this.state !== State.NotAGitRepository) { @@ -435,7 +442,7 @@ export class Repository implements IRepository, Disposable { resources.forEach(r => { const raw = r.toString(); - const scmResource = find(this.workingTreeGroup.resources, sr => sr.resourceUri.toString() === raw); + const scmResource = find(this.workingTreeGroup.resourceStates, sr => sr.resourceUri.toString() === raw); if (!scmResource) { return; @@ -731,37 +738,59 @@ export class Repository implements IRepository, Disposable { const renameUri = raw.rename ? Uri.file(path.join(this.repository.root, raw.rename)) : undefined; switch (raw.x + raw.y) { - case '??': return workingTree.push(new Resource(this.workspaceRoot, this.workingTreeGroup, uri, Status.UNTRACKED)); - case '!!': return workingTree.push(new Resource(this.workspaceRoot, this.workingTreeGroup, uri, Status.IGNORED)); - case 'DD': return merge.push(new Resource(this.workspaceRoot, this.mergeGroup, uri, Status.BOTH_DELETED)); - case 'AU': return merge.push(new Resource(this.workspaceRoot, this.mergeGroup, uri, Status.ADDED_BY_US)); - case 'UD': return merge.push(new Resource(this.workspaceRoot, this.mergeGroup, uri, Status.DELETED_BY_THEM)); - case 'UA': return merge.push(new Resource(this.workspaceRoot, this.mergeGroup, uri, Status.ADDED_BY_THEM)); - case 'DU': return merge.push(new Resource(this.workspaceRoot, this.mergeGroup, uri, Status.DELETED_BY_US)); - case 'AA': return merge.push(new Resource(this.workspaceRoot, this.mergeGroup, uri, Status.BOTH_ADDED)); - case 'UU': return merge.push(new Resource(this.workspaceRoot, this.mergeGroup, uri, Status.BOTH_MODIFIED)); + case '??': return workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED)); + case '!!': return workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.IGNORED)); + case 'DD': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_DELETED)); + case 'AU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.ADDED_BY_US)); + case 'UD': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM)); + case 'UA': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM)); + case 'DU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.DELETED_BY_US)); + case 'AA': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_ADDED)); + case 'UU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED)); } let isModifiedInIndex = false; switch (raw.x) { - case 'M': index.push(new Resource(this.workspaceRoot, this.indexGroup, uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break; - case 'A': index.push(new Resource(this.workspaceRoot, this.indexGroup, uri, Status.INDEX_ADDED)); break; - case 'D': index.push(new Resource(this.workspaceRoot, this.indexGroup, uri, Status.INDEX_DELETED)); break; - case 'R': index.push(new Resource(this.workspaceRoot, this.indexGroup, uri, Status.INDEX_RENAMED, renameUri)); break; - case 'C': index.push(new Resource(this.workspaceRoot, this.indexGroup, uri, Status.INDEX_COPIED, renameUri)); break; + case 'M': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break; + case 'A': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_ADDED)); break; + case 'D': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_DELETED)); break; + case 'R': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, renameUri)); break; + case 'C': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_COPIED, renameUri)); break; } switch (raw.y) { - case 'M': workingTree.push(new Resource(this.workspaceRoot, this.workingTreeGroup, uri, Status.MODIFIED, renameUri)); break; - case 'D': workingTree.push(new Resource(this.workspaceRoot, this.workingTreeGroup, uri, Status.DELETED, renameUri)); break; + case 'M': workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, renameUri)); break; + case 'D': workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.DELETED, renameUri)); break; } }); - this._mergeGroup = new MergeGroup(merge); - this._indexGroup = new IndexGroup(index); - this._workingTreeGroup = new WorkingTreeGroup(workingTree); - this._onDidChangeResources.fire(); + // set resource groups + this.mergeGroup.resourceStates = merge; + this.indexGroup.resourceStates = index; + this.workingTreeGroup.resourceStates = workingTree; + + // set count badge + const countBadge = workspace.getConfiguration('git').get('countBadge'); + let count = merge.length + index.length + workingTree.length; + + switch (countBadge) { + case 'off': count = 0; break; + case 'tracked': count = count - workingTree.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break; + } + + this._sourceControl.count = count; + + // set context key + let stateContextKey = ''; + + switch (this.state) { + case State.Uninitialized: stateContextKey = 'uninitialized'; break; + case State.Idle: stateContextKey = 'idle'; break; + case State.NotAGitRepository: stateContextKey = 'norepo'; break; + } + + commands.executeCommand('setContext', 'gitState', stateContextKey); } private onFSChange(uri: Uri): void { diff --git a/extensions/git/src/scmProvider.ts b/extensions/git/src/scmProvider.ts deleted file mode 100644 index 2b8ccc8465b8e..0000000000000 --- a/extensions/git/src/scmProvider.ts +++ /dev/null @@ -1,120 +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 { scm, Uri, Disposable, SourceControl, SourceControlResourceGroup, Event, workspace, commands } from 'vscode'; -import { Repository, State, Status } from './repository'; -import { StatusBarCommands } from './statusbar'; -import { mapEvent } from './util'; -import { toGitUri } from './uri'; -import * as nls from 'vscode-nls'; - -const localize = nls.loadMessageBundle(); - -export class GitSCMProvider { - - private disposables: Disposable[] = []; - get contextKey(): string { return 'git'; } - - get onDidChange(): Event { - return mapEvent(this.model.onDidChange, () => this); - } - - get label(): string { return 'Git'; } - - get stateContextKey(): string { - switch (this.model.state) { - case State.Uninitialized: return 'uninitialized'; - case State.Idle: return 'idle'; - case State.NotAGitRepository: return 'norepo'; - default: return ''; - } - } - - get count(): number { - const countBadge = workspace.getConfiguration('git').get('countBadge'); - const total = this.model.mergeGroup.resources.length - + this.model.indexGroup.resources.length - + this.model.workingTreeGroup.resources.length; - - switch (countBadge) { - case 'off': return 0; - case 'tracked': return total - this.model.workingTreeGroup.resources.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; - default: return total; - } - } - - private _sourceControl: SourceControl; - - get sourceControl(): SourceControl { - return this._sourceControl; - } - - private mergeGroup: SourceControlResourceGroup; - private indexGroup: SourceControlResourceGroup; - private workingTreeGroup: SourceControlResourceGroup; - - constructor( - private model: Repository, - private statusBarCommands: StatusBarCommands - ) { - this._sourceControl = scm.createSourceControl('git', 'Git'); - this.disposables.push(this._sourceControl); - - this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") }; - this._sourceControl.quickDiffProvider = this; - - this.statusBarCommands.onDidChange(this.onDidStatusBarCommandsChange, this, this.disposables); - this.onDidStatusBarCommandsChange(); - - this.mergeGroup = this._sourceControl.createResourceGroup(model.mergeGroup.id, model.mergeGroup.label); - this.indexGroup = this._sourceControl.createResourceGroup(model.indexGroup.id, model.indexGroup.label); - this.workingTreeGroup = this._sourceControl.createResourceGroup(model.workingTreeGroup.id, model.workingTreeGroup.label); - - this.mergeGroup.hideWhenEmpty = true; - this.indexGroup.hideWhenEmpty = true; - - this.disposables.push(this.mergeGroup); - this.disposables.push(this.indexGroup); - this.disposables.push(this.workingTreeGroup); - - model.onDidChange(this.onDidModelChange, this, this.disposables); - this.updateCommitTemplate(); - } - - private async updateCommitTemplate(): Promise { - try { - this._sourceControl.commitTemplate = await this.model.getCommitTemplate(); - } catch (e) { - // noop - } - } - - provideOriginalResource(uri: Uri): Uri | undefined { - if (uri.scheme !== 'file') { - return; - } - - return toGitUri(uri, '', true); - } - - private onDidModelChange(): void { - this.mergeGroup.resourceStates = this.model.mergeGroup.resources; - this.indexGroup.resourceStates = this.model.indexGroup.resources; - this.workingTreeGroup.resourceStates = this.model.workingTreeGroup.resources; - this._sourceControl.count = this.count; - commands.executeCommand('setContext', 'gitState', this.stateContextKey); - } - - private onDidStatusBarCommandsChange(): void { - this._sourceControl.statusBarCommands = this.statusBarCommands.commands; - } - - dispose(): void { - this.disposables.forEach(d => d.dispose()); - this.disposables = []; - } -} \ No newline at end of file diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index bf88320a8d6ea..73b2951f9022c 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -19,25 +19,25 @@ class CheckoutStatusBar { get onDidChange(): Event { return this._onDidChange.event; } private disposables: Disposable[] = []; - constructor(private model: Repository) { - model.onDidChange(this._onDidChange.fire, this._onDidChange, this.disposables); + constructor(private repository: Repository) { + repository.onDidChange(this._onDidChange.fire, this._onDidChange, this.disposables); } get command(): Command | undefined { - const HEAD = this.model.HEAD; + const HEAD = this.repository.HEAD; if (!HEAD) { return undefined; } - const tag = this.model.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; + const tag = this.repository.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; const tagName = tag && tag.name; const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); const title = '$(git-branch) ' + head - + (this.model.workingTreeGroup.resources.length > 0 ? '*' : '') - + (this.model.indexGroup.resources.length > 0 ? '+' : '') - + (this.model.mergeGroup.resources.length > 0 ? '!' : ''); + + (this.repository.workingTreeGroup.resourceStates.length > 0 ? '*' : '') + + (this.repository.indexGroup.resourceStates.length > 0 ? '+' : '') + + (this.repository.mergeGroup.resourceStates.length > 0 ? '!' : ''); return { command: 'git.checkout', @@ -76,24 +76,24 @@ class SyncStatusBar { this._onDidChange.fire(); } - constructor(private model: Repository) { - model.onDidChange(this.onModelChange, this, this.disposables); - model.onDidChangeOperations(this.onOperationsChange, this, this.disposables); + constructor(private repository: Repository) { + repository.onDidChange(this.onModelChange, this, this.disposables); + repository.onDidChangeOperations(this.onOperationsChange, this, this.disposables); this._onDidChange.fire(); } private onOperationsChange(): void { this.state = { ...this.state, - isSyncRunning: this.model.operations.isRunning(Operation.Sync) + isSyncRunning: this.repository.operations.isRunning(Operation.Sync) }; } private onModelChange(): void { this.state = { ...this.state, - hasRemotes: this.model.remotes.length > 0, - HEAD: this.model.HEAD + hasRemotes: this.repository.remotes.length > 0, + HEAD: this.repository.HEAD }; } @@ -149,9 +149,9 @@ export class StatusBarCommands { private checkoutStatusBar: CheckoutStatusBar; private disposables: Disposable[] = []; - constructor(model: Repository) { - this.syncStatusBar = new SyncStatusBar(model); - this.checkoutStatusBar = new CheckoutStatusBar(model); + constructor(repository: Repository) { + this.syncStatusBar = new SyncStatusBar(repository); + this.checkoutStatusBar = new CheckoutStatusBar(repository); } get onDidChange(): Event { From dc80da4b5a965eea195cb1d168a259fc2724437a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 18:15:28 +0200 Subject: [PATCH 19/67] git: remove git init --- extensions/git/package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 491e5ca439080..2d1e4c36fd608 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -432,11 +432,6 @@ } ], "scm/title": [ - { - "command": "git.init", - "group": "navigation", - "when": "config.git.enabled && scmProvider == git" - }, { "command": "git.commit", "group": "navigation", From fb60b01d965ec5ec1fe291fd3e961303aa2b1d26 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 18:46:08 +0200 Subject: [PATCH 20/67] git: model -> repository --- extensions/git/src/commands.ts | 82 +++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 29a036a726a59..fa81bdcbadd30 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -101,7 +101,7 @@ class CreateBranchItem implements QuickPickItem { } interface CommandOptions { - model?: boolean; + repository?: boolean; diff?: boolean; } @@ -145,12 +145,12 @@ export class CommandCenter { }); } - @command('git.refresh', { model: true }) + @command('git.refresh', { repository: true }) async refresh(repository: Repository): Promise { await repository.status(); } - @command('git.openResource', { model: true }) + @command('git.openResource', { repository: true }) async openResource(repository: Repository, resource: Resource): Promise { await this._openResource(repository, resource); } @@ -306,7 +306,7 @@ export class CommandCenter { // await model.init(); } - @command('git.openFile', { model: true }) + @command('git.openFile', { repository: true }) async openFile(repository: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let uris: Uri[] | undefined; @@ -356,7 +356,7 @@ export class CommandCenter { } } - @command('git.openHEADFile', { model: true }) + @command('git.openHEADFile', { repository: true }) async openHEADFile(repository: Repository, arg?: Resource | Uri): Promise { let resource: Resource | undefined = undefined; @@ -382,7 +382,7 @@ export class CommandCenter { return await commands.executeCommand('vscode.open', HEAD); } - @command('git.openChange', { model: true }) + @command('git.openChange', { repository: true }) async openChange(repository: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let resources: Resource[] | undefined = undefined; @@ -438,13 +438,13 @@ export class CommandCenter { await this.model.add(resources); } - @command('git.stageAll', { model: true }) + @command('git.stageAll', { repository: true }) async stageAll(repository: Repository): Promise { await repository.add([]); } // TODO@Joao does this command really receive a model? - @command('git.stageSelectedRanges', { model: true, diff: true }) + @command('git.stageSelectedRanges', { repository: true, diff: true }) async stageSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; @@ -476,7 +476,7 @@ export class CommandCenter { } // TODO@Joao does this command really receive a model? - @command('git.revertSelectedRanges', { model: true, diff: true }) + @command('git.revertSelectedRanges', { repository: true, diff: true }) async revertSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; @@ -521,7 +521,7 @@ export class CommandCenter { workspace.applyEdit(edit); } - @command('git.unstage', { model: true }) + @command('git.unstage', { repository: true }) async unstage(repository: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(); @@ -545,13 +545,13 @@ export class CommandCenter { return await repository.revert(resources); } - @command('git.unstageAll', { model: true }) + @command('git.unstageAll', { repository: true }) async unstageAll(repository: Repository): Promise { return await repository.revert([]); } // TODO@Joao does this command really receive a model? - @command('git.unstageSelectedRanges', { model: true, diff: true }) + @command('git.unstageSelectedRanges', { repository: true, diff: true }) async unstageSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; @@ -589,7 +589,7 @@ export class CommandCenter { await repository.stage(modifiedUri, result); } - @command('git.clean', { model: true }) + @command('git.clean', { repository: true }) async clean(repository: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(); @@ -636,7 +636,7 @@ export class CommandCenter { await repository.clean(resources.map(r => r.resourceUri)); } - @command('git.cleanAll', { model: true }) + @command('git.cleanAll', { repository: true }) async cleanAll(repository: Repository): Promise { const config = workspace.getConfiguration('git'); let scope = config.get('discardAllScope') || 'prompt'; @@ -765,12 +765,12 @@ export class CommandCenter { } } - @command('git.commit', { model: true }) + @command('git.commit', { repository: true }) async commit(repository: Repository): Promise { await this.commitWithAnyInput(repository); } - @command('git.commitWithInput', { model: true }) + @command('git.commitWithInput', { repository: true }) async commitWithInput(repository: Repository): Promise { if (!scm.inputBox.value) { return; @@ -783,37 +783,37 @@ export class CommandCenter { } } - @command('git.commitStaged', { model: true }) + @command('git.commitStaged', { repository: true }) async commitStaged(repository: Repository): Promise { await this.commitWithAnyInput(repository, { all: false }); } - @command('git.commitStagedSigned', { model: true }) + @command('git.commitStagedSigned', { repository: true }) async commitStagedSigned(repository: Repository): Promise { await this.commitWithAnyInput(repository, { all: false, signoff: true }); } - @command('git.commitStagedAmend', { model: true }) + @command('git.commitStagedAmend', { repository: true }) async commitStagedAmend(repository: Repository): Promise { await this.commitWithAnyInput(repository, { all: false, amend: true }); } - @command('git.commitAll', { model: true }) + @command('git.commitAll', { repository: true }) async commitAll(repository: Repository): Promise { await this.commitWithAnyInput(repository, { all: true }); } - @command('git.commitAllSigned', { model: true }) + @command('git.commitAllSigned', { repository: true }) async commitAllSigned(repository: Repository): Promise { await this.commitWithAnyInput(repository, { all: true, signoff: true }); } - @command('git.commitAllAmend', { model: true }) + @command('git.commitAllAmend', { repository: true }) async commitAllAmend(repository: Repository): Promise { await this.commitWithAnyInput(repository, { all: true, amend: true }); } - @command('git.undoCommit', { model: true }) + @command('git.undoCommit', { repository: true }) async undoCommit(repository: Repository): Promise { const HEAD = repository.HEAD; @@ -826,7 +826,7 @@ export class CommandCenter { scm.inputBox.value = commit.message; } - @command('git.checkout', { model: true }) + @command('git.checkout', { repository: true }) async checkout(repository: Repository, treeish: string): Promise { if (typeof treeish === 'string') { return await repository.checkout(treeish); @@ -859,7 +859,7 @@ export class CommandCenter { await choice.run(repository); } - @command('git.branch', { model: true }) + @command('git.branch', { repository: true }) async branch(repository: Repository): Promise { const result = await window.showInputBox({ placeHolder: localize('branch name', "Branch name"), @@ -875,7 +875,7 @@ export class CommandCenter { await repository.branch(name); } - @command('git.deleteBranch', { model: true }) + @command('git.deleteBranch', { repository: true }) async deleteBranch(repository: Repository, name: string, force?: boolean): Promise { let run: (force?: boolean) => Promise; if (typeof name === 'string') { @@ -912,7 +912,7 @@ export class CommandCenter { } } - @command('git.merge', { model: true }) + @command('git.merge', { repository: true }) async merge(repository: Repository): Promise { const config = workspace.getConfiguration('git'); const checkoutType = config.get('checkoutType') || 'all'; @@ -946,7 +946,7 @@ export class CommandCenter { } } - @command('git.createTag', { model: true }) + @command('git.createTag', { repository: true }) async createTag(repository: Repository): Promise { const inputTagName = await window.showInputBox({ placeHolder: localize('tag name', "Tag name"), @@ -969,7 +969,7 @@ export class CommandCenter { await repository.tag(name, message); } - @command('git.pullFrom', { model: true }) + @command('git.pullFrom', { repository: true }) async pullFrom(repository: Repository): Promise { const remotes = repository.remotes; @@ -999,7 +999,7 @@ export class CommandCenter { repository.pull(false, pick.label, branchName); } - @command('git.pull', { model: true }) + @command('git.pull', { repository: true }) async pull(repository: Repository): Promise { const remotes = repository.remotes; @@ -1011,7 +1011,7 @@ export class CommandCenter { await repository.pull(); } - @command('git.pullRebase', { model: true }) + @command('git.pullRebase', { repository: true }) async pullRebase(repository: Repository): Promise { const remotes = repository.remotes; @@ -1023,7 +1023,7 @@ export class CommandCenter { await repository.pullWithRebase(); } - @command('git.push', { model: true }) + @command('git.push', { repository: true }) async push(repository: Repository): Promise { const remotes = repository.remotes; @@ -1035,7 +1035,7 @@ export class CommandCenter { await repository.push(); } - @command('git.pushWithTags', { model: true }) + @command('git.pushWithTags', { repository: true }) async pushWithTags(repository: Repository): Promise { const remotes = repository.remotes; @@ -1049,7 +1049,7 @@ export class CommandCenter { window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags.")); } - @command('git.pushTo', { model: true }) + @command('git.pushTo', { repository: true }) async pushTo(repository: Repository): Promise { const remotes = repository.remotes; @@ -1075,7 +1075,7 @@ export class CommandCenter { repository.pushTo(pick.label, branchName); } - @command('git.sync', { model: true }) + @command('git.sync', { repository: true }) async sync(repository: Repository): Promise { const HEAD = repository.HEAD; @@ -1102,7 +1102,7 @@ export class CommandCenter { await repository.sync(); } - @command('git.publish', { model: true }) + @command('git.publish', { repository: true }) async publish(repository: Repository): Promise { const remotes = repository.remotes; @@ -1128,7 +1128,7 @@ export class CommandCenter { this.outputChannel.show(); } - @command('git.ignore', { model: true }) + @command('git.ignore', { repository: true }) async ignore(repository: Repository, ...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const uri = window.activeTextEditor && window.activeTextEditor.document.uri; @@ -1151,7 +1151,7 @@ export class CommandCenter { await repository.ignore(uris); } - @command('git.stash', { model: true }) + @command('git.stash', { repository: true }) async stash(repository: Repository): Promise { if (repository.workingTreeGroup.resourceStates.length === 0) { window.showInformationMessage(localize('no changes stash', "There are no changes to stash.")); @@ -1170,7 +1170,7 @@ export class CommandCenter { await repository.createStash(message); } - @command('git.stashPop', { model: true }) + @command('git.stashPop', { repository: true }) async stashPop(repository: Repository): Promise { const stashes = await repository.getStashes(); @@ -1190,7 +1190,7 @@ export class CommandCenter { await repository.popStash(choice.id); } - @command('git.stashPopLatest', { model: true }) + @command('git.stashPopLatest', { repository: true }) async stashPopLatest(repository: Repository): Promise { const stashes = await repository.getStashes(); @@ -1211,7 +1211,7 @@ export class CommandCenter { let result: Promise; - if (!options.model) { + if (!options.repository) { result = Promise.resolve(method.apply(this, args)); } else { result = this.model.pickRepository().then(repository => { From f8fcaf44fc169fcf96d0957bce34edb1b218ed51 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 15 Aug 2017 18:49:26 +0200 Subject: [PATCH 21/67] git: open file, open changes --- extensions/git/package.json | 10 +++++----- extensions/git/src/commands.ts | 32 +++++++++++++++++++------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 2d1e4c36fd608..da149b64b6df7 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -665,27 +665,27 @@ { "command": "git.openFile", "group": "navigation", - "when": "config.git.enabled && scmProvider == git && isInDiffEditor && resourceScheme != extension && resourceScheme != merge-conflict.conflict-diff" + "when": "config.git.enabled && isInDiffEditor && resourceScheme != extension && resourceScheme != merge-conflict.conflict-diff" }, { "command": "git.openChange", "group": "navigation", - "when": "config.git.enabled && scmProvider == git && !isInDiffEditor && resourceScheme != extension" + "when": "config.git.enabled && !isInDiffEditor && resourceScheme != extension" }, { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "config.git.enabled && scmProvider == git && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" + "when": "config.git.enabled && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "config.git.enabled && scmProvider == git && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" + "when": "config.git.enabled && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "config.git.enabled && scmProvider == git && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" + "when": "config.git.enabled && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" } ] }, diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index fa81bdcbadd30..c8c3fc103cbbd 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -150,14 +150,14 @@ export class CommandCenter { await repository.status(); } - @command('git.openResource', { repository: true }) - async openResource(repository: Repository, resource: Resource): Promise { - await this._openResource(repository, resource); + @command('git.openResource') + async openResource(resource: Resource): Promise { + await this._openResource(resource); } - private async _openResource(repository: Repository, resource: Resource, preview?: boolean): Promise { + private async _openResource(resource: Resource, preview?: boolean): Promise { const left = this.getLeftResource(resource); - const right = this.getRightResource(repository, resource); + const right = this.getRightResource(resource); const title = this.getTitle(resource); if (!right) { @@ -201,7 +201,7 @@ export class CommandCenter { } } - private getRightResource(repository: Repository, resource: Resource): Uri | undefined { + private getRightResource(resource: Resource): Uri | undefined { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: @@ -217,6 +217,12 @@ export class CommandCenter { case Status.MODIFIED: case Status.UNTRACKED: case Status.IGNORED: + const repository = this.model.getRepository(resource.resourceUri); + + if (!repository) { + return; + } + const uriString = resource.resourceUri.toString(); const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); @@ -306,8 +312,8 @@ export class CommandCenter { // await model.init(); } - @command('git.openFile', { repository: true }) - async openFile(repository: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { + @command('git.openFile') + async openFile(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let uris: Uri[] | undefined; if (arg instanceof Uri) { @@ -356,8 +362,8 @@ export class CommandCenter { } } - @command('git.openHEADFile', { repository: true }) - async openHEADFile(repository: Repository, arg?: Resource | Uri): Promise { + @command('git.openHEADFile') + async openHEADFile(arg?: Resource | Uri): Promise { let resource: Resource | undefined = undefined; if (arg instanceof Resource) { @@ -382,8 +388,8 @@ export class CommandCenter { return await commands.executeCommand('vscode.open', HEAD); } - @command('git.openChange', { repository: true }) - async openChange(repository: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { + @command('git.openChange') + async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { let resources: Resource[] | undefined = undefined; if (arg instanceof Uri) { @@ -411,7 +417,7 @@ export class CommandCenter { const preview = resources.length === 1 ? undefined : false; for (const resource of resources) { - await this._openResource(repository, resource, preview); + await this._openResource(resource, preview); } } From 69b30c7c54ec27ec1edd046a3bae2ffcc254cf35 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 11:20:47 +0200 Subject: [PATCH 22/67] use scoped scm label --- extensions/git/src/repository.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index b4b7d594d5a49..af429aad98dcd 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -360,7 +360,8 @@ export class Repository implements IRepository, Disposable { this.onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); this.disposables.push(fsWatcher); - this._sourceControl = scm.createSourceControl('git', 'Git'); + const label = `Git - ${path.basename(workspaceRoot.fsPath)}`; + this._sourceControl = scm.createSourceControl('git', label); this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") }; this._sourceControl.quickDiffProvider = this; this.disposables.push(this._sourceControl); From b5be40a5574813d498b08bcb6e9dbe68eb2d5bab Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 11:22:23 +0200 Subject: [PATCH 23/67] git: multiroot unstage --- extensions/git/src/commands.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index c8c3fc103cbbd..9c0672f706fac 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -527,8 +527,8 @@ export class CommandCenter { workspace.applyEdit(edit); } - @command('git.unstage', { repository: true }) - async unstage(repository: Repository, ...resourceStates: SourceControlResourceState[]): Promise { + @command('git.unstage') + async unstage(...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(); @@ -547,8 +547,7 @@ export class CommandCenter { } const resources = scmResources.map(r => r.resourceUri); - - return await repository.revert(resources); + await this.model.revert(resources); } @command('git.unstageAll', { repository: true }) From 773a6547a73799343987c62398c6828f83ca100c Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 11:23:23 +0200 Subject: [PATCH 24/67] git: multiroot discard --- extensions/git/src/commands.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 9c0672f706fac..c4582db790bd3 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -594,8 +594,8 @@ export class CommandCenter { await repository.stage(modifiedUri, result); } - @command('git.clean', { repository: true }) - async clean(repository: Repository, ...resourceStates: SourceControlResourceState[]): Promise { + @command('git.clean') + async clean(...resourceStates: SourceControlResourceState[]): Promise { if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) { const resource = this.getSCMResource(); @@ -606,26 +606,26 @@ export class CommandCenter { resourceStates = [resource]; } - const resources = resourceStates + const scmResources = resourceStates .filter(s => s instanceof Resource && s.resourceGroupType === ResourceGroupType.WorkingTree) as Resource[]; - if (!resources.length) { + if (!scmResources.length) { return; } - const untrackedCount = resources.reduce((s, r) => s + (r.type === Status.UNTRACKED ? 1 : 0), 0); + const untrackedCount = scmResources.reduce((s, r) => s + (r.type === Status.UNTRACKED ? 1 : 0), 0); let message: string; let yes = localize('discard', "Discard Changes"); - if (resources.length === 1) { + if (scmResources.length === 1) { if (untrackedCount > 0) { - message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(resources[0].resourceUri.fsPath)); + message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(scmResources[0].resourceUri.fsPath)); yes = localize('delete file', "Delete file"); } else { - message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath)); + message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(scmResources[0].resourceUri.fsPath)); } } else { - message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", resources.length); + message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", scmResources.length); if (untrackedCount > 0) { message = `${message}\n\n${localize('warn untracked', "This will DELETE {0} untracked files!", untrackedCount)}`; @@ -638,7 +638,8 @@ export class CommandCenter { return; } - await repository.clean(resources.map(r => r.resourceUri)); + const resources = scmResources.map(r => r.resourceUri); + await this.model.clean(resources); } @command('git.cleanAll', { repository: true }) From 6a9a529a894f5946fffbe596987e113e81b5e2f7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 11:46:54 +0200 Subject: [PATCH 25/67] git: multirepo stageAll, unstageAll, cleanAll --- extensions/git/src/commands.ts | 62 +++++++++++++++++++++++++++++----- extensions/git/src/model.ts | 27 ++++++++++++--- 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index c4582db790bd3..028678a4cf55c 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; +import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository'; import { Model } from './model'; @@ -444,8 +444,22 @@ export class CommandCenter { await this.model.add(resources); } - @command('git.stageAll', { repository: true }) - async stageAll(repository: Repository): Promise { + @command('git.stageAll') + async stageAll(group?: SourceControlResourceGroup): Promise { + let repository: Repository | undefined = undefined; + + if (group) { + repository = this.model.getRepositoryFromResourceGroup(group); + } + + if (!repository) { + repository = await this.model.pickRepository(); + } + + if (!repository) { + return; + } + await repository.add([]); } @@ -550,9 +564,23 @@ export class CommandCenter { await this.model.revert(resources); } - @command('git.unstageAll', { repository: true }) - async unstageAll(repository: Repository): Promise { - return await repository.revert([]); + @command('git.unstageAll') + async unstageAll(group?: SourceControlResourceGroup): Promise { + let repository: Repository | undefined = undefined; + + if (group) { + repository = this.model.getRepositoryFromResourceGroup(group); + } + + if (!repository) { + repository = await this.model.pickRepository(); + } + + if (!repository) { + return; + } + + await repository.revert([]); } // TODO@Joao does this command really receive a model? @@ -642,8 +670,22 @@ export class CommandCenter { await this.model.clean(resources); } - @command('git.cleanAll', { repository: true }) - async cleanAll(repository: Repository): Promise { + @command('git.cleanAll') + async cleanAll(group?: SourceControlResourceGroup): Promise { + let repository: Repository | undefined = undefined; + + if (group) { + repository = this.model.getRepositoryFromResourceGroup(group); + } + + if (!repository) { + repository = await this.model.pickRepository(); + } + + if (!repository) { + return; + } + const config = workspace.getConfiguration('git'); let scope = config.get('discardAllScope') || 'prompt'; let resources = repository.workingTreeGroup.resourceStates; @@ -1220,6 +1262,10 @@ export class CommandCenter { if (!options.repository) { result = Promise.resolve(method.apply(this, args)); } else { + console.log(args[0]); + // if (args[0] instanceof SourceControlResourceGroup) { + // } + result = this.model.pickRepository().then(repository => { if (!repository) { return Promise.reject(localize('modelnotfound', "Git model not found")); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 5f7f4f728f345..1db5cda376721 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, window, QuickPickItem, Disposable } from 'vscode'; +import { Uri, window, QuickPickItem, Disposable, SourceControlResourceGroup } from 'vscode'; import { Repository, IRepository, State } from './repository'; import { memoize } from './decorators'; import { toDisposable, filterEvent, once } from './util'; @@ -44,6 +44,15 @@ export class Model implements IRepository { } async pickRepository(): Promise { + if (this.repositories.size === 0) { + throw new Error(localize('no repositories', "There are no available repositories")); + } + + // TODO@joao enable this code + // if (this.repositories.size === 1) { + // return this.repositories.values().next().value; + // } + const picks = Array.from(this.repositories.entries(), ([uri, model]) => new RepositoryPick(uri, model)); const placeHolder = localize('pick repo', "Choose a repository"); const pick = await window.showQuickPick(picks, { placeHolder }); @@ -51,15 +60,25 @@ export class Model implements IRepository { return pick && pick.repository; } + getRepositoryFromResourceGroup(resourceGroup?: SourceControlResourceGroup): Repository | undefined { + for (let [, repository] of this.repositories) { + if (resourceGroup === repository.mergeGroup || resourceGroup === repository.indexGroup || resourceGroup === repository.workingTreeGroup) { + return repository; + } + } + + return undefined; + } + getRepository(resource: Uri): Repository | undefined { const resourcePath = resource.fsPath; - for (let [repositoryRoot, model] of this.repositories) { - const repositoryRootPath = repositoryRoot.fsPath; + for (let [root, repository] of this.repositories) { + const repositoryRootPath = root.fsPath; const relativePath = path.relative(repositoryRootPath, resourcePath); if (!/^\./.test(relativePath)) { - return model; + return repository; } } From 1e00878d20c3f34b3091e332643b519360144461 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 12:02:53 +0200 Subject: [PATCH 26/67] :lipstick: --- extensions/git/src/commands.ts | 38 ++++++++++++++++++++-- extensions/git/src/model.ts | 54 ++------------------------------ extensions/git/src/repository.ts | 9 +----- 3 files changed, 38 insertions(+), 63 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 028678a4cf55c..88c8be29701a2 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -441,7 +441,7 @@ export class CommandCenter { } const resources = scmResources.map(r => r.resourceUri); - await this.model.add(resources); + await this.runByRepository(resources, async (repository, resources) => repository.add(resources)); } @command('git.stageAll') @@ -561,7 +561,7 @@ export class CommandCenter { } const resources = scmResources.map(r => r.resourceUri); - await this.model.revert(resources); + await this.runByRepository(resources, async (repository, resources) => repository.revert(resources)); } @command('git.unstageAll') @@ -667,7 +667,7 @@ export class CommandCenter { } const resources = scmResources.map(r => r.resourceUri); - await this.model.clean(resources); + await this.runByRepository(resources, async (repository, resources) => repository.clean(resources)); } @command('git.cleanAll') @@ -1349,6 +1349,38 @@ export class CommandCenter { } } + private runByRepository(resources: Uri, fn: (repository: Repository, resources: Uri) => Promise): Promise; + private runByRepository(resources: Uri[], fn: (repository: Repository, resources: Uri[]) => Promise): Promise; + private async runByRepository(arg: Uri | Uri[], fn: (repository: Repository, resources: any) => Promise): Promise { + const resources = arg instanceof Uri ? [arg] : arg; + const isSingleResource = arg instanceof Uri; + + const groups = resources.reduce((result, resource) => { + const repository = this.model.getRepository(resource); + + // TODO@Joao: what should happen? + if (!repository) { + console.warn('Could not find git repository for ', resource); + return result; + } + + const tuple = result.filter(p => p[0] === repository)[0]; + + if (tuple) { + tuple.resources.push(resource); + } else { + result.push({ repository, resources: [resource] }); + } + + return result; + }, [] as { repository: Repository, resources: Uri[] }[]); + + const promises = groups + .map(({ repository, resources }) => fn(repository as Repository, isSingleResource ? resources[0] : resources)); + + return Promise.all(promises); + } + dispose(): void { this.disposables.forEach(d => d.dispose()); } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 1db5cda376721..6e3a92df69250 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -6,7 +6,7 @@ 'use strict'; import { Uri, window, QuickPickItem, Disposable, SourceControlResourceGroup } from 'vscode'; -import { Repository, IRepository, State } from './repository'; +import { Repository, State } from './repository'; import { memoize } from './decorators'; import { toDisposable, filterEvent, once } from './util'; import * as path from 'path'; @@ -20,7 +20,7 @@ class RepositoryPick implements QuickPickItem { constructor(protected repositoryRoot: Uri, public readonly repository: Repository) { } } -export class Model implements IRepository { +export class Model { private repositories: Map = new Map(); @@ -84,54 +84,4 @@ export class Model implements IRepository { return undefined; } - - private runByRepository(resources: Uri, fn: (repository: Repository, resources: Uri) => Promise): Promise; - private runByRepository(resources: Uri[], fn: (repository: Repository, resources: Uri[]) => Promise): Promise; - private async runByRepository(arg: Uri | Uri[], fn: (repository: Repository, resources: any) => Promise): Promise { - const resources = arg instanceof Uri ? [arg] : arg; - const isSingleResource = arg instanceof Uri; - - const groups = resources.reduce((result, resource) => { - const repository = this.getRepository(resource); - - // TODO@Joao: what should happen? - if (!repository) { - console.warn('Could not find git repository for ', resource); - return result; - } - - const tuple = result.filter(p => p[0] === repository)[0]; - - if (tuple) { - tuple.resources.push(resource); - } else { - result.push({ repository, resources: [resource] }); - } - - return result; - }, [] as { repository: Repository, resources: Uri[] }[]); - - const promises = groups - .map(({ repository, resources }) => fn(repository as Repository, isSingleResource ? resources[0] : resources)); - - return Promise.all(promises); - } - - // IRepository - - async add(resources: Uri[]): Promise { - await this.runByRepository(resources, async (repository, resources) => repository.add(resources)); - } - - async stage(resource: Uri, contents: string): Promise { - await this.runByRepository(resource, async (repository, uri) => repository.stage(uri, contents)); - } - - async revert(resources: Uri[]): Promise { - await this.runByRepository(resources, async (repository, resources) => repository.revert(resources)); - } - - async clean(resources: Uri[]): Promise { - await this.runByRepository(resources, async (repository, resources) => repository.clean(resources)); - } } \ No newline at end of file diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index af429aad98dcd..b6fee70464271 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -263,18 +263,11 @@ export interface CommitOptions { signCommit?: boolean; } -export interface IRepository { - add(resources: Uri[]): Promise; - stage(resource: Uri, contents: string): Promise; - revert(resources: Uri[]): Promise; - clean(resources: Uri[]): Promise; -} - export interface GitResourceGroup extends SourceControlResourceGroup { resourceStates: Resource[]; } -export class Repository implements IRepository, Disposable { +export class Repository implements Disposable { private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; From 31a07d16a931b50a17a3154c74a338240fa132b0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 12:07:52 +0200 Subject: [PATCH 27/67] git: multirepo range staging --- extensions/git/src/commands.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 88c8be29701a2..5d3c0d7ef47d9 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -463,9 +463,8 @@ export class CommandCenter { await repository.add([]); } - // TODO@Joao does this command really receive a model? - @command('git.stageSelectedRanges', { repository: true, diff: true }) - async stageSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { + @command('git.stageSelectedRanges', { diff: true }) + async stageSelectedRanges(diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -492,12 +491,11 @@ export class CommandCenter { const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs); - await repository.stage(modifiedUri, result); + await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result)); } - // TODO@Joao does this command really receive a model? - @command('git.revertSelectedRanges', { repository: true, diff: true }) - async revertSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { + @command('git.revertSelectedRanges', { diff: true }) + async revertSelectedRanges(diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -583,9 +581,8 @@ export class CommandCenter { await repository.revert([]); } - // TODO@Joao does this command really receive a model? - @command('git.unstageSelectedRanges', { repository: true, diff: true }) - async unstageSelectedRanges(repository: Repository, diffs: LineChange[]): Promise { + @command('git.unstageSelectedRanges', { diff: true }) + async unstageSelectedRanges(diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -619,7 +616,7 @@ export class CommandCenter { const invertedDiffs = selectedDiffs.map(invertLineChange); const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs); - await repository.stage(modifiedUri, result); + await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result)); } @command('git.clean') @@ -1349,7 +1346,7 @@ export class CommandCenter { } } - private runByRepository(resources: Uri, fn: (repository: Repository, resources: Uri) => Promise): Promise; + private runByRepository(resource: Uri, fn: (repository: Repository, resource: Uri) => Promise): Promise; private runByRepository(resources: Uri[], fn: (repository: Repository, resources: Uri[]) => Promise): Promise; private async runByRepository(arg: Uri | Uri[], fn: (repository: Repository, resources: any) => Promise): Promise { const resources = arg instanceof Uri ? [arg] : arg; From 3db1cf08d683e30e083f30fb7311d5dcaf4be735 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 12:12:23 +0200 Subject: [PATCH 28/67] scm: title --- .../parts/scm/electron-browser/scmViewlet.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index ad5845e3854c9..51ea8641cb4d9 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -540,13 +540,13 @@ export class SCMViewlet extends ComposedViewsViewlet { getTitle(): string { const title = localize('source control', "Source Control"); - const providerLabel = this.scmService.activeProvider && this.scmService.activeProvider.label; + // const providerLabel = this.scmService.activeProvider && this.scmService.activeProvider.label; - if (providerLabel) { - return localize('viewletTitle', "{0}: {1}", title, providerLabel); - } else { - return title; - } + // if (providerLabel) { + // return localize('viewletTitle', "{0}: {1}", title, providerLabel); + // } else { + return title; + // } } @memoize From d7a2be05b69e2e24fddd230e0967d99973902fc2 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 12:51:30 +0200 Subject: [PATCH 29/67] scm: multiroot refresh --- extensions/git/src/commands.ts | 20 ++++++++++++++++--- extensions/git/src/model.ts | 14 +++++++++++-- .../api/electron-browser/mainThreadSCM.ts | 7 +++++++ src/vs/workbench/api/node/extHostSCM.ts | 8 ++++++++ .../parts/scm/electron-browser/scmMenus.ts | 3 ++- .../parts/scm/electron-browser/scmViewlet.ts | 13 ++++++++++++ src/vs/workbench/parts/views/browser/views.ts | 7 +++++++ 7 files changed, 66 insertions(+), 6 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 5d3c0d7ef47d9..bc244991a5be2 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; +import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControl, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository'; import { Model } from './model'; @@ -145,8 +145,22 @@ export class CommandCenter { }); } - @command('git.refresh', { repository: true }) - async refresh(repository: Repository): Promise { + @command('git.refresh') + async refresh(sourceControl?: SourceControl): Promise { + let repository: Repository | undefined = undefined; + + if (sourceControl) { + repository = this.model.getRepositoryFromSourceControl(sourceControl); + } + + if (!repository) { + repository = await this.model.pickRepository(); + } + + if (!repository) { + return; + } + await repository.status(); } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 6e3a92df69250..af41e5f8a6c13 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, window, QuickPickItem, Disposable, SourceControlResourceGroup } from 'vscode'; +import { Uri, window, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup } from 'vscode'; import { Repository, State } from './repository'; import { memoize } from './decorators'; import { toDisposable, filterEvent, once } from './util'; @@ -60,7 +60,17 @@ export class Model { return pick && pick.repository; } - getRepositoryFromResourceGroup(resourceGroup?: SourceControlResourceGroup): Repository | undefined { + getRepositoryFromSourceControl(sourceControl: SourceControl): Repository | undefined { + for (let [, repository] of this.repositories) { + if (sourceControl === repository.sourceControl) { + return repository; + } + } + + return undefined; + } + + getRepositoryFromResourceGroup(resourceGroup: SourceControlResourceGroup): Repository | undefined { for (let [, repository] of this.repositories) { if (resourceGroup === repository.mergeGroup || resourceGroup === repository.indexGroup || resourceGroup === repository.workingTreeGroup) { return repository; diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index fb8d1f155a554..9144743dea983 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -199,6 +199,13 @@ class MainThreadSCMProvider implements ISCMProvider { return this.proxy.$provideOriginalResource(this.handle, uri); } + toJSON(): any { + return { + $mid: 5, + handle: this.handle + }; + } + dispose(): void { } diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index 126466f2f14f2..8dd6656aa916f 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -307,6 +307,14 @@ export class ExtHostSCM { } return sourceControl.getResourceGroup(arg.groupHandle); + } else if (arg && arg.$mid === 5) { + const sourceControl = this._sourceControls.get(arg.handle); + + if (!sourceControl) { + return arg; + } + + return sourceControl; } return arg; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts index 84015f00e6f55..3e1850b0dc86f 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts @@ -46,7 +46,8 @@ export class SCMMenus implements IDisposable { private updateTitleActions(): void { this.titleActions = []; this.titleSecondaryActions = []; - fillInActions(this.titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }); + // TODO@joao: second arg used to be null + fillInActions(this.titleMenu, { shouldForwardArgs: true }, { primary: this.titleActions, secondary: this.titleSecondaryActions }); this._onDidChangeTitle.fire(); } diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 51ea8641cb4d9..650a3ea33089b 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -253,6 +253,7 @@ class SourceControlView extends CollapsibleView { @IContextMenuService protected contextMenuService: IContextMenuService, @IListService protected listService: IListService, @ICommandService protected commandService: ICommandService, + @IMessageService protected messageService: IMessageService, @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, @IEditorGroupService protected editorGroupService: IEditorGroupService, @IInstantiationService protected instantiationService: IInstantiationService @@ -318,6 +319,18 @@ class SourceControlView extends CollapsibleView { return this.menus.getTitleSecondaryActions(); } + getActionItem(action: IAction): IActionItem { + if (!(action instanceof MenuItemAction)) { + return undefined; + } + + return new SCMMenuItemActionItem(action, this.keybindingService, this.messageService); + } + + getActionsContext(): any { + return this.provider; + } + private updateList(): void { const elements = this.provider.resources .reduce<(ISCMResourceGroup | ISCMResource)[]>((r, g) => [...r, g, ...g.resources.sort(resourceSorter)], []); diff --git a/src/vs/workbench/parts/views/browser/views.ts b/src/vs/workbench/parts/views/browser/views.ts index 09471ced8de31..fcedaed1c83f7 100644 --- a/src/vs/workbench/parts/views/browser/views.ts +++ b/src/vs/workbench/parts/views/browser/views.ts @@ -72,6 +72,8 @@ export interface IView extends IBaseView, IThemable { getActionItem(action: IAction): IActionItem; + getActionsContext(): any; + showHeader(): boolean; hideHeader(): boolean; @@ -171,6 +173,7 @@ export abstract class CollapsibleView extends AbstractCollapsibleView implements protected updateActions(): void { this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); + this.toolBar.context = this.getActionsContext(); } protected renderViewTree(container: HTMLElement): HTMLElement { @@ -228,6 +231,10 @@ export abstract class CollapsibleView extends AbstractCollapsibleView implements return null; } + public getActionsContext(): any { + return undefined; + } + public shutdown(): void { // Subclass to implement } From 4a1bd6fbf77c7e87072467ac6e83f6f77745951b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 16:00:50 +0200 Subject: [PATCH 30/67] scm: always show scm actions --- .../parts/scm/electron-browser/media/scmViewlet.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css index 64a0df645330c..4e86fbea0de34 100644 --- a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css +++ b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css @@ -8,6 +8,12 @@ -webkit-mask-size: 19px; } +.monaco-workbench .viewlet.scm-viewlet .split-view-view .actions, +.monaco-workbench .viewlet.scm-viewlet .collapsible.header .actions { + width: initial; + flex: 1; +} + .scm-viewlet .scm-status { height: 100%; } From eb04eaa1c480f9fb65e9cbea3448bf2d6f1707dd Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 16:03:18 +0200 Subject: [PATCH 31/67] git: contextual repositories --- extensions/git/src/commands.ts | 87 +++++----------------------------- extensions/git/src/model.ts | 43 +++++++++-------- 2 files changed, 35 insertions(+), 95 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index bc244991a5be2..4d54d04c780ba 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControl, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; +import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository'; import { Model } from './model'; @@ -145,22 +145,8 @@ export class CommandCenter { }); } - @command('git.refresh') - async refresh(sourceControl?: SourceControl): Promise { - let repository: Repository | undefined = undefined; - - if (sourceControl) { - repository = this.model.getRepositoryFromSourceControl(sourceControl); - } - - if (!repository) { - repository = await this.model.pickRepository(); - } - - if (!repository) { - return; - } - + @command('git.refresh', { repository: true }) + async refresh(repository: Repository): Promise { await repository.status(); } @@ -458,22 +444,8 @@ export class CommandCenter { await this.runByRepository(resources, async (repository, resources) => repository.add(resources)); } - @command('git.stageAll') - async stageAll(group?: SourceControlResourceGroup): Promise { - let repository: Repository | undefined = undefined; - - if (group) { - repository = this.model.getRepositoryFromResourceGroup(group); - } - - if (!repository) { - repository = await this.model.pickRepository(); - } - - if (!repository) { - return; - } - + @command('git.stageAll', { repository: true }) + async stageAll(repository: Repository): Promise { await repository.add([]); } @@ -576,22 +548,8 @@ export class CommandCenter { await this.runByRepository(resources, async (repository, resources) => repository.revert(resources)); } - @command('git.unstageAll') - async unstageAll(group?: SourceControlResourceGroup): Promise { - let repository: Repository | undefined = undefined; - - if (group) { - repository = this.model.getRepositoryFromResourceGroup(group); - } - - if (!repository) { - repository = await this.model.pickRepository(); - } - - if (!repository) { - return; - } - + @command('git.unstageAll', { repository: true }) + async unstageAll(repository: Repository): Promise { await repository.revert([]); } @@ -681,22 +639,8 @@ export class CommandCenter { await this.runByRepository(resources, async (repository, resources) => repository.clean(resources)); } - @command('git.cleanAll') - async cleanAll(group?: SourceControlResourceGroup): Promise { - let repository: Repository | undefined = undefined; - - if (group) { - repository = this.model.getRepositoryFromResourceGroup(group); - } - - if (!repository) { - repository = await this.model.pickRepository(); - } - - if (!repository) { - return; - } - + @command('git.cleanAll', { repository: true }) + async cleanAll(repository: Repository): Promise { const config = workspace.getConfiguration('git'); let scope = config.get('discardAllScope') || 'prompt'; let resources = repository.workingTreeGroup.resourceStates; @@ -1263,21 +1207,16 @@ export class CommandCenter { private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any { const result = (...args) => { - // if (!skipModelCheck && !this.model) { - // window.showInformationMessage(localize('disabled', "Git is either disabled or not supported in this workspace")); - // return; - // } - let result: Promise; if (!options.repository) { result = Promise.resolve(method.apply(this, args)); } else { - console.log(args[0]); - // if (args[0] instanceof SourceControlResourceGroup) { - // } + // try to guess the repository based on the first argument + const repository = this.model.getRepository(args[0]); + const repositoryPromise = repository ? Promise.resolve(repository) : this.model.pickRepository(); - result = this.model.pickRepository().then(repository => { + result = repositoryPromise.then(repository => { if (!repository) { return Promise.reject(localize('modelnotfound', "Git model not found")); } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index af41e5f8a6c13..33d71d2127c21 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -60,34 +60,35 @@ export class Model { return pick && pick.repository; } - getRepositoryFromSourceControl(sourceControl: SourceControl): Repository | undefined { - for (let [, repository] of this.repositories) { - if (sourceControl === repository.sourceControl) { - return repository; - } + getRepository(sourceControl: SourceControl): Repository | undefined; + getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; + getRepository(resource: Uri): Repository | undefined; + getRepository(hint: any): Repository | undefined { + if (!hint) { + return undefined; } - return undefined; - } + if (hint instanceof Uri) { + const resourcePath = hint.fsPath; - getRepositoryFromResourceGroup(resourceGroup: SourceControlResourceGroup): Repository | undefined { - for (let [, repository] of this.repositories) { - if (resourceGroup === repository.mergeGroup || resourceGroup === repository.indexGroup || resourceGroup === repository.workingTreeGroup) { - return repository; - } - } + for (let [root, repository] of this.repositories) { + const repositoryRootPath = root.fsPath; + const relativePath = path.relative(repositoryRootPath, resourcePath); - return undefined; - } + if (!/^\./.test(relativePath)) { + return repository; + } + } - getRepository(resource: Uri): Repository | undefined { - const resourcePath = resource.fsPath; + return undefined; + } - for (let [root, repository] of this.repositories) { - const repositoryRootPath = root.fsPath; - const relativePath = path.relative(repositoryRootPath, resourcePath); + for (let [, repository] of this.repositories) { + if (hint === repository.sourceControl) { + return repository; + } - if (!/^\./.test(relativePath)) { + if (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup) { return repository; } } From e3b15fca018e2946a71bfab39b22ef058bebf449 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Aug 2017 16:53:09 +0200 Subject: [PATCH 32/67] wip: register multiple source control providers --- extensions/git/src/main.ts | 28 ++-- extensions/git/src/model.ts | 53 ++++++-- extensions/git/src/repository.ts | 122 +++++++----------- .../parts/scm/electron-browser/scmViewlet.ts | 99 +++++++------- src/vs/workbench/services/scm/common/scm.ts | 3 + .../services/scm/common/scmService.ts | 5 + 6 files changed, 165 insertions(+), 145 deletions(-) diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index aa0cd238b38d7..79ecc2c2ae4a0 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -12,8 +12,8 @@ import { findGit, Git, IGit } from './git'; import { Repository } from './repository'; import { Model } from './model'; import { CommandCenter } from './commands'; -import { GitContentProvider } from './contentProvider'; -import { AutoFetcher } from './autofetch'; +// import { GitContentProvider } from './contentProvider'; +// import { AutoFetcher } from './autofetch'; import { Askpass } from './askpass'; import { toDisposable } from './util'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -28,24 +28,26 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi const config = workspace.getConfiguration('git'); const enabled = config.get('enabled') === true; - const workspaceRootPath = workspace.rootPath; - const pathHint = workspace.getConfiguration('git').get('path'); const info = await findGit(pathHint); const askpass = new Askpass(); const env = await askpass.getEnv(); const git = new Git({ gitPath: info.path, version: info.version, env }); const model = new Model(); + disposables.push(model); - if (!workspaceRootPath || !enabled) { + if (!enabled) { const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); disposables.push(commandCenter); return; } - const workspaceRoot = Uri.file(workspaceRootPath); - const repository = new Repository(git, workspaceRoot); - model.register(workspaceRoot, repository); + for (const folder of workspace.workspaceFolders || []) { + const repositoryRoot = await git.getRepositoryRoot(folder.uri.fsPath); + const repository = new Repository(git.open(repositoryRoot)); + + model.register(repository); + } outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path)); @@ -54,14 +56,14 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); - const contentProvider = new GitContentProvider(repository); - const autoFetcher = new AutoFetcher(repository); + // const contentProvider = new GitContentProvider(repository); + // const autoFetcher = new AutoFetcher(repository); disposables.push( commandCenter, - contentProvider, - autoFetcher, - repository + // contentProvider, + // autoFetcher, + // repository ); await checkGitVersion(info); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 33d71d2127c21..3aa6f35e5f82d 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -15,28 +15,30 @@ import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); class RepositoryPick implements QuickPickItem { - @memoize get label(): string { return path.basename(this.repositoryRoot.fsPath); } - @memoize get description(): string { return path.dirname(this.repositoryRoot.fsPath); } - constructor(protected repositoryRoot: Uri, public readonly repository: Repository) { } + @memoize get label(): string { return path.basename(this.repositoryRoot); } + @memoize get description(): string { return path.dirname(this.repositoryRoot); } + constructor(protected repositoryRoot: string, public readonly repository: Repository) { } } export class Model { - private repositories: Map = new Map(); + private repositories: Map = new Map(); - register(uri: Uri, repository: Repository): Disposable { - if (this.repositories.has(uri)) { + register(repository: Repository): Disposable { + const root = repository.root; + + if (this.repositories.has(root)) { // TODO@Joao: what should happen? throw new Error('Cant register repository with the same URI'); } - this.repositories.set(uri, repository); + this.repositories.set(root, repository); const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.NotAGitRepository); const listener = onDidDisappearRepository(() => disposable.dispose()); const disposable = toDisposable(once(() => { - this.repositories.delete(uri); + this.repositories.delete(root); listener.dispose(); })); @@ -72,8 +74,7 @@ export class Model { const resourcePath = hint.fsPath; for (let [root, repository] of this.repositories) { - const repositoryRootPath = root.fsPath; - const relativePath = path.relative(repositoryRootPath, resourcePath); + const relativePath = path.relative(root, resourcePath); if (!/^\./.test(relativePath)) { return repository; @@ -95,4 +96,36 @@ export class Model { return undefined; } + + // private async assertIdleState(): Promise { + // if (this.state === State.Idle) { + // return; + // } + + // const disposables: Disposable[] = []; + // const repositoryRoot = await this.git.getRepositoryRoot(this.workspaceRoot.fsPath); + // this.repository = this.git.open(repositoryRoot); + + // const onGitChange = filterEvent(this.onWorkspaceChange, uri => /\/\.git\//.test(uri.path)); + // const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.path)); + + // onRelevantGitChange(this.onFSChange, this, disposables); + // onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, disposables); + + // const onNonGitChange = filterEvent(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.path)); + // onNonGitChange(this.onFSChange, this, disposables); + + // this.repositoryDisposable = combinedDisposable(disposables); + // this.isRepositoryHuge = false; + // this.didWarnAboutLimit = false; + // this.state = State.Idle; + // } + + dispose(): void { + for (let [, repository] of this.repositories) { + repository.dispose(); + } + + this.repositories.clear(); + } } \ No newline at end of file diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index b6fee70464271..5a76865e73361 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -141,8 +141,10 @@ export class Resource implements SourceControlResourceState { @memoize private get faded(): boolean { - const workspaceRootPath = this.workspaceRoot.fsPath; - return this.resourceUri.fsPath.substr(0, workspaceRootPath.length) !== workspaceRootPath; + // TODO@joao + return false; + // const workspaceRootPath = this.workspaceRoot.fsPath; + // return this.resourceUri.fsPath.substr(0, workspaceRootPath.length) !== workspaceRootPath; } get decorations(): SourceControlResourceDecorations { @@ -155,7 +157,6 @@ export class Resource implements SourceControlResourceState { } constructor( - private workspaceRoot: Uri, private _resourceGroupType: ResourceGroupType, private _resourceUri: Uri, private _type: Status, @@ -269,6 +270,8 @@ export interface GitResourceGroup extends SourceControlResourceGroup { export class Repository implements Disposable { + private static handle = 0; + private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; @@ -321,8 +324,6 @@ export class Repository implements Disposable { private _operations = new OperationsImpl(); get operations(): Operations { return this._operations; } - private repository: BaseRepository; - private _state = State.Uninitialized; get state(): State { return this._state; } set state(state: State) { @@ -339,22 +340,27 @@ export class Repository implements Disposable { commands.executeCommand('setContext', 'gitState', ''); } - private onWorkspaceChange: Event; + get root(): string { + return this.repository.root; + } + private isRepositoryHuge = false; private didWarnAboutLimit = false; - private repositoryDisposable: Disposable = EmptyDisposable; private disposables: Disposable[] = []; constructor( - private _git: Git, - private workspaceRoot: Uri + private readonly repository: BaseRepository ) { const fsWatcher = workspace.createFileSystemWatcher('**'); - this.onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); this.disposables.push(fsWatcher); - const label = `Git - ${path.basename(workspaceRoot.fsPath)}`; - this._sourceControl = scm.createSourceControl('git', label); + const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); + onWorkspaceChange(this.onFSChange, this, this.disposables); + + const id = `git${Repository.handle++}`; + const label = `Git - ${path.basename(repository.root)}`; + + this._sourceControl = scm.createSourceControl(id, label); this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") }; this._sourceControl.quickDiffProvider = this; this.disposables.push(this._sourceControl); @@ -391,15 +397,15 @@ export class Repository implements Disposable { } } - @throttle - async init(): Promise { - if (this.state !== State.NotAGitRepository) { - return; - } + // @throttle + // async init(): Promise { + // if (this.state !== State.NotAGitRepository) { + // return; + // } - await this._git.init(this.workspaceRoot.fsPath); - await this.status(); - } + // await this.git.init(this.workspaceRoot.fsPath); + // await this.status(); + // } @throttle async status(): Promise { @@ -594,13 +600,15 @@ export class Repository implements Disposable { } private async run(operation: Operation, runOperation: () => Promise = () => Promise.resolve(null)): Promise { + if (this.state !== State.Idle) { + throw new Error('Repository not initialized'); + } + const run = async () => { this._operations = this._operations.start(operation); this._onRunOperation.fire(operation); try { - await this.assertIdleState(); - const result = await this.retryRun(runOperation); if (!isReadOnly(operation)) { @@ -610,12 +618,6 @@ export class Repository implements Disposable { return result; } catch (err) { if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { - this.repositoryDisposable.dispose(); - - const disposables: Disposable[] = []; - this.onWorkspaceChange(this.onFSChange, this, disposables); - this.repositoryDisposable = combinedDisposable(disposables); - this.state = State.NotAGitRepository; } @@ -649,37 +651,6 @@ export class Repository implements Disposable { } } - /* We use the native Node `watch` for faster, non debounced events. - * That way we hopefully get the events during the operations we're - * performing, thus sparing useless `git status` calls to refresh - * the model's state. - */ - private async assertIdleState(): Promise { - if (this.state === State.Idle) { - return; - } - - this.repositoryDisposable.dispose(); - - const disposables: Disposable[] = []; - const repositoryRoot = await this._git.getRepositoryRoot(this.workspaceRoot.fsPath); - this.repository = this._git.open(repositoryRoot); - - const onGitChange = filterEvent(this.onWorkspaceChange, uri => /\/\.git\//.test(uri.path)); - const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.path)); - - onRelevantGitChange(this.onFSChange, this, disposables); - onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, disposables); - - const onNonGitChange = filterEvent(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.path)); - onNonGitChange(this.onFSChange, this, disposables); - - this.repositoryDisposable = combinedDisposable(disposables); - this.isRepositoryHuge = false; - this.didWarnAboutLimit = false; - this.state = State.Idle; - } - @throttle private async updateModelState(): Promise { const { status, didHitLimit } = await this.repository.getStatus(); @@ -732,30 +703,30 @@ export class Repository implements Disposable { const renameUri = raw.rename ? Uri.file(path.join(this.repository.root, raw.rename)) : undefined; switch (raw.x + raw.y) { - case '??': return workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED)); - case '!!': return workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.IGNORED)); - case 'DD': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_DELETED)); - case 'AU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.ADDED_BY_US)); - case 'UD': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM)); - case 'UA': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM)); - case 'DU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.DELETED_BY_US)); - case 'AA': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_ADDED)); - case 'UU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED)); + case '??': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED)); + case '!!': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED)); + case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED)); + case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US)); + case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM)); + case 'UA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM)); + case 'DU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_US)); + case 'AA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_ADDED)); + case 'UU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED)); } let isModifiedInIndex = false; switch (raw.x) { - case 'M': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break; - case 'A': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_ADDED)); break; - case 'D': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_DELETED)); break; - case 'R': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, renameUri)); break; - case 'C': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_COPIED, renameUri)); break; + case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break; + case 'A': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_ADDED)); break; + case 'D': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_DELETED)); break; + case 'R': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_RENAMED, renameUri)); break; + case 'C': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_COPIED, renameUri)); break; } switch (raw.y) { - case 'M': workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, renameUri)); break; - case 'D': workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.DELETED, renameUri)); break; + case 'M': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.MODIFIED, renameUri)); break; + case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, renameUri)); break; } }); @@ -825,7 +796,6 @@ export class Repository implements Disposable { } dispose(): void { - this.repositoryDisposable.dispose(); this.disposables = dispose(this.disposables); } } diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 650a3ea33089b..de59b293e6c4e 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -11,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { chain } from 'vs/base/common/event'; import { memoize } from 'vs/base/common/decorators'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Builder, Dimension } from 'vs/base/browser/builder'; import { ComposedViewsViewlet, CollapsibleView, ICollapsibleViewOptions, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views'; import { append, $, toggleClass } from 'vs/base/browser/dom'; @@ -399,10 +399,10 @@ class InstallAdditionalSCMProvidersAction extends Action { export class SCMViewlet extends ComposedViewsViewlet { - private activeProvider: ISCMProvider | undefined; - private cachedDimension: Dimension; - private inputBoxContainer: HTMLElement; - private inputBox: InputBox; + // private activeProvider: ISCMProvider | undefined; + // private cachedDimension: Dimension; + // private inputBoxContainer: HTMLElement; + // private inputBox: InputBox; private providerChangeDisposable: IDisposable = EmptyDisposable; private disposables: IDisposable[] = []; @@ -428,30 +428,37 @@ export class SCMViewlet extends ComposedViewsViewlet { telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); } - private setActiveProvider(activeProvider: ISCMProvider | undefined): void { + private onDidProvidersChange(): void { this.providerChangeDisposable.dispose(); - this.activeProvider = activeProvider; + // this.activeProvider = activeProvider; - if (activeProvider) { - const disposables = []; + const providers = this.scmService.providers; + const ids = providers.map(provider => provider.id); + const views = providers.map(provider => new SourceControlViewDescriptor(provider)); - // if (activeProvider.onDidChangeCommitTemplate) { - // disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this)); - // } + ViewsRegistry.registerViews(views); + this.providerChangeDisposable = toDisposable(() => ViewsRegistry.deregisterViews(ids, ViewLocation.SCM)); - const id = activeProvider.id; - ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]); + // if (activeProvider) { + // const disposables = []; - disposables.push({ - dispose: () => { - ViewsRegistry.deregisterViews([id], ViewLocation.SCM); - } - }); + // // if (activeProvider.onDidChangeCommitTemplate) { + // // disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this)); + // // } - this.providerChangeDisposable = combinedDisposable(disposables); - } else { - this.providerChangeDisposable = EmptyDisposable; - } + // const id = activeProvider.id; + // ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]); + + // disposables.push({ + // dispose: () => { + // ViewsRegistry.deregisterViews([id], ViewLocation.SCM); + // } + // }); + + // this.providerChangeDisposable = combinedDisposable(disposables); + // } else { + // this.providerChangeDisposable = EmptyDisposable; + // } // this.updateInputBox(); // this.updateTitleArea(); @@ -483,8 +490,8 @@ export class SCMViewlet extends ComposedViewsViewlet { // .on(this.onDidAcceptInput, this, this.disposables); - this.setActiveProvider(this.scmService.activeProvider); - this.scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); + this.onDidProvidersChange(); + this.scmService.onDidChangeProviders(this.onDidProvidersChange, this, this.disposables); // this.themeService.onThemeChange(this.update, this, this.disposables); // return TPromise.as(null); @@ -498,33 +505,33 @@ export class SCMViewlet extends ComposedViewsViewlet { return this.instantiationService.createInstance(viewDescriptor.ctor, options); } - private onDidAcceptInput(): void { - if (!this.activeProvider) { - return; - } + // private onDidAcceptInput(): void { + // if (!this.activeProvider) { + // return; + // } - if (!this.activeProvider.acceptInputCommand) { - return; - } + // if (!this.activeProvider.acceptInputCommand) { + // return; + // } - const id = this.activeProvider.acceptInputCommand.id; - const args = this.activeProvider.acceptInputCommand.arguments; + // const id = this.activeProvider.acceptInputCommand.id; + // const args = this.activeProvider.acceptInputCommand.arguments; - this.commandService.executeCommand(id, ...args) - .done(undefined, onUnexpectedError); - } + // this.commandService.executeCommand(id, ...args) + // .done(undefined, onUnexpectedError); + // } - private updateInputBox(): void { - if (!this.activeProvider) { - return; - } + // private updateInputBox(): void { + // if (!this.activeProvider) { + // return; + // } - if (typeof this.activeProvider.commitTemplate === 'undefined') { - return; - } + // if (typeof this.activeProvider.commitTemplate === 'undefined') { + // return; + // } - this.inputBox.value = this.activeProvider.commitTemplate; - } + // this.inputBox.value = this.activeProvider.commitTemplate; + // } // layout(dimension: Dimension = this.cachedDimension): void { // if (!dimension) { diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 2af470e880132..f0169943edf77 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -63,6 +63,9 @@ export interface ISCMService { readonly _serviceBrand: any; readonly onDidChangeProvider: Event; + + // TODO@joao fix name + readonly onDidChangeProviders: Event; readonly providers: ISCMProvider[]; readonly input: ISCMInput; activeProvider: ISCMProvider | undefined; diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index 06cdc638fe186..9bfae91061e4d 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -51,6 +51,9 @@ export class SCMService implements ISCMService { private _providers: ISCMProvider[] = []; get providers(): ISCMProvider[] { return [...this._providers]; } + private _onDidChangeProviders = new Emitter(); + get onDidChangeProviders(): Event { return this._onDidChangeProviders.event; } + private _onDidChangeProvider = new Emitter(); get onDidChangeProvider(): Event { return this._onDidChangeProvider.event; } @@ -91,6 +94,8 @@ export class SCMService implements ISCMService { this.setActiveSCMProvider(provider); } + this._onDidChangeProviders.fire(); + return toDisposable(() => { const index = this._providers.indexOf(provider); From cf6b77d23e72793254042d1d6ac193a35cfe44b1 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 17 Aug 2017 10:29:42 +0200 Subject: [PATCH 33/67] safeguard deregisterViews --- .../workbench/parts/views/browser/viewsRegistry.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/views/browser/viewsRegistry.ts b/src/vs/workbench/parts/views/browser/viewsRegistry.ts index e215db3e40eab..b41252f6b671b 100644 --- a/src/vs/workbench/parts/views/browser/viewsRegistry.ts +++ b/src/vs/workbench/parts/views/browser/viewsRegistry.ts @@ -99,10 +99,18 @@ export const ViewsRegistry: IViewsRegistry = new class { } deregisterViews(ids: string[], location: ViewLocation): void { - const viewsToDeregister = this._views.get(location).filter(view => ids.indexOf(view.id) !== -1); + const views = this._views.get(location); + + if (!views) { + return; + } + + const viewsToDeregister = views.filter(view => ids.indexOf(view.id) !== -1); + if (viewsToDeregister.length) { - this._views.set(location, this._views.get(location).filter(view => ids.indexOf(view.id) === -1)); + this._views.set(location, views.filter(view => ids.indexOf(view.id) === -1)); } + this._onViewsDeregistered.fire(viewsToDeregister); } From 20b7747ac4150a8285a54e2fc965d24c007c95c1 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 17 Aug 2017 11:17:50 +0200 Subject: [PATCH 34/67] git: multiroot content provider --- extensions/git/src/contentProvider.ts | 38 +++++++++++++++---- extensions/git/src/main.ts | 6 +-- extensions/git/src/model.ts | 17 +++++++-- .../parts/scm/electron-browser/scmViewlet.ts | 5 +++ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index e8f92a82ccdf0..730ed093f91ae 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -8,7 +8,7 @@ import { workspace, Uri, Disposable, Event, EventEmitter, window } from 'vscode'; import { debounce } from './decorators'; import { fromGitUri } from './uri'; -import { Repository } from './repository'; +import { Model, ModelChangeEvent } from './model'; interface CacheRow { uri: Uri; @@ -27,29 +27,53 @@ export class GitContentProvider { private onDidChangeEmitter = new EventEmitter(); get onDidChange(): Event { return this.onDidChangeEmitter.event; } + private changedRepositoryRoots = new Set(); private cache: Cache = Object.create(null); private disposables: Disposable[] = []; - constructor(private repository: Repository) { + constructor(private model: Model) { this.disposables.push( - repository.onDidChangeRepository(this.eventuallyFireChangeEvents, this), + model.onDidChangeRepository(this.onDidChangeRepository, this), workspace.registerTextDocumentContentProvider('git', this) ); setInterval(() => this.cleanup(), FIVE_MINUTES); } + private onDidChangeRepository({ repository }: ModelChangeEvent): void { + this.changedRepositoryRoots.add(repository.root); + this.eventuallyFireChangeEvents(); + } + @debounce(1100) private eventuallyFireChangeEvents(): void { this.fireChangeEvents(); } private fireChangeEvents(): void { - Object.keys(this.cache) - .forEach(key => this.onDidChangeEmitter.fire(this.cache[key].uri)); + this.changedRepositoryRoots.clear(); + + Object.keys(this.cache).forEach(key => { + const uri = this.cache[key].uri; + const fsPath = uri.fsPath; + + for (const root of this.changedRepositoryRoots) { + if (fsPath.startsWith(root)) { + this.onDidChangeEmitter.fire(uri); + return; + } + } + }); } async provideTextDocumentContent(uri: Uri): Promise { + const repository = this.model.getRepository(uri); + + if (!repository) { + console.warn('git provideTextDocumentContent: could not find repository'); + return ''; + } + const cacheKey = uri.toString(); const timestamp = new Date().getTime(); const cacheValue = { uri, timestamp }; @@ -61,12 +85,12 @@ export class GitContentProvider { if (ref === '~') { const fileUri = Uri.file(path); const uriString = fileUri.toString(); - const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.original.toString() === uriString); + const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.original.toString() === uriString); ref = indexStatus ? '' : 'HEAD'; } try { - return await this.repository.show(ref, path); + return await repository.show(ref, path); } catch (err) { return ''; } diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 79ecc2c2ae4a0..cdbbe50340599 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -12,7 +12,7 @@ import { findGit, Git, IGit } from './git'; import { Repository } from './repository'; import { Model } from './model'; import { CommandCenter } from './commands'; -// import { GitContentProvider } from './contentProvider'; +import { GitContentProvider } from './contentProvider'; // import { AutoFetcher } from './autofetch'; import { Askpass } from './askpass'; import { toDisposable } from './util'; @@ -56,12 +56,12 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); - // const contentProvider = new GitContentProvider(repository); + const contentProvider = new GitContentProvider(model); // const autoFetcher = new AutoFetcher(repository); disposables.push( commandCenter, - // contentProvider, + contentProvider, // autoFetcher, // repository ); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 3aa6f35e5f82d..7fcbb5edce29b 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, window, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup } from 'vscode'; +import { Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup } from 'vscode'; import { Repository, State } from './repository'; import { memoize } from './decorators'; import { toDisposable, filterEvent, once } from './util'; @@ -20,8 +20,16 @@ class RepositoryPick implements QuickPickItem { constructor(protected repositoryRoot: string, public readonly repository: Repository) { } } +export interface ModelChangeEvent { + repository: Repository; + uri: Uri; +} + export class Model { + private _onDidChangeRepository = new EventEmitter(); + readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; + private repositories: Map = new Map(); register(repository: Repository): Disposable { @@ -35,11 +43,12 @@ export class Model { this.repositories.set(root, repository); const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.NotAGitRepository); - const listener = onDidDisappearRepository(() => disposable.dispose()); - + const disappearListener = onDidDisappearRepository(() => disposable.dispose()); + const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); const disposable = toDisposable(once(() => { + disappearListener.dispose(); + changeListener.dispose(); this.repositories.delete(root); - listener.dispose(); })); return disposable; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index de59b293e6c4e..832900597a5f9 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -433,6 +433,11 @@ export class SCMViewlet extends ComposedViewsViewlet { // this.activeProvider = activeProvider; const providers = this.scmService.providers; + + if (providers.length === 0) { + return; + } + const ids = providers.map(provider => provider.id); const views = providers.map(provider => new SourceControlViewDescriptor(provider)); From f0c93eb9f7e88b83a2b191add42f3b9eaa74b862 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 17 Aug 2017 11:36:50 +0200 Subject: [PATCH 35/67] git: fix content provider events --- extensions/git/src/contentProvider.ts | 4 ++-- extensions/git/src/model.ts | 27 ++------------------------- extensions/git/src/repository.ts | 18 ++++++++++-------- 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index 730ed093f91ae..4a8262433dd2b 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -51,8 +51,6 @@ export class GitContentProvider { } private fireChangeEvents(): void { - this.changedRepositoryRoots.clear(); - Object.keys(this.cache).forEach(key => { const uri = this.cache[key].uri; const fsPath = uri.fsPath; @@ -64,6 +62,8 @@ export class GitContentProvider { } } }); + + this.changedRepositoryRoots.clear(); } async provideTextDocumentContent(uri: Uri): Promise { diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 7fcbb5edce29b..61740cdc3fd76 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -42,9 +42,10 @@ export class Model { this.repositories.set(root, repository); - const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.NotAGitRepository); + const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.Disposed); const disappearListener = onDidDisappearRepository(() => disposable.dispose()); const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); + const disposable = toDisposable(once(() => { disappearListener.dispose(); changeListener.dispose(); @@ -106,30 +107,6 @@ export class Model { return undefined; } - // private async assertIdleState(): Promise { - // if (this.state === State.Idle) { - // return; - // } - - // const disposables: Disposable[] = []; - // const repositoryRoot = await this.git.getRepositoryRoot(this.workspaceRoot.fsPath); - // this.repository = this.git.open(repositoryRoot); - - // const onGitChange = filterEvent(this.onWorkspaceChange, uri => /\/\.git\//.test(uri.path)); - // const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.path)); - - // onRelevantGitChange(this.onFSChange, this, disposables); - // onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, disposables); - - // const onNonGitChange = filterEvent(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.path)); - // onNonGitChange(this.onFSChange, this, disposables); - - // this.repositoryDisposable = combinedDisposable(disposables); - // this.isRepositoryHuge = false; - // this.didWarnAboutLimit = false; - // this.state = State.Idle; - // } - dispose(): void { for (let [, repository] of this.repositories) { repository.dispose(); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 5a76865e73361..ea3c0799451f9 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -6,8 +6,8 @@ 'use strict'; import { Uri, Command, EventEmitter, Event, scm, commands, SourceControl, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; -import { Git, Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git'; -import { anyEvent, eventToPromise, filterEvent, EmptyDisposable, combinedDisposable, dispose, find } from './util'; +import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git'; +import { anyEvent, filterEvent, eventToPromise, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; import { toGitUri } from './uri'; import * as path from 'path'; @@ -24,9 +24,8 @@ function getIconUri(iconName: string, theme: string): Uri { } export enum State { - Uninitialized, Idle, - NotAGitRepository + Disposed } export enum Status { @@ -324,7 +323,7 @@ export class Repository implements Disposable { private _operations = new OperationsImpl(); get operations(): Operations { return this._operations; } - private _state = State.Uninitialized; + private _state = State.Idle; get state(): State { return this._state; } set state(state: State) { this._state = state; @@ -357,6 +356,10 @@ export class Repository implements Disposable { const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); onWorkspaceChange(this.onFSChange, this, this.disposables); + const onGitChange = filterEvent(onWorkspaceChange, uri => /\/\.git\//.test(uri.path)); + const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.path)); + onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables); + const id = `git${Repository.handle++}`; const label = `Git - ${path.basename(repository.root)}`; @@ -618,7 +621,7 @@ export class Repository implements Disposable { return result; } catch (err) { if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { - this.state = State.NotAGitRepository; + this.state = State.Disposed; } throw err; @@ -750,9 +753,8 @@ export class Repository implements Disposable { let stateContextKey = ''; switch (this.state) { - case State.Uninitialized: stateContextKey = 'uninitialized'; break; case State.Idle: stateContextKey = 'idle'; break; - case State.NotAGitRepository: stateContextKey = 'norepo'; break; + case State.Disposed: stateContextKey = 'norepo'; break; } commands.executeCommand('setContext', 'gitState', stateContextKey); From a01de6506119586ceaa6e5ee3857ae6d185bd0e3 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Aug 2017 12:33:01 +0200 Subject: [PATCH 36/67] scm: ignore uniqueness of `SourceControl.id` --- extensions/git/src/repository.ts | 5 +---- src/vs/vscode.d.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ea3c0799451f9..d6405df0255d1 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -269,8 +269,6 @@ export interface GitResourceGroup extends SourceControlResourceGroup { export class Repository implements Disposable { - private static handle = 0; - private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; @@ -360,10 +358,9 @@ export class Repository implements Disposable { const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.path)); onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables); - const id = `git${Repository.handle++}`; const label = `Git - ${path.basename(repository.root)}`; - this._sourceControl = scm.createSourceControl(id, label); + this._sourceControl = scm.createSourceControl('git', label); this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") }; this._sourceControl.quickDiffProvider = this; this.disposables.push(this._sourceControl); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 0862cff6edcbb..4e60cbb388c68 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5448,7 +5448,7 @@ declare module 'vscode' { /** * Creates a new [source control](#SourceControl) instance. * - * @param id A unique `id` for the source control. Something short, eg: `git`. + * @param id An `id` for the source control. Something short, eg: `git`. * @param label A human-readable string for the source control. Eg: `Git`. * @return An instance of [source control](#SourceControl). */ From 93011ff01cbe7a5e51baec6cfd3b73c477db33bc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Aug 2017 17:35:24 +0200 Subject: [PATCH 37/67] git: improved multirepo model --- extensions/git/src/main.ts | 11 +- extensions/git/src/model.ts | 142 +++++++++++++----- .../parts/scm/electron-browser/scmViewlet.ts | 8 +- .../services/scm/common/scmService.ts | 2 + 4 files changed, 111 insertions(+), 52 deletions(-) diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index cdbbe50340599..d9c2fb702915e 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -9,7 +9,6 @@ import * as nls from 'vscode-nls'; const localize = nls.config(process.env.VSCODE_NLS_CONFIG)(); import { ExtensionContext, workspace, window, Disposable, commands, Uri } from 'vscode'; import { findGit, Git, IGit } from './git'; -import { Repository } from './repository'; import { Model } from './model'; import { CommandCenter } from './commands'; import { GitContentProvider } from './contentProvider'; @@ -33,7 +32,7 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi const askpass = new Askpass(); const env = await askpass.getEnv(); const git = new Git({ gitPath: info.path, version: info.version, env }); - const model = new Model(); + const model = new Model(git); disposables.push(model); if (!enabled) { @@ -42,13 +41,6 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi return; } - for (const folder of workspace.workspaceFolders || []) { - const repositoryRoot = await git.getRepositoryRoot(folder.uri.fsPath); - const repository = new Repository(git.open(repositoryRoot)); - - model.register(repository); - } - outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path)); const onOutput = str => outputChannel.append(str); @@ -63,7 +55,6 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi commandCenter, contentProvider, // autoFetcher, - // repository ); await checkGitVersion(info); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 61740cdc3fd76..1017c3318aedf 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -5,19 +5,20 @@ 'use strict'; -import { Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup } from 'vscode'; -import { Repository, State } from './repository'; +import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor } from 'vscode'; +import { Repository } from './repository'; import { memoize } from './decorators'; -import { toDisposable, filterEvent, once } from './util'; +import { dispose } from './util'; +import { Git, GitErrorCodes } from './git'; import * as path from 'path'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); class RepositoryPick implements QuickPickItem { - @memoize get label(): string { return path.basename(this.repositoryRoot); } - @memoize get description(): string { return path.dirname(this.repositoryRoot); } - constructor(protected repositoryRoot: string, public readonly repository: Repository) { } + @memoize get label(): string { return path.basename(this.repository.root); } + @memoize get description(): string { return path.dirname(this.repository.root); } + constructor(public readonly repository: Repository) { } } export interface ModelChangeEvent { @@ -25,47 +26,101 @@ export interface ModelChangeEvent { uri: Uri; } +interface OpenRepository extends Disposable { + repository: Repository; +} + export class Model { private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; - private repositories: Map = new Map(); + private openRepositories: OpenRepository[] = []; + get repositories(): Repository[] { return this.openRepositories.map(r => r.repository); } - register(repository: Repository): Disposable { - const root = repository.root; + private disposables: Disposable[] = []; - if (this.repositories.has(root)) { - // TODO@Joao: what should happen? - throw new Error('Cant register repository with the same URI'); - } + constructor(private git: Git) { + workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables); + this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }); - this.repositories.set(root, repository); + window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables); + this.onDidChangeVisibleTextEditors(window.visibleTextEditors); + } - const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.Disposed); - const disappearListener = onDidDisappearRepository(() => disposable.dispose()); - const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); + private async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise { + const possibleRepositoryFolders = added + .filter(folder => !this.getOpenRepository(folder.uri)); + + const activeRepositoriesList = window.visibleTextEditors + .map(editor => this.getRepository(editor.document.uri)) + .filter(repository => !!repository) as Repository[]; + + const activeRepositories = new Set(activeRepositoriesList); + const openRepositoriesToDispose = removed + .map(folder => this.getOpenRepository(folder.uri)) + .filter(r => !!r && !activeRepositories.has(r.repository)) as OpenRepository[]; + + console.log('lets dispose', openRepositoriesToDispose); + + possibleRepositoryFolders.forEach(p => this.findRepository(p.uri.fsPath)); + openRepositoriesToDispose.forEach(r => r.dispose()); + } + + private onDidChangeVisibleTextEditors(editors: TextEditor[]): void { + editors.forEach(editor => { + const uri = editor.document.uri; + + if (uri.scheme !== 'file') { + return; + } + + const repository = this.getRepository(uri); + + if (repository) { + return; + } - const disposable = toDisposable(once(() => { - disappearListener.dispose(); + this.findRepository(path.dirname(uri.fsPath)); + }); + } + + private async findRepository(dirPath: string): Promise { + try { + const repositoryRoot = await this.git.getRepositoryRoot(dirPath); + const repository = new Repository(this.git.open(repositoryRoot)); + + this.open(repository); + } catch (err) { + if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { + return; + } + + console.error('Failed to find repository:', err); + } + } + + private open(repository: Repository): void { + // const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.Disposed); + // const disappearListener = onDidDisappearRepository(() => disposable.dispose()); + const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); + const dispose = () => { + // disappearListener.dispose(); changeListener.dispose(); - this.repositories.delete(root); - })); + repository.dispose(); + this.openRepositories = this.openRepositories.filter(e => e !== openRepository); + }; - return disposable; + const openRepository = { repository, dispose }; + this.openRepositories.push(openRepository); } async pickRepository(): Promise { - if (this.repositories.size === 0) { + if (this.openRepositories.length === 0) { throw new Error(localize('no repositories', "There are no available repositories")); } - // TODO@joao enable this code - // if (this.repositories.size === 1) { - // return this.repositories.values().next().value; - // } - - const picks = Array.from(this.repositories.entries(), ([uri, model]) => new RepositoryPick(uri, model)); + const picks = this.openRepositories.map(e => new RepositoryPick(e.repository)); const placeHolder = localize('pick repo', "Choose a repository"); const pick = await window.showQuickPick(picks, { placeHolder }); @@ -76,6 +131,14 @@ export class Model { getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; getRepository(resource: Uri): Repository | undefined; getRepository(hint: any): Repository | undefined { + const liveRepository = this.getOpenRepository(hint); + return liveRepository && liveRepository.repository; + } + + private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined; + private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined; + private getOpenRepository(resource: Uri): OpenRepository | undefined; + private getOpenRepository(hint: any): OpenRepository | undefined { if (!hint) { return undefined; } @@ -83,24 +146,26 @@ export class Model { if (hint instanceof Uri) { const resourcePath = hint.fsPath; - for (let [root, repository] of this.repositories) { - const relativePath = path.relative(root, resourcePath); + for (const liveRepository of this.openRepositories) { + const relativePath = path.relative(liveRepository.repository.root, resourcePath); if (!/^\./.test(relativePath)) { - return repository; + return liveRepository; } } return undefined; } - for (let [, repository] of this.repositories) { + for (const liveRepository of this.openRepositories) { + const repository = liveRepository.repository; + if (hint === repository.sourceControl) { - return repository; + return liveRepository; } if (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup) { - return repository; + return liveRepository; } } @@ -108,10 +173,7 @@ export class Model { } dispose(): void { - for (let [, repository] of this.repositories) { - repository.dispose(); - } - - this.repositories.clear(); + [...this.openRepositories].forEach(r => r.dispose()); + this.disposables = dispose(this.disposables); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 832900597a5f9..4fd51991c96e1 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -228,7 +228,8 @@ function resourceSorter(a: ISCMResource, b: ISCMResource): number { class SourceControlViewDescriptor implements IViewDescriptor { get provider(): ISCMProvider { return this._provider; } - get id(): string { return this._provider.id; } + // TODO@joao change this so we dont need IDS + get id(): string { return this._provider.label; } get name(): string { return this._provider.label; } get ctor(): any { return null; } get location(): ViewLocation { return ViewLocation.SCM; } @@ -438,9 +439,12 @@ export class SCMViewlet extends ComposedViewsViewlet { return; } - const ids = providers.map(provider => provider.id); + // TODO@joao change this so we dont need ids + const ids = providers.map(provider => provider.label); const views = providers.map(provider => new SourceControlViewDescriptor(provider)); + // console.log(provider.label); + ViewsRegistry.registerViews(views); this.providerChangeDisposable = toDisposable(() => ViewsRegistry.deregisterViews(ids, ViewLocation.SCM)); diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index 9bfae91061e4d..7e02c3a662643 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -108,6 +108,8 @@ export class SCMService implements ISCMService { if (this.activeProvider === provider) { this.activeProvider = this._providers[0]; } + + this._onDidChangeProviders.fire(); }); } From e11edbbf8484e0af57c7941797033537c11bbb0f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Aug 2017 17:37:31 +0200 Subject: [PATCH 38/67] git: multirepo autofetcher --- extensions/git/src/main.ts | 10 ++-------- extensions/git/src/repository.ts | 3 +++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index d9c2fb702915e..6439824bcacd7 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -12,7 +12,6 @@ import { findGit, Git, IGit } from './git'; import { Model } from './model'; import { CommandCenter } from './commands'; import { GitContentProvider } from './contentProvider'; -// import { AutoFetcher } from './autofetch'; import { Askpass } from './askpass'; import { toDisposable } from './util'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -47,14 +46,9 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi git.onOutput.addListener('log', onOutput); disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); - const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); - const contentProvider = new GitContentProvider(model); - // const autoFetcher = new AutoFetcher(repository); - disposables.push( - commandCenter, - contentProvider, - // autoFetcher, + new CommandCenter(git, model, outputChannel, telemetryReporter), + new GitContentProvider(model), ); await checkGitVersion(info); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index d6405df0255d1..30efcea5d2504 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -10,6 +10,7 @@ import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCode import { anyEvent, filterEvent, eventToPromise, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; import { toGitUri } from './uri'; +import { AutoFetcher } from './autofetch'; import * as path from 'path'; import * as nls from 'vscode-nls'; import * as fs from 'fs'; @@ -376,6 +377,8 @@ export class Repository implements Disposable { this.disposables.push(this.indexGroup); this.disposables.push(this.workingTreeGroup); + this.disposables.push(new AutoFetcher(this)); + this.updateCommitTemplate(); this.status(); } From e4d6ed30cecce9133d26672e79a899a4559f34d2 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Aug 2017 17:56:59 +0200 Subject: [PATCH 39/67] fix splitview header actions --- src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css | 2 +- .../workbench/parts/scm/electron-browser/media/scmViewlet.css | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index ff35e05291930..312dae4c2727f 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -26,7 +26,7 @@ width: 0; /* not using display: none for keyboard a11y reasons */ } -.monaco-workbench .viewlet .split-view-view:hover .actions, +.monaco-workbench .viewlet .split-view-view:hover > .header .actions, .monaco-workbench .viewlet .collapsible.header.focused .actions { width: initial; flex: 1; diff --git a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css index 4e86fbea0de34..5e997fccd73b6 100644 --- a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css +++ b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css @@ -8,7 +8,6 @@ -webkit-mask-size: 19px; } -.monaco-workbench .viewlet.scm-viewlet .split-view-view .actions, .monaco-workbench .viewlet.scm-viewlet .collapsible.header .actions { width: initial; flex: 1; From 2800d4628c7c0d346e69086cfcb40214f05efbc7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Aug 2017 12:12:04 +0200 Subject: [PATCH 40/67] remove log --- extensions/git/src/model.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 1017c3318aedf..49ba2dcd8e1c4 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -61,8 +61,6 @@ export class Model { .map(folder => this.getOpenRepository(folder.uri)) .filter(r => !!r && !activeRepositories.has(r.repository)) as OpenRepository[]; - console.log('lets dispose', openRepositoriesToDispose); - possibleRepositoryFolders.forEach(p => this.findRepository(p.uri.fsPath)); openRepositoriesToDispose.forEach(r => r.dispose()); } From 6d9e8ba68ad2719a89ad67affe6d7fb5ae00c2f9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Aug 2017 12:26:01 +0200 Subject: [PATCH 41/67] scm: better view ids --- .../parts/scm/electron-browser/scmViewlet.ts | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 0c2167e215b20..1b84110a573d9 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -11,12 +11,10 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { chain } from 'vs/base/common/event'; import { memoize } from 'vs/base/common/decorators'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { Builder, Dimension } from 'vs/base/browser/builder'; -import { ComposedViewsViewlet, CollapsibleView, ICollapsibleViewOptions, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views'; +import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Builder } from 'vs/base/browser/builder'; +import { ComposedViewsViewlet, CollapsibleView, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views'; import { append, $, toggleClass } from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IDelegate, IRenderer, IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; @@ -39,10 +37,9 @@ import { MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionIt import { SCMMenus } from './scmMenus'; import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; -import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { comparePaths } from 'vs/base/common/comparers'; import { isSCMResource } from './scmUtil'; -import { attachInputBoxStyler, attachListStyler, attachBadgeStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler, attachBadgeStyler } from 'vs/platform/theme/common/styler'; import Severity from 'vs/base/common/severity'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -229,13 +226,12 @@ function resourceSorter(a: ISCMResource, b: ISCMResource): number { class SourceControlViewDescriptor implements IViewDescriptor { get provider(): ISCMProvider { return this._provider; } - // TODO@joao change this so we dont need IDS - get id(): string { return this._provider.label; } + get id(): string { return this._id; } get name(): string { return this._provider.label; } get ctor(): any { return null; } get location(): ViewLocation { return ViewLocation.SCM; } - constructor(private _provider: ISCMProvider) { + constructor(private _id: string, private _provider: ISCMProvider) { } } @@ -405,6 +401,9 @@ export class SCMViewlet extends ComposedViewsViewlet { // private cachedDimension: Dimension; // private inputBoxContainer: HTMLElement; // private inputBox: InputBox; + + private providerIdHandle = 0; + private providerIds = new Map(); private providerChangeDisposable: IDisposable = EmptyDisposable; private disposables: IDisposable[] = []; @@ -440,14 +439,23 @@ export class SCMViewlet extends ComposedViewsViewlet { return; } - // TODO@joao change this so we dont need ids - const ids = providers.map(provider => provider.label); - const views = providers.map(provider => new SourceControlViewDescriptor(provider)); + const result = providers.map(provider => { + let id = this.providerIds.get(provider); + + if (!id) { + id = `scm${this.providerIdHandle++}`; + this.providerIds.set(provider, id); + } + + const view = new SourceControlViewDescriptor(id, provider); + + return { id, provider, view }; + }); // console.log(provider.label); - ViewsRegistry.registerViews(views); - this.providerChangeDisposable = toDisposable(() => ViewsRegistry.deregisterViews(ids, ViewLocation.SCM)); + ViewsRegistry.registerViews(result.map(r => r.view)); + this.providerChangeDisposable = toDisposable(() => ViewsRegistry.deregisterViews(result.map(r => r.id), ViewLocation.SCM)); // if (activeProvider) { // const disposables = []; From 49bedbf8a768c4b48f8bf7be87f1bdb4a1248ce2 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Aug 2017 15:06:07 +0200 Subject: [PATCH 42/67] scm: add/remove views --- .../api/electron-browser/mainThreadSCM.ts | 8 +- .../parts/scm/electron-browser/scmMenus.ts | 2 +- .../parts/scm/electron-browser/scmViewlet.ts | 92 ++++++++----------- src/vs/workbench/services/scm/common/scm.ts | 6 +- .../services/scm/common/scmService.ts | 22 +++-- 5 files changed, 63 insertions(+), 67 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index f08747986cdf2..685f6242f9cb6 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -62,6 +62,10 @@ class MainThreadSCMResource implements ISCMResource { class MainThreadSCMProvider implements ISCMProvider { + private static ID_HANDLE = 0; + private _id = `scm${MainThreadSCMProvider.ID_HANDLE++}`; + get id(): string { return this._id; } + private _groups: MainThreadSCMResourceGroup[] = []; private _groupsByHandle: { [handle: number]: MainThreadSCMResourceGroup; } = Object.create(null); @@ -77,7 +81,7 @@ class MainThreadSCMProvider implements ISCMProvider { get handle(): number { return this._handle; } get label(): string { return this._label; } - get id(): string { return this._id; } + get contextValue(): string { return this._contextValue; } get commitTemplate(): string | undefined { return this.features.commitTemplate; } get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; } @@ -92,7 +96,7 @@ class MainThreadSCMProvider implements ISCMProvider { constructor( private proxy: ExtHostSCMShape, private _handle: number, - private _id: string, + private _contextValue: string, private _label: string, @ISCMService scmService: ISCMService, @ICommandService private commandService: ICommandService diff --git a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts index 3e1850b0dc86f..c22ee5b75e86f 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts @@ -34,7 +34,7 @@ export class SCMMenus implements IDisposable { ) { this.contextKeyService = contextKeyService.createScoped(); const scmProviderKey = this.contextKeyService.createKey('scmProvider', void 0); - scmProviderKey.set(provider.id); + scmProviderKey.set(provider.contextValue); this.titleMenu = this.menuService.createMenu(MenuId.SCMTitle, this.contextKeyService); this.disposables.push(this.titleMenu); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 1b84110a573d9..a7c1eb4f16af6 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -11,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { chain } from 'vs/base/common/event'; import { memoize } from 'vs/base/common/decorators'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Builder } from 'vs/base/browser/builder'; import { ComposedViewsViewlet, CollapsibleView, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views'; import { append, $, toggleClass } from 'vs/base/browser/dom'; @@ -68,10 +68,10 @@ function identityProvider(r: ISCMResourceGroup | ISCMResource): string { if (isSCMResource(r)) { const group = r.resourceGroup; const provider = group.provider; - return `${provider.id}/${group.id}/${r.sourceUri.toString()}`; + return `${provider.contextValue}/${group.id}/${r.sourceUri.toString()}`; } else { const provider = r.provider; - return `${provider.id}/${r.id}`; + return `${provider.contextValue}/${r.id}`; } } @@ -226,12 +226,12 @@ function resourceSorter(a: ISCMResource, b: ISCMResource): number { class SourceControlViewDescriptor implements IViewDescriptor { get provider(): ISCMProvider { return this._provider; } - get id(): string { return this._id; } + get id(): string { return this._provider.id; } get name(): string { return this._provider.label; } get ctor(): any { return null; } get location(): ViewLocation { return ViewLocation.SCM; } - constructor(private _id: string, private _provider: ISCMProvider) { + constructor(private _provider: ISCMProvider) { } } @@ -402,9 +402,7 @@ export class SCMViewlet extends ComposedViewsViewlet { // private inputBoxContainer: HTMLElement; // private inputBox: InputBox; - private providerIdHandle = 0; - private providerIds = new Map(); - private providerChangeDisposable: IDisposable = EmptyDisposable; + // private providers = new Map(); private disposables: IDisposable[] = []; constructor( @@ -429,58 +427,42 @@ export class SCMViewlet extends ComposedViewsViewlet { telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); } - private onDidProvidersChange(): void { - this.providerChangeDisposable.dispose(); - // this.activeProvider = activeProvider; - - const providers = this.scmService.providers; - - if (providers.length === 0) { - return; - } - - const result = providers.map(provider => { - let id = this.providerIds.get(provider); - - if (!id) { - id = `scm${this.providerIdHandle++}`; - this.providerIds.set(provider, id); - } - - const view = new SourceControlViewDescriptor(id, provider); - - return { id, provider, view }; - }); + private onDidAddProvider(provider: ISCMProvider): void { + const view = new SourceControlViewDescriptor(provider); + ViewsRegistry.registerViews([view]); + } - // console.log(provider.label); + private onDidRemoveProvider(provider: ISCMProvider): void { + ViewsRegistry.deregisterViews([provider.id], ViewLocation.SCM); + } - ViewsRegistry.registerViews(result.map(r => r.view)); - this.providerChangeDisposable = toDisposable(() => ViewsRegistry.deregisterViews(result.map(r => r.id), ViewLocation.SCM)); + // private onDidProvidersChange(): void { + // this.activeProvider = activeProvider; - // if (activeProvider) { - // const disposables = []; + // if (activeProvider) { + // const disposables = []; - // // if (activeProvider.onDidChangeCommitTemplate) { - // // disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this)); - // // } + // // if (activeProvider.onDidChangeCommitTemplate) { + // // disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this)); + // // } - // const id = activeProvider.id; - // ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]); + // const id = activeProvider.id; + // ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]); - // disposables.push({ - // dispose: () => { - // ViewsRegistry.deregisterViews([id], ViewLocation.SCM); - // } - // }); + // disposables.push({ + // dispose: () => { + // ViewsRegistry.deregisterViews([id], ViewLocation.SCM); + // } + // }); - // this.providerChangeDisposable = combinedDisposable(disposables); - // } else { - // this.providerChangeDisposable = EmptyDisposable; - // } + // this.providerChangeDisposable = combinedDisposable(disposables); + // } else { + // this.providerChangeDisposable = EmptyDisposable; + // } - // this.updateInputBox(); - // this.updateTitleArea(); - } + // this.updateInputBox(); + // this.updateTitleArea(); + // } async create(parent: Builder): TPromise { await super.create(parent); @@ -507,9 +489,9 @@ export class SCMViewlet extends ComposedViewsViewlet { // .filter(e => e.equals(KeyMod.CtrlCmd | KeyCode.Enter) || e.equals(KeyMod.CtrlCmd | KeyCode.KEY_S)) // .on(this.onDidAcceptInput, this, this.disposables); - - this.onDidProvidersChange(); - this.scmService.onDidChangeProviders(this.onDidProvidersChange, this, this.disposables); + this.scmService.onDidAddProvider(this.onDidAddProvider, this, this.disposables); + this.scmService.onDidRemoveProvider(this.onDidRemoveProvider, this, this.disposables); + this.scmService.providers.forEach(p => this.onDidAddProvider(p)); // this.themeService.onThemeChange(this.update, this, this.disposables); // return TPromise.as(null); diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 116e7e5764f34..66cc07504ccc9 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -44,6 +44,7 @@ export interface ISCMResourceGroup { export interface ISCMProvider extends IDisposable { readonly label: string; readonly id: string; + readonly contextValue: string; readonly resources: ISCMResourceGroup[]; readonly onDidChange: Event; readonly count?: number; @@ -63,10 +64,9 @@ export interface ISCMInput { export interface ISCMService { readonly _serviceBrand: any; + readonly onDidAddProvider: Event; + readonly onDidRemoveProvider: Event; readonly onDidChangeProvider: Event; - - // TODO@joao fix name - readonly onDidChangeProviders: Event; readonly providers: ISCMProvider[]; readonly input: ISCMInput; activeProvider: ISCMProvider | undefined; diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index 7e02c3a662643..5313e3e02736a 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -45,14 +45,18 @@ export class SCMService implements ISCMService { set activeProvider(provider: ISCMProvider | undefined) { this.setActiveSCMProvider(provider); - this.storageService.store(DefaultSCMProviderIdStorageKey, provider.id, StorageScope.WORKSPACE); + this.storageService.store(DefaultSCMProviderIdStorageKey, provider.contextValue, StorageScope.WORKSPACE); } + private _providerIds = new Set(); private _providers: ISCMProvider[] = []; get providers(): ISCMProvider[] { return [...this._providers]; } - private _onDidChangeProviders = new Emitter(); - get onDidChangeProviders(): Event { return this._onDidChangeProviders.event; } + private _onDidAddProvider = new Emitter(); + get onDidAddProvider(): Event { return this._onDidAddProvider.event; } + + private _onDidRemoveProvider = new Emitter(); + get onDidRemoveProvider(): Event { return this._onDidRemoveProvider.event; } private _onDidChangeProvider = new Emitter(); get onDidChangeProvider(): Event { return this._onDidChangeProvider.event; } @@ -86,15 +90,20 @@ export class SCMService implements ISCMService { } registerSCMProvider(provider: ISCMProvider): IDisposable { + if (this._providerIds.has(provider.id)) { + throw new Error(`SCM Provider ${provider.id} already exists.`); + } + + this._providerIds.add(provider.id); this._providers.push(provider); const defaultProviderId = this.storageService.get(DefaultSCMProviderIdStorageKey, StorageScope.WORKSPACE); - if (this._providers.length === 1 || defaultProviderId === provider.id) { + if (this._providers.length === 1 || defaultProviderId === provider.contextValue) { this.setActiveSCMProvider(provider); } - this._onDidChangeProviders.fire(); + this._onDidAddProvider.fire(provider); return toDisposable(() => { const index = this._providers.indexOf(provider); @@ -103,13 +112,14 @@ export class SCMService implements ISCMService { return; } + this._providerIds.delete(provider.id); this._providers.splice(index, 1); if (this.activeProvider === provider) { this.activeProvider = this._providers[0]; } - this._onDidChangeProviders.fire(); + this._onDidRemoveProvider.fire(provider); }); } From b6c3338ddf6e43737e077866231d94022c11ec77 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Aug 2017 16:01:20 +0200 Subject: [PATCH 43/67] git: fix same repo multiple times --- extensions/git/src/decorators.ts | 13 +++++++++++++ extensions/git/src/model.ts | 23 ++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/decorators.ts b/extensions/git/src/decorators.ts index 819f12659a848..905d9e8501ba3 100644 --- a/extensions/git/src/decorators.ts +++ b/extensions/git/src/decorators.ts @@ -78,6 +78,19 @@ function _throttle(fn: Function, key: string): Function { export const throttle = decorate(_throttle); +function _sequentialize(fn: Function, key: string): Function { + const currentKey = `__$sequence$${key}`; + + return function (...args: any[]) { + const currentPromise = this[currentKey] as Promise || Promise.resolve(null); + const run = async () => await fn.apply(this, args); + this[currentKey] = currentPromise.then(run, run); + return this[currentKey]; + }; +} + +export const sequentialize = decorate(_sequentialize); + export function debounce(delay: number): Function { return decorate((fn, key) => { const timerKey = `$debounce$${key}`; diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 49ba2dcd8e1c4..d12a907a43c8b 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -7,7 +7,7 @@ import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor } from 'vscode'; import { Repository } from './repository'; -import { memoize } from './decorators'; +import { memoize, sequentialize } from './decorators'; import { dispose } from './util'; import { Git, GitErrorCodes } from './git'; import * as path from 'path'; @@ -61,7 +61,7 @@ export class Model { .map(folder => this.getOpenRepository(folder.uri)) .filter(r => !!r && !activeRepositories.has(r.repository)) as OpenRepository[]; - possibleRepositoryFolders.forEach(p => this.findRepository(p.uri.fsPath)); + possibleRepositoryFolders.forEach(p => this.tryOpenRepository(p.uri.fsPath)); openRepositoriesToDispose.forEach(r => r.dispose()); } @@ -79,13 +79,20 @@ export class Model { return; } - this.findRepository(path.dirname(uri.fsPath)); + this.tryOpenRepository(path.dirname(uri.fsPath)); }); } - private async findRepository(dirPath: string): Promise { + @sequentialize + private async tryOpenRepository(path: string): Promise { + const repository = this.getRepository(path); + + if (repository) { + return; + } + try { - const repositoryRoot = await this.git.getRepositoryRoot(dirPath); + const repositoryRoot = await this.git.getRepositoryRoot(path); const repository = new Repository(this.git.open(repositoryRoot)); this.open(repository); @@ -127,6 +134,7 @@ export class Model { getRepository(sourceControl: SourceControl): Repository | undefined; getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; + getRepository(path: string): Repository | undefined; getRepository(resource: Uri): Repository | undefined; getRepository(hint: any): Repository | undefined { const liveRepository = this.getOpenRepository(hint); @@ -135,12 +143,17 @@ export class Model { private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined; private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined; + private getOpenRepository(path: string): OpenRepository | undefined; private getOpenRepository(resource: Uri): OpenRepository | undefined; private getOpenRepository(hint: any): OpenRepository | undefined { if (!hint) { return undefined; } + if (typeof hint === 'string') { + hint = Uri.file(hint); + } + if (hint instanceof Uri) { const resourcePath = hint.fsPath; From da84e2e4dda5dbb30e9eae004e1d1a1ef252d027 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Aug 2017 16:27:25 +0200 Subject: [PATCH 44/67] scm: input boxes --- .../scm/electron-browser/media/scmViewlet.css | 12 +- .../parts/scm/electron-browser/scmViewlet.ts | 116 +++++++++--------- 2 files changed, 66 insertions(+), 62 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css index 5e997fccd73b6..dfe23f32439e2 100644 --- a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css +++ b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css @@ -92,27 +92,27 @@ background-repeat: no-repeat; } -.scm-viewlet > .scm-editor { +.scm-viewlet .scm-editor { padding: 5px 9px 5px 16px; } -.scm-viewlet > .scm-editor { +.scm-viewlet .scm-editor { box-sizing: border-box; padding: 5px 9px 5px 16px; } -.scm-viewlet > .scm-editor > .monaco-inputbox { +.scm-viewlet .scm-editor > .monaco-inputbox { width: 100%; } -.scm-viewlet > .scm-editor > .monaco-inputbox > .wrapper > .mirror { +.scm-viewlet .scm-editor > .monaco-inputbox > .wrapper > .mirror { max-height: 134px; } -.scm-viewlet > .scm-editor > .monaco-inputbox > .wrapper > textarea.input { +.scm-viewlet .scm-editor > .monaco-inputbox > .wrapper > textarea.input { min-height: 26px; } -.scm-viewlet > .scm-editor.scroll > .monaco-inputbox > .wrapper > textarea.input { +.scm-viewlet .scm-editor.scroll > .monaco-inputbox > .wrapper > textarea.input { overflow-y: scroll; } \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index a7c1eb4f16af6..4859e9d6e645d 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -39,7 +39,7 @@ import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/act import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { comparePaths } from 'vs/base/common/comparers'; import { isSCMResource } from './scmUtil'; -import { attachListStyler, attachBadgeStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler, attachBadgeStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import Severity from 'vs/base/common/severity'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -48,6 +48,11 @@ import { ViewLocation, ViewsRegistry, IViewDescriptor } from 'vs/workbench/parts import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; +import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import * as platform from "vs/base/common/platform"; +import { domEvent } from "vs/base/browser/event"; +import { StandardKeyboardEvent } from "vs/base/browser/keyboardEvent"; +import { KeyMod, KeyCode } from "vs/base/common/keyCodes"; // TODO@Joao // Need to subclass MenuItemActionItem in order to respect @@ -238,6 +243,8 @@ class SourceControlViewDescriptor implements IViewDescriptor { class SourceControlView extends CollapsibleView { + private inputBoxContainer: HTMLElement; + private inputBox: InputBox; private listContainer: HTMLElement; private list: List; private menus: SCMMenus; @@ -249,6 +256,8 @@ class SourceControlView extends CollapsibleView { @IKeybindingService protected keybindingService: IKeybindingService, @IThemeService protected themeService: IThemeService, @IContextMenuService protected contextMenuService: IContextMenuService, + @IContextViewService protected contextViewService: IContextViewService, + @ISCMService protected scmService: ISCMService, @IListService protected listService: IListService, @ICommandService protected commandService: ICommandService, @IMessageService protected messageService: IMessageService, @@ -270,6 +279,36 @@ class SourceControlView extends CollapsibleView { } renderBody(container: HTMLElement): void { + // Input + + this.inputBoxContainer = append(container, $('.scm-editor')); + + this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { + placeholder: localize('commitMessage', "Message (press {0} to commit)", platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'), + flexibleHeight: true + }); + this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService)); + this.disposables.push(this.inputBox); + + this.inputBox.value = this.scmService.input.value; + this.inputBox.onDidChange(value => this.scmService.input.value = value, null, this.disposables); + this.scmService.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); + // this.disposables.push(this.inputBox.onDidHeightChange(() => this.layout())); + + chain(domEvent(this.inputBox.inputElement, 'keydown')) + .map(e => new StandardKeyboardEvent(e)) + .filter(e => e.equals(KeyMod.CtrlCmd | KeyCode.Enter) || e.equals(KeyMod.CtrlCmd | KeyCode.KEY_S)) + .on(this.onDidAcceptInput, this, this.disposables); + + + if (this.provider.onDidChangeCommitTemplate) { + this.provider.onDidChangeCommitTemplate(this.updateInputBox, this, this.disposables); + } + + this.updateInputBox(); + + // List + this.listContainer = append(container, $('.scm-status.show-file-icons')); const delegate = new Delegate(); @@ -374,6 +413,26 @@ class SourceControlView extends CollapsibleView { .filter(r => isSCMResource(r)) as ISCMResource[]; } + private updateInputBox(): void { + if (typeof this.provider.commitTemplate === 'undefined') { + return; + } + + this.inputBox.value = this.provider.commitTemplate; + } + + private onDidAcceptInput(): void { + if (!this.provider.acceptInputCommand) { + return; + } + + const id = this.provider.acceptInputCommand.id; + const args = this.provider.acceptInputCommand.arguments; + + this.commandService.executeCommand(id, ...args) + .done(undefined, onUnexpectedError); + } + dispose(): void { this.disposables = dispose(this.disposables); super.dispose(); @@ -399,8 +458,6 @@ export class SCMViewlet extends ComposedViewsViewlet { // private activeProvider: ISCMProvider | undefined; // private cachedDimension: Dimension; - // private inputBoxContainer: HTMLElement; - // private inputBox: InputBox; // private providers = new Map(); private disposables: IDisposable[] = []; @@ -442,10 +499,6 @@ export class SCMViewlet extends ComposedViewsViewlet { // if (activeProvider) { // const disposables = []; - // // if (activeProvider.onDidChangeCommitTemplate) { - // // disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this)); - // // } - // const id = activeProvider.id; // ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]); @@ -460,7 +513,6 @@ export class SCMViewlet extends ComposedViewsViewlet { // this.providerChangeDisposable = EmptyDisposable; // } - // this.updateInputBox(); // this.updateTitleArea(); // } @@ -469,26 +521,6 @@ export class SCMViewlet extends ComposedViewsViewlet { parent.addClass('scm-viewlet'); - // const root = parent.getHTMLElement(); - // this.inputBoxContainer = append(root, $('.scm-editor')); - - // this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { - // placeholder: localize('commitMessage', "Message (press {0} to commit)", platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'), - // flexibleHeight: true - // }); - // this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService)); - // this.disposables.push(this.inputBox); - - // this.inputBox.value = this.scmService.input.value; - // this.inputBox.onDidChange(value => this.scmService.input.value = value, null, this.disposables); - // this.scmService.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); - // this.disposables.push(this.inputBox.onDidHeightChange(() => this.layout())); - - // chain(domEvent(this.inputBox.inputElement, 'keydown')) - // .map(e => new StandardKeyboardEvent(e)) - // .filter(e => e.equals(KeyMod.CtrlCmd | KeyCode.Enter) || e.equals(KeyMod.CtrlCmd | KeyCode.KEY_S)) - // .on(this.onDidAcceptInput, this, this.disposables); - this.scmService.onDidAddProvider(this.onDidAddProvider, this, this.disposables); this.scmService.onDidRemoveProvider(this.onDidRemoveProvider, this, this.disposables); this.scmService.providers.forEach(p => this.onDidAddProvider(p)); @@ -505,34 +537,6 @@ export class SCMViewlet extends ComposedViewsViewlet { return this.instantiationService.createInstance(viewDescriptor.ctor, options); } - // private onDidAcceptInput(): void { - // if (!this.activeProvider) { - // return; - // } - - // if (!this.activeProvider.acceptInputCommand) { - // return; - // } - - // const id = this.activeProvider.acceptInputCommand.id; - // const args = this.activeProvider.acceptInputCommand.arguments; - - // this.commandService.executeCommand(id, ...args) - // .done(undefined, onUnexpectedError); - // } - - // private updateInputBox(): void { - // if (!this.activeProvider) { - // return; - // } - - // if (typeof this.activeProvider.commitTemplate === 'undefined') { - // return; - // } - - // this.inputBox.value = this.activeProvider.commitTemplate; - // } - // layout(dimension: Dimension = this.cachedDimension): void { // if (!dimension) { // return; From 32f721f81ac1fa92d5c373887994fb47658177cc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 10:44:55 +0200 Subject: [PATCH 45/67] scm: deprecate scm.inputBox --- src/vs/vscode.d.ts | 5 +- .../api/electron-browser/mainThreadSCM.ts | 15 ++---- src/vs/workbench/api/node/extHost.api.impl.ts | 4 +- src/vs/workbench/api/node/extHost.protocol.ts | 4 +- src/vs/workbench/api/node/extHostSCM.ts | 54 +++++++++---------- 5 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index b05bb0735d692..bd13a07e96d87 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5447,7 +5447,10 @@ declare module 'vscode' { export namespace scm { /** - * The [input box](#SourceControlInputBox) in the Source Control viewlet. + * The [input box](#SourceControlInputBox) for the last source control + * created by the extension. + * + * @deprecated Use [SourceControl.inputBox](#SourceControl.inputBox) instead */ export const inputBox: SourceControlInputBox; diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index 685f6242f9cb6..d9119994320ca 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import Event, { Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { ISCMService, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations } from 'vs/workbench/services/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -231,9 +231,6 @@ export class MainThreadSCM implements MainThreadSCMShape { @ICommandService private commandService: ICommandService ) { this._proxy = extHostContext.get(ExtHostContext.ExtHostSCM); - - this.scmService.onDidChangeProvider(this.onDidChangeProvider, this, this._disposables); - this.scmService.input.onDidChange(this._proxy.$onInputBoxValueChange, this._proxy, this._disposables); } dispose(): void { @@ -251,7 +248,10 @@ export class MainThreadSCM implements MainThreadSCMShape { $registerSourceControl(handle: number, id: string, label: string): void { const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, this.scmService, this.commandService); this._sourceControls[handle] = provider; - this._sourceControlDisposables[handle] = this.scmService.registerSCMProvider(provider); + + const providerDisposable = this.scmService.registerSCMProvider(provider); + const inputDisposable = this.scmService.input.onDidChange(value => this._proxy.$onInputBoxValueChange(handle, value)); + this._sourceControlDisposables[handle] = combinedDisposable([providerDisposable, inputDisposable]); } $updateSourceControl(handle: number, features: SCMProviderFeatures): void { @@ -331,9 +331,4 @@ export class MainThreadSCM implements MainThreadSCMShape { $setInputBoxValue(value: string): void { this.scmService.input.value = value; } - - private onDidChangeProvider(provider: ISCMProvider): void { - const handle = Object.keys(this._sourceControls).filter(handle => this._sourceControls[handle] === provider)[0]; - this._proxy.$onActiveSourceControlChange(handle && parseInt(handle)); - } } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 76658ed41a1da..49ab193559e28 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -472,7 +472,7 @@ export function createApiFactory( // namespace: scm const scm: typeof vscode.scm = { get inputBox() { - return extHostSCM.inputBox; + return extHostSCM.getLastInputBox(extension); }, createSourceControl(id: string, label: string) { telemetryService.publicLog('registerSCMProvider', { @@ -481,7 +481,7 @@ export function createApiFactory( providerLabel: label }); - return extHostSCM.createSourceControl(id, label); + return extHostSCM.createSourceControl(extension, id, label); } }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 6e9b29c06ac95..31490f5317e6d 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -508,9 +508,7 @@ export interface ExtHostTerminalServiceShape { export interface ExtHostSCMShape { $provideOriginalResource(sourceControlHandle: number, uri: URI): TPromise; - $onActiveSourceControlChange(sourceControlHandle: number): TPromise; - $onInputBoxValueChange(value: string): TPromise; - $onInputBoxAcceptChanges(): TPromise; + $onInputBoxValueChange(sourceControlHandle: number, value: string): TPromise; } export interface ExtHostTaskShape { diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index 059ce5e86fe60..16b1c4ef6da10 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -8,6 +8,7 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import Event, { Emitter } from 'vs/base/common/event'; import { asWinJsPromise } from 'vs/base/common/async'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { MainContext, MainThreadSCMShape, SCMRawResource, IMainContext } from './extHost.protocol'; import * as vscode from 'vscode'; @@ -42,12 +43,6 @@ export class ExtHostSCMInputBox { return this._onDidChange.event; } - private _onDidAccept = new Emitter(); - - get onDidAccept(): Event { - return this._onDidAccept.event; - } - constructor(private _proxy: MainThreadSCMShape) { // noop } @@ -56,10 +51,6 @@ export class ExtHostSCMInputBox { this.updateValue(value); } - $onInputBoxAcceptChanges(): void { - this._onDidAccept.fire(this._value); - } - private updateValue(value: string): void { this._value = value; this._onDidChange.fire(value); @@ -171,6 +162,9 @@ class ExtHostSourceControl implements vscode.SourceControl { return this._label; } + private _inputBox: ExtHostSCMInputBox; + get inputBox(): ExtHostSCMInputBox { return this._inputBox; } + private _count: number | undefined = undefined; get count(): number | undefined { @@ -238,6 +232,7 @@ class ExtHostSourceControl implements vscode.SourceControl { private _id: string, private _label: string, ) { + this._inputBox = new ExtHostSCMInputBox(this._proxy); this._proxy.$registerSourceControl(this._handle, _id, _label); } @@ -266,22 +261,16 @@ export class ExtHostSCM { private _proxy: MainThreadSCMShape; private _sourceControls: Map = new Map(); + private _sourceControlsByExtension: Map = new Map(); private _onDidChangeActiveProvider = new Emitter(); get onDidChangeActiveProvider(): Event { return this._onDidChangeActiveProvider.event; } - private _activeProvider: vscode.SourceControl | undefined; - get activeProvider(): vscode.SourceControl | undefined { return this._activeProvider; } - - private _inputBox: ExtHostSCMInputBox; - get inputBox(): ExtHostSCMInputBox { return this._inputBox; } - constructor( mainContext: IMainContext, private _commands: ExtHostCommands ) { this._proxy = mainContext.get(MainContext.MainThreadSCM); - this._inputBox = new ExtHostSCMInputBox(this._proxy); _commands.registerArgumentProcessor({ processArgument: arg => { @@ -322,14 +311,27 @@ export class ExtHostSCM { }); } - createSourceControl(id: string, label: string): vscode.SourceControl { + createSourceControl(extension: IExtensionDescription, id: string, label: string): vscode.SourceControl { const handle = ExtHostSCM._handlePool++; const sourceControl = new ExtHostSourceControl(this._proxy, this._commands.converter, id, label); this._sourceControls.set(handle, sourceControl); + const sourceControls = this._sourceControlsByExtension.get(extension.id) || []; + sourceControls.push(sourceControl); + this._sourceControlsByExtension.set(extension.id, sourceControls); + return sourceControl; } + // Deprecated + getLastInputBox(extension: IExtensionDescription): ExtHostSCMInputBox { + const sourceControls = this._sourceControlsByExtension.get(extension.id); + const sourceControl = sourceControls && sourceControls[sourceControls.length - 1]; + const inputBox = sourceControl && sourceControl.inputBox; + + return inputBox; + } + $provideOriginalResource(sourceControlHandle: number, uri: URI): TPromise { const sourceControl = this._sourceControls.get(sourceControlHandle); @@ -343,18 +345,14 @@ export class ExtHostSCM { }); } - $onActiveSourceControlChange(handle: number): TPromise { - this._activeProvider = this._sourceControls.get(handle); - return TPromise.as(null); - } + $onInputBoxValueChange(sourceControlHandle: number, value: string): TPromise { + const sourceControl = this._sourceControls.get(sourceControlHandle); - $onInputBoxValueChange(value: string): TPromise { - this._inputBox.$onInputBoxValueChange(value); - return TPromise.as(null); - } + if (!sourceControl || !sourceControl.quickDiffProvider) { + return TPromise.as(null); + } - $onInputBoxAcceptChanges(): TPromise { - this._inputBox.$onInputBoxAcceptChanges(); + sourceControl.inputBox.$onInputBoxValueChange(value); return TPromise.as(null); } } From a1ec10d76ff6da929dbbf52c3e441ef271fbab60 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 11:11:28 +0200 Subject: [PATCH 46/67] scm: remove global input --- .../api/electron-browser/mainThreadSCM.ts | 82 +++++++++------- src/vs/workbench/api/node/extHost.protocol.ts | 2 +- src/vs/workbench/api/node/extHostSCM.ts | 6 +- .../electron-browser/dirtydiffDecorator.ts | 8 +- .../scm/electron-browser/scm.contribution.ts | 8 +- .../parts/scm/electron-browser/scmActivity.ts | 21 ++-- .../parts/scm/electron-browser/scmViewlet.ts | 57 ++++++----- src/vs/workbench/services/scm/common/scm.ts | 19 ++-- .../services/scm/common/scmService.ts | 95 +++++++++++-------- 9 files changed, 166 insertions(+), 132 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index d9119994320ca..5aba93ce44717 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -9,8 +9,8 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import Event, { Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; -import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ISCMService, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations } from 'vs/workbench/services/scm/common/scm'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations } from 'vs/workbench/services/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResource, SCMGroupFeatures, MainContext, IExtHostContext } from '../node/extHost.protocol'; @@ -220,8 +220,8 @@ class MainThreadSCMProvider implements ISCMProvider { export class MainThreadSCM implements MainThreadSCMShape { private _proxy: ExtHostSCMShape; - private _sourceControls: { [handle: number]: MainThreadSCMProvider; } = Object.create(null); - private _sourceControlDisposables: { [handle: number]: IDisposable; } = Object.create(null); + private _repositories: { [handle: number]: ISCMRepository; } = Object.create(null); + private _inputDisposables: { [handle: number]: IDisposable; } = Object.create(null); private _disposables: IDisposable[] = []; constructor( @@ -234,101 +234,113 @@ export class MainThreadSCM implements MainThreadSCMShape { } dispose(): void { - Object.keys(this._sourceControls) - .forEach(id => this._sourceControls[id].dispose()); - this._sourceControls = Object.create(null); + Object.keys(this._repositories) + .forEach(id => this._repositories[id].dispose()); + this._repositories = Object.create(null); - Object.keys(this._sourceControlDisposables) - .forEach(id => this._sourceControlDisposables[id].dispose()); - this._sourceControlDisposables = Object.create(null); + Object.keys(this._inputDisposables) + .forEach(id => this._inputDisposables[id].dispose()); + this._inputDisposables = Object.create(null); this._disposables = dispose(this._disposables); } $registerSourceControl(handle: number, id: string, label: string): void { const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, this.scmService, this.commandService); - this._sourceControls[handle] = provider; + const repository = this.scmService.registerSCMProvider(provider); + this._repositories[handle] = repository; - const providerDisposable = this.scmService.registerSCMProvider(provider); - const inputDisposable = this.scmService.input.onDidChange(value => this._proxy.$onInputBoxValueChange(handle, value)); - this._sourceControlDisposables[handle] = combinedDisposable([providerDisposable, inputDisposable]); + const inputDisposable = repository.input.onDidChange(value => this._proxy.$onInputBoxValueChange(handle, value)); + this._inputDisposables[handle] = inputDisposable; } $updateSourceControl(handle: number, features: SCMProviderFeatures): void { - const sourceControl = this._sourceControls[handle]; + const repository = this._repositories[handle]; - if (!sourceControl) { + if (!repository) { return; } - sourceControl.$updateSourceControl(features); + const provider = repository.provider as MainThreadSCMProvider; + provider.$updateSourceControl(features); } $unregisterSourceControl(handle: number): void { - const sourceControl = this._sourceControls[handle]; + const repository = this._repositories[handle]; - if (!sourceControl) { + if (!repository) { return; } - this._sourceControlDisposables[handle].dispose(); - delete this._sourceControlDisposables[handle]; + this._inputDisposables[handle].dispose(); + delete this._inputDisposables[handle]; - sourceControl.dispose(); - delete this._sourceControls[handle]; + repository.dispose(); + delete this._repositories[handle]; } $registerGroup(sourceControlHandle: number, groupHandle: number, id: string, label: string): void { - const provider = this._sourceControls[sourceControlHandle]; + const repository = this._repositories[sourceControlHandle]; - if (!provider) { + if (!repository) { return; } + const provider = repository.provider as MainThreadSCMProvider; provider.$registerGroup(groupHandle, id, label); } $updateGroup(sourceControlHandle: number, groupHandle: number, features: SCMGroupFeatures): void { - const provider = this._sourceControls[sourceControlHandle]; + const repository = this._repositories[sourceControlHandle]; - if (!provider) { + if (!repository) { return; } + const provider = repository.provider as MainThreadSCMProvider; provider.$updateGroup(groupHandle, features); } $updateGroupLabel(sourceControlHandle: number, groupHandle: number, label: string): void { - const provider = this._sourceControls[sourceControlHandle]; + const repository = this._repositories[sourceControlHandle]; - if (!provider) { + if (!repository) { return; } + const provider = repository.provider as MainThreadSCMProvider; provider.$updateGroupLabel(groupHandle, label); } $updateGroupResourceStates(sourceControlHandle: number, groupHandle: number, resources: SCMRawResource[]): void { - const provider = this._sourceControls[sourceControlHandle]; + const repository = this._repositories[sourceControlHandle]; - if (!provider) { + if (!repository) { return; } + const provider = repository.provider as MainThreadSCMProvider; provider.$updateGroupResourceStates(groupHandle, resources); } $unregisterGroup(sourceControlHandle: number, handle: number): void { - const provider = this._sourceControls[sourceControlHandle]; + const repository = this._repositories[sourceControlHandle]; - if (!provider) { + if (!repository) { return; } + const provider = repository.provider as MainThreadSCMProvider; provider.$unregisterGroup(handle); } - $setInputBoxValue(value: string): void { - this.scmService.input.value = value; + $setInputBoxValue(sourceControlHandle: number, value: string): void { + const repository = this._repositories[sourceControlHandle]; + + if (!repository) { + return; + } + + repository.input.value = value; } } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 31490f5317e6d..41b81d2d12e10 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -336,7 +336,7 @@ export interface MainThreadSCMShape extends IDisposable { $updateGroupResourceStates(sourceControlHandle: number, groupHandle: number, resources: SCMRawResource[]): void; $unregisterGroup(sourceControlHandle: number, handle: number): void; - $setInputBoxValue(value: string): void; + $setInputBoxValue(sourceControlHandle: number, value: string): void; } export type DebugSessionUUID = string; diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index 16b1c4ef6da10..327bb8510306c 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -33,7 +33,7 @@ export class ExtHostSCMInputBox { } set value(value: string) { - this._proxy.$setInputBoxValue(value); + this._proxy.$setInputBoxValue(this._sourceControlHandle, value); this.updateValue(value); } @@ -43,7 +43,7 @@ export class ExtHostSCMInputBox { return this._onDidChange.event; } - constructor(private _proxy: MainThreadSCMShape) { + constructor(private _proxy: MainThreadSCMShape, private _sourceControlHandle: number) { // noop } @@ -232,7 +232,7 @@ class ExtHostSourceControl implements vscode.SourceControl { private _id: string, private _label: string, ) { - this._inputBox = new ExtHostSCMInputBox(this._proxy); + this._inputBox = new ExtHostSCMInputBox(this._proxy, this._handle); this._proxy.$registerSourceControl(this._handle, _id, _label); } diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts index 3aa3675295f21..46d2f520c00a0 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts @@ -81,7 +81,7 @@ class DirtyDiffModelDecorator { this.toDispose = []; this.triggerDiff(); this.toDispose.push(model.onDidChangeContent(() => this.triggerDiff())); - this.toDispose.push(scmService.onDidChangeProvider(() => this.triggerDiff())); + this.toDispose.push(scmService.onDidChangeRepository(() => this.triggerDiff())); } private triggerDiff(): winjs.Promise { @@ -123,13 +123,13 @@ class DirtyDiffModelDecorator { return this._originalURIPromise; } - const provider = this.scmService.activeProvider; + const repository = this.scmService.activeRepository; - if (!provider) { + if (!repository) { return winjs.TPromise.as(null); } - this._originalURIPromise = provider.getOriginalResource(this.uri) + this._originalURIPromise = repository.provider.getOriginalResource(this.uri) .then(originalUri => { if (!originalUri) { return null; diff --git a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts index cf3f2c4cac552..5c396f0269dc3 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -49,9 +49,9 @@ export class SwitchProvider extends Action { } run(): TPromise { - const picks: IPickOpenEntry[] = this.scmService.providers.map(provider => ({ - label: provider.label, - run: () => this.scmService.activeProvider = provider + const picks: IPickOpenEntry[] = this.scmService.repositories.map(repository => ({ + label: repository.provider.label, + run: () => this.scmService.activeRepository = repository })); picks.push({ label: localize('installAdditionalSCMProviders', "Install Additional SCM Providers..."), @@ -61,7 +61,7 @@ export class SwitchProvider extends Action { viewlet.search('category:"SCM Providers" @sort:installs'); viewlet.focus(); }); - return this.scmService.activeProvider; + return this.scmService.activeRepository; }, separator: { border: true } }); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts index 4abe15c32e666..265c38c2d5226 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { IDisposable, dispose, empty as EmptyDisposable, OneDisposable } from 'vs/base/common/lifecycle'; import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; -import { ISCMService, ISCMProvider } from 'vs/workbench/services/scm/common/scm'; +import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; import { IActivityBarService, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -24,8 +24,8 @@ export class StatusUpdater implements IWorkbenchContribution { @ISCMService private scmService: ISCMService, @IActivityBarService private activityBarService: IActivityBarService ) { - this.scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); - this.setActiveProvider(this.scmService.activeProvider); + this.scmService.onDidChangeRepository(this.setActiveRepository, this, this.disposables); + this.setActiveRepository(this.scmService.activeRepository); this.disposables.push(this.badgeHandle); } @@ -33,21 +33,22 @@ export class StatusUpdater implements IWorkbenchContribution { return StatusUpdater.ID; } - private setActiveProvider(activeProvider: ISCMProvider | undefined): void { + private setActiveRepository(repository: ISCMRepository | undefined): void { this.providerChangeDisposable.dispose(); - this.providerChangeDisposable = activeProvider ? activeProvider.onDidChange(this.update, this) : EmptyDisposable; + this.providerChangeDisposable = repository ? repository.provider.onDidChange(this.update, this) : EmptyDisposable; this.update(); } private update(): void { - const provider = this.scmService.activeProvider; + const repository = this.scmService.activeRepository; + let count = 0; - if (provider) { - if (typeof provider.count === 'number') { - count = provider.count; + if (repository) { + if (typeof repository.provider.count === 'number') { + count = repository.provider.count; } else { - count = provider.resources.reduce((r, g) => r + g.resources.length, 0); + count = repository.provider.resources.reduce((r, g) => r + g.resources.length, 0); } } diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 4859e9d6e645d..c099453912e2d 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -21,7 +21,7 @@ import { IDelegate, IRenderer, IListContextMenuEvent } from 'vs/base/browser/ui/ import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; import { FileLabel } from 'vs/workbench/browser/labels'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { ISCMService, ISCMProvider, ISCMResourceGroup, ISCMResource } from 'vs/workbench/services/scm/common/scm'; +import { ISCMService, ISCMRepository, ISCMResourceGroup, ISCMResource } from 'vs/workbench/services/scm/common/scm'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -230,13 +230,13 @@ function resourceSorter(a: ISCMResource, b: ISCMResource): number { class SourceControlViewDescriptor implements IViewDescriptor { - get provider(): ISCMProvider { return this._provider; } - get id(): string { return this._provider.id; } - get name(): string { return this._provider.label; } + get repository(): ISCMRepository { return this._repository; } + get id(): string { return this._repository.provider.id; } + get name(): string { return this._repository.provider.label; } get ctor(): any { return null; } get location(): ViewLocation { return ViewLocation.SCM; } - constructor(private _provider: ISCMProvider) { + constructor(private _repository: ISCMRepository) { } } @@ -251,13 +251,12 @@ class SourceControlView extends CollapsibleView { private disposables: IDisposable[] = []; constructor( - private provider: ISCMProvider, + private repository: ISCMRepository, options: IViewletViewOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IThemeService protected themeService: IThemeService, @IContextMenuService protected contextMenuService: IContextMenuService, @IContextViewService protected contextViewService: IContextViewService, - @ISCMService protected scmService: ISCMService, @IListService protected listService: IListService, @ICommandService protected commandService: ICommandService, @IMessageService protected messageService: IMessageService, @@ -267,7 +266,7 @@ class SourceControlView extends CollapsibleView { ) { super({ ...(options as IViewOptions), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService); - this.menus = instantiationService.createInstance(SCMMenus, provider); + this.menus = instantiationService.createInstance(SCMMenus, repository.provider); this.menus.onDidChangeTitle(this.updateActions, this, this.disposables); } @@ -290,9 +289,9 @@ class SourceControlView extends CollapsibleView { this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService)); this.disposables.push(this.inputBox); - this.inputBox.value = this.scmService.input.value; - this.inputBox.onDidChange(value => this.scmService.input.value = value, null, this.disposables); - this.scmService.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); + this.inputBox.value = this.repository.input.value; + this.inputBox.onDidChange(value => this.repository.input.value = value, null, this.disposables); + this.repository.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); // this.disposables.push(this.inputBox.onDidHeightChange(() => this.layout())); chain(domEvent(this.inputBox.inputElement, 'keydown')) @@ -301,8 +300,8 @@ class SourceControlView extends CollapsibleView { .on(this.onDidAcceptInput, this, this.disposables); - if (this.provider.onDidChangeCommitTemplate) { - this.provider.onDidChangeCommitTemplate(this.updateInputBox, this, this.disposables); + if (this.repository.provider.onDidChangeCommitTemplate) { + this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this, this.disposables); } this.updateInputBox(); @@ -340,7 +339,7 @@ class SourceControlView extends CollapsibleView { this.list.onContextMenu(this.onListContextMenu, this, this.disposables); this.disposables.push(this.list); - this.provider.onDidChange(this.updateList, this, this.disposables); + this.repository.provider.onDidChange(this.updateList, this, this.disposables); this.updateList(); } @@ -365,11 +364,11 @@ class SourceControlView extends CollapsibleView { } getActionsContext(): any { - return this.provider; + return this.repository.provider; } private updateList(): void { - const elements = this.provider.resources + const elements = this.repository.provider.resources .reduce<(ISCMResourceGroup | ISCMResource)[]>((r, g) => [...r, g, ...g.resources.sort(resourceSorter)], []); this.list.splice(0, this.list.length, elements); @@ -414,20 +413,20 @@ class SourceControlView extends CollapsibleView { } private updateInputBox(): void { - if (typeof this.provider.commitTemplate === 'undefined') { + if (typeof this.repository.provider.commitTemplate === 'undefined') { return; } - this.inputBox.value = this.provider.commitTemplate; + this.inputBox.value = this.repository.provider.commitTemplate; } private onDidAcceptInput(): void { - if (!this.provider.acceptInputCommand) { + if (!this.repository.provider.acceptInputCommand) { return; } - const id = this.provider.acceptInputCommand.id; - const args = this.provider.acceptInputCommand.arguments; + const id = this.repository.provider.acceptInputCommand.id; + const args = this.repository.provider.acceptInputCommand.arguments; this.commandService.executeCommand(id, ...args) .done(undefined, onUnexpectedError); @@ -484,13 +483,13 @@ export class SCMViewlet extends ComposedViewsViewlet { telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); } - private onDidAddProvider(provider: ISCMProvider): void { - const view = new SourceControlViewDescriptor(provider); + private onDidAddRepository(repository: ISCMRepository): void { + const view = new SourceControlViewDescriptor(repository); ViewsRegistry.registerViews([view]); } - private onDidRemoveProvider(provider: ISCMProvider): void { - ViewsRegistry.deregisterViews([provider.id], ViewLocation.SCM); + private onDidRemoveRepository(repository: ISCMRepository): void { + ViewsRegistry.deregisterViews([repository.provider.id], ViewLocation.SCM); } // private onDidProvidersChange(): void { @@ -521,9 +520,9 @@ export class SCMViewlet extends ComposedViewsViewlet { parent.addClass('scm-viewlet'); - this.scmService.onDidAddProvider(this.onDidAddProvider, this, this.disposables); - this.scmService.onDidRemoveProvider(this.onDidRemoveProvider, this, this.disposables); - this.scmService.providers.forEach(p => this.onDidAddProvider(p)); + this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); + this.scmService.repositories.forEach(p => this.onDidAddRepository(p)); // this.themeService.onThemeChange(this.update, this, this.disposables); // return TPromise.as(null); @@ -531,7 +530,7 @@ export class SCMViewlet extends ComposedViewsViewlet { protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): IView { if (viewDescriptor instanceof SourceControlViewDescriptor) { - return this.instantiationService.createInstance(SourceControlView, viewDescriptor.provider, options); + return this.instantiationService.createInstance(SourceControlView, viewDescriptor.repository, options); } return this.instantiationService.createInstance(viewDescriptor.ctor, options); diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 66cc07504ccc9..06fef1b791b48 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -61,15 +61,20 @@ export interface ISCMInput { readonly onDidChange: Event; } +export interface ISCMRepository extends IDisposable { + readonly provider: ISCMProvider; + readonly input: ISCMInput; +} + export interface ISCMService { readonly _serviceBrand: any; - readonly onDidAddProvider: Event; - readonly onDidRemoveProvider: Event; - readonly onDidChangeProvider: Event; - readonly providers: ISCMProvider[]; - readonly input: ISCMInput; - activeProvider: ISCMProvider | undefined; + readonly onDidAddRepository: Event; + readonly onDidRemoveRepository: Event; + readonly onDidChangeRepository: Event; + + readonly repositories: ISCMRepository[]; + activeRepository: ISCMRepository | undefined; - registerSCMProvider(provider: ISCMProvider): IDisposable; + registerSCMProvider(provider: ISCMProvider): ISCMRepository; } \ No newline at end of file diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index 5313e3e02736a..bb0dac7be2ae8 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -7,11 +7,10 @@ import { IDisposable, toDisposable, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; -import { memoize } from 'vs/base/common/decorators'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ISCMService, ISCMProvider, ISCMInput, DefaultSCMProviderIdStorageKey } from './scm'; +import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, DefaultSCMProviderIdStorageKey } from './scm'; class SCMInput implements ISCMInput { @@ -30,6 +29,21 @@ class SCMInput implements ISCMInput { get onDidChange(): Event { return this._onDidChange.event; } } +class SCMRepository implements ISCMRepository { + + readonly input: ISCMInput = new SCMInput(); + + constructor( + public readonly provider: ISCMProvider, + private disposable: IDisposable + ) { } + + dispose(): void { + this.disposable.dispose(); + this.provider.dispose(); + } +} + export class SCMService implements ISCMService { _serviceBrand; @@ -37,32 +51,30 @@ export class SCMService implements ISCMService { private activeProviderDisposable: IDisposable = EmptyDisposable; private statusBarDisposable: IDisposable = EmptyDisposable; - private _activeProvider: ISCMProvider | undefined; + private _activeRepository: ISCMRepository | undefined; - get activeProvider(): ISCMProvider | undefined { - return this._activeProvider; + get activeRepository(): ISCMRepository | undefined { + return this._activeRepository; } - set activeProvider(provider: ISCMProvider | undefined) { - this.setActiveSCMProvider(provider); - this.storageService.store(DefaultSCMProviderIdStorageKey, provider.contextValue, StorageScope.WORKSPACE); + set activeRepository(repository: ISCMRepository | undefined) { + this.setActiveSCMProvider(repository); + this.storageService.store(DefaultSCMProviderIdStorageKey, repository.provider.contextValue, StorageScope.WORKSPACE); } private _providerIds = new Set(); - private _providers: ISCMProvider[] = []; - get providers(): ISCMProvider[] { return [...this._providers]; } + private _repositories: ISCMRepository[] = []; + get repositories(): ISCMRepository[] { return [...this._repositories]; } - private _onDidAddProvider = new Emitter(); - get onDidAddProvider(): Event { return this._onDidAddProvider.event; } + private _onDidAddProvider = new Emitter(); + get onDidAddRepository(): Event { return this._onDidAddProvider.event; } - private _onDidRemoveProvider = new Emitter(); - get onDidRemoveProvider(): Event { return this._onDidRemoveProvider.event; } + private _onDidRemoveProvider = new Emitter(); + get onDidRemoveRepository(): Event { return this._onDidRemoveProvider.event; } - private _onDidChangeProvider = new Emitter(); - get onDidChangeProvider(): Event { return this._onDidChangeProvider.event; } + private _onDidChangeProvider = new Emitter(); + get onDidChangeRepository(): Event { return this._onDidChangeProvider.event; } - @memoize - get input(): ISCMInput { return new SCMInput(); } constructor( @IContextKeyService contextKeyService: IContextKeyService, @@ -70,57 +82,62 @@ export class SCMService implements ISCMService { @IStatusbarService private statusbarService: IStatusbarService ) { } - private setActiveSCMProvider(provider: ISCMProvider): void { + private setActiveSCMProvider(repository: ISCMRepository): void { this.activeProviderDisposable.dispose(); - if (!provider) { + if (!repository) { throw new Error('invalid provider'); } - if (provider && this._providers.indexOf(provider) === -1) { + if (repository && this._repositories.indexOf(repository) === -1) { throw new Error('Provider not registered'); } - this._activeProvider = provider; + this._activeRepository = repository; + const provider = repository.provider; this.activeProviderDisposable = provider.onDidChange(() => this.onDidProviderChange(provider)); this.onDidProviderChange(provider); - this._onDidChangeProvider.fire(provider); + this._onDidChangeProvider.fire(repository); } - registerSCMProvider(provider: ISCMProvider): IDisposable { + registerSCMProvider(provider: ISCMProvider): ISCMRepository { if (this._providerIds.has(provider.id)) { throw new Error(`SCM Provider ${provider.id} already exists.`); } this._providerIds.add(provider.id); - this._providers.push(provider); - - const defaultProviderId = this.storageService.get(DefaultSCMProviderIdStorageKey, StorageScope.WORKSPACE); - if (this._providers.length === 1 || defaultProviderId === provider.contextValue) { - this.setActiveSCMProvider(provider); - } - - this._onDidAddProvider.fire(provider); - - return toDisposable(() => { - const index = this._providers.indexOf(provider); + const disposable = toDisposable(() => { + const index = this._repositories.indexOf(repository); if (index < 0) { return; } this._providerIds.delete(provider.id); - this._providers.splice(index, 1); + this._repositories.splice(index, 1); - if (this.activeProvider === provider) { - this.activeProvider = this._providers[0]; + if (this.activeRepository === repository) { + this.activeRepository = this._repositories[0]; } - this._onDidRemoveProvider.fire(provider); + this._onDidRemoveProvider.fire(repository); }); + + const repository = new SCMRepository(provider, disposable); + this._repositories.push(repository); + + const defaultProviderId = this.storageService.get(DefaultSCMProviderIdStorageKey, StorageScope.WORKSPACE); + + if (this._repositories.length === 1 || defaultProviderId === provider.contextValue) { + this.setActiveSCMProvider(repository); + } + + this._onDidAddProvider.fire(repository); + + return repository; } private onDidProviderChange(provider: ISCMProvider): void { From cfbc4f67ad7a5bc98b3eed6b639d3e3b57ed8ea6 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 12:21:06 +0200 Subject: [PATCH 47/67] scm: SourceControl.inputBox --- extensions/git/src/commands.ts | 14 +++++++------- extensions/git/src/repository.ts | 4 +++- src/vs/vscode.d.ts | 5 +++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 8b03dbf441640..ba52056053a0b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation } from 'vscode'; +import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository'; import { Model } from './model'; @@ -754,7 +754,7 @@ export class CommandCenter { } private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise { - const message = scm.inputBox.value; + const message = repository.inputBox.value; const getCommitMessage = async () => { if (message) { return message; @@ -770,7 +770,7 @@ export class CommandCenter { const didCommit = await this.smartCommit(repository, getCommitMessage, opts); if (message && didCommit) { - scm.inputBox.value = await repository.getCommitTemplate(); + repository.inputBox.value = await repository.getCommitTemplate(); } } @@ -781,14 +781,14 @@ export class CommandCenter { @command('git.commitWithInput', { repository: true }) async commitWithInput(repository: Repository): Promise { - if (!scm.inputBox.value) { + if (!repository.inputBox.value) { return; } - const didCommit = await this.smartCommit(repository, async () => scm.inputBox.value); + const didCommit = await this.smartCommit(repository, async () => repository.inputBox.value); if (didCommit) { - scm.inputBox.value = await repository.getCommitTemplate(); + repository.inputBox.value = await repository.getCommitTemplate(); } } @@ -832,7 +832,7 @@ export class CommandCenter { const commit = await repository.getCommit('HEAD'); await repository.reset('HEAD~'); - scm.inputBox.value = commit.message; + repository.inputBox.value = commit.message; } @command('git.checkout', { repository: true }) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6edb0366a5e0f..77e116bd0d8c5 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, Command, EventEmitter, Event, scm, commands, SourceControl, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; +import { Uri, Command, EventEmitter, Event, scm, commands, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git'; import { anyEvent, filterEvent, eventToPromise, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; @@ -318,6 +318,8 @@ export class Repository implements Disposable { private _sourceControl: SourceControl; get sourceControl(): SourceControl { return this._sourceControl; } + get inputBox(): SourceControlInputBox { return this._sourceControl.inputBox; } + private _mergeGroup: SourceControlResourceGroup; get mergeGroup(): GitResourceGroup { return this._mergeGroup as GitResourceGroup; } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index bd13a07e96d87..eb863af9ef5ed 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5396,6 +5396,11 @@ declare module 'vscode' { */ readonly label: string; + /** + * The [input box](#SourceControlInputBox) for this source control. + */ + readonly inputBox: SourceControlInputBox; + /** * The UI-visible count of [resource states](#SourceControlResourceState) of * this source control. From 00020e6354b02e753d096f186dc1833b6b949ce8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 12:26:31 +0200 Subject: [PATCH 48/67] scm: commit from input box --- extensions/git/src/repository.ts | 2 +- src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 77e116bd0d8c5..34b11e4199f50 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -387,7 +387,7 @@ export class Repository implements Disposable { const label = `Git - ${path.basename(repository.root)}`; this._sourceControl = scm.createSourceControl('git', label); - this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") }; + this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] }; this._sourceControl.quickDiffProvider = this; this.disposables.push(this._sourceControl); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index c099453912e2d..d4af6764b37e1 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -299,7 +299,6 @@ class SourceControlView extends CollapsibleView { .filter(e => e.equals(KeyMod.CtrlCmd | KeyCode.Enter) || e.equals(KeyMod.CtrlCmd | KeyCode.KEY_S)) .on(this.onDidAcceptInput, this, this.disposables); - if (this.repository.provider.onDidChangeCommitTemplate) { this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this, this.disposables); } From 441842e0c51988bcaf7573f883aba22d358c6a41 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 12:40:14 +0200 Subject: [PATCH 49/67] scm: remove activeRepository from SCMActivity --- .../parts/scm/electron-browser/scmActivity.ts | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts index 265c38c2d5226..75e7962dc8675 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts @@ -6,7 +6,8 @@ 'use strict'; import { localize } from 'vs/nls'; -import { IDisposable, dispose, empty as EmptyDisposable, OneDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { filterEvent } from 'vs/base/common/event'; import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; import { IActivityBarService, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService'; @@ -16,51 +17,53 @@ export class StatusUpdater implements IWorkbenchContribution { static ID = 'vs.scm.statusUpdater'; - private providerChangeDisposable: IDisposable = EmptyDisposable; - private badgeHandle = new OneDisposable(); + private badgeDisposable: IDisposable = EmptyDisposable; private disposables: IDisposable[] = []; constructor( @ISCMService private scmService: ISCMService, @IActivityBarService private activityBarService: IActivityBarService ) { - this.scmService.onDidChangeRepository(this.setActiveRepository, this, this.disposables); - this.setActiveRepository(this.scmService.activeRepository); - this.disposables.push(this.badgeHandle); + this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + this.render(this.scmService.repositories); } getId(): string { return StatusUpdater.ID; } - private setActiveRepository(repository: ISCMRepository | undefined): void { - this.providerChangeDisposable.dispose(); - this.providerChangeDisposable = repository ? repository.provider.onDidChange(this.update, this) : EmptyDisposable; - this.update(); - } + private onDidAddRepository(repository: ISCMRepository): void { + const changeDisposable = repository.provider.onDidChange(() => this.render(this.scmService.repositories)); - private update(): void { - const repository = this.scmService.activeRepository; + const onDidRemoveThisRepository = filterEvent(this.scmService.onDidRemoveRepository, r => r === repository); + const removeDisposable = onDidRemoveThisRepository(() => { + disposable.dispose(); + this.disposables = this.disposables.filter(d => d !== removeDisposable); + }); - let count = 0; + const disposable = combinedDisposable([changeDisposable, removeDisposable]); + this.disposables.push(disposable); + } - if (repository) { + private render(repositories: ISCMRepository[]): void { + const count = repositories.reduce((r, repository) => { if (typeof repository.provider.count === 'number') { - count = repository.provider.count; + return r + repository.provider.count; } else { - count = repository.provider.resources.reduce((r, g) => r + g.resources.length, 0); + return r + repository.provider.resources.reduce((r, g) => r + g.resources.length, 0); } - } + }, 0); if (count > 0) { const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); - this.badgeHandle.value = this.activityBarService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); + this.badgeDisposable = this.activityBarService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); } else { - this.badgeHandle.value = null; + this.badgeDisposable = EmptyDisposable; } } dispose(): void { + this.badgeDisposable.dispose(); this.disposables = dispose(this.disposables); } } From 632d0213bd6a93715d8dca7883d3106101ae99d1 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 12:49:03 +0200 Subject: [PATCH 50/67] scm: remove activeRepository from dirty diff decorator --- .../electron-browser/dirtydiffDecorator.ts | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts index 46d2f520c00a0..057383a067427 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts @@ -8,7 +8,7 @@ import 'vs/css!./media/dirtydiffDecorator'; import { ThrottledDelayer, always } from 'vs/base/common/async'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import * as winjs from 'vs/base/common/winjs.base'; +import { TPromise } from 'vs/base/common/winjs.base'; import * as ext from 'vs/workbench/common/contributions'; import * as common from 'vs/editor/common/editorCommon'; import * as widget from 'vs/editor/browser/codeEditor'; @@ -63,7 +63,7 @@ class DirtyDiffModelDecorator { private decorations: string[]; private baselineModel: common.IModel; private diffDelayer: ThrottledDelayer; - private _originalURIPromise: winjs.TPromise; + private _originalURIPromise: TPromise; private toDispose: IDisposable[]; constructor( @@ -84,9 +84,9 @@ class DirtyDiffModelDecorator { this.toDispose.push(scmService.onDidChangeRepository(() => this.triggerDiff())); } - private triggerDiff(): winjs.Promise { + private triggerDiff(): TPromise { if (!this.diffDelayer) { - return winjs.TPromise.as(null); + return TPromise.as(null); } return this.diffDelayer @@ -104,32 +104,26 @@ class DirtyDiffModelDecorator { }); } - private diff(): winjs.TPromise { + private diff(): TPromise { return this.getOriginalURIPromise().then(originalURI => { if (!this.model || this.model.isDisposed() || !originalURI) { - return winjs.TPromise.as([]); // disposed + return TPromise.as([]); // disposed } if (!this.editorWorkerService.canComputeDirtyDiff(originalURI, this.model.uri)) { - return winjs.TPromise.as([]); // Files too large + return TPromise.as([]); // Files too large } return this.editorWorkerService.computeDirtyDiff(originalURI, this.model.uri, true); }); } - private getOriginalURIPromise(): winjs.TPromise { + private getOriginalURIPromise(): TPromise { if (this._originalURIPromise) { return this._originalURIPromise; } - const repository = this.scmService.activeRepository; - - if (!repository) { - return winjs.TPromise.as(null); - } - - this._originalURIPromise = repository.provider.getOriginalResource(this.uri) + this._originalURIPromise = this.getOriginalResource() .then(originalUri => { if (!originalUri) { return null; @@ -151,6 +145,18 @@ class DirtyDiffModelDecorator { }); } + private async getOriginalResource(): TPromise { + for (const repository of this.scmService.repositories) { + const result = repository.provider.getOriginalResource(this.uri); + + if (result) { + return result; + } + } + + return null; + } + private static changesToDecorations(diff: common.IChange[]): common.IModelDeltaDecoration[] { return diff.map((change) => { const startLineNumber = change.modifiedStartLineNumber; From 015887518cb9782696f9849a5043f68d16500c00 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 12:49:47 +0200 Subject: [PATCH 51/67] scm: remove switch provider action --- .../scm/electron-browser/scm.contribution.ts | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts index 5c396f0269dc3..7c9d1e164fd04 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -6,19 +6,14 @@ 'use strict'; import { localize } from 'vs/nls'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { Action } from 'vs/base/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { DirtyDiffDecorator } from './dirtydiffDecorator'; -import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ToggleViewletAction } from 'vs/workbench/browser/viewlet'; import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actionRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; -import { ISCMService } from 'vs/workbench/services/scm/common/scm'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { StatusUpdater } from './scmActivity'; @@ -33,43 +28,6 @@ class OpenSCMViewletAction extends ToggleViewletAction { } } -export class SwitchProvider extends Action { - - static readonly ID = 'scm.switch'; - static readonly LABEL = 'Switch SCM Provider'; - - constructor( - id = SwitchProvider.ID, - label = SwitchProvider.LABEL, - @ISCMService private scmService: ISCMService, - @IQuickOpenService private quickOpenService: IQuickOpenService, - @IViewletService private viewletService: IViewletService - ) { - super('scm.switchprovider', 'Switch SCM Provider', '', true); - } - - run(): TPromise { - const picks: IPickOpenEntry[] = this.scmService.repositories.map(repository => ({ - label: repository.provider.label, - run: () => this.scmService.activeRepository = repository - })); - picks.push({ - label: localize('installAdditionalSCMProviders', "Install Additional SCM Providers..."), - run: () => { - this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true).then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('category:"SCM Providers" @sort:installs'); - viewlet.focus(); - }); - return this.scmService.activeRepository; - }, - separator: { border: true } - }); - - return this.quickOpenService.pick(picks); - } -} - Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(DirtyDiffDecorator); @@ -99,6 +57,3 @@ Registry.as(WorkbenchActionExtensions.WorkbenchActions 'View: Show SCM', localize('view', "View") ); - -Registry.as(WorkbenchActionExtensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(SwitchProvider, SwitchProvider.ID, SwitchProvider.LABEL), 'SCM: Switch SCM Provider', 'SCM'); From 1775a08ef3c61a04ed0b1f978bf1d584f5ad6ef6 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 15:58:50 +0200 Subject: [PATCH 52/67] git: status bar --- extensions/git/src/repository.ts | 6 ++ .../scm/electron-browser/scm.contribution.ts | 5 +- .../parts/scm/electron-browser/scmActivity.ts | 88 ++++++++++++++++--- .../parts/scm/electron-browser/scmViewlet.ts | 7 +- src/vs/workbench/services/scm/common/scm.ts | 4 +- .../services/scm/common/scmService.ts | 80 +++-------------- 6 files changed, 104 insertions(+), 86 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 34b11e4199f50..3167af0af7aa8 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -14,6 +14,7 @@ import { AutoFetcher } from './autofetch'; import * as path from 'path'; import * as nls from 'vscode-nls'; import * as fs from 'fs'; +import { StatusBarCommands } from "./statusbar"; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -404,6 +405,11 @@ export class Repository implements Disposable { this.disposables.push(new AutoFetcher(this)); + const statusBar = new StatusBarCommands(this); + this.disposables.push(statusBar); + statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables); + this._sourceControl.statusBarCommands = statusBar.commands; + this.updateCommitTemplate(); this.status(); } diff --git a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts index 7c9d1e164fd04..5b962ef04ef6f 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -16,7 +16,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { StatusUpdater } from './scmActivity'; +import { StatusUpdater, StatusBarController } from './scmActivity'; class OpenSCMViewletAction extends ToggleViewletAction { @@ -46,6 +46,9 @@ Registry.as(ViewletExtensions.Viewlets) Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(StatusUpdater); +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(StatusBarController); + // Register Action to Open Viewlet Registry.as(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction( new SyncActionDescriptor(OpenSCMViewletAction, VIEWLET_ID, localize('toggleSCMViewlet', "Show SCM"), { diff --git a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts index 75e7962dc8675..70248bd015331 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts @@ -12,10 +12,11 @@ import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; import { IActivityBarService, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; export class StatusUpdater implements IWorkbenchContribution { - static ID = 'vs.scm.statusUpdater'; + private static ID = 'vs.scm.statusUpdater'; private badgeDisposable: IDisposable = EmptyDisposable; private disposables: IDisposable[] = []; @@ -25,18 +26,14 @@ export class StatusUpdater implements IWorkbenchContribution { @IActivityBarService private activityBarService: IActivityBarService ) { this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); - this.render(this.scmService.repositories); - } - - getId(): string { - return StatusUpdater.ID; + this.render(); } private onDidAddRepository(repository: ISCMRepository): void { - const changeDisposable = repository.provider.onDidChange(() => this.render(this.scmService.repositories)); + const changeDisposable = repository.provider.onDidChange(() => this.render()); - const onDidRemoveThisRepository = filterEvent(this.scmService.onDidRemoveRepository, r => r === repository); - const removeDisposable = onDidRemoveThisRepository(() => { + const onDidRemove = filterEvent(this.scmService.onDidRemoveRepository, e => e === repository); + const removeDisposable = onDidRemove(() => { disposable.dispose(); this.disposables = this.disposables.filter(d => d !== removeDisposable); }); @@ -45,8 +42,12 @@ export class StatusUpdater implements IWorkbenchContribution { this.disposables.push(disposable); } - private render(repositories: ISCMRepository[]): void { - const count = repositories.reduce((r, repository) => { + getId(): string { + return StatusUpdater.ID; + } + + private render(): void { + const count = this.scmService.repositories.reduce((r, repository) => { if (typeof repository.provider.count === 'number') { return r + repository.provider.count; } else { @@ -67,3 +68,68 @@ export class StatusUpdater implements IWorkbenchContribution { this.disposables = dispose(this.disposables); } } + +export class StatusBarController implements IWorkbenchContribution { + + private static ID = 'vs.scm.statusBarController'; + + private statusBarDisposable: IDisposable = EmptyDisposable; + private focusDisposable: IDisposable = EmptyDisposable; + private disposables: IDisposable[] = []; + + constructor( + @ISCMService private scmService: ISCMService, + @IStatusbarService private statusbarService: IStatusbarService + ) { + this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + + if (this.scmService.repositories.length > 0) { + this.onDidFocusRepository(this.scmService.repositories[0]); + } + } + + getId(): string { + return StatusBarController.ID; + } + + private onDidAddRepository(repository: ISCMRepository): void { + const changeDisposable = repository.onDidFocus(() => this.onDidFocusRepository(repository)); + const onDidRemove = filterEvent(this.scmService.onDidRemoveRepository, e => e === repository); + const removeDisposable = onDidRemove(() => { + disposable.dispose(); + this.disposables = this.disposables.filter(d => d !== removeDisposable); + }); + + const disposable = combinedDisposable([changeDisposable, removeDisposable]); + this.disposables.push(disposable); + + if (this.scmService.repositories.length === 1) { + this.onDidFocusRepository(repository); + } + } + + private onDidFocusRepository(repository: ISCMRepository): void { + this.focusDisposable.dispose(); + this.focusDisposable = repository.provider.onDidChange(() => this.render(repository)); + this.render(repository); + } + + private render(repository: ISCMRepository): void { + this.statusBarDisposable.dispose(); + + const commands = repository.provider.statusBarCommands || []; + const disposables = commands.map(c => this.statusbarService.addEntry({ + text: c.title, + tooltip: c.tooltip, + command: c.id + }, MainThreadStatusBarAlignment.LEFT, 10000)); + + this.statusBarDisposable = combinedDisposable(disposables); + } + + dispose(): void { + this.focusDisposable.dispose(); + this.statusBarDisposable.dispose(); + this.disposables = dispose(this.disposables); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index d4af6764b37e1..56f85b3cc5b99 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -14,7 +14,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Builder } from 'vs/base/browser/builder'; import { ComposedViewsViewlet, CollapsibleView, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views'; -import { append, $, toggleClass } from 'vs/base/browser/dom'; +import { append, $, toggleClass, trackFocus } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IDelegate, IRenderer, IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; @@ -278,8 +278,11 @@ class SourceControlView extends CollapsibleView { } renderBody(container: HTMLElement): void { - // Input + const focusTracker = trackFocus(container); + this.disposables.push(focusTracker.addFocusListener(() => this.repository.focus())); + this.disposables.push(focusTracker); + // Input this.inputBoxContainer = append(container, $('.scm-editor')); this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 06fef1b791b48..902c80182bd5d 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -17,7 +17,6 @@ export interface IBaselineResourceProvider { } export const ISCMService = createDecorator('scm'); -export const DefaultSCMProviderIdStorageKey = 'settings.workspace.scm.defaultProviderId'; export interface ISCMResourceDecorations { icon?: URI; @@ -62,8 +61,10 @@ export interface ISCMInput { } export interface ISCMRepository extends IDisposable { + readonly onDidFocus: Event; readonly provider: ISCMProvider; readonly input: ISCMInput; + focus(): void; } export interface ISCMService { @@ -74,7 +75,6 @@ export interface ISCMService { readonly onDidChangeRepository: Event; readonly repositories: ISCMRepository[]; - activeRepository: ISCMRepository | undefined; registerSCMProvider(provider: ISCMProvider): ISCMRepository; } \ No newline at end of file diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index bb0dac7be2ae8..d88e80f3f4eb6 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -5,12 +5,9 @@ 'use strict'; -import { IDisposable, toDisposable, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, DefaultSCMProviderIdStorageKey } from './scm'; +import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository } from './scm'; class SCMInput implements ISCMInput { @@ -31,6 +28,9 @@ class SCMInput implements ISCMInput { class SCMRepository implements ISCMRepository { + private _onDidFocus = new Emitter(); + readonly onDidFocus: Event = this._onDidFocus.event; + readonly input: ISCMInput = new SCMInput(); constructor( @@ -38,6 +38,10 @@ class SCMRepository implements ISCMRepository { private disposable: IDisposable ) { } + focus(): void { + this._onDidFocus.fire(); + } + dispose(): void { this.disposable.dispose(); this.provider.dispose(); @@ -48,20 +52,6 @@ export class SCMService implements ISCMService { _serviceBrand; - private activeProviderDisposable: IDisposable = EmptyDisposable; - private statusBarDisposable: IDisposable = EmptyDisposable; - - private _activeRepository: ISCMRepository | undefined; - - get activeRepository(): ISCMRepository | undefined { - return this._activeRepository; - } - - set activeRepository(repository: ISCMRepository | undefined) { - this.setActiveSCMProvider(repository); - this.storageService.store(DefaultSCMProviderIdStorageKey, repository.provider.contextValue, StorageScope.WORKSPACE); - } - private _providerIds = new Set(); private _repositories: ISCMRepository[] = []; get repositories(): ISCMRepository[] { return [...this._repositories]; } @@ -75,32 +65,7 @@ export class SCMService implements ISCMService { private _onDidChangeProvider = new Emitter(); get onDidChangeRepository(): Event { return this._onDidChangeProvider.event; } - - constructor( - @IContextKeyService contextKeyService: IContextKeyService, - @IStorageService private storageService: IStorageService, - @IStatusbarService private statusbarService: IStatusbarService - ) { } - - private setActiveSCMProvider(repository: ISCMRepository): void { - this.activeProviderDisposable.dispose(); - - if (!repository) { - throw new Error('invalid provider'); - } - - if (repository && this._repositories.indexOf(repository) === -1) { - throw new Error('Provider not registered'); - } - - this._activeRepository = repository; - const provider = repository.provider; - - this.activeProviderDisposable = provider.onDidChange(() => this.onDidProviderChange(provider)); - this.onDidProviderChange(provider); - - this._onDidChangeProvider.fire(repository); - } + constructor() { } registerSCMProvider(provider: ISCMProvider): ISCMRepository { if (this._providerIds.has(provider.id)) { @@ -118,38 +83,13 @@ export class SCMService implements ISCMService { this._providerIds.delete(provider.id); this._repositories.splice(index, 1); - - if (this.activeRepository === repository) { - this.activeRepository = this._repositories[0]; - } - this._onDidRemoveProvider.fire(repository); }); const repository = new SCMRepository(provider, disposable); this._repositories.push(repository); - - const defaultProviderId = this.storageService.get(DefaultSCMProviderIdStorageKey, StorageScope.WORKSPACE); - - if (this._repositories.length === 1 || defaultProviderId === provider.contextValue) { - this.setActiveSCMProvider(repository); - } - this._onDidAddProvider.fire(repository); return repository; } - - private onDidProviderChange(provider: ISCMProvider): void { - this.statusBarDisposable.dispose(); - - const commands = provider.statusBarCommands || []; - const disposables = commands.map(c => this.statusbarService.addEntry({ - text: c.title, - tooltip: c.tooltip, - command: c.id - }, MainThreadStatusBarAlignment.LEFT, 10000)); - - this.statusBarDisposable = combinedDisposable(disposables); - } } \ No newline at end of file From 0215be15d2f2c155a4bc58121d3c016cc0c61023 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 16:15:45 +0200 Subject: [PATCH 53/67] git: status bar commands scope --- extensions/git/src/repository.ts | 7 +++---- extensions/git/src/statusbar.ts | 12 ++++++++---- src/vs/platform/statusbar/common/statusbar.ts | 5 +++++ .../browser/parts/statusbar/statusbarPart.ts | 7 ++++--- .../parts/scm/electron-browser/scmActivity.ts | 3 ++- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 3167af0af7aa8..4cd04213c417a 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -300,10 +300,8 @@ export class Repository implements Disposable { private _onDidChangeState = new EventEmitter(); readonly onDidChangeState: Event = this._onDidChangeState.event; - @memoize - get onDidChange(): Event { - return anyEvent(this.onDidChangeState); - } + private _onDidChangeStatus = new EventEmitter(); + readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; private _onRunOperation = new EventEmitter(); readonly onRunOperation: Event = this._onRunOperation.event; @@ -789,6 +787,7 @@ export class Repository implements Disposable { } commands.executeCommand('setContext', 'gitState', stateContextKey); + this._onDidChangeStatus.fire(); } private onFSChange(uri: Uri): void { diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index 73b2951f9022c..3b1121ba93db2 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -20,7 +20,7 @@ class CheckoutStatusBar { private disposables: Disposable[] = []; constructor(private repository: Repository) { - repository.onDidChange(this._onDidChange.fire, this._onDidChange, this.disposables); + repository.onDidChangeStatus(this._onDidChange.fire, this._onDidChange, this.disposables); } get command(): Command | undefined { @@ -42,7 +42,8 @@ class CheckoutStatusBar { return { command: 'git.checkout', tooltip: localize('checkout', 'Checkout...'), - title + title, + arguments: [this.repository.sourceControl] }; } @@ -77,7 +78,7 @@ class SyncStatusBar { } constructor(private repository: Repository) { - repository.onDidChange(this.onModelChange, this, this.disposables); + repository.onDidChangeStatus(this.onModelChange, this, this.disposables); repository.onDidChangeOperations(this.onOperationsChange, this, this.disposables); this._onDidChange.fire(); } @@ -98,6 +99,7 @@ class SyncStatusBar { } get command(): Command | undefined { + console.log(this.repository.remotes, this.state.hasRemotes); if (!this.state.hasRemotes) { return undefined; } @@ -134,7 +136,8 @@ class SyncStatusBar { return { command, title: [icon, text].join(' ').trim(), - tooltip + tooltip, + arguments: [this.repository.sourceControl] }; } @@ -171,6 +174,7 @@ export class StatusBarCommands { } const sync = this.syncStatusBar.command; + console.log(sync); if (sync) { result.push(sync); diff --git a/src/vs/platform/statusbar/common/statusbar.ts b/src/vs/platform/statusbar/common/statusbar.ts index 62121575b7a4d..a7287a62e9fc5 100644 --- a/src/vs/platform/statusbar/common/statusbar.ts +++ b/src/vs/platform/statusbar/common/statusbar.ts @@ -42,6 +42,11 @@ export interface IStatusbarEntry { */ command?: string; + /** + * Optional arguments for the command. + */ + arguments?: any[]; + /** * An optional extension ID if this entry is provided from an extension. */ diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index f772cae81ac6c..2dfa79c7a1aea 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -238,7 +238,7 @@ class StatusBarEntryItem implements IStatusbarItem { if (this.entry.command) { textContainer = document.createElement('a'); - $(textContainer).on('click', () => this.executeCommand(this.entry.command), toDispose); + $(textContainer).on('click', () => this.executeCommand(this.entry.command, this.entry.arguments), toDispose); } else { textContainer = document.createElement('span'); } @@ -287,7 +287,8 @@ class StatusBarEntryItem implements IStatusbarItem { }; } - private executeCommand(id: string) { + private executeCommand(id: string, args?: any[]) { + args = args || []; // Lookup built in commands const builtInActionDescriptor = Registry.as(ActionExtensions.WorkbenchActions).getWorkbenchAction(id); @@ -314,7 +315,7 @@ class StatusBarEntryItem implements IStatusbarItem { } // Fallback to the command service for any other case - this.commandService.executeCommand(id).done(undefined, err => this.messageService.show(Severity.Error, toErrorMessage(err))); + this.commandService.executeCommand(id, ...args).done(undefined, err => this.messageService.show(Severity.Error, toErrorMessage(err))); } } diff --git a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts index 70248bd015331..21aa0b1a9e0d4 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts @@ -121,7 +121,8 @@ export class StatusBarController implements IWorkbenchContribution { const disposables = commands.map(c => this.statusbarService.addEntry({ text: c.title, tooltip: c.tooltip, - command: c.id + command: c.id, + arguments: c.arguments }, MainThreadStatusBarAlignment.LEFT, 10000)); this.statusBarDisposable = combinedDisposable(disposables); From 7d3b4faab5e1ddf7081b966c51611e64b20f8b7c Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Aug 2017 16:57:39 +0200 Subject: [PATCH 54/67] git, scm cleanup --- extensions/git/src/commands.ts | 10 +- .../parts/scm/electron-browser/scmViewlet.ts | 102 +++++++----------- 2 files changed, 49 insertions(+), 63 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index ba52056053a0b..8834af7ecff7e 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1220,7 +1220,15 @@ export class CommandCenter { } else { // try to guess the repository based on the first argument const repository = this.model.getRepository(args[0]); - const repositoryPromise = repository ? Promise.resolve(repository) : this.model.pickRepository(); + let repositoryPromise: Promise; + + if (repository) { + repositoryPromise = Promise.resolve(repository); + } else if (this.model.repositories.length === 1) { + repositoryPromise = Promise.resolve(this.model.repositories[0]); + } else { + repositoryPromise = this.model.pickRepository(); + } result = repositoryPromise.then(repository => { if (!repository) { diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 56f85b3cc5b99..f6714be1f62f2 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -139,6 +139,7 @@ interface ResourceTemplate { fileLabel: FileLabel; decorationIcon: HTMLElement; actionBar: ActionBar; + dispose: () => void; } class MultipleSelectionActionRunner extends ActionRunner { @@ -188,7 +189,12 @@ class ResourceRenderer implements IRenderer { const decorationIcon = append(element, $('.decoration-icon')); - return { element, name, fileLabel, decorationIcon, actionBar }; + return { + element, name, fileLabel, decorationIcon, actionBar, dispose: () => { + actionBar.dispose(); + fileLabel.dispose(); + } + }; } renderElement(resource: ISCMResource, index: number, template: ResourceTemplate): void { @@ -211,7 +217,7 @@ class ResourceRenderer implements IRenderer { } disposeTemplate(template: ResourceTemplate): void { - // noop + template.dispose(); } } @@ -236,13 +242,12 @@ class SourceControlViewDescriptor implements IViewDescriptor { get ctor(): any { return null; } get location(): ViewLocation { return ViewLocation.SCM; } - constructor(private _repository: ISCMRepository) { - - } + constructor(private _repository: ISCMRepository) { } } class SourceControlView extends CollapsibleView { + private cachedHeight: number | undefined; private inputBoxContainer: HTMLElement; private inputBox: InputBox; private listContainer: HTMLElement; @@ -295,7 +300,7 @@ class SourceControlView extends CollapsibleView { this.inputBox.value = this.repository.input.value; this.inputBox.onDidChange(value => this.repository.input.value = value, null, this.disposables); this.repository.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); - // this.disposables.push(this.inputBox.onDidHeightChange(() => this.layout())); + this.disposables.push(this.inputBox.onDidHeightChange(() => this.layoutBody())); chain(domEvent(this.inputBox.inputElement, 'keydown')) .map(e => new StandardKeyboardEvent(e)) @@ -345,8 +350,25 @@ class SourceControlView extends CollapsibleView { this.updateList(); } - layoutBody(size: number): void { - this.list.layout(size); + layoutBody(height: number = this.cachedHeight): void { + if (!height === undefined) { + return; + } + + this.list.layout(height); + this.cachedHeight = height; + this.inputBox.layout(); + + const editorHeight = this.inputBox.height; + const listHeight = height - (editorHeight + 12 /* margin */); + this.listContainer.style.height = `${listHeight}px`; + this.list.layout(listHeight); + + toggleClass(this.inputBoxContainer, 'scroll', editorHeight >= 134); + } + + focusBody(): void { + this.inputBox.focus(); } getActions(): IAction[] { @@ -457,10 +479,6 @@ class InstallAdditionalSCMProvidersAction extends Action { export class SCMViewlet extends ComposedViewsViewlet { - // private activeProvider: ISCMProvider | undefined; - // private cachedDimension: Dimension; - - // private providers = new Map(); private disposables: IDisposable[] = []; constructor( @@ -481,42 +499,21 @@ export class SCMViewlet extends ComposedViewsViewlet { @IStorageService storageService: IStorageService, @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, ViewLocation.SCM, `${VIEWLET_ID}.state`, false, + super(VIEWLET_ID, ViewLocation.SCM, `${VIEWLET_ID}.state`, true, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); } private onDidAddRepository(repository: ISCMRepository): void { const view = new SourceControlViewDescriptor(repository); ViewsRegistry.registerViews([view]); + this.updateTitleArea(); } private onDidRemoveRepository(repository: ISCMRepository): void { ViewsRegistry.deregisterViews([repository.provider.id], ViewLocation.SCM); + this.updateTitleArea(); } - // private onDidProvidersChange(): void { - // this.activeProvider = activeProvider; - - // if (activeProvider) { - // const disposables = []; - - // const id = activeProvider.id; - // ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]); - - // disposables.push({ - // dispose: () => { - // ViewsRegistry.deregisterViews([id], ViewLocation.SCM); - // } - // }); - - // this.providerChangeDisposable = combinedDisposable(disposables); - // } else { - // this.providerChangeDisposable = EmptyDisposable; - // } - - // this.updateTitleArea(); - // } - async create(parent: Builder): TPromise { await super.create(parent); @@ -525,9 +522,6 @@ export class SCMViewlet extends ComposedViewsViewlet { this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); this.scmService.repositories.forEach(p => this.onDidAddRepository(p)); - // this.themeService.onThemeChange(this.update, this, this.disposables); - - // return TPromise.as(null); } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): IView { @@ -538,40 +532,24 @@ export class SCMViewlet extends ComposedViewsViewlet { return this.instantiationService.createInstance(viewDescriptor.ctor, options); } - // layout(dimension: Dimension = this.cachedDimension): void { - // if (!dimension) { - // return; - // } - - // this.cachedDimension = dimension; - // this.inputBox.layout(); - - // const editorHeight = this.inputBox.height; - // const listHeight = dimension.height - (editorHeight + 12 /* margin */); - // this.listContainer.style.height = `${listHeight}px`; - // this.list.layout(listHeight); - - // toggleClass(this.inputBoxContainer, 'scroll', editorHeight >= 134); - // } - getOptimalWidth(): number { return 400; } focus(): void { super.focus(); - // this.inputBox.focus(); } getTitle(): string { const title = localize('source control', "Source Control"); - // const providerLabel = this.scmService.activeProvider && this.scmService.activeProvider.label; + const views = ViewsRegistry.getViews(ViewLocation.SCM); - // if (providerLabel) { - // return localize('viewletTitle', "{0}: {1}", title, providerLabel); - // } else { - return title; - // } + if (views.length === 1) { + const view = views[0]; + return localize('viewletTitle', "{0}: {1}", title, view.name); + } else { + return title; + } } @memoize From a4d229f2207c20cb3b850c6852b9f201de3ec456 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 23 Aug 2017 12:34:22 +0200 Subject: [PATCH 55/67] remove gitState --- extensions/git/src/repository.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 4cd04213c417a..87a59d9232dd1 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, Command, EventEmitter, Event, scm, commands, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; +import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode'; import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git'; import { anyEvent, filterEvent, eventToPromise, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; @@ -359,7 +359,6 @@ export class Repository implements Disposable { this.indexGroup.resourceStates = []; this.workingTreeGroup.resourceStates = []; this._sourceControl.count = 0; - commands.executeCommand('setContext', 'gitState', ''); } get root(): string { @@ -786,7 +785,6 @@ export class Repository implements Disposable { case State.Disposed: stateContextKey = 'norepo'; break; } - commands.executeCommand('setContext', 'gitState', stateContextKey); this._onDidChangeStatus.fire(); } From f9207f124a90f61b6f89b1f5112e6133088b693d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 23 Aug 2017 12:37:43 +0200 Subject: [PATCH 56/67] remove console.log --- extensions/git/src/statusbar.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index 3b1121ba93db2..f82fda91881e5 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -99,7 +99,6 @@ class SyncStatusBar { } get command(): Command | undefined { - console.log(this.repository.remotes, this.state.hasRemotes); if (!this.state.hasRemotes) { return undefined; } @@ -174,7 +173,6 @@ export class StatusBarCommands { } const sync = this.syncStatusBar.command; - console.log(sync); if (sync) { result.push(sync); From f36f53f8e3d42b34ace7bab206c69b8874fd5b67 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 23 Aug 2017 12:42:52 +0200 Subject: [PATCH 57/67] git: cant open resources starting with `.` --- extensions/git/src/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index d12a907a43c8b..ed44caf52d5cd 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -160,7 +160,7 @@ export class Model { for (const liveRepository of this.openRepositories) { const relativePath = path.relative(liveRepository.repository.root, resourcePath); - if (!/^\./.test(relativePath)) { + if (!/^\.\./.test(relativePath)) { return liveRepository; } } From ca068b36955f6504d5dff042a0d8f59da3662048 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 23 Aug 2017 15:55:41 +0200 Subject: [PATCH 58/67] fix git.pullFrom group --- extensions/git/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index da149b64b6df7..e7efb7e3e8971 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -459,6 +459,7 @@ }, { "command": "git.pullFrom", + "group": "1_sync", "when": "config.git.enabled && scmProvider == git" }, { From 89ac4c9124661e5e5a80b0f59b9ac409daa19341 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 23 Aug 2017 15:56:52 +0200 Subject: [PATCH 59/67] scm: show single scm actions in title --- .../parts/scm/electron-browser/scmViewlet.ts | 27 ++++++++++++++----- src/vs/workbench/parts/views/browser/views.ts | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index f6714be1f62f2..cb3ba2a757678 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -9,7 +9,6 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { chain } from 'vs/base/common/event'; -import { memoize } from 'vs/base/common/decorators'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Builder } from 'vs/base/browser/builder'; @@ -35,7 +34,7 @@ import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { IAction, Action, IActionItem, ActionRunner } from 'vs/base/common/actions'; import { MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; import { SCMMenus } from './scmMenus'; -import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionItemProvider, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { comparePaths } from 'vs/base/common/comparers'; import { isSCMResource } from './scmUtil'; @@ -552,11 +551,27 @@ export class SCMViewlet extends ComposedViewsViewlet { } } - @memoize + getActions(): IAction[] { + if (this.showHeaderInTitleArea() && this.views.length === 1) { + return this.views[0].getActions(); + } + + return []; + } + getSecondaryActions(): IAction[] { - return [ - this.instantiationService.createInstance(InstallAdditionalSCMProvidersAction) - ]; + let result: IAction[] = []; + + if (this.showHeaderInTitleArea() && this.views.length === 1) { + result = [ + ...this.views[0].getSecondaryActions(), + new Separator() + ]; + } + + result.push(this.instantiationService.createInstance(InstallAdditionalSCMProvidersAction)); + + return result; } getActionItem(action: IAction): IActionItem { diff --git a/src/vs/workbench/parts/views/browser/views.ts b/src/vs/workbench/parts/views/browser/views.ts index fcedaed1c83f7..75e9d49783ca9 100644 --- a/src/vs/workbench/parts/views/browser/views.ts +++ b/src/vs/workbench/parts/views/browser/views.ts @@ -652,7 +652,7 @@ export class ComposedViewsViewlet extends Viewlet { }); } - private showHeaderInTitleArea(): boolean { + protected showHeaderInTitleArea(): boolean { if (!this.showHeaderInTitleWhenSingleView) { return false; } From df2143f533665d0eab2cef7a9e3cf68cb18d3c4c Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 23 Aug 2017 16:06:06 +0200 Subject: [PATCH 60/67] remove todos --- extensions/git/src/commands.ts | 3 --- extensions/git/src/repository.ts | 1 - extensions/git/src/util.ts | 1 - 3 files changed, 5 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 8834af7ecff7e..aee82cbe4fece 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -744,7 +744,6 @@ export class CommandCenter { const message = await getCommitMessage(); if (!message) { - // TODO@joao: show modal dialog to confirm empty message commit return false; } @@ -1287,7 +1286,6 @@ export class CommandCenter { return result; } - // TODO@Joao: possibly remove? do we really need to return resources? private getSCMResource(uri?: Uri): Resource | undefined { uri = uri ? uri : window.activeTextEditor && window.activeTextEditor.document.uri; @@ -1322,7 +1320,6 @@ export class CommandCenter { const groups = resources.reduce((result, resource) => { const repository = this.model.getRepository(resource); - // TODO@Joao: what should happen? if (!repository) { console.warn('Could not find git repository for ', resource); return result; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 87a59d9232dd1..d72b4783f23ef 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -411,7 +411,6 @@ export class Repository implements Disposable { this.status(); } - // TODO@Joao reorganize this provideOriginalResource(uri: Uri): Uri | undefined { if (uri.scheme !== 'file') { return; diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 8d5c9b202517f..632fd5a0a86e8 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -83,7 +83,6 @@ export function once(fn: (...args: any[]) => any): (...args: any[]) => any { }; } -// TODO@Joao: replace with Object.assign export function assign(destination: T, ...sources: any[]): T { for (const source of sources) { Object.keys(source).forEach(key => destination[key] = source[key]); From bcf2f1dabbb4df0804bbd5d2f811afdb19208cca Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 23 Aug 2017 16:12:46 +0200 Subject: [PATCH 61/67] git: init --- extensions/git/src/commands.ts | 19 +++++++++++++++++-- extensions/git/src/model.ts | 2 +- extensions/git/src/repository.ts | 17 ++++++++--------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index aee82cbe4fece..aa9f74a5ada5d 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -311,8 +311,23 @@ export class CommandCenter { @command('git.init') async init(): Promise { - // TODO@joao - // await model.init(); + const value = workspace.workspaceFolders && workspace.workspaceFolders.length > 0 + ? workspace.workspaceFolders[0].uri.fsPath + : os.homedir(); + + const path = await window.showInputBox({ + placeHolder: localize('path to init', "Folder path"), + prompt: localize('provide path', "Please provide a folder path to initialize a Git repository"), + value, + ignoreFocusOut: true + }); + + if (!path) { + return; + } + + await this.git.init(path); + await this.model.tryOpenRepository(path); } @command('git.openFile') diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index ed44caf52d5cd..278a095b38d18 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -84,7 +84,7 @@ export class Model { } @sequentialize - private async tryOpenRepository(path: string): Promise { + async tryOpenRepository(path: string): Promise { const repository = this.getRepository(path); if (repository) { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index d72b4783f23ef..ed2af08ee4f71 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -201,15 +201,14 @@ export enum Operation { Pull = 1 << 9, Push = 1 << 10, Sync = 1 << 11, - Init = 1 << 12, - Show = 1 << 13, - Stage = 1 << 14, - GetCommitTemplate = 1 << 15, - DeleteBranch = 1 << 16, - Merge = 1 << 17, - Ignore = 1 << 18, - Tag = 1 << 19, - Stash = 1 << 20 + Show = 1 << 12, + Stage = 1 << 13, + GetCommitTemplate = 1 << 14, + DeleteBranch = 1 << 15, + Merge = 1 << 16, + Ignore = 1 << 17, + Tag = 1 << 18, + Stash = 1 << 19 } // function getOperationName(operation: Operation): string { From e9798613ad1aae96b442b8641c71d9b44f95352f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 23 Aug 2017 16:25:20 +0200 Subject: [PATCH 62/67] git: gitOpenRepositoryCount --- extensions/git/package.json | 94 ++++++++++++++++++++----------------- extensions/git/src/main.ts | 5 ++ extensions/git/src/model.ts | 8 ++++ 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index e7efb7e3e8971..49ec32dc84e91 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -280,155 +280,163 @@ }, { "command": "git.refresh", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.openFile", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.openHEADFile", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.openChange", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.stage", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.stageAll", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.stageSelectedRanges", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.revertSelectedRanges", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.unstage", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.unstageAll", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.unstageSelectedRanges", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.clean", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.cleanAll", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.commit", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.commitStaged", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.commitStagedSigned", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.commitStagedAmend", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.commitAll", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.commitAllSigned", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.commitAllAmend", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.undoCommit", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.checkout", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.branch", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.deleteBranch", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.pull", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.pullFrom", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.pullRebase", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.pullFrom", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.merge", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.createTag", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.push", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.pushTo", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.pushWithTags", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.sync", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.publish", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.showOutput", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.ignore", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.stash", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.stashPop", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.stashPopLatest", - "when": "config.git.enabled" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" } ], "scm/title": [ @@ -666,27 +674,27 @@ { "command": "git.openFile", "group": "navigation", - "when": "config.git.enabled && isInDiffEditor && resourceScheme != extension && resourceScheme != merge-conflict.conflict-diff" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != extension && resourceScheme != merge-conflict.conflict-diff" }, { "command": "git.openChange", "group": "navigation", - "when": "config.git.enabled && !isInDiffEditor && resourceScheme != extension" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme != extension" }, { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "config.git.enabled && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "config.git.enabled && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "config.git.enabled && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" + "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff" } ] }, diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 6439824bcacd7..acc30eeb81d1f 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -34,6 +34,11 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi const model = new Model(git); disposables.push(model); + const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`); + model.onDidOpenRepository(onRepository, null, disposables); + model.onDidCloseRepository(onRepository, null, disposables); + onRepository(); + if (!enabled) { const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter); disposables.push(commandCenter); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 278a095b38d18..b13e2b91b4bbe 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -32,6 +32,12 @@ interface OpenRepository extends Disposable { export class Model { + private _onDidOpenRepository = new EventEmitter(); + readonly onDidOpenRepository: Event = this._onDidOpenRepository.event; + + private _onDidCloseRepository = new EventEmitter(); + readonly onDidCloseRepository: Event = this._onDidCloseRepository.event; + private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; @@ -114,10 +120,12 @@ export class Model { changeListener.dispose(); repository.dispose(); this.openRepositories = this.openRepositories.filter(e => e !== openRepository); + this._onDidCloseRepository.fire(repository); }; const openRepository = { repository, dispose }; this.openRepositories.push(openRepository); + this._onDidOpenRepository.fire(repository); } async pickRepository(): Promise { From 9d89f73586d7abe685e31f2a6b91809fd8b52a72 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 24 Aug 2017 17:08:34 +0200 Subject: [PATCH 63/67] views viewlet: deregister doesn't remove splitview --- .../electron-browser/extensionsViewlet.ts | 4 ++-- src/vs/workbench/parts/views/browser/views.ts | 17 +++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 218262d04283a..1ecb5c5f22b7c 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -307,10 +307,10 @@ export class ExtensionsViewlet extends ComposedViewsViewlet implements IExtensio this.searchInstalledExtensionsContextKey.set(InstalledExtensionsView.isInsalledExtensionsQuery(value)); this.searchRecommendedExtensionsContextKey.set(RecommendedExtensionsView.isRecommendedExtensionsQuery(value)); - await this.updateViews(!!value); + await this.updateViews([], !!value); } - protected async updateViews(showAll?: boolean): TPromise { + protected async updateViews(unregisteredViews: IViewDescriptor[] = [], showAll = false): TPromise { const created = await super.updateViews(); const toShow = showAll ? this.views : created; if (toShow.length) { diff --git a/src/vs/workbench/parts/views/browser/views.ts b/src/vs/workbench/parts/views/browser/views.ts index 75e9d49783ca9..dffdeb6ed4b06 100644 --- a/src/vs/workbench/parts/views/browser/views.ts +++ b/src/vs/workbench/parts/views/browser/views.ts @@ -349,8 +349,8 @@ export class ComposedViewsViewlet extends Viewlet { this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); this.viewsStates = this.loadViewsStates(); - this._register(ViewsRegistry.onViewsRegistered(() => this.onViewDescriptorsChanged())); - this._register(ViewsRegistry.onViewsDeregistered(() => this.onViewDescriptorsChanged())); + this._register(ViewsRegistry.onViewsRegistered(this.onViewsRegistered, this)); + this._register(ViewsRegistry.onViewsDeregistered(this.onViewsDeregistered, this)); this._register(contextKeyService.onDidChangeContext(keys => this.onContextChanged(keys))); extensionService.onReady().then(() => { @@ -373,7 +373,7 @@ export class ComposedViewsViewlet extends Viewlet { } })); - return this.onViewDescriptorsChanged() + return this.onViewsRegistered(ViewsRegistry.getViews(this.location)) .then(() => { this.lastFocusedView = this.splitView.getViews()[0]; this.focus(); @@ -469,7 +469,7 @@ export class ComposedViewsViewlet extends Viewlet { this.updateViews(); } - private onViewDescriptorsChanged(): TPromise { + private onViewsRegistered(views: IViewDescriptor[]): TPromise { this.viewsContextKeys.clear(); for (const viewDescriptor of this.getViewDescriptorsFromRegistry()) { if (viewDescriptor.when) { @@ -478,9 +478,14 @@ export class ComposedViewsViewlet extends Viewlet { } } } + return this.updateViews(); } + private onViewsDeregistered(views: IViewDescriptor[]): TPromise { + return this.updateViews(views); + } + private onContextChanged(keys: string[]): void { if (!keys) { return; @@ -499,7 +504,7 @@ export class ComposedViewsViewlet extends Viewlet { } } - protected updateViews(): TPromise { + protected updateViews(unregisteredViews: IViewDescriptor[] = []): TPromise { if (this.splitView) { const registeredViews = this.getViewDescriptorsFromRegistry(); @@ -521,7 +526,7 @@ export class ComposedViewsViewlet extends Viewlet { return result; - }, [[], [], []]); + }, [[], [], unregisteredViews]); const toCreate = []; From 7eaafc9d4c0578864648ce46743d82940ac90d3e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 24 Aug 2017 17:38:23 +0200 Subject: [PATCH 64/67] views: PersistentViewsViewlet --- .../parts/debug/browser/debugViewlet.ts | 4 +- .../electron-browser/extensionsViewlet.ts | 4 +- .../parts/files/browser/explorerViewlet.ts | 4 +- .../parts/scm/electron-browser/scmViewlet.ts | 6 +- src/vs/workbench/parts/views/browser/views.ts | 91 +++++++++++-------- 5 files changed, 64 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index ada15c7f4a8d7..43aa1630336f8 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -9,7 +9,7 @@ import * as DOM from 'vs/base/browser/dom'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAction } from 'vs/base/common/actions'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ComposedViewsViewlet } from 'vs/workbench/parts/views/browser/views'; +import { PersistentViewsViewlet } from 'vs/workbench/parts/views/browser/views'; import { IDebugService, VIEWLET_ID, State } from 'vs/workbench/parts/debug/common/debug'; import { StartAction, ToggleReplAction, ConfigureAction } from 'vs/workbench/parts/debug/browser/debugActions'; import { StartDebugActionItem } from 'vs/workbench/parts/debug/browser/debugActionItems'; @@ -24,7 +24,7 @@ import { ViewLocation } from 'vs/workbench/parts/views/browser/viewsRegistry'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -export class DebugViewlet extends ComposedViewsViewlet { +export class DebugViewlet extends PersistentViewsViewlet { private actions: IAction[]; private startDebugActionItem: StartDebugActionItem; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 1ecb5c5f22b7c..eb578a11c94d8 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -46,7 +46,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { inputForeground, inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/parts/views/browser/viewsRegistry'; -import { ComposedViewsViewlet, IView } from 'vs/workbench/parts/views/browser/views'; +import { PersistentViewsViewlet, IView } from 'vs/workbench/parts/views/browser/views'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -62,7 +62,7 @@ const SearchExtensionsContext = new RawContextKey('searchExtensions', f const SearchInstalledExtensionsContext = new RawContextKey('searchInstalledExtensions', false); const SearchRecommendedExtensionsContext = new RawContextKey('searchRecommendedExtensions', false); -export class ExtensionsViewlet extends ComposedViewsViewlet implements IExtensionsViewlet { +export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtensionsViewlet { private onSearchChange: EventOf; private extensionsViewletVisibleContextKey: IContextKey; diff --git a/src/vs/workbench/parts/files/browser/explorerViewlet.ts b/src/vs/workbench/parts/files/browser/explorerViewlet.ts index 4c690b9b22228..1ae651f5585d5 100644 --- a/src/vs/workbench/parts/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/browser/explorerViewlet.ts @@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as DOM from 'vs/base/browser/dom'; import { Builder } from 'vs/base/browser/builder'; import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, OpenEditorsVisibleCondition } from 'vs/workbench/parts/files/common/files'; -import { ComposedViewsViewlet, IView, IViewletViewOptions } from 'vs/workbench/parts/views/browser/views'; +import { PersistentViewsViewlet, IView, IViewletViewOptions } from 'vs/workbench/parts/views/browser/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ActionRunner, FileViewletState } from 'vs/workbench/parts/files/browser/views/explorerViewer'; @@ -35,7 +35,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/parts/views/browser/viewsRegistry'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -export class ExplorerViewlet extends ComposedViewsViewlet { +export class ExplorerViewlet extends PersistentViewsViewlet { private static EXPLORER_VIEWS_STATE = 'workbench.explorer.views.state'; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index cb3ba2a757678..deef9d1102199 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -12,7 +12,7 @@ import { chain } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Builder } from 'vs/base/browser/builder'; -import { ComposedViewsViewlet, CollapsibleView, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views'; +import { ViewsViewlet, CollapsibleView, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views'; import { append, $, toggleClass, trackFocus } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { List } from 'vs/base/browser/ui/list/listWidget'; @@ -476,7 +476,7 @@ class InstallAdditionalSCMProvidersAction extends Action { } } -export class SCMViewlet extends ComposedViewsViewlet { +export class SCMViewlet extends ViewsViewlet { private disposables: IDisposable[] = []; @@ -498,7 +498,7 @@ export class SCMViewlet extends ComposedViewsViewlet { @IStorageService storageService: IStorageService, @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, ViewLocation.SCM, `${VIEWLET_ID}.state`, true, + super(VIEWLET_ID, ViewLocation.SCM, true, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); } diff --git a/src/vs/workbench/parts/views/browser/views.ts b/src/vs/workbench/parts/views/browser/views.ts index dffdeb6ed4b06..89aece3ffba3c 100644 --- a/src/vs/workbench/parts/views/browser/views.ts +++ b/src/vs/workbench/parts/views/browser/views.ts @@ -304,7 +304,7 @@ export interface IViewletViewOptions extends IViewOptions { } -interface IViewState { +export interface IViewState { collapsed: boolean; @@ -316,7 +316,7 @@ interface IViewState { } -export class ComposedViewsViewlet extends Viewlet { +export class ViewsViewlet extends Viewlet { protected viewletContainer: HTMLElement; protected lastFocusedView: IView; @@ -327,13 +327,12 @@ export class ComposedViewsViewlet extends Viewlet { private viewletSettings: object; private readonly viewsContextKeys: Set = new Set(); - private readonly viewsStates: Map; + protected viewsStates: Map = new Map(); private areExtensionsReady: boolean = false; constructor( id: string, private location: ViewLocation, - private viewletStateStorageId: string, private showHeaderInTitleWhenSingleView: boolean, @ITelemetryService telemetryService: ITelemetryService, @IStorageService protected storageService: IStorageService, @@ -347,7 +346,6 @@ export class ComposedViewsViewlet extends Viewlet { super(id, telemetryService, themeService); this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); - this.viewsStates = this.loadViewsStates(); this._register(ViewsRegistry.onViewsRegistered(this.onViewsRegistered, this)); this._register(ViewsRegistry.onViewsDeregistered(this.onViewsDeregistered, this)); @@ -440,7 +438,6 @@ export class ComposedViewsViewlet extends Viewlet { } public shutdown(): void { - this.saveViewsStates(); this.splitView.getViews().forEach((view) => view.shutdown()); super.shutdown(); } @@ -677,7 +674,7 @@ export class ComposedViewsViewlet extends Viewlet { return true; } - private getViewDescriptorsFromRegistry(defaultOrder: boolean = false): IViewDescriptor[] { + protected getViewDescriptorsFromRegistry(defaultOrder: boolean = false): IViewDescriptor[] { return ViewsRegistry.getViews(this.location) .sort((a, b) => { const viewStateA = this.viewsStates.get(a.id); @@ -696,34 +693,6 @@ export class ComposedViewsViewlet extends Viewlet { }); } - private saveViewsStates(): void { - const viewsStates = {}; - const registeredViewDescriptors = this.getViewDescriptorsFromRegistry(); - this.viewsStates.forEach((viewState, id) => { - const view = this.getView(id); - if (view) { - viewState = this.createViewState(view); - viewsStates[id] = { size: viewState.size, collapsed: viewState.collapsed, isHidden: viewState.isHidden, order: viewState.order }; - } else { - const viewDescriptor = registeredViewDescriptors.filter(v => v.id === id)[0]; - if (viewDescriptor) { - viewsStates[id] = viewState; - } - } - }); - - this.storageService.store(this.viewletStateStorageId, JSON.stringify(viewsStates), this.contextService.hasWorkspace() ? StorageScope.WORKSPACE : StorageScope.GLOBAL); - } - - private loadViewsStates(): Map { - const viewsStates = JSON.parse(this.storageService.get(this.viewletStateStorageId, this.contextService.hasWorkspace() ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}')); - return Object.keys(viewsStates).reduce((result, id) => { - const viewState = viewsStates[id]; - result.set(id, viewState); - return result; - }, new Map()); - } - protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): IView { return this.instantiationService.createInstance(viewDescriptor.ctor, options); } @@ -742,7 +711,7 @@ export class ComposedViewsViewlet extends Viewlet { return currentState ? { ...currentState, collapsed: newViewState.collapsed, size: newViewState.size } : newViewState; } - private createViewState(view: IView): IViewState { + protected createViewState(view: IView): IViewState { const collapsed = !view.isExpanded(); const size = collapsed && view instanceof CollapsibleView ? view.previousSize : view.size; return { @@ -752,4 +721,54 @@ export class ComposedViewsViewlet extends Viewlet { order: this.splitView.getViews().indexOf(view) }; } +} + +export class PersistentViewsViewlet extends ViewsViewlet { + + constructor( + id: string, + location: ViewLocation, + private viewletStateStorageId: string, + showHeaderInTitleWhenSingleView: boolean, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IContextKeyService contextKeyService: IContextKeyService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService + ) { + super(id, location, showHeaderInTitleWhenSingleView, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService); + this.loadViewsStates(); + } + + shutdown(): void { + this.saveViewsStates(); + super.shutdown(); + } + + private saveViewsStates(): void { + const viewsStates = {}; + const registeredViewDescriptors = this.getViewDescriptorsFromRegistry(); + this.viewsStates.forEach((viewState, id) => { + const view = this.getView(id); + if (view) { + viewState = this.createViewState(view); + viewsStates[id] = { size: viewState.size, collapsed: viewState.collapsed, isHidden: viewState.isHidden, order: viewState.order }; + } else { + const viewDescriptor = registeredViewDescriptors.filter(v => v.id === id)[0]; + if (viewDescriptor) { + viewsStates[id] = viewState; + } + } + }); + + this.storageService.store(this.viewletStateStorageId, JSON.stringify(viewsStates), this.contextService.hasWorkspace() ? StorageScope.WORKSPACE : StorageScope.GLOBAL); + } + + private loadViewsStates(): void { + const viewsStates = JSON.parse(this.storageService.get(this.viewletStateStorageId, this.contextService.hasWorkspace() ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}')); + Object.keys(viewsStates).forEach(id => this.viewsStates.set(id, viewsStates[id])); + } } \ No newline at end of file From 7341dcd4df35fa698cee32a86f88ded87d696d55 Mon Sep 17 00:00:00 2001 From: Joao Date: Fri, 25 Aug 2017 11:28:46 +0200 Subject: [PATCH 65/67] use ~~ for deprecation --- src/vs/vscode.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index eb863af9ef5ed..3fb2bd8ce4fc2 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5452,8 +5452,8 @@ declare module 'vscode' { export namespace scm { /** - * The [input box](#SourceControlInputBox) for the last source control - * created by the extension. + * ~~The [input box](#SourceControlInputBox) for the last source control + * created by the extension.~~ * * @deprecated Use [SourceControl.inputBox](#SourceControl.inputBox) instead */ From 25cfb9e05ffc09e928407a576f64cec1845707d4 Mon Sep 17 00:00:00 2001 From: Joao Date: Fri, 25 Aug 2017 15:37:09 +0200 Subject: [PATCH 66/67] fix lint warnings --- src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index deef9d1102199..f28d8682be7c4 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -48,10 +48,10 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import * as platform from "vs/base/common/platform"; -import { domEvent } from "vs/base/browser/event"; -import { StandardKeyboardEvent } from "vs/base/browser/keyboardEvent"; -import { KeyMod, KeyCode } from "vs/base/common/keyCodes"; +import * as platform from 'vs/base/common/platform'; +import { domEvent } from 'vs/base/browser/event'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; // TODO@Joao // Need to subclass MenuItemActionItem in order to respect From adbb783ed0517af19cce53ba20509faeb9655c04 Mon Sep 17 00:00:00 2001 From: Joao Date: Fri, 25 Aug 2017 15:46:27 +0200 Subject: [PATCH 67/67] scm: keep `scmProvider` context key --- .../parts/scm/electron-browser/scmActivity.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts index 21aa0b1a9e0d4..d97d5a2c53204 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts @@ -12,6 +12,7 @@ import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; import { IActivityBarService, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; export class StatusUpdater implements IWorkbenchContribution { @@ -75,12 +76,16 @@ export class StatusBarController implements IWorkbenchContribution { private statusBarDisposable: IDisposable = EmptyDisposable; private focusDisposable: IDisposable = EmptyDisposable; + private focusedRepository: ISCMRepository | undefined = undefined; + private focusedProviderContextKey: IContextKey; private disposables: IDisposable[] = []; constructor( @ISCMService private scmService: ISCMService, - @IStatusbarService private statusbarService: IStatusbarService + @IStatusbarService private statusbarService: IStatusbarService, + @IContextKeyService contextKeyService: IContextKeyService ) { + this.focusedProviderContextKey = contextKeyService.createKey('scmProvider', void 0); this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); if (this.scmService.repositories.length > 0) { @@ -98,6 +103,12 @@ export class StatusBarController implements IWorkbenchContribution { const removeDisposable = onDidRemove(() => { disposable.dispose(); this.disposables = this.disposables.filter(d => d !== removeDisposable); + + if (this.scmService.repositories.length === 0) { + this.focusedProviderContextKey.set(undefined); + } else if (this.focusedRepository === repository) { + this.scmService.repositories[0].focus(); + } }); const disposable = combinedDisposable([changeDisposable, removeDisposable]); @@ -109,6 +120,11 @@ export class StatusBarController implements IWorkbenchContribution { } private onDidFocusRepository(repository: ISCMRepository): void { + if (this.focusedRepository !== repository) { + this.focusedRepository = repository; + this.focusedProviderContextKey.set(repository.provider.id); + } + this.focusDisposable.dispose(); this.focusDisposable = repository.provider.onDidChange(() => this.render(repository)); this.render(repository);