Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998 (#7880)

* Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998

* fix pipelines

* fix strict-null-checks

* add missing files
This commit is contained in:
Anthony Dresser
2019-10-21 22:12:22 -07:00
committed by GitHub
parent 7c9be74970
commit 1e22f47304
913 changed files with 18898 additions and 16536 deletions

View File

@@ -17,7 +17,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm';
import { ISCMService, ISCMRepository, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { registerThemingParticipant, ITheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
@@ -37,7 +37,7 @@ import { IDiffEditorOptions, EditorOption } from 'vs/editor/common/config/editor
import { Action, IAction, ActionRunner } from 'vs/base/common/actions';
import { IActionBarOptions, ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { basename } from 'vs/base/common/resources';
import { basename, isEqualOrParent } from 'vs/base/common/resources';
import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon';
@@ -149,7 +149,7 @@ function getChangeTypeColor(theme: ITheme, changeType: ChangeType): Color | unde
}
}
function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor | undefined {
function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor | null {
const diffEditors = accessor.get(ICodeEditorService).listDiffEditors();
for (const diffEditor of diffEditors) {
@@ -163,11 +163,11 @@ function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor |
class DirtyDiffWidget extends PeekViewWidget {
private diffEditor: EmbeddedDiffEditorWidget;
private diffEditor!: EmbeddedDiffEditorWidget;
private title: string;
private menu: IMenu;
private index: number;
private change: IChange;
private index: number = 0;
private change: IChange | undefined;
private height: number | undefined = undefined;
private contextKeyService: IContextKeyService;
@@ -320,7 +320,7 @@ class DirtyDiffWidget extends PeekViewWidget {
super._doLayoutBody(height, width);
this.diffEditor.layout({ height, width });
if (typeof this.height === 'undefined') {
if (typeof this.height === 'undefined' && this.change) {
this.revealChange(this.change);
}
@@ -556,7 +556,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
export class DirtyDiffController extends Disposable implements IEditorContribution {
private static readonly ID = 'editor.contrib.dirtydiff';
public static readonly ID = 'editor.contrib.dirtydiff';
static get(editor: ICodeEditor): DirtyDiffController {
return editor.getContribution<DirtyDiffController>(DirtyDiffController.ID);
@@ -567,7 +567,7 @@ export class DirtyDiffController extends Disposable implements IEditorContributi
private model: DirtyDiffModel | null = null;
private widget: DirtyDiffWidget | null = null;
private currentIndex: number = -1;
private readonly isDirtyDiffVisible: IContextKey<boolean>;
private readonly isDirtyDiffVisible!: IContextKey<boolean>;
private session: IDisposable = Disposable.None;
private mouseDownInfo: { lineNumber: number } | null = null;
private enabled = false;
@@ -588,10 +588,6 @@ export class DirtyDiffController extends Disposable implements IEditorContributi
}
}
getId(): string {
return DirtyDiffController.ID;
}
canNavigate(): boolean {
return this.currentIndex === -1 || (!!this.model && this.model.changes.length > 1);
}
@@ -676,7 +672,10 @@ export class DirtyDiffController extends Disposable implements IEditorContributi
const disposables = new DisposableStore();
disposables.add(Event.once(this.widget.onDidClose)(this.close, this));
disposables.add(model.onDidChange(this.onDidModelChange, this));
Event.chain(model.onDidChange)
.filter(e => e.diff.length > 0)
.map(e => e.diff)
.event(this.onDidModelChange, this, disposables);
disposables.add(this.widget);
disposables.add(toDisposable(() => {
@@ -951,9 +950,26 @@ function compareChanges(a: IChange, b: IChange): number {
return a.originalEndLineNumber - b.originalEndLineNumber;
}
function createProviderComparer(uri: URI): (a: ISCMProvider, b: ISCMProvider) => number {
return (a, b) => {
const aIsParent = isEqualOrParent(uri, a.rootUri!);
const bIsParent = isEqualOrParent(uri, b.rootUri!);
if (aIsParent && bIsParent) {
return a.rootUri!.fsPath.length - b.rootUri!.fsPath.length;
} else if (aIsParent) {
return -1;
} else if (bIsParent) {
return 1;
} else {
return 0;
}
};
}
export class DirtyDiffModel extends Disposable {
private _originalModel: ITextModel | null;
private _originalModel: ITextModel | null = null;
get original(): ITextModel | null { return this._originalModel; }
get modified(): ITextModel | null { return this._editorModel; }
@@ -962,13 +978,11 @@ export class DirtyDiffModel extends Disposable {
private repositoryDisposables = new Set<IDisposable>();
private readonly originalModelDisposables = this._register(new DisposableStore());
private readonly _onDidChange = new Emitter<ISplice<IChange>[]>();
readonly onDidChange: Event<ISplice<IChange>[]> = this._onDidChange.event;
private readonly _onDidChange = new Emitter<{ changes: IChange[], diff: ISplice<IChange>[] }>();
readonly onDidChange: Event<{ changes: IChange[], diff: ISplice<IChange>[] }> = this._onDidChange.event;
private _changes: IChange[] = [];
get changes(): IChange[] {
return this._changes;
}
get changes(): IChange[] { return this._changes; }
private _editorModel: ITextModel | null;
@@ -1022,10 +1036,7 @@ export class DirtyDiffModel extends Disposable {
const diff = sortedDiff(this._changes, changes, compareChanges);
this._changes = changes;
if (diff.length > 0) {
this._onDidChange.fire(diff);
}
this._onDidChange.fire({ changes, diff });
});
}
@@ -1082,13 +1093,25 @@ export class DirtyDiffModel extends Disposable {
});
}
private getOriginalResource(): Promise<URI | null> {
private async getOriginalResource(): Promise<URI | null> {
if (!this._editorModel) {
return Promise.resolve(null);
}
const uri = this._editorModel.uri;
return first(this.scmService.repositories.map(r => () => r.provider.getOriginalResource(uri)));
const providers = this.scmService.repositories.map(r => r.provider);
const rootedProviders = providers.filter(p => !!p.rootUri);
rootedProviders.sort(createProviderComparer(uri));
const result = await first(rootedProviders.map(p => () => p.getOriginalResource(uri)));
if (result) {
return result;
}
const nonRootedProviders = providers.filter(p => !p.rootUri);
return first(nonRootedProviders.map(p => () => p.getOriginalResource(uri)));
}
findNextClosestChange(lineNumber: number, inclusive = true): number {
@@ -1177,6 +1200,10 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor
const onDidChangeDiffWidthConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorationsGutterWidth'));
onDidChangeDiffWidthConfiguration(this.onDidChangeDiffWidthConfiguration, this);
this.onDidChangeDiffWidthConfiguration();
const onDidChangeDiffVisibilityConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorationsGutterVisibility'));
onDidChangeDiffVisibilityConfiguration(this.onDidChangeDiffVisibiltiyConfiguration, this);
this.onDidChangeDiffVisibiltiyConfiguration();
}
private onDidChangeConfiguration(): void {
@@ -1199,6 +1226,16 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor
this.stylesheet.innerHTML = `.monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${width}px;}`;
}
private onDidChangeDiffVisibiltiyConfiguration(): void {
const visibility = this.configurationService.getValue<string>('scm.diffDecorationsGutterVisibility');
this.stylesheet.innerHTML = `
.monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added, .monaco-editor .dirty-diff-deleted {
opacity: ${visibility === 'always' ? 1 : 0};
}
`;
}
private enable(): void {
if (this.enabled) {
this.disable();
@@ -1278,7 +1315,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor
}
}
registerEditorContribution(DirtyDiffController);
registerEditorContribution(DirtyDiffController.ID, DirtyDiffController);
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const editorGutterModifiedBackgroundColor = theme.getColor(editorGutterModifiedBackground);
@@ -1286,10 +1323,14 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
collector.addRule(`
.monaco-editor .dirty-diff-modified {
border-left: 3px solid ${editorGutterModifiedBackgroundColor};
transition: opacity 0.5s;
}
.monaco-editor .dirty-diff-modified:before {
background: ${editorGutterModifiedBackgroundColor};
}
.monaco-editor .margin:hover .dirty-diff-modified {
opacity: 1;
}
`);
}
@@ -1298,10 +1339,14 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
collector.addRule(`
.monaco-editor .dirty-diff-added {
border-left: 3px solid ${editorGutterAddedBackgroundColor};
transition: opacity 0.5s;
}
.monaco-editor .dirty-diff-added:before {
background: ${editorGutterAddedBackgroundColor};
}
.monaco-editor .margin:hover .dirty-diff-added {
opacity: 1;
}
`);
}
@@ -1310,10 +1355,14 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
collector.addRule(`
.monaco-editor .dirty-diff-deleted:after {
border-left: 4px solid ${editorGutteDeletedBackgroundColor};
transition: opacity 0.5s;
}
.monaco-editor .dirty-diff-deleted:before {
background: ${editorGutteDeletedBackgroundColor};
}
.monaco-editor .margin:hover .dirty-diff-added {
opacity: 1;
}
`);
}
});

View File

@@ -10,7 +10,6 @@ import { basename } from 'vs/base/common/resources';
import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { append, $, toggleClass } from 'vs/base/browser/dom';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list';
import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
@@ -26,7 +25,7 @@ import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionba
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
import { Command } from 'vs/editor/common/modes';
import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewDescriptor } from 'vs/workbench/common/views';
@@ -82,8 +81,8 @@ class StatusBarActionViewItem extends ActionViewItem {
}
updateLabel(): void {
if (this.options.label) {
this.label.innerHTML = renderOcticons(this.getAction().label);
if (this.options.label && this.label) {
this.label.innerHTML = renderCodicons(this.getAction().label);
}
}
}
@@ -173,7 +172,7 @@ export class MainPanel extends ViewletPanel {
static readonly ID = 'scm.mainPanel';
static readonly TITLE = localize('scm providers', "Source Control Providers");
private list: List<ISCMRepository>;
private list!: WorkbenchList<ISCMRepository>;
constructor(
protected viewModel: IViewModel,

View File

@@ -41,8 +41,25 @@
}
.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;
}
.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label .codicon {
font-size: 14px;
}
.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 {
@@ -97,7 +114,7 @@
}
.scm-viewlet .monaco-list-row .resource-group > .count {
padding: 0 8px;
padding: 0 12px 0 8px;
display: flex;
}
@@ -106,6 +123,7 @@
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 {
@@ -142,7 +160,7 @@
.scm-viewlet .scm-editor {
box-sizing: border-box;
padding: 5px 9px 5px 16px;
padding: 5px 12px 5px 16px;
}
.scm-viewlet .scm-editor.hidden {
@@ -170,3 +188,7 @@
width: 8px !important;
margin-right: 0 !important;
}
.scm-viewlet .scm-status.show-file-icons.hide-arrows.tree-view-mode .monaco-tl-indent .indent-guide:first-child {
border: none;
}

View File

@@ -32,12 +32,12 @@ import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { format } from 'vs/base/common/strings';
import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ThrottledDelayer } from 'vs/base/common/async';
import { ThrottledDelayer, disposableTimeout } from 'vs/base/common/async';
import { INotificationService } from 'vs/platform/notification/common/notification';
import * as platform from 'vs/base/common/platform';
import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree';
import { ISequence, ISplice } from 'vs/base/common/sequence';
import { ResourceTree, IBranchNode, INode } from 'vs/base/common/resourceTree';
import { ObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree';
import { Iterator } from 'vs/base/common/iterator';
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
@@ -50,8 +50,10 @@ import { localize } from 'vs/nls';
import { flatten } from 'vs/base/common/arrays';
import { memoize } from 'vs/base/common/decorators';
import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { toResource, SideBySideEditor } from 'vs/workbench/common/editor';
type TreeElement = ISCMResourceGroup | IBranchNode<ISCMResource, ISCMResourceGroup> | ISCMResource;
type TreeElement = ISCMResourceGroup | IResourceNode<ISCMResource, ISCMResourceGroup> | ISCMResource;
interface ResourceGroupTemplate {
readonly name: HTMLElement;
@@ -63,7 +65,7 @@ interface ResourceGroupTemplate {
class ResourceGroupRenderer implements ICompressibleTreeRenderer<ISCMResourceGroup, FuzzyScore, ResourceGroupTemplate> {
static TEMPLATE_ID = 'resource group';
static readonly TEMPLATE_ID = 'resource group';
get templateId(): string { return ResourceGroupRenderer.TEMPLATE_ID; }
constructor(
@@ -130,11 +132,11 @@ interface ResourceTemplate {
class MultipleSelectionActionRunner extends ActionRunner {
constructor(private getSelectedResources: () => (ISCMResource | IBranchNode<ISCMResource, ISCMResourceGroup>)[]) {
constructor(private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[]) {
super();
}
runAction(action: IAction, context: ISCMResource | IBranchNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
if (!(action instanceof MenuItemAction)) {
return super.runAction(action, context);
}
@@ -142,21 +144,21 @@ class MultipleSelectionActionRunner extends ActionRunner {
const selection = this.getSelectedResources();
const contextIsSelected = selection.some(s => s === context);
const actualContext = contextIsSelected ? selection : [context];
const args = flatten(actualContext.map(e => ResourceTree.isBranchNode(e) ? ResourceTree.collect(e) : [e]));
const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e]));
return action.run(...args);
}
}
class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IBranchNode<ISCMResource, ISCMResourceGroup>, FuzzyScore, ResourceTemplate> {
class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore, ResourceTemplate> {
static TEMPLATE_ID = 'resource';
static readonly TEMPLATE_ID = 'resource';
get templateId(): string { return ResourceRenderer.TEMPLATE_ID; }
constructor(
private viewModelProvider: () => ViewModel,
private labels: ResourceLabels,
private actionViewItemProvider: IActionViewItemProvider,
private getSelectedResources: () => (ISCMResource | IBranchNode<ISCMResource, ISCMResourceGroup>)[],
private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
private themeService: IThemeService,
private menus: SCMMenus
) { }
@@ -177,16 +179,17 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IBran
return { element, name, fileLabel, decorationIcon, actionBar, elementDisposables: Disposable.None, disposables };
}
renderElement(node: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<IBranchNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
renderElement(node: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
template.elementDisposables.dispose();
const elementDisposables = new DisposableStore();
const resourceOrFolder = node.element;
const theme = this.themeService.getTheme();
const icon = !ResourceTree.isBranchNode(resourceOrFolder) && (theme.type === LIGHT ? resourceOrFolder.decorations.icon : resourceOrFolder.decorations.iconDark);
const iconResource = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.element : resourceOrFolder;
const icon = iconResource && (theme.type === LIGHT ? iconResource.decorations.icon : iconResource.decorations.iconDark);
const uri = ResourceTree.isBranchNode(resourceOrFolder) ? resourceOrFolder.uri : resourceOrFolder.sourceUri;
const fileKind = ResourceTree.isBranchNode(resourceOrFolder) ? FileKind.FOLDER : FileKind.FILE;
const uri = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.uri : resourceOrFolder.sourceUri;
const fileKind = ResourceTree.isResourceNode(resourceOrFolder) ? FileKind.FOLDER : FileKind.FILE;
const viewModel = this.viewModelProvider();
template.fileLabel.setFile(uri, {
@@ -199,17 +202,23 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IBran
template.actionBar.clear();
template.actionBar.context = resourceOrFolder;
if (ResourceTree.isBranchNode(resourceOrFolder)) {
elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceFolderMenu(resourceOrFolder.context), template.actionBar));
removeClass(template.name, 'strike-through');
removeClass(template.element, 'faded');
if (ResourceTree.isResourceNode(resourceOrFolder)) {
if (resourceOrFolder.element) {
elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resourceOrFolder.element.resourceGroup), template.actionBar));
toggleClass(template.name, 'strike-through', resourceOrFolder.element.decorations.strikeThrough);
toggleClass(template.element, 'faded', resourceOrFolder.element.decorations.faded);
} else {
elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceFolderMenu(resourceOrFolder.context), template.actionBar));
removeClass(template.name, 'strike-through');
removeClass(template.element, 'faded');
}
} else {
elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resourceOrFolder.resourceGroup), template.actionBar));
toggleClass(template.name, 'strike-through', resourceOrFolder.decorations.strikeThrough);
toggleClass(template.element, 'faded', resourceOrFolder.decorations.faded);
}
const tooltip = !ResourceTree.isBranchNode(resourceOrFolder) && resourceOrFolder.decorations.tooltip || '';
const tooltip = !ResourceTree.isResourceNode(resourceOrFolder) && resourceOrFolder.decorations.tooltip || '';
if (icon) {
template.decorationIcon.style.display = '';
@@ -225,15 +234,15 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IBran
template.elementDisposables = elementDisposables;
}
disposeElement(resource: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<IBranchNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
disposeElement(resource: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
template.elementDisposables.dispose();
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IBranchNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
template.elementDisposables.dispose();
const elementDisposables = new DisposableStore();
const compressed = node.element as ICompressedTreeNode<IBranchNode<ISCMResource, ISCMResourceGroup>>;
const compressed = node.element as ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>;
const folder = compressed.elements[compressed.elements.length - 1];
const label = compressed.elements.map(e => e.name).join('/');
@@ -259,7 +268,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IBran
template.elementDisposables = elementDisposables;
}
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IBranchNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
template.elementDisposables.dispose();
}
@@ -274,7 +283,7 @@ class ProviderListDelegate implements IListVirtualDelegate<TreeElement> {
getHeight() { return 22; }
getTemplateId(element: TreeElement) {
if (ResourceTree.isBranchNode(element) || isSCMResource(element)) {
if (ResourceTree.isResourceNode(element) || isSCMResource(element)) {
return ResourceRenderer.TEMPLATE_ID;
} else {
return ResourceGroupRenderer.TEMPLATE_ID;
@@ -285,7 +294,7 @@ class ProviderListDelegate implements IListVirtualDelegate<TreeElement> {
class SCMTreeFilter implements ITreeFilter<TreeElement> {
filter(element: TreeElement): boolean {
if (ResourceTree.isBranchNode(element)) {
if (ResourceTree.isResourceNode(element)) {
return true;
} else if (isSCMResourceGroup(element)) {
return element.elements.length > 0 || !element.hideWhenEmpty;
@@ -311,15 +320,15 @@ export class SCMTreeSorter implements ITreeSorter<TreeElement> {
return 0;
}
const oneIsDirectory = ResourceTree.isBranchNode(one);
const otherIsDirectory = ResourceTree.isBranchNode(other);
const oneIsDirectory = ResourceTree.isResourceNode(one);
const otherIsDirectory = ResourceTree.isResourceNode(other);
if (oneIsDirectory !== otherIsDirectory) {
return oneIsDirectory ? -1 : 1;
}
const oneName = ResourceTree.isBranchNode(one) ? one.name : basename((one as ISCMResource).sourceUri);
const otherName = ResourceTree.isBranchNode(other) ? other.name : basename((other as ISCMResource).sourceUri);
const oneName = ResourceTree.isResourceNode(one) ? one.name : basename((one as ISCMResource).sourceUri);
const otherName = ResourceTree.isResourceNode(other) ? other.name : basename((other as ISCMResource).sourceUri);
return compareFileNames(oneName, otherName);
}
@@ -328,7 +337,7 @@ export class SCMTreeSorter implements ITreeSorter<TreeElement> {
export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider<TreeElement> {
getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } | undefined {
if (ResourceTree.isBranchNode(element)) {
if (ResourceTree.isResourceNode(element)) {
return element.name;
} else if (isSCMResourceGroup(element)) {
return element.label;
@@ -338,7 +347,7 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb
}
getCompressedNodeKeyboardNavigationLabel(elements: TreeElement[]): { toString(): string | undefined; } | undefined {
const folders = elements as IBranchNode<ISCMResource, ISCMResourceGroup>[];
const folders = elements as IResourceNode<ISCMResource, ISCMResourceGroup>[];
return folders.map(e => e.name).join('/');
}
}
@@ -346,7 +355,7 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb
class SCMResourceIdentityProvider implements IIdentityProvider<TreeElement> {
getId(element: TreeElement): string {
if (ResourceTree.isBranchNode(element)) {
if (ResourceTree.isResourceNode(element)) {
const group = element.context;
return `${group.provider.contextValue}/${group.id}/$FOLDER/${element.uri.toString()}`;
} else if (isSCMResource(element)) {
@@ -375,22 +384,17 @@ function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode): ICompres
return { element: item.group, children, incompressible: true, collapsible: true };
}
function asTreeElement(node: INode<ISCMResource, ISCMResourceGroup>, incompressible: boolean): ICompressedTreeElement<TreeElement> {
if (ResourceTree.isBranchNode(node)) {
return {
element: node,
children: Iterator.map(node.children, node => asTreeElement(node, false)),
incompressible,
collapsed: false
};
}
return { element: node.element, incompressible: true };
function asTreeElement(node: IResourceNode<ISCMResource, ISCMResourceGroup>, forceIncompressible: boolean): ICompressedTreeElement<TreeElement> {
return {
element: (node.childrenCount === 0 && node.element) ? node.element : node,
children: Iterator.map(node.children, node => asTreeElement(node, false)),
incompressible: !!node.element || forceIncompressible
};
}
const enum ViewModelMode {
List,
Tree
List = 'list',
Tree = 'tree'
}
class ViewModel {
@@ -401,6 +405,17 @@ class ViewModel {
get mode(): ViewModelMode { return this._mode; }
set mode(mode: ViewModelMode) {
this._mode = mode;
for (const item of this.items) {
item.tree.clear();
if (mode === ViewModelMode.Tree) {
for (const resource of item.resources) {
item.tree.add(resource.sourceUri, resource);
}
}
}
this.refresh();
this._onDidChangeMode.fire(mode);
}
@@ -408,12 +423,15 @@ class ViewModel {
private items: IGroupItem[] = [];
private visibilityDisposables = new DisposableStore();
private scrollTop: number | undefined;
private firstVisible = true;
private disposables = new DisposableStore();
constructor(
private groups: ISequence<ISCMResourceGroup>,
private tree: ObjectTree<TreeElement, FuzzyScore>,
private _mode: ViewModelMode
private _mode: ViewModelMode,
@IEditorService protected editorService: IEditorService,
@IConfigurationService protected configurationService: IConfigurationService,
) { }
private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice<ISCMResourceGroup>): void {
@@ -427,10 +445,12 @@ class ViewModel {
group.onDidSplice(splice => this.onDidSpliceGroup(item, splice))
);
const item = { group, resources, tree, disposable };
const item: IGroupItem = { group, resources, tree, disposable };
for (const resource of resources) {
item.tree.add(resource.sourceUri, resource);
if (this._mode === ViewModelMode.Tree) {
for (const resource of resources) {
item.tree.add(resource.sourceUri, resource);
}
}
itemsToInsert.push(item);
@@ -446,14 +466,18 @@ class ViewModel {
}
private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice<ISCMResource>): void {
for (const resource of toInsert) {
item.tree.add(resource.sourceUri, resource);
if (this._mode === ViewModelMode.Tree) {
for (const resource of toInsert) {
item.tree.add(resource.sourceUri, resource);
}
}
const deleted = item.resources.splice(start, deleteCount, ...toInsert);
for (const resource of deleted) {
item.tree.delete(resource.sourceUri);
if (this._mode === ViewModelMode.Tree) {
for (const resource of deleted) {
item.tree.delete(resource.sourceUri);
}
}
this.refresh(item);
@@ -469,6 +493,9 @@ class ViewModel {
this.tree.scrollTop = this.scrollTop;
this.scrollTop = undefined;
}
this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables);
this.onDidActiveEditorChange();
} else {
this.visibilityDisposables.dispose();
this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: [] });
@@ -484,6 +511,42 @@ class ViewModel {
}
}
private onDidActiveEditorChange(): void {
if (!this.configurationService.getValue<boolean>('scm.autoReveal')) {
return;
}
if (this.firstVisible) {
this.firstVisible = false;
this.visibilityDisposables.add(disposableTimeout(() => this.onDidActiveEditorChange(), 250));
return;
}
const editor = this.editorService.activeEditor;
if (!editor) {
return;
}
const uri = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
if (!uri) {
return;
}
// go backwards from last group
for (let i = this.items.length - 1; i >= 0; i--) {
const node = this.items[i].tree.getNode(uri);
if (node && node.element) {
this.tree.reveal(node.element);
this.tree.setSelection([node.element]);
this.tree.setFocus([node.element]);
return;
}
}
}
dispose(): void {
this.visibilityDisposables.dispose();
this.disposables.dispose();
@@ -524,15 +587,16 @@ export class RepositoryPanel extends ViewletPanel {
private cachedHeight: number | undefined = undefined;
private cachedWidth: number | undefined = undefined;
private inputBoxContainer: HTMLElement;
private inputBox: InputBox;
private listContainer: HTMLElement;
private tree: ObjectTree<TreeElement, FuzzyScore>;
private viewModel: ViewModel;
private listLabels: ResourceLabels;
private inputBoxContainer!: HTMLElement;
private inputBox!: InputBox;
private listContainer!: HTMLElement;
private tree!: ObjectTree<TreeElement, FuzzyScore>;
private viewModel!: ViewModel;
private listLabels!: ResourceLabels;
private menus: SCMMenus;
private toggleViewModelModeAction: ToggleViewModeAction | undefined;
protected contextKeyService: IContextKeyService;
private commitTemplate = '';
constructor(
readonly repository: ISCMRepository,
@@ -547,7 +611,8 @@ export class RepositoryPanel extends ViewletPanel {
@IInstantiationService protected instantiationService: IInstantiationService,
@IConfigurationService protected configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService protected menuService: IMenuService
@IMenuService protected menuService: IMenuService,
@IStorageService private storageService: IStorageService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService);
@@ -634,10 +699,10 @@ export class RepositoryPanel extends ViewletPanel {
this._register(this.inputBox.onDidHeightChange(() => this.layoutBody()));
if (this.repository.provider.onDidChangeCommitTemplate) {
this._register(this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this));
this._register(this.repository.provider.onDidChangeCommitTemplate(this.onDidChangeCommitTemplate, this));
}
this.updateInputBox();
this.onDidChangeCommitTemplate();
// Input box visibility
this._register(this.repository.input.onDidChangeVisibility(this.updateInputBoxVisibility, this));
@@ -683,33 +748,38 @@ export class RepositoryPanel extends ViewletPanel {
this._register(Event.chain(this.tree.onDidOpen)
.map(e => e.elements[0])
.filter(e => !!e && !ResourceTree.isBranchNode(e) && isSCMResource(e))
.filter(e => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e))
.on(this.open, this));
this._register(Event.chain(this.tree.onDidPin)
.map(e => e.elements[0])
.filter(e => !!e && !ResourceTree.isBranchNode(e) && isSCMResource(e))
.filter(e => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e))
.on(this.pin, this));
this._register(this.tree.onContextMenu(this.onListContextMenu, this));
this._register(this.tree);
const mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree;
this.viewModel = new ViewModel(this.repository.provider.groups, this.tree, mode);
let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree;
const rootUri = this.repository.provider.rootUri;
if (typeof rootUri !== 'undefined') {
const storageMode = this.storageService.get(`scm.repository.viewMode:${rootUri.toString()}`, StorageScope.WORKSPACE) as ViewModelMode;
if (typeof storageMode === 'string') {
mode = storageMode;
}
}
this.viewModel = this.instantiationService.createInstance(ViewModel, this.repository.provider.groups, this.tree, mode);
this._register(this.viewModel);
addClass(this.listContainer, 'file-icon-themable-tree');
addClass(this.listContainer, 'show-file-icons');
const updateIndentStyles = (theme: IFileIconTheme) => {
toggleClass(this.listContainer, 'list-view-mode', this.viewModel.mode === ViewModelMode.List);
toggleClass(this.listContainer, 'align-icons-and-twisties', this.viewModel.mode === ViewModelMode.Tree && theme.hasFileIcons && !theme.hasFolderIcons);
toggleClass(this.listContainer, 'hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true);
};
updateIndentStyles(this.themeService.getFileIconTheme());
this._register(this.themeService.onDidFileIconThemeChange(updateIndentStyles));
this._register(this.viewModel.onDidChangeMode(() => updateIndentStyles(this.themeService.getFileIconTheme())));
this.updateIndentStyles(this.themeService.getFileIconTheme());
this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this));
this._register(this.viewModel.onDidChangeMode(this.onDidChangeMode, this));
this.toggleViewModelModeAction = new ToggleViewModeAction(this.viewModel);
this._register(this.toggleViewModelModeAction);
@@ -719,6 +789,25 @@ export class RepositoryPanel extends ViewletPanel {
this.updateActions();
}
private updateIndentStyles(theme: IFileIconTheme): void {
toggleClass(this.listContainer, 'list-view-mode', this.viewModel.mode === ViewModelMode.List);
toggleClass(this.listContainer, 'tree-view-mode', this.viewModel.mode === ViewModelMode.Tree);
toggleClass(this.listContainer, 'align-icons-and-twisties', this.viewModel.mode === ViewModelMode.Tree && theme.hasFileIcons && !theme.hasFolderIcons);
toggleClass(this.listContainer, 'hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true);
}
private onDidChangeMode(): void {
this.updateIndentStyles(this.themeService.getFileIconTheme());
const rootUri = this.repository.provider.rootUri;
if (typeof rootUri === 'undefined') {
return;
}
this.storageService.store(`scm.repository.viewMode:${rootUri.toString()}`, this.viewModel.mode, StorageScope.WORKSPACE);
}
layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void {
if (height === undefined) {
return;
@@ -809,12 +898,16 @@ export class RepositoryPanel extends ViewletPanel {
const element = e.element;
let actions: IAction[] = [];
if (ResourceTree.isBranchNode(element)) {
actions = this.menus.getResourceFolderContextActions(element.context);
} else if (isSCMResource(element)) {
actions = this.menus.getResourceContextActions(element);
} else {
if (isSCMResourceGroup(element)) {
actions = this.menus.getResourceGroupContextActions(element);
} else if (ResourceTree.isResourceNode(element)) {
if (element.element) {
actions = this.menus.getResourceContextActions(element.element);
} else {
actions = this.menus.getResourceFolderContextActions(element.context);
}
} else {
actions = this.menus.getResourceContextActions(element);
}
this.contextMenuService.showContextMenu({
@@ -825,17 +918,24 @@ export class RepositoryPanel extends ViewletPanel {
});
}
private getSelectedResources(): (ISCMResource | IBranchNode<ISCMResource, ISCMResourceGroup>)[] {
private getSelectedResources(): (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[] {
return this.tree.getSelection()
.filter(r => !!r && !isSCMResourceGroup(r))! as any;
}
private updateInputBox(): void {
if (typeof this.repository.provider.commitTemplate === 'undefined' || !this.repository.input.visible || this.inputBox.value) {
private onDidChangeCommitTemplate(): void {
if (typeof this.repository.provider.commitTemplate === 'undefined' || !this.repository.input.visible) {
return;
}
this.inputBox.value = this.repository.provider.commitTemplate;
const oldCommitTemplate = this.commitTemplate;
this.commitTemplate = this.repository.provider.commitTemplate;
if (this.inputBox.value && this.inputBox.value !== oldCommitTemplate) {
return;
}
this.inputBox.value = this.commitTemplate;
}
private updateInputBoxVisibility(): void {

View File

@@ -28,7 +28,7 @@ import { SCMService } from 'vs/workbench/contrib/scm/common/scmService';
class OpenSCMViewletAction extends ShowViewletAction {
static readonly ID = VIEWLET_ID;
static LABEL = localize('toggleGitViewlet', "Show Git");
static readonly LABEL = localize('toggleGitViewlet', "Show Git");
constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService) {
super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService);
@@ -91,6 +91,16 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
default: 3,
description: localize('diffGutterWidth', "Controls the width(px) of diff decorations in gutter (added & modified).")
},
'scm.diffDecorationsGutterVisibility': {
type: 'string',
enum: ['always', 'hover'],
enumDescriptions: [
localize('scm.diffDecorationsGutterVisibility.always', "Show the diff decorator in the gutter at all times."),
localize('scm.diffDecorationsGutterVisibility.hover', "Show the diff decorator in the gutter only on hover.")
],
description: localize('scm.diffDecorationsGutterVisibility', "Controls the visibilty of the Source Control diff decorator in the gutter."),
default: 'always'
},
'scm.alwaysShowActions': {
type: 'boolean',
description: localize('alwaysShowActions', "Controls whether inline actions are always visible in the Source Control view."),
@@ -115,8 +125,13 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
localize('scm.defaultViewMode.list', "Show the repository changes as a list.")
],
description: localize('scm.defaultViewMode', "Controls the default Source Control repository view mode."),
default: 'tree'
}
default: 'list'
},
'scm.autoReveal': {
type: 'boolean',
description: localize('autoReveal', "Controls whether the SCM view should automatically reveal and select files when opening them."),
default: true
},
}
});

View File

@@ -54,7 +54,7 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel {
private static readonly STATE_KEY = 'workbench.scm.views.state';
private el: HTMLElement;
private el!: HTMLElement;
private message: HTMLElement;
private menus: SCMMenus;
private _repositories: ISCMRepository[] = [];

View File

@@ -56,8 +56,8 @@ export interface ISCMProvider extends IDisposable {
readonly rootUri?: URI;
readonly count?: number;
readonly commitTemplate?: string;
readonly onDidChangeCommitTemplate?: Event<string>;
readonly commitTemplate: string;
readonly onDidChangeCommitTemplate: Event<string>;
readonly onDidChangeStatusBarCommands?: Event<Command[]>;
readonly acceptInputCommand?: Command;
readonly statusBarCommands?: Command[];