Merge from vscode 2cd495805cf99b31b6926f08ff4348124b2cf73d

This commit is contained in:
ADS Merger
2020-06-30 04:40:21 +00:00
committed by AzureDataStudio
parent a8a7559229
commit 1388493cc1
602 changed files with 16375 additions and 12940 deletions

View File

@@ -92,7 +92,7 @@ export class SCMStatusController implements IWorkbenchContribution {
}
private onDidAddRepository(repository: ISCMRepository): void {
const focusDisposable = repository.onDidFocus(() => this.focusRepository(repository));
const selectedDisposable = Event.filter(repository.onDidChangeSelection, selected => selected)(() => this.focusRepository(repository));
const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources);
const changeDisposable = onDidChange(() => this.renderActivityCount());
@@ -104,14 +104,12 @@ export class SCMStatusController implements IWorkbenchContribution {
if (this.scmService.repositories.length === 0) {
this.focusRepository(undefined);
} else if (this.focusedRepository === repository) {
this.scmService.repositories[0].focus();
}
this.renderActivityCount();
});
const disposable = combinedDisposable(focusDisposable, changeDisposable, removeDisposable);
const disposable = combinedDisposable(selectedDisposable, changeDisposable, removeDisposable);
this.disposables.push(disposable);
if (this.focusedRepository) {

View File

@@ -1,362 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/scmViewlet';
import { localize } from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { basename } from 'vs/base/common/resources';
import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { append, $, toggleClass } from 'vs/base/browser/dom';
import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list';
import { ISCMRepository } from 'vs/workbench/contrib/scm/common/scm';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IAction, Action } from 'vs/base/common/actions';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
import { Command } from 'vs/editor/common/modes';
import { renderCodicons, Codicon } from 'vs/base/common/codicons';
import { escape } from 'vs/base/common/strings';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export interface ISpliceEvent<T> {
index: number;
deleteCount: number;
elements: T[];
}
export interface IViewModel {
readonly repositories: ISCMRepository[];
readonly onDidSplice: Event<ISpliceEvent<ISCMRepository>>;
readonly visibleRepositories: ISCMRepository[];
readonly onDidChangeVisibleRepositories: Event<ISCMRepository[]>;
setVisibleRepositories(repositories: ISCMRepository[]): void;
isVisible(): boolean;
readonly onDidChangeVisibility: Event<boolean>;
}
class ProvidersListDelegate implements IListVirtualDelegate<ISCMRepository> {
getHeight(): number {
return 22;
}
getTemplateId(): string {
return 'provider';
}
}
class StatusBarAction extends Action {
constructor(
private command: Command,
private commandService: ICommandService
) {
super(`statusbaraction{${command.id}}`, command.title, '', true);
this.tooltip = command.tooltip || '';
}
run(): Promise<void> {
return this.commandService.executeCommand(this.command.id, ...(this.command.arguments || []));
}
}
class StatusBarActionViewItem extends ActionViewItem {
constructor(action: StatusBarAction) {
super(null, action, {});
}
updateLabel(): void {
if (this.options.label && this.label) {
this.label.innerHTML = renderCodicons(escape(this.getAction().label));
}
}
}
interface RepositoryTemplateData {
title: HTMLElement;
type: HTMLElement;
countContainer: HTMLElement;
count: CountBadge;
actionBar: ActionBar;
disposable: IDisposable;
templateDisposable: IDisposable;
}
class ProviderRenderer implements IListRenderer<ISCMRepository, RepositoryTemplateData> {
readonly templateId = 'provider';
private readonly _onDidRenderElement = new Emitter<ISCMRepository>();
readonly onDidRenderElement = this._onDidRenderElement.event;
constructor(
@ICommandService protected commandService: ICommandService,
@IThemeService protected themeService: IThemeService
) { }
renderTemplate(container: HTMLElement): RepositoryTemplateData {
const provider = append(container, $('.scm-provider'));
const name = append(provider, $('.name'));
const title = append(name, $('span.title'));
const type = append(name, $('span.type'));
const countContainer = append(provider, $('.count'));
const count = new CountBadge(countContainer);
const badgeStyler = attachBadgeStyler(count, this.themeService);
const actionBar = new ActionBar(provider, { actionViewItemProvider: a => new StatusBarActionViewItem(a as StatusBarAction) });
const disposable = Disposable.None;
const templateDisposable = combinedDisposable(actionBar, badgeStyler);
return { title, type, countContainer, count, actionBar, disposable, templateDisposable };
}
renderElement(repository: ISCMRepository, index: number, templateData: RepositoryTemplateData): void {
templateData.disposable.dispose();
const disposables = new DisposableStore();
if (repository.provider.rootUri) {
templateData.title.textContent = basename(repository.provider.rootUri);
templateData.type.textContent = repository.provider.label;
} else {
templateData.title.textContent = repository.provider.label;
templateData.type.textContent = '';
}
const actions: IAction[] = [];
const disposeActions = () => dispose(actions);
disposables.add({ dispose: disposeActions });
const update = () => {
disposeActions();
const commands = repository.provider.statusBarCommands || [];
actions.splice(0, actions.length, ...commands.map(c => new StatusBarAction(c, this.commandService)));
templateData.actionBar.clear();
templateData.actionBar.push(actions);
const count = repository.provider.count || 0;
toggleClass(templateData.countContainer, 'hidden', count === 0);
templateData.count.setCount(count);
this._onDidRenderElement.fire(repository);
};
disposables.add(repository.provider.onDidChange(update, null));
update();
templateData.disposable = disposables;
}
disposeTemplate(templateData: RepositoryTemplateData): void {
templateData.disposable.dispose();
templateData.templateDisposable.dispose();
}
}
export class MainPane extends ViewPane {
static readonly ID = 'scm.mainPane';
static readonly TITLE = localize('scm providers', "Source Control Providers");
private list!: WorkbenchList<ISCMRepository>;
constructor(
protected viewModel: IViewModel,
options: IViewPaneOptions,
@IKeybindingService protected keybindingService: IKeybindingService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService private readonly menuService: IMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@ICommandService private readonly commandService: ICommandService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
}
protected renderBody(container: HTMLElement): void {
super.renderBody(container);
const delegate = new ProvidersListDelegate();
const renderer = this.instantiationService.createInstance(ProviderRenderer);
const identityProvider = { getId: (r: ISCMRepository) => r.provider.id };
this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, container, delegate, [renderer], {
identityProvider,
horizontalScrolling: false,
overrideStyles: {
listBackground: SIDE_BAR_BACKGROUND
},
accessibilityProvider: {
getAriaLabel(r: ISCMRepository) {
return r.provider.label;
},
getWidgetAriaLabel() {
return MainPane.TITLE;
}
}
}) as WorkbenchList<ISCMRepository>;
this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null));
this._register(this.list.onDidChangeSelection(this.onListSelectionChange, this));
this._register(this.list.onDidChangeFocus(this.onListFocusChange, this));
this._register(this.list.onContextMenu(this.onListContextMenu, this));
this._register(this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this));
this._register(this.viewModel.onDidSplice(({ index, deleteCount, elements }) => this.splice(index, deleteCount, elements), null));
this.splice(0, 0, this.viewModel.repositories);
this._register(this.list);
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('scm.providers.visible')) {
this.updateBodySize();
}
}));
this.updateListSelection();
}
private splice(index: number, deleteCount: number, repositories: ISCMRepository[] = []): void {
this.list.splice(index, deleteCount, repositories);
const empty = this.list.length === 0;
toggleClass(this.element, 'empty', empty);
this.updateBodySize();
}
focus(): void {
this.list.domFocus();
}
protected layoutBody(height: number, width: number): void {
super.layoutBody(height, width);
this.list.layout(height, width);
}
private updateBodySize(): void {
const visibleCount = this.configurationService.getValue<number>('scm.providers.visible');
const empty = this.list.length === 0;
const size = Math.min(this.viewModel.repositories.length, visibleCount) * 22;
this.minimumBodySize = visibleCount === 0 ? 22 : size;
this.maximumBodySize = visibleCount === 0 ? Number.POSITIVE_INFINITY : empty ? Number.POSITIVE_INFINITY : size;
}
private onListContextMenu(e: IListContextMenuEvent<ISCMRepository>): void {
if (!e.element) {
return;
}
const repository = e.element;
const contextKeyService = this.contextKeyService.createScoped();
const scmProviderKey = contextKeyService.createKey<string | undefined>('scmProvider', undefined);
scmProviderKey.set(repository.provider.contextValue);
const menu = this.menuService.createMenu(MenuId.SCMSourceControl, contextKeyService);
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
const disposable = createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => g === 'inline');
menu.dispose();
contextKeyService.dispose();
if (repository.provider.rootUri) {
secondary.push(new Action('_openInTerminal', localize('open in terminal', "Open In Terminal"), undefined, true, async () => {
await this.commandService.executeCommand('openInTerminal', repository.provider.rootUri);
}));
}
if (secondary.length === 0) {
return;
}
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => secondary,
getActionsContext: () => repository.provider
});
disposable.dispose();
}
private onListSelectionChange(e: IListEvent<ISCMRepository>): void {
if (e.browserEvent && e.elements.length > 0) {
const scrollTop = this.list.scrollTop;
this.viewModel.setVisibleRepositories(e.elements);
this.list.scrollTop = scrollTop;
}
}
private onListFocusChange(e: IListEvent<ISCMRepository>): void {
if (e.browserEvent && e.elements.length > 0) {
e.elements[0].focus();
}
}
private updateListSelection(): void {
const set = new Set();
for (const repository of this.viewModel.visibleRepositories) {
set.add(repository);
}
const selection: number[] = [];
for (let i = 0; i < this.list.length; i++) {
if (set.has(this.list.element(i))) {
selection.push(i);
}
}
this.list.setSelection(selection);
if (selection.length > 0) {
this.list.setFocus([selection[0]]);
}
}
}
export class MainPaneDescriptor implements IViewDescriptor {
readonly id = MainPane.ID;
readonly name = MainPane.TITLE;
readonly containerIcon = Codicon.sourceControl.classNames;
readonly ctorDescriptor: SyncDescriptor<MainPane>;
readonly canToggleVisibility = true;
readonly hideByDefault = false;
readonly order = -1000;
readonly workspace = true;
readonly when = ContextKeyExpr.or(ContextKeyExpr.equals('config.scm.alwaysShowProviders', true), ContextKeyExpr.and(ContextKeyExpr.notEquals('scm.providerCount', 0), ContextKeyExpr.notEquals('scm.providerCount', 1)));
constructor(viewModel: IViewModel) {
this.ctorDescriptor = new SyncDescriptor(MainPane, [viewModel]);
}
}

View File

@@ -0,0 +1,216 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.scm-view {
height: 100%;
position: relative;
}
.scm-view .monaco-tl-contents > div {
margin-right: 12px;
overflow: hidden;
}
.scm-view .count {
display: flex;
}
.scm-view .count {
margin-left: 6px;
}
.scm-view .count.hidden {
display: none;
}
.scm-view .scm-provider {
display: flex;
flex-direction: column;
height: 100%;
align-items: center;
flex-flow: nowrap;
}
.scm-view .scm-provider > .label {
display: flex;
flex-shrink: 1;
overflow: hidden;
}
.scm-view .scm-provider > .actions {
flex: 1;
padding-left: 10px;
overflow: hidden;
justify-content: flex-end;
}
.scm-view .scm-provider > .actions .monaco-action-bar .action-item {
margin-left: 4px;
text-overflow: ellipsis;
display: flex;
align-items: center;
min-width: 14px;
}
.scm-view .scm-provider > .actions .monaco-action-bar .action-label {
text-overflow: ellipsis;
overflow: hidden;
min-width: 14px; /* for flex */
height: 100%;
}
.scm-view .scm-provider > .actions .monaco-action-bar .action-label .codicon {
vertical-align: sub;
display: inline-flex;
}
.scm-view .scm-provider > .actions .monaco-action-bar .action-item:last-of-type {
padding-right: 0;
}
.scm-view .scm-provider > .body {
flex-grow: 1;
}
.scm-view .monaco-list-row {
line-height: 22px;
}
.scm-view .monaco-list-row .resource-group {
display: flex;
height: 100%;
align-items: center;
}
.scm-view .monaco-list-row .resource-group > .name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.scm-view .monaco-list-row .resource {
display: flex;
height: 100%;
}
.scm-view .monaco-list-row .resource.faded {
opacity: 0.7;
}
.scm-view .monaco-list-row .resource > .name {
flex: 1;
overflow: hidden;
}
.scm-view .monaco-list-row .resource > .name > .monaco-icon-label::after {
padding-right: 3px;
}
.scm-view .monaco-list-row .resource > .name.strike-through > .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
text-decoration: line-through;
}
.scm-view .monaco-list-row .resource > .decoration-icon {
width: 16px;
height: 100%;
background-repeat: no-repeat;
background-position: 50% 50%;
margin-right: 8px;
}
.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
flex-grow: 100;
}
.scm-view .monaco-list .monaco-list-row .resource-group > .actions,
.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
display: none;
max-width: fit-content;
}
.scm-view .monaco-list .monaco-list-row:hover .resource-group > .actions,
.scm-view .monaco-list .monaco-list-row.selected .resource-group > .actions,
.scm-view .monaco-list .monaco-list-row.focused .resource-group > .actions,
.scm-view .monaco-list .monaco-list-row:hover .resource > .name > .monaco-icon-label > .actions,
.scm-view .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions,
.scm-view .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions,
.scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions {
display: block;
}
.scm-view.show-actions .scm-provider > .actions,
.scm-view.show-actions > .monaco-list .monaco-list-row .resource-group > .actions,
.scm-view.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
display: block;
}
.scm-view .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label,
.scm-view .monaco-list-row .resource-group > .actions .action-label {
width: 16px;
height: 100%;
background-position: 50% 50%;
background-repeat: no-repeat;
}
.scm-view .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon {
line-height: 22px;
}
.scm-view .scm-input {
height: 100%;
margin-left: 9px;
}
.scm-view .scm-editor {
box-sizing: border-box;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.scm-view .scm-editor.hidden {
display: none;
}
.scm-view .scm-editor-container {
position: relative;
box-sizing: border-box;
padding: 1px;
outline-offset: -1px;
}
.scm-editor-validation {
box-sizing: border-box;
font-size: 0.9em;
padding: 1px 3px;
display: block;
border-width: 1px;
border-style: solid;
border-top: none;
}
.scm-view .scm-editor-placeholder {
position: absolute;
pointer-events: none;
z-index: 1;
padding: 2px 4px 3px 4px;
box-sizing: border-box;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.scm-view .scm-editor-placeholder.hidden {
display: none;
}
.scm-view .scm-editor-container .monaco-editor-background,
.scm-view .scm-editor-container .monaco-editor,
.scm-view .scm-editor-container .mtk1 {
color: inherit;
}

View File

@@ -1,215 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .viewlet.scm-viewlet .collapsible.header .actions {
width: initial;
flex: 1;
}
.scm-viewlet .scm-status {
height: 100%;
position: relative;
}
.scm-viewlet .monaco-list-row > .scm-provider {
display: flex;
align-items: center;
flex-wrap: wrap;
height: 100%;
padding: 0 12px 0 20px;
}
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar {
flex: 1;
}
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-item {
padding: 0 4px;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
min-width: 14px;
}
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label {
text-overflow: ellipsis;
overflow: hidden;
min-width: 14px; /* minimum size of icons */
}
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label .codicon {
font-size: 14px;
vertical-align: sub;
display: inline-flex;
}
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-item:last-of-type {
padding-right: 0;
}
.scm-viewlet .scm-provider > .name,
.scm-viewlet .scm-provider > .count {
display: flex;
align-items: center;
}
.scm-viewlet .scm-provider > .count {
margin: 0 0.5em;
}
.scm-viewlet .scm-provider > .count.hidden {
display: none;
}
.scm-viewlet .scm-provider > .type,
.scm-viewlet .scm-provider > .name > .type {
opacity: 0.7;
margin-left: 0.5em;
font-size: 0.9em;
}
.scm-viewlet .monaco-list-row {
line-height: 22px;
}
.scm-viewlet .monaco-list-row .resource-group {
display: flex;
height: 100%;
align-items: center;
}
.scm-viewlet .monaco-list-row .resource-group > .name {
flex: 1;
font-size: 11px;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
}
.scm-viewlet .monaco-list-row .resource {
display: flex;
height: 100%;
}
.scm-viewlet .monaco-list-row .resource.faded {
opacity: 0.7;
}
.scm-viewlet .monaco-list-row .resource > .name {
flex: 1;
overflow: hidden;
}
.scm-viewlet .monaco-list-row .resource > .name.strike-through > .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
text-decoration: line-through;
}
.scm-viewlet .monaco-list-row .resource-group > .count {
padding: 0 12px 0 8px;
display: flex;
}
.scm-viewlet .monaco-list-row .resource > .decoration-icon {
width: 16px;
height: 100%;
background-repeat: no-repeat;
background-position: 50% 50%;
margin-right: 8px;
}
.scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
flex-grow: 100;
}
.scm-viewlet .monaco-list .monaco-list-row .resource-group > .actions,
.scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
display: none;
max-width: fit-content;
}
.scm-viewlet .monaco-list .monaco-list-row:hover .resource-group > .actions,
.scm-viewlet .monaco-list .monaco-list-row:hover .resource > .name > .monaco-icon-label > .actions,
.scm-viewlet .monaco-list .monaco-list-row.selected .resource-group > .actions,
.scm-viewlet .monaco-list .monaco-list-row.focused .resource-group > .actions,
.scm-viewlet .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions,
.scm-viewlet .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions,
.scm-viewlet .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions {
display: block;
}
.scm-viewlet .scm-status.show-actions > .monaco-list .monaco-list-row .resource-group > .actions,
.scm-viewlet .scm-status.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
display: block;
}
.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label,
.scm-viewlet .monaco-list-row .resource-group > .actions .action-label {
width: 16px;
height: 100%;
background-position: 50% 50%;
background-repeat: no-repeat;
}
.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon {
line-height: 22px;
}
.scm-viewlet .scm-editor {
box-sizing: border-box;
padding: 5px 12px 5px 16px;
}
.scm-viewlet .scm-editor.hidden {
display: none;
}
.scm-viewlet .scm-editor-container {
padding: 1px;
position: relative;
outline-offset: -1px;
}
.scm-viewlet .scm-editor-container > .scm-editor-validation {
position: absolute;
width: 100%;
z-index: 10;
left: 0px;
box-sizing: border-box;
font-size: 0.9em;
padding: 1px 3px;
display: none;
}
.scm-viewlet .scm-editor-container.synthetic-focus > .scm-editor-validation {
display: block;
}
.scm-viewlet .scm-editor-placeholder {
position: absolute;
pointer-events: none;
z-index: 1;
margin: 1px;
padding: 3px 4px;
box-sizing: border-box;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.scm-viewlet .scm-editor-placeholder.hidden {
display: none;
}
.scm-viewlet .scm-editor-container .monaco-editor-background,
.scm-viewlet .scm-editor-container .monaco-editor,
.scm-viewlet .scm-editor-container .mtk1 {
color: inherit;
}
.scm-viewlet .scm-status.show-file-icons.hide-arrows.tree-view-mode .monaco-tl-indent .indent-guide:first-child {
border: none;
}

View File

@@ -3,18 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/scmViewlet';
import 'vs/css!./media/scm';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { IAction } from 'vs/base/common/actions';
import { IAction, Action } from 'vs/base/common/actions';
import { createAndFillInContextMenuActions, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { ISCMProvider, ISCMResource, ISCMResourceGroup } from 'vs/workbench/contrib/scm/common/scm';
import { ISCMResource, ISCMResourceGroup, ISCMProvider, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm';
import { isSCMResource } from './util';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { equals } from 'vs/base/common/arrays';
import { ISplice } from 'vs/base/common/sequence';
import { ISplice, ISequence } from 'vs/base/common/sequence';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
function actionEquals(a: IAction, b: IAction): boolean {
return a.id === b.id;
@@ -35,11 +38,11 @@ export function getSCMResourceContextKey(resource: ISCMResourceGroup | ISCMResou
return isSCMResource(resource) ? resource.resourceGroup.id : resource.id;
}
export class SCMMenus implements IDisposable {
export class SCMRepositoryMenus implements IDisposable {
private contextKeyService: IContextKeyService;
private titleMenu: IMenu;
readonly titleMenu: IMenu;
private titleActionDisposable: IDisposable = Disposable.None;
private titleActions: IAction[] = [];
private titleSecondaryActions: IAction[] = [];
@@ -53,8 +56,9 @@ export class SCMMenus implements IDisposable {
private readonly disposables = new DisposableStore();
constructor(
provider: ISCMProvider | undefined,
readonly provider: ISCMProvider | undefined,
@IContextKeyService contextKeyService: IContextKeyService,
@ICommandService private readonly commandService: ICommandService,
@IMenuService private readonly menuService: IMenuService,
@IContextMenuService private readonly contextMenuService: IContextMenuService
) {
@@ -63,15 +67,14 @@ export class SCMMenus implements IDisposable {
if (provider) {
scmProviderKey.set(provider.contextValue);
this.onDidSpliceGroups({ start: 0, deleteCount: 0, toInsert: provider.groups.elements });
provider.groups.onDidSplice(this.onDidSpliceGroups, this, this.disposables);
this.onDidSpliceGroups({ start: 0, deleteCount: 0, toInsert: provider.groups.elements });
} else {
scmProviderKey.set('');
}
this.titleMenu = this.menuService.createMenu(MenuId.SCMTitle, this.contextKeyService);
this.disposables.add(this.titleMenu);
this.titleMenu.onDidChange(this.updateTitleActions, this, this.disposables);
this.updateTitleActions();
}
@@ -103,6 +106,34 @@ export class SCMMenus implements IDisposable {
return this.titleSecondaryActions;
}
getRepositoryContextActions(): IAction[] {
if (!this.provider) {
return [];
}
const contextKeyService = this.contextKeyService.createScoped();
const scmProviderKey = contextKeyService.createKey<string | undefined>('scmProvider', undefined);
scmProviderKey.set(this.provider.contextValue);
const menu = this.menuService.createMenu(MenuId.SCMSourceControl, contextKeyService);
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
const disposable = createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => g === 'inline');
disposable.dispose();
menu.dispose();
contextKeyService.dispose();
if (this.provider.rootUri) {
secondary.push(new Action('_openInTerminal', localize('open in terminal', "Open In Terminal"), undefined, true, async () => {
await this.commandService.executeCommand('openInTerminal', this.provider!.rootUri);
}));
}
return secondary;
}
getResourceGroupContextActions(group: ISCMResourceGroup): IAction[] {
return this.getActions(MenuId.SCMResourceGroupContext, group).secondary;
}
@@ -183,3 +214,49 @@ export class SCMMenus implements IDisposable {
this.resourceGroupMenuEntries.forEach(e => e.disposable.dispose());
}
}
export class SCMMenus {
private readonly disposables = new DisposableStore();
private readonly entries: { repository: ISCMRepository, dispose: () => void }[] = [];
private readonly menus = new Map<ISCMProvider, SCMRepositoryMenus>();
constructor(
repositories: ISequence<ISCMRepository>,
@IInstantiationService private instantiationService: IInstantiationService
) {
repositories.onDidSplice(this.onDidSplice, this, this.disposables);
this.onDidSplice({ start: 0, deleteCount: 0, toInsert: repositories.elements });
}
getRepositoryMenus(provider: ISCMProvider): SCMRepositoryMenus {
if (!this.menus.has(provider)) {
throw new Error('SCM Repository menu not found');
}
return this.menus.get(provider)!;
}
private onDidSplice({ start, deleteCount, toInsert }: ISplice<ISCMRepository>): void {
const entriesToInsert = toInsert.map(repository => {
const menus = this.instantiationService.createInstance(SCMRepositoryMenus, repository.provider);
const dispose = () => {
menus.dispose();
this.menus.delete(repository.provider);
};
this.menus.set(repository.provider, menus);
return { repository, dispose };
});
const deletedEntries = this.entries.splice(start, deleteCount, ...entriesToInsert);
for (const entry of deletedEntries) {
entry.dispose();
}
}
dispose(): void {
this.disposables.dispose();
}
}

View File

@@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { DirtyDiffWorkbenchController } from './dirtydiffDecorator';
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { VIEWLET_ID, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm';
import { VIEWLET_ID, ISCMRepository, ISCMService, VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
@@ -23,11 +23,12 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { SCMService } from 'vs/workbench/contrib/scm/common/scmService';
import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
import { SCMViewPaneContainer } from 'vs/workbench/contrib/scm/browser/scmViewlet';
import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views';
import { SCMViewPaneContainer } from 'vs/workbench/contrib/scm/browser/scmViewPaneContainer';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
import { Codicon } from 'vs/base/common/codicons';
import { SCMViewPane } from 'vs/workbench/contrib/scm/browser/scmViewPane';
class OpenSCMViewletAction extends ShowViewletAction {
@@ -48,16 +49,33 @@ ModesRegistry.registerLanguage({
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(DirtyDiffWorkbenchController, LifecyclePhase.Restored);
Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
const viewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
id: VIEWLET_ID,
name: localize('source control', "Source Control"),
ctorDescriptor: new SyncDescriptor(SCMViewPaneContainer),
storageId: 'workbench.scm.views.state',
icon: Codicon.sourceControl.classNames,
alwaysUseContainerInfo: true,
order: 12 // {{SQL CARBON EDIT}}
order: 12, // {{SQL CARBON EDIT}}
hideIfEmpty: true
}, ViewContainerLocation.Sidebar);
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
viewsRegistry.registerViewWelcomeContent(VIEW_PANE_ID, {
content: localize('no open repo', "No source control providers registered."),
when: 'default'
});
viewsRegistry.registerViews([{
id: VIEW_PANE_ID,
name: localize('source control', "Source Control"),
ctorDescriptor: new SyncDescriptor(SCMViewPane),
canToggleVisibility: true,
workspace: true,
canMoveView: true
}], viewContainer);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored);
@@ -80,16 +98,6 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
type: 'object',
scope: ConfigurationScope.RESOURCE,
properties: {
'scm.alwaysShowProviders': {
type: 'boolean',
description: localize('alwaysShowProviders', "Controls whether to show the Source Control Provider section even when there's only one Provider registered."),
default: false
},
'scm.providers.visible': {
type: 'number',
description: localize('providersVisible', "Controls how many providers are visible in the Source Control Provider section. Set to `0` to be able to manually resize the view."),
default: 10
},
'scm.diffDecorations': {
type: 'string',
enum: ['all', 'gutter', 'overview', 'minimap', 'none'],

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/scm';
import { localize } from 'vs/nls';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { VIEWLET_ID, ISCMService } from 'vs/workbench/contrib/scm/common/scm';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { SCMRepositoryMenus } from './menus';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { addClass } from 'vs/base/browser/dom';
export class SCMViewPaneContainer extends ViewPaneContainer {
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@ITelemetryService telemetryService: ITelemetryService,
@ISCMService protected scmService: ISCMService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IContextViewService protected contextViewService: IContextViewService,
@IKeybindingService protected keybindingService: IKeybindingService,
@INotificationService protected notificationService: INotificationService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IThemeService protected themeService: IThemeService,
@ICommandService protected commandService: ICommandService,
@IStorageService storageService: IStorageService,
@IConfigurationService configurationService: IConfigurationService,
@IExtensionService extensionService: IExtensionService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
) {
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
const menus = instantiationService.createInstance(SCMRepositoryMenus, undefined);
this._register(menus);
this._register(menus.onDidChangeTitle(this.updateTitleArea, this));
}
create(parent: HTMLElement): void {
super.create(parent);
addClass(parent, 'scm-viewlet');
}
getOptimalWidth(): number {
return 400;
}
getTitle(): string {
return localize('source control', "Source Control");
}
}

View File

@@ -1,313 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/scmViewlet';
import { localize } from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { MenuItemAction } from 'vs/platform/actions/common/actions';
import { IAction, IActionViewItem } from 'vs/base/common/actions';
import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { SCMMenus } from './menus';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IViewsRegistry, Extensions, IViewDescriptorService, IViewDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform';
import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane';
import { MainPaneDescriptor, MainPane, IViewModel } from 'vs/workbench/contrib/scm/browser/mainPane';
import { ViewPaneContainer, IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { debounce } from 'vs/base/common/decorators';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { addClass } from 'vs/base/browser/dom';
import { Codicon } from 'vs/base/common/codicons';
export interface ISpliceEvent<T> {
index: number;
deleteCount: number;
elements: T[];
}
export class EmptyPane extends ViewPane {
static readonly ID = 'workbench.scm';
static readonly TITLE = localize('scm', "Source Control");
constructor(
options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IInstantiationService instantiationService: IInstantiationService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
}
shouldShowWelcome(): boolean {
return true;
}
}
export class EmptyPaneDescriptor implements IViewDescriptor {
readonly id = EmptyPane.ID;
readonly name = EmptyPane.TITLE;
readonly containerIcon = Codicon.sourceControl.classNames;
readonly ctorDescriptor = new SyncDescriptor(EmptyPane);
readonly canToggleVisibility = true;
readonly hideByDefault = false;
readonly order = -1000;
readonly workspace = true;
readonly when = ContextKeyExpr.equals('scm.providerCount', 0);
}
export class SCMViewPaneContainer extends ViewPaneContainer implements IViewModel {
private menus: SCMMenus;
private _repositories: ISCMRepository[] = [];
private repositoryCountKey: IContextKey<number>;
private viewDescriptors: RepositoryViewDescriptor[] = [];
private readonly _onDidSplice = new Emitter<ISpliceEvent<ISCMRepository>>();
readonly onDidSplice: Event<ISpliceEvent<ISCMRepository>> = this._onDidSplice.event;
private _height: number | undefined = undefined;
get height(): number | undefined { return this._height; }
get repositories(): ISCMRepository[] {
return this._repositories;
}
get visibleRepositories(): ISCMRepository[] {
return this.panes.filter(pane => pane instanceof RepositoryPane)
.map(pane => (pane as RepositoryPane).repository);
}
get onDidChangeVisibleRepositories(): Event<ISCMRepository[]> {
const modificationEvent = Event.debounce(Event.any(this.viewContainerModel.onDidAddVisibleViewDescriptors, this.viewContainerModel.onDidRemoveVisibleViewDescriptors), () => null, 0);
return Event.map(modificationEvent, () => this.visibleRepositories);
}
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@ITelemetryService telemetryService: ITelemetryService,
@ISCMService protected scmService: ISCMService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IContextViewService protected contextViewService: IContextViewService,
@IKeybindingService protected keybindingService: IKeybindingService,
@INotificationService protected notificationService: INotificationService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IThemeService protected themeService: IThemeService,
@ICommandService protected commandService: ICommandService,
@IStorageService storageService: IStorageService,
@IConfigurationService configurationService: IConfigurationService,
@IExtensionService extensionService: IExtensionService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IContextKeyService contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
) {
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
this.menus = instantiationService.createInstance(SCMMenus, undefined);
this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this));
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
viewsRegistry.registerViewWelcomeContent(EmptyPane.ID, {
content: localize('no open repo', "No source control providers registered."),
when: 'default'
});
viewsRegistry.registerViews([new EmptyPaneDescriptor()], this.viewContainer);
viewsRegistry.registerViews([new MainPaneDescriptor(this)], this.viewContainer);
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('scm.alwaysShowProviders') && configurationService.getValue<boolean>('scm.alwaysShowProviders')) {
this.viewContainerModel.setVisible(MainPane.ID, true);
}
}));
this.repositoryCountKey = contextKeyService.createKey('scm.providerCount', 0);
this._register(this.viewContainerModel.onDidAddVisibleViewDescriptors(this.onDidShowView, this));
this._register(this.viewContainerModel.onDidRemoveVisibleViewDescriptors(this.onDidHideView, this));
}
create(parent: HTMLElement): void {
super.create(parent);
addClass(parent, 'scm-viewlet');
this._register(this.scmService.onDidAddRepository(this.onDidAddRepository, this));
this._register(this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this));
this.scmService.repositories.forEach(r => this.onDidAddRepository(r));
}
private onDidAddRepository(repository: ISCMRepository): void {
const index = this._repositories.length;
this._repositories.push(repository);
const viewDescriptor = new RepositoryViewDescriptor(repository, false);
Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).registerViews([viewDescriptor], this.viewContainer);
this.viewDescriptors.push(viewDescriptor);
this._onDidSplice.fire({ index, deleteCount: 0, elements: [repository] });
this.updateTitleArea();
this.onDidChangeRepositories();
}
private onDidRemoveRepository(repository: ISCMRepository): void {
const index = this._repositories.indexOf(repository);
if (index === -1) {
return;
}
Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).deregisterViews([this.viewDescriptors[index]], this.viewContainer);
this._repositories.splice(index, 1);
this.viewDescriptors.splice(index, 1);
this._onDidSplice.fire({ index, deleteCount: 1, elements: [] });
this.updateTitleArea();
this.onDidChangeRepositories();
}
private onDidChangeRepositories(): void {
this.repositoryCountKey.set(this.repositories.length);
}
private onDidShowView(e: IAddedViewDescriptorRef[]): void {
for (const ref of e) {
if (ref.viewDescriptor instanceof RepositoryViewDescriptor) {
ref.viewDescriptor.repository.setSelected(true);
}
}
}
private onDidHideView(e: IViewDescriptorRef[]): void {
for (const ref of e) {
if (ref.viewDescriptor instanceof RepositoryViewDescriptor) {
ref.viewDescriptor.repository.setSelected(false);
}
}
this.afterOnDidHideView();
}
@debounce(0)
private afterOnDidHideView(): void {
if (this.repositoryCountKey.get()! > 0 && this.viewDescriptors.every(d => !this.viewContainerModel.isVisible(d.id))) {
this.viewContainerModel.setVisible(this.viewDescriptors[0].id, true);
}
}
focus(): void {
const repository = this.visibleRepositories[0];
if (repository) {
const pane = this.panes
.filter(pane => pane instanceof RepositoryPane && pane.repository === repository)[0] as RepositoryPane | undefined;
if (pane) {
pane.focus();
} else {
super.focus();
}
} else {
super.focus();
}
}
getOptimalWidth(): number {
return 400;
}
getTitle(): string {
const title = localize('source control', "Source Control");
if (this.visibleRepositories.length === 1) {
const [repository] = this.repositories;
return localize('viewletTitle', "{0}: {1}", title, repository.provider.label);
} else {
return title;
}
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
if (!(action instanceof MenuItemAction)) {
return undefined;
}
return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
}
getActions(): IAction[] {
if (this.repositories.length > 0) {
return super.getActions();
}
return this.menus.getTitleActions();
}
getSecondaryActions(): IAction[] {
if (this.repositories.length > 0) {
return super.getSecondaryActions();
}
return this.menus.getTitleSecondaryActions();
}
getActionsContext(): any {
if (this.visibleRepositories.length === 1) {
return this.repositories[0].provider;
}
}
setVisibleRepositories(repositories: ISCMRepository[]): void {
const visibleViewDescriptors = this.viewContainerModel.visibleViewDescriptors;
const toSetVisible = this.viewContainerModel.activeViewDescriptors
.filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) > -1 && visibleViewDescriptors.indexOf(d) === -1);
const toSetInvisible = visibleViewDescriptors
.filter((d): d is RepositoryViewDescriptor => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) === -1);
let size: number | undefined;
const oneToOne = toSetVisible.length === 1 && toSetInvisible.length === 1;
for (const viewDescriptor of toSetInvisible) {
if (oneToOne) {
const pane = this.panes.filter(pane => pane.id === viewDescriptor.id)[0];
if (pane) {
size = this.getPaneSize(pane);
}
}
this.viewContainerModel.setVisible(viewDescriptor.id, false);
}
for (const viewDescriptor of toSetVisible) {
this.viewContainerModel.setVisible(viewDescriptor.id, true, size);
}
}
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ISCMResource, ISCMRepository, ISCMResourceGroup } from 'vs/workbench/contrib/scm/common/scm';
import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput } from 'vs/workbench/contrib/scm/common/scm';
import { IMenu } from 'vs/platform/actions/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IDisposable, Disposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
@@ -15,6 +15,10 @@ export function isSCMRepository(element: any): element is ISCMRepository {
return !!(element as ISCMRepository).provider && typeof (element as ISCMRepository).setSelected === 'function';
}
export function isSCMInput(element: any): element is ISCMInput {
return !!(element as ISCMInput).validateInput && typeof (element as ISCMInput).value === 'string';
}
export function isSCMResourceGroup(element: any): element is ISCMResourceGroup {
return !!(element as ISCMResourceGroup).provider && !!(element as ISCMResourceGroup).elements;
}
@@ -23,31 +27,42 @@ export function isSCMResource(element: any): element is ISCMResource {
return !!(element as ISCMResource).sourceUri && isSCMResourceGroup((element as ISCMResource).resourceGroup);
}
export function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: ActionBar): IDisposable {
const compareActions = (a: IAction, b: IAction) => a.id === b.id;
export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], secondary: IAction[]) => void, isPrimaryGroup?: (group: string) => boolean): IDisposable {
let cachedDisposable: IDisposable = Disposable.None;
let cachedPrimary: IAction[] = [];
let cachedSecondary: IAction[] = [];
const updateActions = () => {
const primary: IAction[] = [];
const secondary: IAction[] = [];
const disposable = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary, secondary }, g => /^inline/.test(g));
const disposable = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary, secondary }, isPrimaryGroup);
if (equals(cachedPrimary, primary, (a, b) => a.id === b.id)) {
if (equals(cachedPrimary, primary, compareActions) && equals(cachedSecondary, secondary, compareActions)) {
disposable.dispose();
return;
}
cachedDisposable = disposable;
cachedPrimary = primary;
cachedSecondary = secondary;
actionBar.clear();
actionBar.push(primary, { icon: true, label: false });
callback(primary, secondary);
};
updateActions();
return combinedDisposable(menu.onDidChange(updateActions), toDisposable(() => {
cachedDisposable.dispose();
}));
return combinedDisposable(
menu.onDidChange(updateActions),
toDisposable(() => cachedDisposable.dispose())
);
}
export function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: ActionBar): IDisposable {
return connectPrimaryMenu(menu, (primary) => {
actionBar.clear();
actionBar.push(primary, { icon: true, label: false });
}, g => /^inline/.test(g));
}

View File

@@ -11,6 +11,7 @@ import { Command } from 'vs/editor/common/modes';
import { ISequence } from 'vs/base/common/sequence';
export const VIEWLET_ID = 'workbench.view.scm';
export const VIEW_PANE_ID = 'workbench.scm';
export interface IBaselineResourceProvider {
getBaselineResource(resource: URI): Promise<URI>;
@@ -79,6 +80,8 @@ export interface IInputValidator {
}
export interface ISCMInput {
readonly repository: ISCMRepository;
value: string;
readonly onDidChange: Event<string>;
@@ -93,12 +96,10 @@ export interface ISCMInput {
}
export interface ISCMRepository extends IDisposable {
readonly onDidFocus: Event<void>;
readonly selected: boolean;
readonly onDidChangeSelection: Event<boolean>;
readonly provider: ISCMProvider;
readonly input: ISCMInput;
focus(): void;
setSelected(selected: boolean): void;
}
@@ -107,10 +108,7 @@ export interface ISCMService {
readonly _serviceBrand: undefined;
readonly onDidAddRepository: Event<ISCMRepository>;
readonly onDidRemoveRepository: Event<ISCMRepository>;
readonly repositories: ISCMRepository[];
readonly selectedRepositories: ISCMRepository[];
readonly onDidChangeSelectedRepositories: Event<ISCMRepository[]>;
registerSCMProvider(provider: ISCMProvider): ISCMRepository;
}

View File

@@ -7,7 +7,6 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator } from './scm';
import { ILogService } from 'vs/platform/log/common/log';
import { equals } from 'vs/base/common/arrays';
class SCMInput implements ISCMInput {
@@ -70,13 +69,12 @@ class SCMInput implements ISCMInput {
private readonly _onDidChangeValidateInput = new Emitter<void>();
readonly onDidChangeValidateInput: Event<void> = this._onDidChangeValidateInput.event;
constructor(readonly repository: ISCMRepository) { }
}
class SCMRepository implements ISCMRepository {
private readonly _onDidFocus = new Emitter<void>();
readonly onDidFocus: Event<void> = this._onDidFocus.event;
private _selected = false;
get selected(): boolean {
return this._selected;
@@ -85,17 +83,13 @@ class SCMRepository implements ISCMRepository {
private readonly _onDidChangeSelection = new Emitter<boolean>();
readonly onDidChangeSelection: Event<boolean> = this._onDidChangeSelection.event;
readonly input: ISCMInput = new SCMInput();
readonly input: ISCMInput = new SCMInput(this);
constructor(
public readonly provider: ISCMProvider,
private disposable: IDisposable
) { }
focus(): void {
this._onDidFocus.fire();
}
setSelected(selected: boolean): void {
if (this._selected === selected) {
return;
@@ -119,11 +113,10 @@ export class SCMService implements ISCMService {
private _repositories: ISCMRepository[] = [];
get repositories(): ISCMRepository[] { return [...this._repositories]; }
private _selectedRepositories: ISCMRepository[] = [];
get selectedRepositories(): ISCMRepository[] { return [...this._selectedRepositories]; }
private _selectedRepository: ISCMRepository | undefined;
private readonly _onDidChangeSelectedRepositories = new Emitter<ISCMRepository[]>();
readonly onDidChangeSelectedRepositories: Event<ISCMRepository[]> = this._onDidChangeSelectedRepositories.event;
private readonly _onDidSelectRepository = new Emitter<ISCMRepository | undefined>();
readonly onDidSelectRepository: Event<ISCMRepository | undefined> = this._onDidSelectRepository.event;
private readonly _onDidAddProvider = new Emitter<ISCMRepository>();
readonly onDidAddRepository: Event<ISCMRepository> = this._onDidAddProvider.event;
@@ -153,26 +146,31 @@ export class SCMService implements ISCMService {
this._providerIds.delete(provider.id);
this._repositories.splice(index, 1);
this._onDidRemoveProvider.fire(repository);
this.onDidChangeSelection();
if (this._selectedRepository === repository) {
this.select(this._repositories[0]);
}
});
const repository = new SCMRepository(provider, disposable);
const selectedDisposable = repository.onDidChangeSelection(this.onDidChangeSelection, this);
const selectedDisposable = Event.map(Event.filter(repository.onDidChangeSelection, selected => selected), _ => repository)(this.select, this);
this._repositories.push(repository);
this._onDidAddProvider.fire(repository);
if (!this._selectedRepository) {
repository.setSelected(true);
}
return repository;
}
private onDidChangeSelection(): void {
const selectedRepositories = this._repositories.filter(r => r.selected);
if (equals(this._selectedRepositories, selectedRepositories)) {
return;
private select(repository: ISCMRepository | undefined): void {
if (this._selectedRepository) {
this._selectedRepository.setSelected(false);
}
this._selectedRepositories = this._repositories.filter(r => r.selected);
this._onDidChangeSelectedRepositories.fire(this.selectedRepositories);
this._selectedRepository = repository;
this._onDidSelectRepository.fire(this._selectedRepository);
}
}