Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -2,18 +2,16 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { Panel } from 'vs/workbench/browser/panel';
import { EditorInput, EditorOptions, IEditor, GroupIdentifier, IEditorMemento } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { LRUCache } from 'vs/base/common/map';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { once, Event } from 'vs/base/common/event';
import { isEmptyObject } from 'vs/base/common/types';
import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
@@ -43,16 +41,17 @@ export abstract class BaseEditor extends Panel implements IEditor {
readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; }> = Event.None;
protected _input: EditorInput;
protected _options: EditorOptions;
private _options: EditorOptions;
private _group: IEditorGroup;
constructor(
id: string,
telemetryService: ITelemetryService,
themeService: IThemeService
themeService: IThemeService,
storageService: IStorageService
) {
super(id, telemetryService, themeService);
super(id, telemetryService, themeService, storageService);
}
get input(): EditorInput {
@@ -82,7 +81,7 @@ export abstract class BaseEditor extends Panel implements IEditor {
this._input = input;
this._options = options;
return TPromise.wrap<void>(null);
return Promise.resolve();
}
/**
@@ -105,15 +104,11 @@ export abstract class BaseEditor extends Panel implements IEditor {
this._options = options;
}
create(parent: HTMLElement): void; // create is sync for editors
create(parent: HTMLElement): TPromise<void>;
create(parent: HTMLElement): TPromise<void> {
const res = super.create(parent);
create(parent: HTMLElement): void {
super.create(parent);
// Create Editor
this.createEditor(parent);
return res;
}
/**
@@ -121,15 +116,10 @@ export abstract class BaseEditor extends Panel implements IEditor {
*/
protected abstract createEditor(parent: HTMLElement): void;
setVisible(visible: boolean, group?: IEditorGroup): void; // setVisible is sync for editors
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void>;
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void> {
const promise = super.setVisible(visible);
setVisible(visible: boolean, group?: IEditorGroup): void {
super.setVisible(visible);
// Propagate to Editor
this.setEditorVisible(visible, group);
return promise;
}
/**
@@ -143,28 +133,28 @@ export abstract class BaseEditor extends Panel implements IEditor {
this._group = group;
}
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
const mementoKey = `${this.getId()}${key}`;
let editorMemento = BaseEditor.EDITOR_MEMENTOS.get(mementoKey);
if (!editorMemento) {
editorMemento = new EditorMemento(this.getId(), key, this.getMemento(storageService), limit, editorGroupService);
editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService);
BaseEditor.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
}
return editorMemento;
}
shutdown(): void {
protected saveState(): void {
// Shutdown all editor memento for this editor type
// Save all editor memento for this editor type
BaseEditor.EDITOR_MEMENTOS.forEach(editorMemento => {
if (editorMemento.id === this.getId()) {
editorMemento.shutdown();
editorMemento.saveState();
}
});
super.shutdown();
super.saveState();
}
dispose(): void {
@@ -195,9 +185,9 @@ export class EditorMemento<T> implements IEditorMemento<T> {
return this._id;
}
saveState(group: IEditorGroup, resource: URI, state: T): void;
saveState(group: IEditorGroup, editor: EditorInput, state: T): void;
saveState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void {
saveEditorState(group: IEditorGroup, resource: URI, state: T): void;
saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void;
saveEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void {
const resource = this.doGetResource(resourceOrEditor);
if (!resource || !group) {
return; // we are not in a good state to save any state for a resource
@@ -216,14 +206,14 @@ export class EditorMemento<T> implements IEditorMemento<T> {
// Automatically clear when editor input gets disposed if any
if (resourceOrEditor instanceof EditorInput) {
once(resourceOrEditor.onDispose)(() => {
this.clearState(resource);
this.clearEditorState(resource);
});
}
}
loadState(group: IEditorGroup, resource: URI): T;
loadState(group: IEditorGroup, editor: EditorInput): T;
loadState(group: IEditorGroup, resourceOrEditor: URI | EditorInput): T {
loadEditorState(group: IEditorGroup, resource: URI): T;
loadEditorState(group: IEditorGroup, editor: EditorInput): T;
loadEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput): T {
const resource = this.doGetResource(resourceOrEditor);
if (!resource || !group) {
return void 0; // we are not in a good state to load any state for a resource
@@ -239,13 +229,21 @@ export class EditorMemento<T> implements IEditorMemento<T> {
return void 0;
}
clearState(resource: URI): void;
clearState(editor: EditorInput): void;
clearState(resourceOrEditor: URI | EditorInput): void {
clearEditorState(resource: URI, group?: IEditorGroup): void;
clearEditorState(editor: EditorInput, group?: IEditorGroup): void;
clearEditorState(resourceOrEditor: URI | EditorInput, group?: IEditorGroup): void {
const resource = this.doGetResource(resourceOrEditor);
if (resource) {
const cache = this.doLoad();
cache.delete(resource.toString());
if (group) {
const resourceViewState = cache.get(resource.toString());
if (resourceViewState) {
delete resourceViewState[group.id];
}
} else {
cache.delete(resource.toString());
}
}
}
@@ -271,7 +269,7 @@ export class EditorMemento<T> implements IEditorMemento<T> {
return this.cache;
}
shutdown(): void {
saveState(): void {
const cache = this.doLoad();
// Cleanup once during shutdown
@@ -300,4 +298,4 @@ export class EditorMemento<T> implements IEditorMemento<T> {
});
});
}
}
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { BINARY_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -12,6 +10,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
import { IStorageService } from 'vs/platform/storage/common/storage';
/**
* An implementation of editor for diffing binary files like images or videos.
@@ -23,9 +22,10 @@ export class BinaryResourceDiffEditor extends SideBySideEditor {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(telemetryService, instantiationService, themeService);
super(telemetryService, instantiationService, themeService, storageService);
}
getMetadata(): string {

View File

@@ -3,12 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import { Builder, $ } from 'vs/base/browser/builder';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
@@ -17,13 +13,15 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer';
import URI from 'vs/base/common/uri';
import { Dimension } from 'vs/base/browser/dom';
import { URI } from 'vs/base/common/uri';
import { Dimension, size, clearNode } from 'vs/base/browser/dom';
import { IFileService } from 'vs/platform/files/common/files';
import { CancellationToken } from 'vs/base/common/cancellation';
import { dispose } from 'vs/base/common/lifecycle';
import { IStorageService } from 'vs/platform/storage/common/storage';
export interface IOpenCallbacks {
openInternal: (input: EditorInput, options: EditorOptions) => void;
openInternal: (input: EditorInput, options: EditorOptions) => Thenable<void>;
openExternal: (uri: URI) => void;
}
@@ -35,9 +33,12 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
private readonly _onMetadataChanged: Emitter<void> = this._register(new Emitter<void>());
get onMetadataChanged(): Event<void> { return this._onMetadataChanged.event; }
private readonly _onDidOpenInPlace: Emitter<void> = this._register(new Emitter<void>());
get onDidOpenInPlace(): Event<void> { return this._onDidOpenInPlace.event; }
private callbacks: IOpenCallbacks;
private metadata: string;
private binaryContainer: Builder;
private binaryContainer: HTMLElement;
private scrollbar: DomScrollableElement;
private resourceViewerContext: ResourceViewerContext;
@@ -47,8 +48,9 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
telemetryService: ITelemetryService,
themeService: IThemeService,
@IFileService private readonly _fileService: IFileService,
@IStorageService storageService: IStorageService
) {
super(id, telemetryService, themeService);
super(id, telemetryService, themeService, storageService);
this.callbacks = callbacks;
}
@@ -60,14 +62,13 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
protected createEditor(parent: HTMLElement): void {
// Container for Binary
const binaryContainerElement = document.createElement('div');
binaryContainerElement.className = 'binary-container';
this.binaryContainer = $(binaryContainerElement);
this.binaryContainer.style('outline', 'none');
this.binaryContainer.tabindex(0); // enable focus support from the editor part (do not remove)
this.binaryContainer = document.createElement('div');
this.binaryContainer.className = 'binary-container';
this.binaryContainer.style.outline = 'none';
this.binaryContainer.tabIndex = 0; // enable focus support from the editor part (do not remove)
// Custom Scrollbars
this.scrollbar = this._register(new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto }));
this.scrollbar = this._register(new DomScrollableElement(this.binaryContainer, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto }));
parent.appendChild(this.scrollbar.getDomNode());
}
@@ -82,16 +83,16 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
// Assert Model instance
if (!(model instanceof BinaryEditorModel)) {
return TPromise.wrapError<void>(new Error('Unable to open file as binary'));
return Promise.reject(new Error('Unable to open file as binary'));
}
// Render Input
this.resourceViewerContext = ResourceViewer.show(
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() },
this._fileService,
this.binaryContainer.getHTMLElement(),
this.binaryContainer,
this.scrollbar,
resource => this.callbacks.openInternal(input, options),
resource => this.handleOpenInternalCallback(input, options),
resource => this.callbacks.openExternal(resource),
meta => this.handleMetadataChanged(meta)
);
@@ -101,6 +102,14 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
});
}
private handleOpenInternalCallback(input: EditorInput, options: EditorOptions) {
this.callbacks.openInternal(input, options).then(() => {
// Signal to listeners that the binary editor has been opened in-place
this._onDidOpenInPlace.fire();
});
}
private handleMetadataChanged(meta: string): void {
this.metadata = meta;
@@ -116,8 +125,9 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
// Clear Meta
this.handleMetadataChanged(null);
// Empty HTML Container
$(this.binaryContainer).empty();
// Clear Resource Viewer
clearNode(this.binaryContainer);
this.resourceViewerContext = dispose(this.resourceViewerContext);
super.clearInput();
}
@@ -125,21 +135,21 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
layout(dimension: Dimension): void {
// Pass on to Binary Container
this.binaryContainer.size(dimension.width, dimension.height);
size(this.binaryContainer, dimension.width, dimension.height);
this.scrollbar.scanDomNode();
if (this.resourceViewerContext) {
if (this.resourceViewerContext && this.resourceViewerContext.layout) {
this.resourceViewerContext.layout(dimension);
}
}
focus(): void {
this.binaryContainer.domFocus();
this.binaryContainer.focus();
}
dispose(): void {
this.binaryContainer.remove();
// Destroy Container
this.binaryContainer.destroy();
this.resourceViewerContext = dispose(this.resourceViewerContext);
super.dispose();
}

View File

@@ -3,18 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { BreadcrumbsWidget } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
import { IDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { GroupIdentifier } from 'vs/workbench/common/editor';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Emitter, Event } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import * as glob from 'vs/base/common/glob';
import { IDisposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { GroupIdentifier } from 'vs/workbench/common/editor';
export const IBreadcrumbsService = createDecorator<IBreadcrumbsService>('IEditorBreadcrumbsService');
@@ -57,8 +56,10 @@ registerSingleton(IBreadcrumbsService, BreadcrumbsService);
export abstract class BreadcrumbsConfig<T> {
name: string;
value: T;
onDidChange: Event<T>;
onDidChange: Event<void>;
abstract getValue(overrides?: IConfigurationOverrides): T;
abstract updateValue(value: T, overrides?: IConfigurationOverrides): Thenable<void>;
abstract dispose(): void;
private constructor() {
@@ -69,30 +70,31 @@ export abstract class BreadcrumbsConfig<T> {
static UseQuickPick = BreadcrumbsConfig._stub<boolean>('breadcrumbs.useQuickPick');
static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath');
static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath');
static SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder');
static FilterOnType = BreadcrumbsConfig._stub<boolean>('breadcrumbs.filterOnType');
static FileExcludes = BreadcrumbsConfig._stub<glob.IExpression>('files.exclude');
private static _stub<T>(name: string): { bindTo(service: IConfigurationService): BreadcrumbsConfig<T> } {
return {
bindTo(service) {
let value: T = service.getValue(name);
let onDidChange = new Emitter<T>();
let onDidChange = new Emitter<void>();
let listener = service.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(name)) {
value = service.getValue(name);
onDidChange.fire(value);
onDidChange.fire(undefined);
}
});
return {
name,
get value() {
return value;
},
set value(newValue: T) {
service.updateValue(name, newValue);
value = newValue;
},
onDidChange: onDidChange.event,
return new class implements BreadcrumbsConfig<T>{
readonly name = name;
readonly onDidChange = onDidChange.event;
getValue(overrides?: IConfigurationOverrides): T {
return service.getValue(name, overrides);
}
updateValue(newValue: T, overrides?: IConfigurationOverrides): Thenable<void> {
return service.updateValue(name, newValue, overrides);
}
dispose(): void {
listener.dispose();
onDidChange.dispose();
@@ -141,6 +143,22 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
localize('symbolpath.last', "Only show the current symbol in the breadcrumbs view."),
]
},
'breadcrumbs.symbolSortOrder': {
description: localize('symbolSortOrder', "Controls how symbols are sorted in the breadcrumbs outline view."),
type: 'string',
default: 'position',
enum: ['position', 'name', 'type'],
enumDescriptions: [
localize('symbolSortOrder.position', "Show symbol outline in file position order."),
localize('symbolSortOrder.name', "Show symbol outline in alphabetical order."),
localize('symbolSortOrder.type', "Show symbol outline in symbol type order."),
]
},
// 'breadcrumbs.filterOnType': {
// description: localize('filterOnType', "Controls whether the breadcrumb picker filters or highlights when typing."),
// type: 'boolean',
// default: false
// },
}
});

View File

@@ -3,43 +3,48 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as dom from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { tail } from 'vs/base/common/arrays';
import { timeout } from 'vs/base/common/async';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/breadcrumbscontrol';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
import { symbolKindToCssClass } from 'vs/editor/common/modes';
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { localize } from 'vs/nls';
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { FileKind, IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { ColorIdentifier, ColorFunction } from 'vs/platform/theme/common/colorRegistry';
import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { FileLabel } from 'vs/workbench/browser/labels';
import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { createBreadcrumbsPicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
import { IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
import { SideBySideEditorInput } from 'vs/workbench/common/editor';
import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { tail } from 'vs/base/common/arrays';
import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
class Item extends BreadcrumbsItem {
@@ -116,20 +121,22 @@ export interface IBreadcrumbsControlOptions {
showFileIcons: boolean;
showSymbolIcons: boolean;
showDecorationColors: boolean;
extraClasses: string[];
breadcrumbsBackground: ColorIdentifier | ColorFunction;
}
export class BreadcrumbsControl {
static HEIGHT = 25;
static HEIGHT = 22;
static readonly Payload_Reveal = {};
static readonly Payload_RevealAside = {};
static readonly Payload_Pick = {};
static CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false);
static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false);
static CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false);
private readonly _ckBreadcrumbsPossible: IContextKey<boolean>;
private readonly _ckBreadcrumbsVisible: IContextKey<boolean>;
private readonly _ckBreadcrumbsActive: IContextKey<boolean>;
@@ -145,29 +152,31 @@ export class BreadcrumbsControl {
constructor(
container: HTMLElement,
private readonly _options: IBreadcrumbsControlOptions,
private readonly _editorGroup: EditorGroupView,
private readonly _editorGroup: IEditorGroupView,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IContextViewService private readonly _contextViewService: IContextViewService,
@IEditorService private readonly _editorService: IEditorService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IThemeService private readonly _themeService: IThemeService,
@IQuickOpenService private readonly _quickOpenService: IQuickOpenService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IFileService private readonly _fileService: IFileService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IBreadcrumbsService breadcrumbsService: IBreadcrumbsService,
) {
this.domNode = document.createElement('div');
dom.addClass(this.domNode, 'breadcrumbs-control');
dom.addClasses(this.domNode, ..._options.extraClasses);
dom.append(container, this.domNode);
this._widget = new BreadcrumbsWidget(this.domNode);
this._widget.onDidSelectItem(this._onSelectEvent, this, this._disposables);
this._widget.onDidFocusItem(this._onFocusEvent, this, this._disposables);
this._widget.onDidChangeFocus(this._updateCkBreadcrumbsActive, this, this._disposables);
this._disposables.push(attachBreadcrumbsStyler(this._widget, this._themeService));
this._disposables.push(attachBreadcrumbsStyler(this._widget, this._themeService, { breadcrumbsBackground: _options.breadcrumbsBackground }));
this._ckBreadcrumbsPossible = BreadcrumbsControl.CK_BreadcrumbsPossible.bindTo(this._contextKeyService);
this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService);
this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService);
@@ -179,6 +188,7 @@ export class BreadcrumbsControl {
dispose(): void {
this._disposables = dispose(this._disposables);
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
this._ckBreadcrumbsPossible.reset();
this._ckBreadcrumbsVisible.reset();
this._ckBreadcrumbsActive.reset();
this._cfUseQuickPick.dispose();
@@ -201,12 +211,18 @@ export class BreadcrumbsControl {
}
update(): boolean {
const input = this._editorGroup.activeEditor;
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
// honor diff editors and such
let input = this._editorGroup.activeEditor;
if (input instanceof SideBySideEditorInput) {
input = input.master;
}
if (!input || !input.getResource() || (input.getResource().scheme !== Schemas.untitled && !this._fileService.canHandleResource(input.getResource()))) {
// cleanup and return when there is no input or when
// we cannot handle this input
this._ckBreadcrumbsPossible.set(false);
if (!this.isHidden()) {
this.hide();
return true;
@@ -217,9 +233,10 @@ export class BreadcrumbsControl {
dom.toggleClass(this.domNode, 'hidden', false);
this._ckBreadcrumbsVisible.set(true);
this._ckBreadcrumbsPossible.set(true);
let control = this._editorGroup.activeControl.getControl() as ICodeEditor;
let model = new EditorBreadcrumbsModel(input.getResource(), isCodeEditor(control) ? control : undefined, this._workspaceService, this._configurationService);
let editor = this._getActiveCodeEditor();
let model = new EditorBreadcrumbsModel(input.getResource(), editor, this._workspaceService, this._configurationService);
dom.toggleClass(this.domNode, 'relative-path', model.isRelative());
let updateBreadcrumbs = () => {
@@ -243,6 +260,17 @@ export class BreadcrumbsControl {
return true;
}
private _getActiveCodeEditor(): ICodeEditor {
let control = this._editorGroup.activeControl.getControl();
let editor: ICodeEditor;
if (isCodeEditor(control)) {
editor = control as ICodeEditor;
} else if (isDiffEditor(control)) {
editor = control.getModifiedEditor();
}
return editor;
}
private _onFocusEvent(event: IBreadcrumbsItemEvent): void {
if (event.item && this._breadcrumbsPickerShowing) {
return this._widget.setSelection(event.item);
@@ -254,8 +282,15 @@ export class BreadcrumbsControl {
return;
}
this._editorGroup.focus();
const { element } = event.item as Item;
this._editorGroup.focus();
/* __GDPR__
"breadcrumbs/select" : {
"type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this._telemetryService.publicLog('breadcrumbs/select', { type: element instanceof TreeElement ? 'symbol' : 'file' });
const group = this._getEditorGroup(event.payload);
if (group !== undefined) {
@@ -266,7 +301,7 @@ export class BreadcrumbsControl {
return;
}
if (this._cfUseQuickPick.value) {
if (this._cfUseQuickPick.getValue()) {
// using quick pick
this._widget.setFocused(undefined);
this._widget.setSelection(undefined);
@@ -276,41 +311,86 @@ export class BreadcrumbsControl {
// show picker
let picker: BreadcrumbsPicker;
let editor = this._getActiveCodeEditor();
let editorDecorations: string[] = [];
let editorViewState: ICodeEditorViewState;
this._contextViewService.showContextView({
render: (parent: HTMLElement) => {
picker = createBreadcrumbsPicker(this._instantiationService, parent, element);
let listener = picker.onDidPickElement(data => {
let selectListener = picker.onDidPickElement(data => {
if (data.target) {
editorViewState = undefined;
}
this._contextViewService.hideContextView(this);
this._revealInEditor(event, data.target, this._getEditorGroup(data.payload && data.payload.originalEvent));
this._revealInEditor(event, data.target, this._getEditorGroup(data.payload && data.payload.originalEvent), (data.payload && data.payload.originalEvent && data.payload.originalEvent.middleButton));
/* __GDPR__
"breadcrumbs/open" : {
"type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this._telemetryService.publicLog('breadcrumbs/open', { type: !data ? 'nothing' : data.target instanceof TreeElement ? 'symbol' : 'file' });
});
let focusListener = picker.onDidFocusElement(data => {
if (!editor || !(data.target instanceof OutlineElement)) {
return;
}
if (!editorViewState) {
editorViewState = editor.saveViewState();
}
const { symbol } = data.target;
editor.revealRangeInCenter(symbol.range, ScrollType.Smooth);
editorDecorations = editor.deltaDecorations(editorDecorations, [{
range: symbol.range,
options: {
className: 'rangeHighlight',
isWholeLine: true
}
}]);
});
this._breadcrumbsPickerShowing = true;
this._updateCkBreadcrumbsActive();
return combinedDisposable([listener, picker]);
return combinedDisposable([selectListener, focusListener, picker]);
},
getAnchor() {
getAnchor: () => {
let maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/;
let maxHeight = Math.min(window.innerHeight * .7, 300);
let pickerHeight = 330;
let pickerWidth = Math.max(220, dom.getTotalWidth(event.node));
let pickerWidth = Math.min(maxInnerWidth, Math.max(240, maxInnerWidth / 4.17));
let pickerArrowSize = 8;
let pickerArrowOffset: number;
let data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement);
let y = data.top + data.height - pickerArrowSize;
let y = data.top + data.height + pickerArrowSize;
if (y + maxHeight >= window.innerHeight) {
maxHeight = window.innerHeight - y - 30 /* room for shadow and status bar*/;
}
let x = data.left;
if (x + pickerWidth >= window.innerWidth) {
x = window.innerWidth - pickerWidth;
if (x + pickerWidth >= maxInnerWidth) {
x = maxInnerWidth - pickerWidth;
}
if (event.payload instanceof StandardMouseEvent) {
pickerArrowOffset = event.payload.posx - x - pickerArrowSize;
let maxPickerArrowOffset = pickerWidth - 2 * pickerArrowSize;
pickerArrowOffset = event.payload.posx - x;
if (pickerArrowOffset > maxPickerArrowOffset) {
x = Math.min(maxInnerWidth - pickerWidth, x + pickerArrowOffset - maxPickerArrowOffset);
pickerArrowOffset = maxPickerArrowOffset;
}
} else {
pickerArrowOffset = (data.left + (data.width * .3)) - x;
}
picker.layout(pickerHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset));
picker.setInput(element);
picker.setInput(element, maxHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset));
return { x, y };
},
onHide: (data) => {
if (editor) {
editor.deltaDecorations(editorDecorations, []);
if (editorViewState) {
editor.restoreViewState(editorViewState);
}
}
this._breadcrumbsPickerShowing = false;
this._updateCkBreadcrumbsActive();
if (data === this) {
@@ -326,11 +406,11 @@ export class BreadcrumbsControl {
this._ckBreadcrumbsActive.set(value);
}
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): void {
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, pinned: boolean = false): void {
if (element instanceof FileElement) {
if (element.kind === FileKind.FILE) {
// open file in editor
this._editorService.openEditor({ resource: element.uri }, group);
// open file in any editor
this._editorService.openEditor({ resource: element.uri, options: { pinned: pinned } }, group);
} else {
// show next picker
let items = this._widget.getItems();
@@ -340,12 +420,15 @@ export class BreadcrumbsControl {
}
} else if (element instanceof OutlineElement) {
// open symbol in editor
// open symbol in code editor
let model = OutlineModel.get(element);
this._editorService.openEditor({
this._codeEditorService.openCodeEditor({
resource: model.textModel.uri,
options: { selection: Range.collapseToStart(element.symbol.selectionRange) }
}, group);
options: {
selection: Range.collapseToStart(element.symbol.selectionRange),
revealInCenterIfOutsideViewport: true
}
}, this._getActiveCodeEditor(), group === SIDE_GROUP);
}
}
@@ -362,16 +445,12 @@ export class BreadcrumbsControl {
//#region commands
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: 'breadcrumbs.focusAndSelect',
title: localize('cmd.focus', "Focus Breadcrumbs")
}
});
// toggle command
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: 'breadcrumbs.toggle',
title: localize('cmd.toggle', "Toggle Breadcrumbs")
title: { value: localize('cmd.toggle', "Toggle Breadcrumbs"), original: 'Toggle Breadcrumbs' },
category: localize('cmd.category', "View")
}
});
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
@@ -379,42 +458,73 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
order: 99,
command: {
id: 'breadcrumbs.toggle',
title: localize('cmd.toggle', "Toggle Breadcrumbs")
title: localize('miToggleBreadcrumbs', "Toggle &&Breadcrumbs"),
toggled: ContextKeyExpr.equals('config.breadcrumbs.enabled', true)
}
});
CommandsRegistry.registerCommand('breadcrumbs.toggle', accessor => {
let config = accessor.get(IConfigurationService);
let value = BreadcrumbsConfig.IsEnabled.bindTo(config).value;
BreadcrumbsConfig.IsEnabled.bindTo(config).value = !value;
let value = BreadcrumbsConfig.IsEnabled.bindTo(config).getValue();
BreadcrumbsConfig.IsEnabled.bindTo(config).updateValue(!value);
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focus',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON,
when: BreadcrumbsControl.CK_BreadcrumbsVisible,
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
// focus/focus-and-select
function focusAndSelectHandler(accessor: ServicesAccessor, select: boolean): void {
// find widget and focus/select
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
if (widget) {
const item = tail(widget.getItems());
widget.setFocused(item);
if (select) {
widget.setSelection(item, BreadcrumbsControl.Payload_Pick);
}
}
}
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: 'breadcrumbs.focusAndSelect',
title: { value: localize('cmd.focus', "Focus Breadcrumbs"), original: 'Focus Breadcrumbs' },
precondition: BreadcrumbsControl.CK_BreadcrumbsVisible
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focusAndSelect',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT,
when: BreadcrumbsControl.CK_BreadcrumbsVisible,
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
const item = tail(widget.getItems());
widget.setFocused(item);
widget.setSelection(item, BreadcrumbsControl.Payload_Pick);
when: BreadcrumbsControl.CK_BreadcrumbsPossible,
handler: accessor => focusAndSelectHandler(accessor, true)
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focus',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON,
when: BreadcrumbsControl.CK_BreadcrumbsPossible,
handler: accessor => focusAndSelectHandler(accessor, false)
});
// this commands is only enabled when breadcrumbs are
// disabled which it then enables and focuses
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.toggleToOn',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT,
when: ContextKeyExpr.not('config.breadcrumbs.enabled'),
handler: async accessor => {
const instant = accessor.get(IInstantiationService);
const config = accessor.get(IConfigurationService);
// check if enabled and iff not enable
const isEnabled = BreadcrumbsConfig.IsEnabled.bindTo(config);
if (!isEnabled.getValue()) {
await isEnabled.updateValue(true);
await timeout(50); // hacky - the widget might not be ready yet...
}
return instant.invokeFunction(focusAndSelectHandler, true);
}
});
// navigation
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focusNext',
weight: KeybindingWeight.WorkbenchContrib,
@@ -492,10 +602,26 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
primary: KeyMod.CtrlCmd | KeyCode.Enter,
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey),
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_RevealAside);
const editors = accessor.get(IEditorService);
const lists = accessor.get(IListService);
const element = <OutlineElement | IFileStat>lists.lastFocusedList.getFocus();
if (element instanceof OutlineElement) {
// open symbol in editor
return editors.openEditor({
resource: OutlineModel.get(element).textModel.uri,
options: { selection: Range.collapseToStart(element.symbol.selectionRange) }
}, SIDE_GROUP);
} else if (URI.isUri(element.resource)) {
// open file in editor
return editors.openEditor({
resource: element.resource,
}, SIDE_GROUP);
} else {
// ignore
return undefined;
}
}
});
//#endregion

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { equals } from 'vs/base/common/arrays';
import { TimeoutTimer } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
@@ -12,9 +10,8 @@ import { size } from 'vs/base/common/collections';
import { onUnexpectedError } from 'vs/base/common/errors';
import { debounceEvent, Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as paths from 'vs/base/common/paths';
import { isEqual } from 'vs/base/common/resources';
import URI from 'vs/base/common/uri';
import { isEqual, dirname } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IPosition } from 'vs/editor/common/core/position';
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
@@ -82,16 +79,16 @@ export class EditorBreadcrumbsModel {
let result: BreadcrumbElement[] = [];
// file path elements
if (this._cfgFilePath.value === 'on') {
if (this._cfgFilePath.getValue() === 'on') {
result = result.concat(this._fileInfo.path);
} else if (this._cfgFilePath.value === 'last' && this._fileInfo.path.length > 0) {
} else if (this._cfgFilePath.getValue() === 'last' && this._fileInfo.path.length > 0) {
result = result.concat(this._fileInfo.path.slice(-1));
}
// symbol path elements
if (this._cfgSymbolPath.value === 'on') {
if (this._cfgSymbolPath.getValue() === 'on') {
result = result.concat(this._outlineElements);
} else if (this._cfgSymbolPath.value === 'last' && this._outlineElements.length > 0) {
} else if (this._cfgSymbolPath.getValue() === 'last' && this._outlineElements.length > 0) {
result = result.concat(this._outlineElements.slice(-1));
}
@@ -117,7 +114,7 @@ export class EditorBreadcrumbsModel {
break;
}
info.path.unshift(new FileElement(uri, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER));
uri = uri.with({ path: paths.dirname(uri.path) });
uri = dirname(uri);
}
if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
@@ -177,7 +174,7 @@ export class EditorBreadcrumbsModel {
this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => {
timeout.cancelAndSet(() => {
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId()) {
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && this._editor.getModel()) {
this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
}
}, 150);

View File

@@ -3,37 +3,47 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { onDidChangeZoomLevel } from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
import { compareFileNames } from 'vs/base/common/comparers';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { createMatches, FuzzyScore, fuzzyScore } from 'vs/base/common/filters';
import * as glob from 'vs/base/common/glob';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { dirname, isEqual } from 'vs/base/common/resources';
import URI from 'vs/base/common/uri';
import { join } from 'vs/base/common/paths';
import { basename, dirname, isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDataSource, IRenderer, ISelectionEvent, ISorter, ITree } from 'vs/base/parts/tree/browser/tree';
import { IDataSource, IFilter, IRenderer, ISorter, ITree } from 'vs/base/parts/tree/browser/tree';
import 'vs/css!./media/breadcrumbscontrol';
import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { OutlineDataSource, OutlineItemComparator, OutlineRenderer } from 'vs/editor/contrib/documentSymbols/outlineTree';
import { OutlineDataSource, OutlineItemComparator, OutlineRenderer, OutlineItemCompareType } from 'vs/editor/contrib/documentSymbols/outlineTree';
import { localize } from 'vs/nls';
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
import { HighlightingWorkbenchTree, IHighlightingTreeConfiguration, IHighlightingRenderer } from 'vs/platform/list/browser/listService';
import { IThemeService, DARK } from 'vs/platform/theme/common/themeService';
import { FileLabel } from 'vs/workbench/browser/labels';
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { onUnexpectedError } from 'vs/base/common/errors';
import { breadcrumbsPickerBackground } from 'vs/platform/theme/common/colorRegistry';
import { FuzzyScore, createMatches, fuzzyScore } from 'vs/base/common/filters';
import { IWorkspaceContextService, IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
import { IConstructorSignature1, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { HighlightingWorkbenchTree, IHighlighter, IHighlightingTreeConfiguration, IHighlightingTreeOptions } from 'vs/platform/list/browser/listService';
import { breadcrumbsPickerBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { FileLabel } from 'vs/workbench/browser/labels';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker {
let ctor: IConstructorSignature1<HTMLElement, BreadcrumbsPicker> = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker;
return instantiationService.createInstance(ctor, parent);
}
interface ILayoutInfo {
maxHeight: number;
width: number;
arrowSize: number;
arrowOffset: number;
inputHeight: number;
}
export abstract class BreadcrumbsPicker {
protected readonly _disposables = new Array<IDisposable>();
@@ -42,14 +52,20 @@ export abstract class BreadcrumbsPicker {
protected readonly _treeContainer: HTMLDivElement;
protected readonly _tree: HighlightingWorkbenchTree;
protected readonly _focus: dom.IFocusTracker;
protected readonly _symbolSortOrder: BreadcrumbsConfig<'position' | 'name' | 'type'>;
private _layoutInfo: ILayoutInfo;
private readonly _onDidPickElement = new Emitter<{ target: any, payload: any }>();
readonly onDidPickElement: Event<{ target: any, payload: any }> = this._onDidPickElement.event;
private readonly _onDidFocusElement = new Emitter<{ target: any, payload: any }>();
readonly onDidFocusElement: Event<{ target: any, payload: any }> = this._onDidFocusElement.event;
constructor(
parent: HTMLElement,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IThemeService protected readonly _themeService: IThemeService,
@IWorkbenchThemeService protected readonly _themeService: IWorkbenchThemeService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) {
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons';
@@ -57,42 +73,71 @@ export abstract class BreadcrumbsPicker {
this._focus = dom.trackFocus(this._domNode);
this._focus.onDidBlur(_ => this._onDidPickElement.fire({ target: undefined, payload: undefined }), undefined, this._disposables);
this._disposables.push(onDidChangeZoomLevel(_ => this._onDidPickElement.fire({ target: undefined, payload: undefined })));
const theme = this._themeService.getTheme();
const color = theme.getColor(breadcrumbsPickerBackground);
this._arrow = document.createElement('div');
this._arrow.style.width = '0';
this._arrow.style.borderStyle = 'solid';
this._arrow.style.borderWidth = '8px';
this._arrow.className = 'arrow';
this._arrow.style.borderColor = `transparent transparent ${color.toString()}`;
this._domNode.appendChild(this._arrow);
this._treeContainer = document.createElement('div');
this._treeContainer.style.background = color.toString();
this._treeContainer.style.paddingTop = '2px';
this._treeContainer.style.boxShadow = `0px 5px 8px ${(theme.type === DARK ? color.darken(.6) : color.darken(.2))}`;
this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getTheme().getColor(widgetShadow)}`;
this._domNode.appendChild(this._treeContainer);
const treeConifg = this._completeTreeConfiguration({ dataSource: undefined, renderer: undefined });
this._symbolSortOrder = BreadcrumbsConfig.SymbolSortOrder.bindTo(this._configurationService);
const filterConfig = BreadcrumbsConfig.FilterOnType.bindTo(this._configurationService);
this._disposables.push(filterConfig);
const treeConfig = this._completeTreeConfiguration({ dataSource: undefined, renderer: undefined, highlighter: undefined });
this._tree = this._instantiationService.createInstance(
HighlightingWorkbenchTree,
this._treeContainer,
treeConifg,
{ useShadows: false },
treeConfig,
<IHighlightingTreeOptions>{ useShadows: false, filterOnType: filterConfig.getValue(), showTwistie: false, twistiePixels: 12 },
{ placeholder: localize('placeholder', "Find") }
);
this._disposables.push(this._tree.onDidChangeSelection(e => {
if (e.payload !== this._tree) {
const target = this._getTargetFromSelectionEvent(e);
if (!target) {
return;
const target = this._getTargetFromEvent(e.selection[0], e.payload);
if (target) {
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
this._onDidPickElement.fire({ target, payload: e.payload });
}, 0);
}
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
this._onDidPickElement.fire({ target, payload: e.payload });
}, 0);
}
}));
this._disposables.push(this._tree.onDidChangeFocus(e => {
const target = this._getTargetFromEvent(e.focus, e.payload);
if (target) {
this._onDidFocusElement.fire({ target, payload: e.payload });
}
}));
this._disposables.push(this._tree.onDidStartFiltering(() => {
this._layoutInfo.inputHeight = 36;
this._layout();
}));
this._disposables.push(this._tree.onDidExpandItem(() => {
this._layout();
}));
this._disposables.push(this._tree.onDidCollapseItem(() => {
this._layout();
}));
// tree icon theme specials
dom.addClass(this._treeContainer, 'file-icon-themable-tree');
dom.addClass(this._treeContainer, 'show-file-icons');
const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => {
dom.toggleClass(this._treeContainer, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons);
dom.toggleClass(this._treeContainer, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true);
};
this._disposables.push(_themeService.onDidFileIconThemeChange(onFileIconThemeChange));
onFileIconThemeChange(_themeService.getFileIconTheme());
this._domNode.focus();
}
@@ -102,14 +147,20 @@ export abstract class BreadcrumbsPicker {
this._onDidPickElement.dispose();
this._tree.dispose();
this._focus.dispose();
this._symbolSortOrder.dispose();
}
setInput(input: any): void {
setInput(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void {
let actualInput = this._getInput(input);
this._tree.setInput(actualInput).then(() => {
this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 };
this._layout();
// use proper selection, reveal
let selection = this._getInitialSelection(this._tree, input);
if (selection) {
this._tree.reveal(selection, .5).then(() => {
return this._tree.reveal(selection, .5).then(() => {
this._tree.setSelection([selection], this._tree);
this._tree.setFocus(selection);
this._tree.domFocus();
@@ -118,25 +169,37 @@ export abstract class BreadcrumbsPicker {
this._tree.focusFirst();
this._tree.setSelection([this._tree.getFocus()], this._tree);
this._tree.domFocus();
return Promise.resolve(null);
}
}, onUnexpectedError);
}
layout(height: number, width: number, arrowSize: number, arrowOffset: number) {
this._domNode.style.height = `${height}px`;
this._domNode.style.width = `${width}px`;
this._arrow.style.borderWidth = `${arrowSize}px`;
this._arrow.style.marginLeft = `${arrowOffset}px`;
private _layout(info: ILayoutInfo = this._layoutInfo): void {
this._treeContainer.style.height = `${height - 2 * arrowSize}px`;
this._treeContainer.style.width = `${width}px`;
let count = 0;
let nav = this._tree.getNavigator(undefined, false);
while (nav.next() && count < 13) { count += 1; }
let headerHeight = 2 * info.arrowSize;
let treeHeight = Math.min(info.maxHeight - headerHeight, count * 22);
let totalHeight = treeHeight + headerHeight;
this._domNode.style.height = `${totalHeight}px`;
this._domNode.style.width = `${info.width}px`;
this._arrow.style.top = `-${2 * info.arrowSize}px`;
this._arrow.style.borderWidth = `${info.arrowSize}px`;
this._arrow.style.marginLeft = `${info.arrowOffset}px`;
this._treeContainer.style.height = `${treeHeight}px`;
this._treeContainer.style.width = `${info.width}px`;
this._tree.layout();
this._layoutInfo = info;
}
protected abstract _getInput(input: BreadcrumbElement): any;
protected abstract _getInitialSelection(tree: ITree, input: BreadcrumbElement): any;
protected abstract _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration;
protected abstract _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined;
protected abstract _getTargetFromEvent(element: any, payload: any): any | undefined;
}
//#region - Files
@@ -167,7 +230,7 @@ export class FileDataSource implements IDataSource {
getChildren(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): TPromise<IWorkspaceFolder[] | IFileStat[]> {
if (IWorkspace.isIWorkspace(element)) {
return TPromise.as(element.folders).then(folders => {
return Promise.resolve(element.folders).then(folders => {
for (let child of folders) {
this._parents.set(element, child);
}
@@ -191,13 +254,80 @@ export class FileDataSource implements IDataSource {
}
getParent(tree: ITree, element: IWorkspace | URI | IWorkspaceFolder | IFileStat): TPromise<IWorkspaceFolder | IFileStat> {
return TPromise.as(this._parents.get(element));
return Promise.resolve(this._parents.get(element));
}
}
export class FileRenderer implements IRenderer, IHighlightingRenderer {
export class FileFilter implements IFilter {
private readonly _scores = new Map<string, FuzzyScore>();
private readonly _cachedExpressions = new Map<string, glob.ParsedExpression>();
private readonly _disposables: IDisposable[] = [];
constructor(
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
@IConfigurationService configService: IConfigurationService,
) {
const config = BreadcrumbsConfig.FileExcludes.bindTo(configService);
const update = () => {
_workspaceService.getWorkspace().folders.forEach(folder => {
const excludesConfig = config.getValue({ resource: folder.uri });
if (!excludesConfig) {
return;
}
// adjust patterns to be absolute in case they aren't
// free floating (**/)
const adjustedConfig: glob.IExpression = {};
for (const pattern in excludesConfig) {
if (typeof excludesConfig[pattern] !== 'boolean') {
continue;
}
let patternAbs = pattern.indexOf('**/') !== 0
? join(folder.uri.path, pattern)
: pattern;
adjustedConfig[patternAbs] = excludesConfig[pattern];
}
this._cachedExpressions.set(folder.uri.toString(), glob.parse(adjustedConfig));
});
};
update();
this._disposables.push(
config,
config.onDidChange(update),
_workspaceService.onDidChangeWorkspaceFolders(update)
);
}
dispose(): void {
dispose(this._disposables);
}
isVisible(tree: ITree, element: IWorkspaceFolder | IFileStat): boolean {
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
// not a file
return true;
}
const folder = this._workspaceService.getWorkspaceFolder(element.resource);
if (!folder || !this._cachedExpressions.has(folder.uri.toString())) {
// no folder or no filer
return true;
}
const expression = this._cachedExpressions.get(folder.uri.toString());
return !expression(element.resource.path, basename(element.resource));
}
}
export class FileHighlighter implements IHighlighter {
getHighlightsStorageKey(element: IFileStat | IWorkspaceFolder): string {
return IWorkspaceFolder.isIWorkspaceFolder(element) ? element.uri.toString() : element.resource.toString();
}
getHighlights(tree: ITree, element: IFileStat | IWorkspaceFolder, pattern: string): FuzzyScore {
return fuzzyScore(pattern, pattern.toLowerCase(), 0, element.name, element.name.toLowerCase(), 0, true);
}
}
export class FileRenderer implements IRenderer {
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@@ -231,29 +361,14 @@ export class FileRenderer implements IRenderer, IHighlightingRenderer {
fileKind,
hidePath: true,
fileDecorations: fileDecorations,
matches: createMatches((this._scores.get(resource.toString()) || [, []])[1])
matches: createMatches((tree as HighlightingWorkbenchTree).getHighlighterScore(element)),
extraClasses: ['picker-item']
});
}
disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void {
templateData.dispose();
}
updateHighlights(tree: ITree, pattern: string): any {
let nav = tree.getNavigator(undefined, false);
let topScore: FuzzyScore;
let topElement: any;
while (nav.next()) {
let element = nav.current() as IFileStat | IWorkspaceFolder;
let score = fuzzyScore(pattern, element.name, undefined, true);
this._scores.set(IWorkspaceFolder.isIWorkspaceFolder(element) ? element.uri.toString() : element.resource.toString(), score);
if (!topScore || score && topScore[0] < score[0]) {
topScore = score;
topElement = element;
}
}
return topElement;
}
}
export class FileSorter implements ISorter {
@@ -278,10 +393,11 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
constructor(
parent: HTMLElement,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
@IConfigurationService configService: IConfigurationService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
) {
super(parent, instantiationService, themeService);
super(parent, instantiationService, themeService, configService);
}
protected _getInput(input: BreadcrumbElement): any {
@@ -308,16 +424,20 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration {
// todo@joh reuse explorer implementations?
const filter = this._instantiationService.createInstance(FileFilter);
this._disposables.push(filter);
config.dataSource = this._instantiationService.createInstance(FileDataSource);
config.renderer = this._instantiationService.createInstance(FileRenderer);
config.sorter = new FileSorter();
config.highlighter = new FileHighlighter();
config.filter = filter;
return config;
}
protected _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined {
let [first] = e.selection;
if (first && !IWorkspaceFolder.isIWorkspaceFolder(first) && !(first as IFileStat).isDirectory) {
return new FileElement((first as IFileStat).resource, FileKind.FILE);
protected _getTargetFromEvent(element: any, _payload: any): any | undefined {
if (element && !IWorkspaceFolder.isIWorkspaceFolder(element) && !(element as IFileStat).isDirectory) {
return new FileElement((element as IFileStat).resource, FileKind.FILE);
}
}
}
@@ -325,11 +445,10 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
//#region - Symbols
class HighlightingOutlineRenderer extends OutlineRenderer implements IHighlightingRenderer {
updateHighlights(tree: ITree, pattern: string): any {
let model = OutlineModel.get(tree.getInput());
return model.updateMatches(pattern);
class OutlineHighlighter implements IHighlighter {
getHighlights(tree: ITree, element: OutlineElement, pattern: string): FuzzyScore {
OutlineModel.get(element).updateMatches(pattern);
return element.score;
}
}
@@ -348,18 +467,30 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration {
config.dataSource = this._instantiationService.createInstance(OutlineDataSource);
config.renderer = this._instantiationService.createInstance(HighlightingOutlineRenderer);
config.sorter = new OutlineItemComparator();
config.renderer = this._instantiationService.createInstance(OutlineRenderer);
config.sorter = new OutlineItemComparator(this._getOutlineItemComparator());
config.highlighter = new OutlineHighlighter();
return config;
}
protected _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined {
if (e.payload && e.payload.didClickOnTwistie) {
protected _getTargetFromEvent(element: any, payload: any): any | undefined {
if (payload && payload.didClickOnTwistie) {
return;
}
let [first] = e.selection;
if (first instanceof OutlineElement) {
return first;
if (element instanceof OutlineElement) {
return element;
}
}
private _getOutlineItemComparator(): OutlineItemCompareType {
switch (this._symbolSortOrder.getValue()) {
case 'name':
return OutlineItemCompareType.ByName;
case 'type':
return OutlineItemCompareType.ByKind;
case 'position':
default:
return OutlineItemCompareType.ByPosition;
}
}
}

View File

@@ -2,16 +2,16 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Registry } from 'vs/platform/registry/common/platform';
import * as nls from 'vs/nls';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { Action, IAction } from 'vs/base/common/actions';
import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { StatusbarItemDescriptor, StatusbarAlignment, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { StatusbarItemDescriptor, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext } from 'vs/workbench/common/editor';
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
@@ -24,7 +24,7 @@ import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/bina
import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { SyncActionDescriptor, MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import {
@@ -38,7 +38,7 @@ import {
SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction,
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction
} from 'vs/workbench/browser/parts/editor/editorActions';
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -48,6 +48,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { isMacintosh } from 'vs/base/common/platform';
import { AllEditorsPicker, ActiveEditorGroupPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
import { Schemas } from 'vs/base/common/network';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets';
// Register String Editor
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
@@ -217,6 +219,9 @@ class SideBySideEditorInputFactory implements IEditorInputFactory {
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
// Register Editor Contributions
registerEditorContribution(OpenWorkspaceButtonContribution);
// Register Editor Status
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* towards the left of the right hand side */));
@@ -322,6 +327,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction,
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors in Group to the Left', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorInAllGroupsAction, CloseEditorInAllGroupsAction.ID, CloseEditorInAllGroupsAction.LABEL), 'View: Close Editor in All Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorOrthogonalAction, SplitEditorOrthogonalAction.ID, SplitEditorOrthogonalAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_BACKSLASH) }), 'View: Split Editor Orthogonal', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorLeftAction, SplitEditorLeftAction.ID, SplitEditorLeftAction.LABEL), 'View: Split Editor Left', category);
@@ -332,7 +338,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, J
registry.registerWorkbenchAction(new SyncActionDescriptor(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join Editors of All Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Sidebar', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Side Bar', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category);
@@ -361,8 +367,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupLeftActi
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupRightAction, NewEditorGroupRightAction.ID, NewEditorGroupRightAction.LABEL), 'View: New Editor Group to the Right', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupAboveAction, NewEditorGroupAboveAction.ID, NewEditorGroupAboveAction.LABEL), 'View: New Editor Group Above', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupBelowAction, NewEditorGroupBelowAction.ID, NewEditorGroupBelowAction.LABEL), 'View: New Editor Group Below', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward');
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back');
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward');
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back');
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateToLastEditLocationAction, NavigateToLastEditLocationAction.ID, NavigateToLastEditLocationAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_Q) }), 'Go to Last Edit Location');
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last');
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History');
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History');
@@ -438,15 +445,15 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '5_split', order: 40 });
// Editor Title Menu
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_INLINE_MODE, title: nls.localize('toggleInlineView', "Toggle Inline View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_SIDE_BY_SIDE, title: nls.localize('toggleSideBySideView', "Toggle Side By Side View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.SHOW_EDITORS_IN_GROUP, title: nls.localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
interface IEditorToolItem { id: string; title: string; iconDark: string; iconLight: string; }
function appendEditorToolItem(primary: IEditorToolItem, alternative: IEditorToolItem, when: ContextKeyExpr, order: number): void {
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr, order: number, alternative?: IEditorToolItem): void {
const item: IMenuItem = {
command: {
id: primary.id,
title: primary.title,
@@ -455,18 +462,23 @@ function appendEditorToolItem(primary: IEditorToolItem, alternative: IEditorTool
light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${primary.iconLight}`))
}
},
alt: {
group: 'navigation',
when,
order
};
if (alternative) {
item.alt = {
id: alternative.id,
title: alternative.title,
iconLocation: {
dark: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconDark}`)),
light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconLight}`))
}
},
group: 'navigation',
when,
order
});
};
}
MenuRegistry.appendMenuItem(MenuId.EditorTitle, item);
}
// Editor Title Menu: Split Editor
@@ -476,14 +488,15 @@ appendEditorToolItem(
title: nls.localize('splitEditorRight', "Split Editor Right"),
iconDark: 'split-editor-horizontal-inverse.svg',
iconLight: 'split-editor-horizontal.svg'
}, {
},
ContextKeyExpr.not('splitEditorsVertically'),
100000, // towards the end
{
id: editorCommands.SPLIT_EDITOR_DOWN,
title: nls.localize('splitEditorDown', "Split Editor Down"),
iconDark: 'split-editor-vertical-inverse.svg',
iconLight: 'split-editor-vertical.svg'
},
ContextKeyExpr.not('splitEditorsVertically'),
100000 /* towards the end */
}
);
appendEditorToolItem(
@@ -492,14 +505,15 @@ appendEditorToolItem(
title: nls.localize('splitEditorDown', "Split Editor Down"),
iconDark: 'split-editor-vertical-inverse.svg',
iconLight: 'split-editor-vertical.svg'
}, {
},
ContextKeyExpr.has('splitEditorsVertically'),
100000, // towards the end
{
id: editorCommands.SPLIT_EDITOR_RIGHT,
title: nls.localize('splitEditorRight', "Split Editor Right"),
iconDark: 'split-editor-horizontal-inverse.svg',
iconLight: 'split-editor-horizontal.svg'
},
ContextKeyExpr.has('splitEditorsVertically'),
100000 // towards the end
}
);
// Editor Title Menu: Close Group (tabs disabled)
@@ -509,14 +523,15 @@ appendEditorToolItem(
title: nls.localize('close', "Close"),
iconDark: 'close-big-inverse-alt.svg',
iconLight: 'close-big-alt.svg'
}, {
},
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')),
1000000, // towards the far end
{
id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
title: nls.localize('closeAll', "Close All"),
iconDark: 'closeall-editors-inverse.svg',
iconLight: 'closeall-editors.svg'
},
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')),
1000000 // towards the end
}
);
appendEditorToolItem(
@@ -525,22 +540,71 @@ appendEditorToolItem(
title: nls.localize('close', "Close"),
iconDark: 'close-dirty-inverse-alt.svg',
iconLight: 'close-dirty-alt.svg'
}, {
},
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')),
1000000, // towards the far end
{
id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
title: nls.localize('closeAll', "Close All"),
iconDark: 'closeall-editors-inverse.svg',
iconLight: 'closeall-editors.svg'
}
);
// Diff Editor Title Menu: Previous Change
appendEditorToolItem(
{
id: editorCommands.GOTO_PREVIOUS_CHANGE,
title: nls.localize('navigate.prev.label', "Previous Change"),
iconDark: 'previous-diff-inverse.svg',
iconLight: 'previous-diff.svg'
},
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')),
1000000 // towards the end
TextCompareEditorActiveContext,
10
);
// Diff Editor Title Menu: Next Change
appendEditorToolItem(
{
id: editorCommands.GOTO_NEXT_CHANGE,
title: nls.localize('navigate.next.label', "Next Change"),
iconDark: 'next-diff-inverse.svg',
iconLight: 'next-diff.svg'
},
TextCompareEditorActiveContext,
11
);
// Diff Editor Title Menu: Toggle Ignore Trim Whitespace (Enabled)
appendEditorToolItem(
{
id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
title: nls.localize('ignoreTrimWhitespace.label', "Ignore Trim Whitespace"),
iconDark: 'paragraph-inverse.svg',
iconLight: 'paragraph.svg'
},
ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', true)),
20
);
// Diff Editor Title Menu: Toggle Ignore Trim Whitespace (Disabled)
appendEditorToolItem(
{
id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
title: nls.localize('showTrimWhitespace.label', "Show Trim Whitespace"),
iconDark: 'paragraph-disabled-inverse.svg',
iconLight: 'paragraph-disabled.svg'
},
ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', false)),
20
);
// Editor Commands for Command Palette
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepEditor', "Keep Editor"), category }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOtherEditors', "Close Other Editors in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: { value: nls.localize('keepEditor', "Keep Editor"), original: 'View: Keep Editor' }, category }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), original: 'View: Close All Editors in Group' }, category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: { value: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), original: 'View: Close Saved Editors in Group' }, category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeOtherEditors', "Close Other Editors in Group"), original: 'View: Close Other Editors in Group' }, category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: { value: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), original: 'View: Close Editors to the Right in Group' }, category } });
// File menu
MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, {
@@ -676,3 +740,177 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
},
order: 9
});
// Main Menu Bar Contributions:
// Forward/Back
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '1_fwd_back',
command: {
id: 'workbench.action.navigateBack',
title: nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"),
precondition: ContextKeyExpr.has('canNavigateBack')
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '1_fwd_back',
command: {
id: 'workbench.action.navigateForward',
title: nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"),
precondition: ContextKeyExpr.has('canNavigateForward')
},
order: 2
});
// Switch Editor
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
group: '1_any',
command: {
id: 'workbench.action.nextEditor',
title: nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
group: '1_any',
command: {
id: 'workbench.action.previousEditor',
title: nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
group: '2_used',
command: {
id: 'workbench.action.openNextRecentlyUsedEditorInGroup',
title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
group: '2_used',
command: {
id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup',
title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '2_switch',
title: nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor"),
submenu: MenuId.MenubarSwitchEditorMenu,
order: 1
});
// Switch Group
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusFirstEditorGroup',
title: nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusSecondEditorGroup',
title: nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusThirdEditorGroup',
title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusFourthEditorGroup',
title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4")
},
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusFifthEditorGroup',
title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5")
},
order: 5
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '2_next_prev',
command: {
id: 'workbench.action.focusNextGroup',
title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '2_next_prev',
command: {
id: 'workbench.action.focusPreviousGroup',
title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '3_directional',
command: {
id: 'workbench.action.focusLeftGroup',
title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '3_directional',
command: {
id: 'workbench.action.focusRightGroup',
title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '3_directional',
command: {
id: 'workbench.action.focusAboveGroup',
title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '3_directional',
command: {
id: 'workbench.action.focusBelowGroup',
title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below")
},
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '2_switch',
title: nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group"),
submenu: MenuId.MenubarSwitchGroupMenu,
order: 2
});

View File

@@ -3,9 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { GroupIdentifier, IWorkbenchEditorConfiguration, IWorkbenchEditorPartConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent } from 'vs/workbench/common/editor';
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
@@ -30,6 +27,7 @@ export interface IEditorPartOptions extends IWorkbenchEditorPartConfiguration {
export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = {
showTabs: true,
highlightModifiedTabs: false,
tabCloseButton: 'right',
tabSizing: 'fit',
showIcons: true,
@@ -104,7 +102,7 @@ export interface IEditorGroupsAccessor {
export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup {
readonly group: EditorGroup;
readonly whenRestored: TPromise<void>;
readonly whenRestored: Thenable<void>;
readonly disposed: boolean;
readonly onDidFocus: Event<void>;
@@ -118,8 +116,6 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito
setActive(isActive: boolean): void;
setLabel(label: string): void;
relayout(): void;
shutdown(): void;
}
export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEditor?: IEditorInput, presetOptions?: EditorOptions): EditorOptions {
@@ -159,5 +155,5 @@ export interface EditorGroupsServiceImpl extends IEditorGroupsService {
/**
* A promise that resolves when groups have been restored.
*/
readonly whenRestored: TPromise<void>;
readonly whenRestored: Thenable<void>;
}

View File

@@ -2,9 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { mixin } from 'vs/base/common/objects';
@@ -37,7 +35,7 @@ export class ExecuteCommandAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
return this.commandService.executeCommand(this.commandId, this.commandArgs);
}
}
@@ -71,10 +69,10 @@ export class BaseSplitEditorAction extends Action {
}));
}
run(context?: IEditorIdentifier): TPromise<any> {
run(context?: IEditorIdentifier): Promise<any> {
splitEditor(this.editorGroupService, this.direction, context);
return TPromise.as(true);
return Promise.resolve(true);
}
dispose(): void {
@@ -189,7 +187,7 @@ export class JoinTwoGroupsAction extends Action {
super(id, label);
}
run(context?: IEditorIdentifier): TPromise<any> {
run(context?: IEditorIdentifier): Promise<any> {
let sourceGroup: IEditorGroup;
if (context && typeof context.groupId === 'number') {
sourceGroup = this.editorGroupService.getGroup(context.groupId);
@@ -203,11 +201,11 @@ export class JoinTwoGroupsAction extends Action {
if (targetGroup && sourceGroup !== targetGroup) {
this.editorGroupService.mergeGroup(sourceGroup, targetGroup);
return TPromise.as(true);
return Promise.resolve(true);
}
}
return TPromise.as(true);
return Promise.resolve(true);
}
}
@@ -224,10 +222,10 @@ export class JoinAllGroupsAction extends Action {
super(id, label);
}
run(context?: IEditorIdentifier): TPromise<any> {
run(context?: IEditorIdentifier): Promise<any> {
mergeAllGroups(this.editorGroupService);
return TPromise.as(true);
return Promise.resolve(true);
}
}
@@ -244,11 +242,11 @@ export class NavigateBetweenGroupsAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true);
nextGroup.focus();
return TPromise.as(true);
return Promise.resolve(true);
}
}
@@ -265,10 +263,10 @@ export class FocusActiveGroupAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.editorGroupService.activeGroup.focus();
return TPromise.as(true);
return Promise.resolve(true);
}
}
@@ -283,13 +281,13 @@ export abstract class BaseFocusGroupAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true);
if (group) {
group.focus();
}
return TPromise.as(true);
return Promise.resolve(true);
}
}
@@ -425,7 +423,7 @@ export class OpenToSideFromQuickOpenAction extends Action {
this.class = (preferredDirection === GroupDirection.RIGHT) ? 'quick-open-sidebyside-vertical' : 'quick-open-sidebyside-horizontal';
}
run(context: any): TPromise<any> {
run(context: any): Thenable<any> {
const entry = toEditorQuickOpenEntry(context);
if (entry) {
const input = entry.getInput();
@@ -439,7 +437,7 @@ export class OpenToSideFromQuickOpenAction extends Action {
return this.editorService.openEditor(resourceInput, SIDE_GROUP);
}
return TPromise.as(false);
return Promise.resolve(false);
}
}
@@ -474,7 +472,7 @@ export class CloseEditorAction extends Action {
super(id, label, 'close-editor-action');
}
run(context?: IEditorCommandsContext): TPromise<any> {
run(context?: IEditorCommandsContext): Promise<any> {
return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, void 0, context);
}
}
@@ -492,7 +490,7 @@ export class CloseOneEditorAction extends Action {
super(id, label, 'close-editor-action');
}
run(context?: IEditorCommandsContext): TPromise<any> {
run(context?: IEditorCommandsContext): Thenable<any> {
let group: IEditorGroup;
let editorIndex: number;
if (context) {
@@ -520,7 +518,7 @@ export class CloseOneEditorAction extends Action {
return group.closeEditor(group.activeEditor);
}
return TPromise.as(false);
return Promise.resolve(false);
}
}
@@ -537,7 +535,7 @@ export class RevertAndCloseEditorAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
const activeControl = this.editorService.activeControl;
if (activeControl) {
const editor = activeControl.input;
@@ -553,7 +551,7 @@ export class RevertAndCloseEditorAction extends Action {
});
}
return TPromise.as(false);
return Promise.resolve(false);
}
}
@@ -571,13 +569,13 @@ export class CloseLeftEditorsInGroupAction extends Action {
super(id, label);
}
run(context?: IEditorIdentifier): TPromise<any> {
run(context?: IEditorIdentifier): Thenable<any> {
const { group, editor } = getTarget(this.editorService, this.editorGroupService, context);
if (group && editor) {
return group.closeEditors({ direction: CloseDirection.LEFT, except: editor });
}
return TPromise.as(false);
return Promise.resolve(false);
}
}
@@ -616,7 +614,7 @@ export abstract class BaseCloseAllAction extends Action {
return groupsToClose;
}
run(): TPromise<any> {
run(): Thenable<any> {
// Just close all if there are no or one dirty editor
if (this.textFileService.getDirty().length < 2) {
@@ -629,7 +627,7 @@ export abstract class BaseCloseAllAction extends Action {
return void 0;
}
let saveOrRevertPromise: TPromise<boolean>;
let saveOrRevertPromise: Thenable<boolean>;
if (confirm === ConfirmResult.DONT_SAVE) {
saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true);
} else {
@@ -646,7 +644,7 @@ export abstract class BaseCloseAllAction extends Action {
});
}
protected abstract doCloseAll(): TPromise<any>;
protected abstract doCloseAll(): Thenable<any>;
}
export class CloseAllEditorsAction extends BaseCloseAllAction {
@@ -663,8 +661,8 @@ export class CloseAllEditorsAction extends BaseCloseAllAction {
super(id, label, 'action-close-all-files', textFileService, editorGroupService);
}
protected doCloseAll(): TPromise<any> {
return TPromise.join(this.groupsToClose.map(g => g.closeAllEditors()));
protected doCloseAll(): Promise<any> {
return Promise.all(this.groupsToClose.map(g => g.closeAllEditors()));
}
}
@@ -682,8 +680,8 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction {
super(id, label, void 0, textFileService, editorGroupService);
}
protected doCloseAll(): TPromise<any> {
return TPromise.join(this.groupsToClose.map(g => g.closeAllEditors())).then(() => {
protected doCloseAll(): Promise<any> {
return Promise.all(this.groupsToClose.map(g => g.closeAllEditors())).then(() => {
this.groupsToClose.forEach(group => this.editorGroupService.removeGroup(group));
});
}
@@ -702,11 +700,11 @@ export class CloseEditorsInOtherGroupsAction extends Action {
super(id, label);
}
run(context?: IEditorIdentifier): TPromise<any> {
run(context?: IEditorIdentifier): Thenable<any> {
const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup;
return TPromise.join(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => {
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => {
if (g.id === groupToSkip.id) {
return TPromise.as(null);
return Promise.resolve(null);
}
return g.closeAllEditors();
@@ -714,6 +712,30 @@ export class CloseEditorsInOtherGroupsAction extends Action {
}
}
export class CloseEditorInAllGroupsAction extends Action {
static readonly ID = 'workbench.action.closeEditorInAllGroups';
static readonly LABEL = nls.localize('closeEditorInAllGroups', "Close Editor in All Groups");
constructor(
id: string,
label: string,
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
@IEditorService private editorService: IEditorService
) {
super(id, label);
}
run(): Thenable<any> {
const activeEditor = this.editorService.activeEditor;
if (activeEditor) {
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor)));
}
return Promise.resolve(null);
}
}
export class BaseMoveGroupAction extends Action {
constructor(
@@ -725,7 +747,7 @@ export class BaseMoveGroupAction extends Action {
super(id, label);
}
run(context?: IEditorIdentifier): TPromise<any> {
run(context?: IEditorIdentifier): Promise<any> {
let sourceGroup: IEditorGroup;
if (context && typeof context.groupId === 'number') {
sourceGroup = this.editorGroupService.getGroup(context.groupId);
@@ -738,7 +760,7 @@ export class BaseMoveGroupAction extends Action {
this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
}
return TPromise.as(true);
return Promise.resolve(true);
}
private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup {
@@ -834,10 +856,10 @@ export class MinimizeOtherGroupsAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
return TPromise.as(false);
return Promise.resolve(false);
}
}
@@ -850,10 +872,10 @@ export class ResetGroupSizesAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN);
return TPromise.as(false);
return Promise.resolve(false);
}
}
@@ -872,14 +894,13 @@ export class MaximizeGroupAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
if (this.editorService.activeEditor) {
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
return this.partService.setSideBarHidden(true);
this.partService.setSideBarHidden(true);
}
return TPromise.as(false);
return Promise.resolve(false);
}
}
@@ -894,15 +915,15 @@ export abstract class BaseNavigateEditorAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
const result = this.navigate();
if (!result) {
return TPromise.as(false);
return Promise.resolve(false);
}
const { groupId, editor } = result;
if (!editor) {
return TPromise.as(false);
return Promise.resolve(false);
}
const group = this.editorGroupService.getGroup(groupId);
@@ -1081,10 +1102,10 @@ export class NavigateForwardAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.historyService.forward();
return TPromise.as(null);
return Promise.resolve(null);
}
}
@@ -1097,10 +1118,26 @@ export class NavigateBackwardsAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.historyService.back();
return TPromise.as(null);
return Promise.resolve(null);
}
}
export class NavigateToLastEditLocationAction extends Action {
static readonly ID = 'workbench.action.navigateToLastEditLocation';
static readonly LABEL = nls.localize('navigateToLastEditLocation', "Go to Last Edit Location");
constructor(id: string, label: string, @IHistoryService private historyService: IHistoryService) {
super(id, label);
}
run(): Thenable<any> {
this.historyService.openLastEditLocation();
return Promise.resolve(null);
}
}
@@ -1113,10 +1150,10 @@ export class NavigateLastAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.historyService.last();
return TPromise.as(null);
return Promise.resolve(null);
}
}
@@ -1133,10 +1170,10 @@ export class ReopenClosedEditorAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.historyService.reopenLastClosedEditor();
return TPromise.as(false);
return Promise.resolve(false);
}
}
@@ -1148,15 +1185,21 @@ export class ClearRecentFilesAction extends Action {
constructor(
id: string,
label: string,
@IWindowsService private windowsService: IWindowsService
@IWindowsService private windowsService: IWindowsService,
@IHistoryService private historyService: IHistoryService
) {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
// Clear global recently opened
this.windowsService.clearRecentlyOpened();
return TPromise.as(false);
// Clear workspace specific recently opened
this.historyService.clearRecentlyOpened();
return Promise.resolve(false);
}
}
@@ -1195,14 +1238,14 @@ export class BaseQuickOpenEditorInGroupAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
const keys = this.keybindingService.lookupKeybindings(this.id);
this.quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX, { quickNavigateConfiguration: { keybindings: keys } });
return TPromise.as(true);
return Promise.resolve(true);
}
}
@@ -1250,12 +1293,12 @@ export class OpenPreviousEditorFromHistoryAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
const keys = this.keybindingService.lookupKeybindings(this.id);
this.quickOpenService.show(null, { quickNavigateConfiguration: { keybindings: keys } });
return TPromise.as(true);
return Promise.resolve(true);
}
}
@@ -1268,10 +1311,10 @@ export class OpenNextRecentlyUsedEditorAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.historyService.forward(true);
return TPromise.as(null);
return Promise.resolve(null);
}
}
@@ -1284,10 +1327,10 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.historyService.back(true);
return TPromise.as(null);
return Promise.resolve(null);
}
}
@@ -1304,12 +1347,12 @@ export class ClearEditorHistoryAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
// Editor history
this.historyService.clear();
return TPromise.as(true);
return Promise.resolve(true);
}
}
@@ -1576,10 +1619,10 @@ export class BaseCreateEditorGroupAction extends Action {
super(id, label);
}
run(): TPromise<any> {
run(): Thenable<any> {
this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true });
return TPromise.as(true);
return Promise.resolve(true);
}
}

View File

@@ -12,17 +12,16 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IListService } from 'vs/platform/list/browser/listService';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { distinct } from 'vs/base/common/arrays';
import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
@@ -36,7 +35,11 @@ export const MOVE_ACTIVE_EDITOR_COMMAND_ID = 'moveActiveEditor';
export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
export const TOGGLE_DIFF_INLINE_MODE = 'toggle.diff.editorMode';
export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide';
export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange';
export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange';
export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace';
export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp';
export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown';
@@ -46,6 +49,8 @@ export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight';
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active ';
export const OPEN_EDITOR_AT_INDEX_COMMAND_ID = 'workbench.action.openEditorAtIndex';
export interface ActiveEditorMoveArguments {
to?: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
by?: 'tab' | 'group';
@@ -77,7 +82,7 @@ function registerActiveEditorMoveCommand(): void {
id: MOVE_ACTIVE_EDITOR_COMMAND_ID,
weight: KeybindingWeight.WorkbenchContrib,
when: EditorContextKeys.editorTextFocus,
primary: null,
primary: 0,
handler: (accessor, args: any) => moveActiveEditor(args, accessor),
description: {
description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
@@ -221,18 +226,18 @@ export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
function registerDiffEditorCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.compareEditor.nextChange',
id: GOTO_NEXT_CHANGE,
weight: KeybindingWeight.WorkbenchContrib,
when: TextCompareEditorVisibleContext,
primary: null,
primary: KeyMod.Alt | KeyCode.F5,
handler: accessor => navigateInDiffEditor(accessor, true)
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.compareEditor.previousChange',
id: GOTO_PREVIOUS_CHANGE,
weight: KeybindingWeight.WorkbenchContrib,
when: TextCompareEditorVisibleContext,
primary: null,
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5,
handler: accessor => navigateInDiffEditor(accessor, false)
});
@@ -245,27 +250,66 @@ function registerDiffEditorCommands(): void {
}
}
function toggleDiffSideBySide(accessor: ServicesAccessor): void {
const configurationService = accessor.get(IConfigurationService);
const newValue = !configurationService.getValue<boolean>('diffEditor.renderSideBySide');
configurationService.updateValue('diffEditor.renderSideBySide', newValue, ConfigurationTarget.USER);
}
function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void {
const configurationService = accessor.get(IConfigurationService);
const newValue = !configurationService.getValue<boolean>('diffEditor.ignoreTrimWhitespace');
configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue, ConfigurationTarget.USER);
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: TOGGLE_DIFF_INLINE_MODE,
id: TOGGLE_DIFF_SIDE_BY_SIDE,
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: void 0,
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
handler: accessor => toggleDiffSideBySide(accessor)
});
const { control } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
if (control instanceof TextDiffEditor) {
const widget = control.getControl();
const isInlineMode = !widget.renderSideBySide;
widget.updateOptions(<IDiffEditorOptions>{
renderSideBySide: isInlineMode
});
}
}
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: TOGGLE_DIFF_SIDE_BY_SIDE,
title: {
value: nls.localize('toggleInlineView', "Toggle Inline View"),
original: 'Compare: Toggle Inline View'
},
category: nls.localize('compare', "Compare")
},
when: ContextKeyExpr.has('textCompareEditorActive')
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: void 0,
handler: accessor => toggleDiffIgnoreTrimWhitespace(accessor)
});
}
function registerOpenEditorAtIndexCommands(): void {
const openEditorAtIndex: ICommandHandler = (accessor: ServicesAccessor, editorIndex: number): void => {
const editorService = accessor.get(IEditorService);
const activeControl = editorService.activeControl;
if (activeControl) {
const editor = activeControl.group.getEditor(editorIndex);
if (editor) {
editorService.openEditor(editor);
}
}
};
// This command takes in the editor index number to open as an argument
CommandsRegistry.registerCommand({
id: OPEN_EDITOR_AT_INDEX_COMMAND_ID,
handler: openEditorAtIndex
});
// Keybindings to focus a specific index in the tab folder if tabs are enabled
for (let i = 0; i < 9; i++) {
@@ -273,24 +317,12 @@ function registerOpenEditorAtIndexCommands(): void {
const visibleIndex = i + 1;
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.openEditorAtIndex' + visibleIndex,
id: OPEN_EDITOR_AT_INDEX_COMMAND_ID + visibleIndex,
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: KeyMod.Alt | toKeyCode(visibleIndex),
mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
handler: accessor => {
const editorService = accessor.get(IEditorService);
const activeControl = editorService.activeControl;
if (activeControl) {
const editor = activeControl.group.getEditor(editorIndex);
if (editor) {
return editorService.openEditor(editor).then(() => void 0);
}
}
return void 0;
}
handler: accessor => openEditorAtIndex(accessor, editorIndex)
});
}
@@ -434,7 +466,7 @@ function registerCloseEditorCommands() {
contexts.push({ groupId: activeGroup.id }); // active group as fallback
}
return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId =>
editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
));
}
@@ -454,7 +486,7 @@ function registerCloseEditorCommands() {
distinctGroupIds.push(editorGroupService.activeGroup.id);
}
return TPromise.join(distinctGroupIds.map(groupId =>
return Promise.all(distinctGroupIds.map(groupId =>
editorGroupService.getGroup(groupId).closeAllEditors()
));
}
@@ -477,7 +509,7 @@ function registerCloseEditorCommands() {
const groupIds = distinct(contexts.map(context => context.groupId));
return TPromise.join(groupIds.map(groupId => {
return Promise.all(groupIds.map(groupId => {
const group = editorGroupService.getGroup(groupId);
const editors = contexts
.filter(context => context.groupId === groupId)
@@ -526,7 +558,7 @@ function registerCloseEditorCommands() {
const groupIds = distinct(contexts.map(context => context.groupId));
return TPromise.join(groupIds.map(groupId => {
return Promise.all(groupIds.map(groupId => {
const group = editorGroupService.getGroup(groupId);
const editors = contexts
.filter(context => context.groupId === groupId)
@@ -551,7 +583,7 @@ function registerCloseEditorCommands() {
return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
}
return TPromise.as(false);
return Promise.resolve(false);
}
});
@@ -568,7 +600,7 @@ function registerCloseEditorCommands() {
return group.pinEditor(editor);
}
return TPromise.as(false);
return Promise.resolve(false);
}
});
@@ -647,7 +679,7 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon
// First check for a focused list to return the selected items from
const list = listService.lastFocusedList;
if (list instanceof List && list.isDOMFocused()) {
if (list instanceof List && list.getHTMLElement() === document.activeElement) {
const elementToContext = (element: IEditorIdentifier | IEditorGroup) => {
if (isEditorGroup(element)) {
return { groupId: element.id, editorIndex: void 0 };

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { Dimension, show, hide, addClass } from 'vs/base/browser/dom';
@@ -15,7 +13,6 @@ import { IPartService } from 'vs/workbench/services/part/common/partService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
import { toWinJsPromise } from 'vs/base/common/async';
import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
import { Event, Emitter } from 'vs/base/common/event';
@@ -171,11 +168,11 @@ export class EditorControl extends Disposable {
// Show progress while setting input after a certain timeout. If the workbench is opening
// be more relaxed about progress showing by increasing the delay a little bit to reduce flicker.
const operation = this.editorOperation.start(this.partService.isCreated() ? 800 : 3200);
const operation = this.editorOperation.start(this.partService.isRestored() ? 800 : 3200);
// Call into editor control
const editorWillChange = !inputMatches;
return toWinJsPromise(control.setInput(editor, options, operation.token)).then(() => {
return TPromise.wrap(control.setInput(editor, options, operation.token)).then(() => {
// Focus (unless prevented or another operation is running)
if (operation.isCurrent()) {
@@ -233,15 +230,9 @@ export class EditorControl extends Disposable {
}
}
shutdown(): void {
// Forward to all editor controls
this.controls.forEach(editor => editor.shutdown());
}
dispose(): void {
this.activeControlDisposeables = dispose(this.activeControlDisposeables);
super.dispose();
}
}
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/editordroptarget';
import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass, removeClass } from 'vs/base/browser/dom';
@@ -17,6 +15,7 @@ import { isMacintosh } from 'vs/base/common/platform';
import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/group/common/editorGroupsService';
import { toDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { RunOnceScheduler } from 'vs/base/common/async';
interface IDropOperation {
splitDirection?: GroupDirection;
@@ -32,6 +31,8 @@ class DropOverlay extends Themable {
private currentDropOperation: IDropOperation;
private _disposed: boolean;
private cleanupOverlayScheduler: RunOnceScheduler;
private readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
private readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
@@ -43,6 +44,8 @@ class DropOverlay extends Themable {
) {
super(themeService);
this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300));
this.create();
}
@@ -118,6 +121,11 @@ class DropOverlay extends Themable {
// Position overlay
this.positionOverlay(e.offsetX, e.offsetY, isDraggingGroup);
// Make sure to stop any running cleanup scheduler to remove the overlay
if (this.cleanupOverlayScheduler.isScheduled()) {
this.cleanupOverlayScheduler.cancel();
}
},
onDragLeave: e => this.dispose(),
@@ -144,9 +152,9 @@ class DropOverlay extends Themable {
// To protect against this issue we always destroy the overlay as soon as we detect a
// mouse event over it. The delay is used to guarantee we are not interfering with the
// actual DROP event that can also trigger a mouse over event.
setTimeout(() => {
this.dispose();
}, 300);
if (!this.cleanupOverlayScheduler.isScheduled()) {
this.cleanupOverlayScheduler.schedule();
}
}));
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/editorgroupview';
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
@@ -26,7 +24,7 @@ import { IProgressService } from 'vs/platform/progress/common/progress';
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { localize } from 'vs/nls';
import { isPromiseCanceledError, isErrorWithActions, IErrorWithActions } from 'vs/base/common/errors';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Severity, INotificationService, INotificationActions } from 'vs/platform/notification/common/notification';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -49,6 +47,9 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
// {{SQL CARBON EDIT}}
import { ICommandService } from 'vs/platform/commands/common/commands';
import { GlobalNewUntitledFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions';
// {{SQL CARBON EDIT}} - End
import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions';
import { URI } from 'vs/base/common/uri';
export class EditorGroupView extends Themable implements IEditorGroupView {
@@ -99,7 +100,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private active: boolean;
private dimension: Dimension;
private _whenRestored: TPromise<void>;
private _whenRestored: Thenable<void>;
private isRestored: boolean;
private scopedInstantiationService: IInstantiationService;
@@ -250,7 +251,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
if (this.isEmpty()) {
EventHelper.stop(e);
// {{SQL CARBON EDIT}}
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).done(undefined, err => this.notificationService.warn(err));
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err));
}
}));
@@ -313,8 +314,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Show it
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.as(actions),
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
getActions: () => actions,
onHide: () => this.focus()
});
}
@@ -404,9 +404,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
private restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): TPromise<void> {
private restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): Thenable<void> {
if (this._group.count === 0) {
return TPromise.as(void 0); // nothing to show
return Promise.resolve(void 0); // nothing to show
}
// Determine editor options
@@ -421,11 +421,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
options.pinned = this._group.isPinned(activeEditor); // preserve pinned state
options.preserveFocus = true; // handle focus after editor is opened
const activeElement = document.activeElement;
// Show active editor
return this.doShowEditor(activeEditor, true, options).then(() => {
// Set focused now if this is the active group
if (this.accessor.activeGroup === this) {
// Set focused now if this is the active group and focus has
// not changed meanwhile. This prevents focus from being
// stolen accidentally on startup when the user already
// clicked somewhere.
if (this.accessor.activeGroup === this && activeElement === document.activeElement) {
this.focus();
}
});
@@ -604,7 +609,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this._disposed;
}
get whenRestored(): TPromise<void> {
get whenRestored(): Thenable<void> {
return this._whenRestored;
}
@@ -728,6 +733,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
openEditor(editor: EditorInput, options?: EditorOptions): TPromise<void> {
// Guard against invalid inputs
if (!editor) {
return TPromise.as(void 0);
}
// Editor opening event allows for prevention
const event = new EditorOpeningEvent(this._group.id, editor, options);
this._onWillOpenEditor.fire(event);
@@ -764,13 +774,21 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.accessor.activateGroup(this);
}
// Actually move the editor if a specific index is provided and we figure
// out that the editor is already opened at a different index. This
// ensures the right set of events are fired to the outside.
if (typeof openEditorOptions.index === 'number') {
const indexOfEditor = this._group.indexOf(editor);
if (indexOfEditor !== -1 && indexOfEditor !== openEditorOptions.index) {
this.doMoveEditorInsideGroup(editor, openEditorOptions);
}
}
// Update model
this._group.openEditor(editor, openEditorOptions);
// Show editor
const showEditorResult = this.doShowEditor(editor, openEditorOptions.active, options);
return showEditorResult;
return this.doShowEditor(editor, openEditorOptions.active, options);
}
private doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): TPromise<void> {
@@ -846,7 +864,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const startingIndex = this.getIndexOfEditor(editor) + 1;
// Open the other ones inactive
return TPromise.join(editors.map(({ editor, options }, index) => {
return Promise.all(editors.map(({ editor, options }, index) => {
const adjustedEditorOptions = options || new EditorOptions();
adjustedEditorOptions.inactive = true;
adjustedEditorOptions.pinned = true;
@@ -963,13 +981,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.doCloseInactiveEditor(editor);
}
// Forward to title control & breadcrumbs
// Forward to title control
this.titleAreaControl.closeEditor(editor);
}
private doCloseActiveEditor(focusNext = this.accessor.activeGroup === this, fromError?: boolean): void {
const editorToClose = this.activeEditor;
const editorHasFocus = isAncestor(document.activeElement, this.element);
const restoreFocus = this.shouldRestoreFocus(this.element);
// Optimization: if we are about to close the last editor in this group and settings
// are configured to close the group since it will be empty, we first set the last
@@ -983,7 +1001,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const mostRecentlyActiveGroups = this.accessor.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
const nextActiveGroup = mostRecentlyActiveGroups[1]; // [0] will be the current one, so take [1]
if (nextActiveGroup) {
if (editorHasFocus) {
if (restoreFocus) {
nextActiveGroup.focus();
} else {
this.accessor.activateGroup(nextActiveGroup);
@@ -1020,7 +1038,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.editorControl.closeEditor(editorToClose);
// Restore focus to group container as needed unless group gets closed
if (editorHasFocus && !closeEmptyGroup) {
if (restoreFocus && !closeEmptyGroup) {
this.focus();
}
@@ -1034,6 +1052,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
private shouldRestoreFocus(target: Element): boolean {
const activeElement = document.activeElement;
if (activeElement === document.body) {
return true; // always restore focus if nothing is focused currently
}
// otherwise check for the active element being an ancestor of the target
return isAncestor(activeElement, target);
}
private doCloseInactiveEditor(editor: EditorInput) {
// Update model
@@ -1368,10 +1397,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#endregion
shutdown(): void {
this.editorControl.shutdown();
}
dispose(): void {
this._disposed = true;
@@ -1427,7 +1452,7 @@ registerThemingParticipant((theme, collector, environment) => {
const letterpress = `resources/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`;
collector.addRule(`
.monaco-workbench > .part.editor > .content .editor-group-container.empty .editor-group-letterpress {
background-image: url('${join(environment.appRoot, letterpress)}')
background-image: url('${URI.file(join(environment.appRoot, letterpress)).toString()}')
}
`);

View File

@@ -3,20 +3,18 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/workbench/browser/parts/editor/editor.contribution';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Part } from 'vs/workbench/browser/part';
import { Dimension, isAncestor, toggleClass, addClass, $ } from 'vs/base/browser/dom';
import { Event, Emitter, once, Relay, anyEvent } from 'vs/base/common/event';
import { contrastBorder, editorBackground, registerColor } from 'vs/platform/theme/common/colorRegistry';
import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument } from 'vs/workbench/services/group/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, ISerializedNode, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid';
import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid';
import { GroupIdentifier, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { values } from 'vs/base/common/map';
import { EDITOR_GROUP_BORDER } from 'vs/workbench/common/theme';
import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme';
import { distinct } from 'vs/base/common/arrays';
import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptions, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, EditorGroupsServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
@@ -24,15 +22,14 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { assign } from 'vs/base/common/objects';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Scope } from 'vs/workbench/common/memento';
import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { TValueCallback, TPromise } from 'vs/base/common/winjs.base';
import { always } from 'vs/base/common/async';
import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget';
import { localize } from 'vs/nls';
import { Color } from 'vs/base/common/color';
import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout';
import { IView, orthogonal } from 'vs/base/browser/ui/grid/gridview';
import { onUnexpectedError } from 'vs/base/common/errors';
// {{SQL CARBON EDIT}}
import { convertEditorInput } from 'sql/parts/common/customInputConverter';
@@ -86,12 +83,6 @@ class GridWidgetView<T extends IView> implements IView {
}
}
export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', {
dark: editorBackground,
light: editorBackground,
hc: editorBackground
}, localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout."));
export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditorGroupsAccessor {
_serviceBrand: any;
@@ -128,7 +119,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
private dimension: Dimension;
private _preferredSize: Dimension;
private memento: object;
private workspaceMemento: object;
private globalMemento: object;
private _partOptions: IEditorPartOptions;
@@ -142,8 +133,8 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
private gridWidget: SerializableGrid<IEditorGroupView>;
private gridWidgetView: GridWidgetView<IEditorGroupView>;
private _whenRestored: TPromise<void>;
private whenRestoredComplete: TValueCallback<void>;
private _whenRestored: Thenable<void>;
private whenRestoredResolve: () => void;
constructor(
id: string,
@@ -151,19 +142,18 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService,
@IStorageService private storageService: IStorageService
@IStorageService storageService: IStorageService
) {
super(id, { hasTitle: false }, themeService);
super(id, { hasTitle: false }, themeService, storageService);
this.gridWidgetView = new GridWidgetView<IEditorGroupView>();
this._partOptions = getEditorPartOptions(this.configurationService.getValue<IWorkbenchEditorConfiguration>());
this.memento = this.getMemento(this.storageService, Scope.WORKSPACE);
this.globalMemento = this.getMemento(this.storageService, Scope.GLOBAL);
this._whenRestored = new TPromise(resolve => {
this.whenRestoredComplete = resolve;
});
this.workspaceMemento = this.getMemento(StorageScope.WORKSPACE);
this.globalMemento = this.getMemento(StorageScope.GLOBAL);
this._whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve));
this.registerListeners();
}
@@ -236,7 +226,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
return this.gridWidget.orientation === Orientation.VERTICAL ? GroupOrientation.VERTICAL : GroupOrientation.HORIZONTAL;
}
get whenRestored(): TPromise<void> {
get whenRestored(): Thenable<void> {
return this._whenRestored;
}
@@ -376,7 +366,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
}
applyLayout(layout: EditorGroupLayout): void {
const gridHasFocus = isAncestor(document.activeElement, this.container);
const restoreFocus = this.shouldRestoreFocus(this.container);
// Determine how many groups we need overall
let layoutGroupsCount = 0;
@@ -429,19 +419,33 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
// Mark preferred size as changed
this.resetPreferredSize();
// Events for groupd that got added
// Events for groups that got added
this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(groupView => {
if (currentGroupViews.indexOf(groupView) === -1) {
this._onDidAddGroup.fire(groupView);
}
});
// Update labels
this.updateGroupLabels();
// Restore focus as needed
if (gridHasFocus) {
if (restoreFocus) {
this._activeGroup.focus();
}
}
private shouldRestoreFocus(target: Element): boolean {
const activeElement = document.activeElement;
if (activeElement === document.body) {
return true; // always restore focus if nothing is focused currently
}
// otherwise check for the active element being an ancestor of the target
return isAncestor(activeElement, target);
}
private isTwoDimensionalGrid(): boolean {
const views = this.gridWidget.getViews();
if (isGridBranchNode(views)) {
@@ -485,6 +489,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
// Event
this._onDidAddGroup.fire(newGroupView);
// Update labels
this.updateGroupLabels();
return newGroupView;
}
@@ -622,7 +629,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
}
private doRemoveEmptyGroup(groupView: IEditorGroupView): void {
const gridHasFocus = isAncestor(document.activeElement, this.container);
const restoreFocus = this.shouldRestoreFocus(this.container);
// Activate next group if the removed one was active
if (this._activeGroup === groupView) {
@@ -637,16 +644,12 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
// Restore focus if we had it previously (we run this after gridWidget.removeView() is called
// because removing a view can mean to reparent it and thus focus would be removed otherwise)
if (gridHasFocus) {
if (restoreFocus) {
this._activeGroup.focus();
}
// Update labels: since our labels are created using the index of the
// group, removing a group might produce gaps. So we iterate over all
// groups and reassign the label based on the index.
this.getGroups(GroupsOrder.CREATION_TIME).forEach((group, index) => {
group.setLabel(this.getGroupLabel(index + 1));
});
// Update labels
this.updateGroupLabels();
// Update container
this.updateContainer();
@@ -658,10 +661,6 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
this._onDidRemoveGroup.fire(groupView);
}
private getGroupLabel(index: number): string {
return localize('groupLabel', "Group {0}", index);
}
moveGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView {
const sourceView = this.assertGroupView(group);
const targetView = this.assertGroupView(location);
@@ -670,15 +669,14 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
throw new Error('Cannot move group into its own');
}
const groupHasFocus = isAncestor(document.activeElement, sourceView.element);
const restoreFocus = this.shouldRestoreFocus(sourceView.element);
// Move is a simple remove and add of the same view
this.gridWidget.removeView(sourceView, Sizing.Distribute);
this.gridWidget.addView(sourceView, Sizing.Distribute, targetView, this.toGridViewDirection(direction));
// Move through grid widget API
this.gridWidget.moveView(sourceView, Sizing.Distribute, targetView, this.toGridViewDirection(direction));
// Restore focus if we had it previously (we run this after gridWidget.removeView() is called
// because removing a view can mean to reparent it and thus focus would be removed otherwise)
if (groupHasFocus) {
if (restoreFocus) {
sourceView.focus();
}
@@ -692,13 +690,13 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
const groupView = this.assertGroupView(group);
const locationView = this.assertGroupView(location);
const groupHasFocus = isAncestor(document.activeElement, groupView.element);
const restoreFocus = this.shouldRestoreFocus(groupView.element);
// Copy the group view
const copiedGroupView = this.doAddGroup(locationView, direction, groupView);
// Restore focus if we had it
if (groupHasFocus) {
if (restoreFocus) {
copiedGroupView.focus();
}
@@ -712,7 +710,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
// Move/Copy editors over into target
let index = (options && typeof options.index === 'number') ? options.index : targetView.count;
sourceView.editors.forEach(editor => {
const inactive = !sourceView.isActive(editor);
const inactive = !sourceView.isActive(editor) || this._activeGroup !== sourceView;
const copyOptions: ICopyEditorOptions = { index, inactive, preserveFocus: inactive };
if (options && options.mode === MergeGroupMode.COPY_EDITORS) {
@@ -802,6 +800,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
centerLayout(active: boolean): void {
this.centeredLayoutWidget.activate(active);
this._activeGroup.focus();
}
isLayoutCentered(): boolean {
@@ -825,27 +824,47 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
}
// Signal restored
always(TPromise.join(this.groups.map(group => group.whenRestored)), () => this.whenRestoredComplete(void 0));
always(Promise.all(this.groups.map(group => group.whenRestored)), () => this.whenRestoredResolve());
// Update container
this.updateContainer();
}
private doCreateGridControlWithPreviousState(): void {
const uiState = this.doGetPreviousState();
const uiState = this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState;
if (uiState && uiState.serializedGrid) {
try {
// MRU
this.mostRecentActiveGroups = uiState.mostRecentActiveGroups;
// MRU
this.mostRecentActiveGroups = uiState.mostRecentActiveGroups;
// Grid Widget
this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup);
// Grid Widget
this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup);
// Ensure last active group has focus
this._activeGroup.focus();
// Ensure last active group has focus
this._activeGroup.focus();
} catch (error) {
this.handleGridRestoreError(error, uiState);
}
}
}
private handleGridRestoreError(error: Error, state: IEditorPartUIState): void {
// Log error
onUnexpectedError(new Error(`Error restoring editor grid widget: ${error} (with state: ${JSON.stringify(state)})`));
// Clear any state we have from the failing restore
if (this.gridWidget) {
this.doSetGridWidget();
}
this.groupViews.forEach(group => group.dispose());
this.groupViews.clear();
this._activeGroup = void 0;
this.mostRecentActiveGroups = [];
}
private doCreateGridControlWithState(serializedGrid: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void {
// Determine group views to reuse if any
@@ -857,8 +876,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
}
// Create new
const groupViews: IEditorGroupView[] = [];
const gridWidget = SerializableGrid.deserialize(serializedGrid, {
fromJSON: (serializedEditorGroup: ISerializedEditorGroup) => {
fromJSON: (serializedEditorGroup: ISerializedEditorGroup | null) => {
let groupView: IEditorGroupView;
if (reuseGroupViews.length > 0) {
groupView = reuseGroupViews.shift();
@@ -866,6 +886,8 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
groupView = this.doCreateGroupView(serializedEditorGroup);
}
groupViews.push(groupView);
if (groupView.id === activeGroupId) {
this.doSetGroupActive(groupView);
}
@@ -874,6 +896,18 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
}
}, { styles: { separatorBorder: this.gridSeparatorBorder } });
// If the active group was not found when restoring the grid
// make sure to make at least one group active. We always need
// an active group.
if (!this._activeGroup) {
this.doSetGroupActive(groupViews[0]);
}
// Validate MRU group views matches grid widget state
if (this.mostRecentActiveGroups.some(groupId => !this.getGroup(groupId))) {
this.mostRecentActiveGroups = groupViews.map(group => group.id);
}
// Set it
this.doSetGridWidget(gridWidget);
}
@@ -893,127 +927,25 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
this.onDidSetGridWidget.fire();
}
private doGetPreviousState(): IEditorPartUIState {
const legacyState = this.doGetPreviousLegacyState();
if (legacyState) {
return legacyState; // TODO@ben remove after a while
}
return this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState;
}
private doGetPreviousLegacyState(): IEditorPartUIState {
const LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.uiState';
const LEGACY_STACKS_MODEL_STORAGE_KEY = 'editorStacks.model';
interface ILegacyEditorPartUIState {
ratio: number[];
groupOrientation: 'vertical' | 'horizontal';
}
interface ISerializedLegacyEditorStacksModel {
groups: ISerializedEditorGroup[];
active: number;
}
let legacyUIState: ISerializedLegacyEditorStacksModel;
const legacyUIStateRaw = this.storageService.get(LEGACY_STACKS_MODEL_STORAGE_KEY, StorageScope.WORKSPACE);
if (legacyUIStateRaw) {
try {
legacyUIState = JSON.parse(legacyUIStateRaw);
} catch (error) { /* ignore */ }
}
if (legacyUIState) {
this.storageService.remove(LEGACY_STACKS_MODEL_STORAGE_KEY, StorageScope.WORKSPACE);
}
const legacyPartState = this.memento[LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY] as ILegacyEditorPartUIState;
if (legacyPartState) {
delete this.memento[LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY];
}
if (legacyUIState && Array.isArray(legacyUIState.groups) && legacyUIState.groups.length > 0) {
const splitHorizontally = legacyPartState && legacyPartState.groupOrientation === 'horizontal';
const legacyState: IEditorPartUIState = Object.create(null);
const positionOneGroup = legacyUIState.groups[0];
const positionTwoGroup = legacyUIState.groups[1];
const positionThreeGroup = legacyUIState.groups[2];
legacyState.activeGroup = legacyUIState.active;
legacyState.mostRecentActiveGroups = [legacyUIState.active];
if (positionTwoGroup || positionThreeGroup) {
if (!positionThreeGroup) {
legacyState.mostRecentActiveGroups.push(legacyState.activeGroup === 0 ? 1 : 0);
} else {
if (legacyState.activeGroup === 0) {
legacyState.mostRecentActiveGroups.push(1, 2);
} else if (legacyState.activeGroup === 1) {
legacyState.mostRecentActiveGroups.push(0, 2);
} else {
legacyState.mostRecentActiveGroups.push(0, 1);
}
}
}
const toNode = function (group: ISerializedEditorGroup, size: number): ISerializedNode {
return {
data: group,
size,
type: 'leaf'
};
};
const baseSize = 1200; // just some number because layout() was not called yet, but we only need the proportions
// No split editor
if (!positionTwoGroup) {
legacyState.serializedGrid = {
width: baseSize,
height: baseSize,
orientation: splitHorizontally ? Orientation.VERTICAL : Orientation.HORIZONTAL,
root: toNode(positionOneGroup, baseSize)
};
}
// Split editor (2 or 3 columns)
else {
const children: ISerializedNode[] = [];
const size = positionThreeGroup ? baseSize / 3 : baseSize / 2;
children.push(toNode(positionOneGroup, size));
children.push(toNode(positionTwoGroup, size));
if (positionThreeGroup) {
children.push(toNode(positionThreeGroup, size));
}
legacyState.serializedGrid = {
width: baseSize,
height: baseSize,
orientation: splitHorizontally ? Orientation.VERTICAL : Orientation.HORIZONTAL,
root: {
data: children,
size: baseSize,
type: 'branch'
}
};
}
return legacyState;
}
return void 0;
}
private updateContainer(): void {
toggleClass(this.container, 'empty', this.isEmpty());
}
private updateGroupLabels(): void {
// Since our labels are created using the index of the
// group, adding/removing a group might produce gaps.
// So we iterate over all groups and reassign the label
// based on the index.
this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach((group, index) => {
group.setLabel(this.getGroupLabel(index + 1));
});
}
private getGroupLabel(index: number): string {
return localize('groupLabel', "Group {0}", index);
}
private isEmpty(): boolean {
return this.groupViews.size === 1 && this._activeGroup.isEmpty();
}
@@ -1041,7 +973,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
//this.editorGroupsControl.refreshTitles();
}
shutdown(): void {
protected saveState(): void {
// Persist grid UI state
if (this.gridWidget) {
@@ -1052,19 +984,21 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
};
if (this.isEmpty()) {
delete this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY];
delete this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY];
} else {
this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState;
this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState;
}
}
// Persist centered view state
this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = this.centeredLayoutWidget.state;
const centeredLayoutState = this.centeredLayoutWidget.state;
if (this.centeredLayoutWidget.isDefault(centeredLayoutState)) {
delete this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY];
} else {
this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = centeredLayoutState;
}
// Forward to all groups
this.groupViews.forEach(group => group.shutdown());
super.shutdown();
super.saveState();
}
dispose(): void {

View File

@@ -2,17 +2,15 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/editorpicker';
import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/workbench/browser/labels';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -20,6 +18,7 @@ import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'v
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditorInput, toResource } from 'vs/workbench/common/editor';
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { CancellationToken } from 'vs/base/common/cancellation';
export class EditorPickerEntry extends QuickOpenEntryGroup {
@@ -91,10 +90,10 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
this.scorerCache = Object.create(null);
}
getResults(searchValue: string): TPromise<QuickOpenModel> {
getResults(searchValue: string, token: CancellationToken): Thenable<QuickOpenModel> {
const editorEntries = this.getEditorEntries();
if (!editorEntries.length) {
return TPromise.as(null);
return Promise.resolve(null);
}
// Prepare search for scoring
@@ -117,7 +116,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
// Sorting
if (query.value) {
const groups = this.editorGroupService.getGroups(GroupsOrder.CREATION_TIME);
const groups = this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
entries.sort((e1, e2) => {
if (e1.group !== e2.group) {
return groups.indexOf(e1.group) - groups.indexOf(e2.group); // older groups first
@@ -139,7 +138,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
});
}
return TPromise.as(new QuickOpenModel(entries));
return Promise.resolve(new QuickOpenModel(entries));
}
onClose(canceled: boolean): void {
@@ -206,7 +205,7 @@ export class AllEditorsPicker extends BaseEditorPicker {
protected getEditorEntries(): EditorPickerEntry[] {
const entries: EditorPickerEntry[] = [];
this.editorGroupService.getGroups(GroupsOrder.CREATION_TIME).forEach(group => {
this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => {
group.editors.forEach(editor => {
entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group));
});
@@ -232,4 +231,4 @@ export class AllEditorsPicker extends BaseEditorPicker {
return super.getAutoFocus(searchValue, context);
}
}
}

View File

@@ -3,22 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/editorstatus';
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { $, append, runAtThisOrScheduleAtNextAnimationFrame, addDisposableListener, getDomNodePagePosition } from 'vs/base/browser/dom';
import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import * as strings from 'vs/base/common/strings';
import * as paths from 'vs/base/common/paths';
import * as types from 'vs/base/common/types';
import uri from 'vs/base/common/uri';
import * as errors from 'vs/base/common/errors';
import { URI as uri } from 'vs/base/common/uri';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { Action } from 'vs/base/common/actions';
import { language, LANGUAGE_DEFAULT, AccessibilitySupport } from 'vs/base/common/platform';
import * as browser from 'vs/base/browser/browser';
import { IMode } from 'vs/editor/common/modes';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput } from 'vs/workbench/common/editor';
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -31,11 +26,11 @@ import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpa
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { SUPPORTED_ENCODINGS, IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
@@ -46,18 +41,16 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { widgetShadow, editorWidgetBackground, foreground, darken, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { deepClone } from 'vs/base/common/objects';
import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Button } from 'vs/base/browser/ui/button/button';
import { Schemas } from 'vs/base/common/network';
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { Themable } from 'vs/workbench/common/theme';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { timeout } from 'vs/base/common/async';
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { once } from 'vs/base/common/event';
class SideBySideEditorEncodingSupport implements IEncodingSupport {
constructor(private master: IEncodingSupport, private details: IEncodingSupport) { }
@@ -294,7 +287,7 @@ export class EditorStatus implements IStatusbarItem {
private activeEditorListeners: IDisposable[];
private delayedRender: IDisposable;
private toRender: StateChange;
private screenReaderExplanation: ScreenReaderDetectedExplanation;
private screenReaderNotification: INotificationHandle;
constructor(
@IEditorService private editorService: IEditorService,
@@ -304,6 +297,7 @@ export class EditorStatus implements IStatusbarItem {
@IModeService private modeService: IModeService,
@ITextFileService private textFileService: ITextFileService,
@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
@INotificationService private readonly notificationService: INotificationService
) {
this.toDispose = [];
this.activeEditorListeners = [];
@@ -494,28 +488,39 @@ export class EditorStatus implements IStatusbarItem {
private onModeClick(): void {
const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);
action.run().done(null, errors.onUnexpectedError);
action.run();
action.dispose();
}
private onIndentationClick(): void {
const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
action.run().done(null, errors.onUnexpectedError);
action.run();
action.dispose();
}
private onScreenReaderModeClick(): void {
const showExplanation = !this.screenReaderExplanation || !this.screenReaderExplanation.visible;
if (!this.screenReaderNotification) {
this.screenReaderNotification = this.notificationService.prompt(
Severity.Info,
// {{SQL CARBON EDIT}}
nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio?"),
[{
label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"),
run: () => {
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
}
}, {
label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"),
run: () => {
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
}
}],
{ sticky: true }
);
if (!this.screenReaderExplanation) {
this.screenReaderExplanation = this.instantiationService.createInstance(ScreenReaderDetectedExplanation);
this.toDispose.push(this.screenReaderExplanation);
}
if (showExplanation) {
this.screenReaderExplanation.show(this.screenRedearModeElement);
} else {
this.screenReaderExplanation.hide();
once(this.screenReaderNotification.onDidClose)(() => {
this.screenReaderNotification = null;
});
}
}
@@ -526,14 +531,14 @@ export class EditorStatus implements IStatusbarItem {
private onEOLClick(): void {
const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);
action.run().done(null, errors.onUnexpectedError);
action.run();
action.dispose();
}
private onEncodingClick(): void {
const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);
action.run().done(null, errors.onUnexpectedError);
action.run();
action.dispose();
}
@@ -617,6 +622,10 @@ export class EditorStatus implements IStatusbarItem {
this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
this.onMetadataChange(activeControl);
}));
this.activeEditorListeners.push(editor.onDidOpenInPlace(() => {
this.updateStatusBar();
}));
});
}
}
@@ -689,8 +698,8 @@ export class EditorStatus implements IStatusbarItem {
screenReaderMode = (editorWidget.getConfiguration().accessibilitySupport === AccessibilitySupport.Enabled);
}
if (screenReaderMode === false && this.screenReaderExplanation && this.screenReaderExplanation.visible) {
this.screenReaderExplanation.hide();
if (screenReaderMode === false && this.screenReaderNotification) {
this.screenReaderNotification.close();
}
this.updateState({ screenReaderMode: screenReaderMode });
@@ -819,7 +828,7 @@ export class ShowLanguageExtensionsAction extends Action {
this.enabled = galleryService.isEnabled();
}
run(): TPromise<void> {
run(): Thenable<void> {
return this.commandService.executeCommand('workbench.extensions.action.showExtensionsForLanguage', this.fileExtension).then(() => void 0);
}
}
@@ -836,7 +845,7 @@ export class ChangeModeAction extends Action {
@IModelService private modelService: IModelService,
@IEditorService private editorService: IEditorService,
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IQuickInputService private quickInputService: IQuickInputService,
@IPreferencesService private preferencesService: IPreferencesService,
@IInstantiationService private instantiationService: IInstantiationService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService
@@ -844,10 +853,10 @@ export class ChangeModeAction extends Action {
super(actionId, actionLabel);
}
run(): TPromise<any> {
run(): Thenable<any> {
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
if (!activeTextEditorWidget) {
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
const textModel = activeTextEditorWidget.getModel();
@@ -868,7 +877,7 @@ export class ChangeModeAction extends Action {
// All languages are valid picks
const languages = this.modeService.getRegisteredLanguageNames();
const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
const picks: QuickPickInput[] = languages.sort().map((lang, index) => {
let description: string;
if (currentModeId === lang) {
description = nls.localize('languageDescription', "({0}) - Configured Language", this.modeService.getModeIdForLanguageName(lang.toLowerCase()));
@@ -888,20 +897,20 @@ export class ChangeModeAction extends Action {
}
}
return <IFilePickOpenEntry>{
return <IQuickPickItem>{
label: lang,
resource: fakeResource,
iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource),
description
};
});
if (hasLanguageSupport) {
picks[0].separator = { border: true, label: nls.localize('languagesPicks', "languages (identifier)") };
picks.unshift({ type: 'separator', label: nls.localize('languagesPicks', "languages (identifier)") });
}
// Offer action to configure via settings
let configureModeAssociations: IPickOpenEntry;
let configureModeSettings: IPickOpenEntry;
let configureModeAssociations: IQuickPickItem;
let configureModeSettings: IQuickPickItem;
let galleryAction: Action;
if (hasLanguageSupport) {
const ext = paths.extname(resource.fsPath) || paths.basename(resource.fsPath);
@@ -918,7 +927,7 @@ export class ChangeModeAction extends Action {
}
// Offer to "Auto Detect"
const autoDetectMode: IPickOpenEntry = {
const autoDetectMode: IQuickPickItem = {
label: nls.localize('autoDetect', "Auto Detect")
};
@@ -926,7 +935,7 @@ export class ChangeModeAction extends Action {
picks.unshift(autoDetectMode);
}
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
if (!pick) {
return;
}
@@ -970,23 +979,22 @@ export class ChangeModeAction extends Action {
}
// Find mode
let mode: TPromise<IMode>;
let languageSelection: ILanguageSelection;
if (pick === autoDetectMode) {
mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true }).fsPath, textModel.getLineContent(1));
// {{SQL CARBON EDIT}} - use activeEditor.input instead of activeEditor
languageSelection = this.modeService.createByFilepathOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true }).fsPath, textModel.getLineContent(1));
} else {
mode = this.modeService.getOrCreateModeByLanguageName(pick.label);
languageSelection = this.modeService.createByLanguageName(pick.label);
}
// {{SQL CARBON EDIT}}
// Change mode
models.forEach(textModel => {
let self = this;
mode.then((modeValue) => {
QueryEditorService.sqlLanguageModeCheck(textModel, modeValue, activeEditor).then((newTextModel) => {
if (newTextModel) {
self.modelService.setMode(newTextModel, modeValue);
}
});
QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then((newTextModel) => {
if (newTextModel) {
self.modelService.setMode(newTextModel, languageSelection);
}
});
});
});
@@ -995,21 +1003,21 @@ export class ChangeModeAction extends Action {
private configureFileAssociation(resource: uri): void {
const extension = paths.extname(resource.fsPath);
const basename = paths.basename(resource.fsPath);
const currentAssociation = this.modeService.getModeIdByFilenameOrFirstLine(basename);
const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(basename);
const languages = this.modeService.getRegisteredLanguageNames();
const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
const picks: IQuickPickItem[] = languages.sort().map((lang, index) => {
const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase());
return <IPickOpenEntry>{
return <IQuickPickItem>{
id,
label: lang,
description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : void 0
};
});
TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).done(() => {
this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).done(language => {
setTimeout(() => {
this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).then(language => {
if (language) {
const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG);
@@ -1037,11 +1045,11 @@ export class ChangeModeAction extends Action {
this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
}
});
});
}, 50 /* quick open is sensitive to being opened so soon after another */);
}
}
export interface IChangeEOLEntry extends IPickOpenEntry {
export interface IChangeEOLEntry extends IQuickPickItem {
eol: EndOfLineSequence;
}
@@ -1054,22 +1062,22 @@ class ChangeIndentationAction extends Action {
actionId: string,
actionLabel: string,
@IEditorService private editorService: IEditorService,
@IQuickOpenService private quickOpenService: IQuickOpenService
@IQuickInputService private quickInputService: IQuickInputService
) {
super(actionId, actionLabel);
}
run(): TPromise<any> {
run(): Thenable<any> {
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
if (!activeTextEditorWidget) {
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
if (!isWritableCodeEditor(activeTextEditorWidget)) {
return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
}
const picks = [
const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
activeTextEditorWidget.getAction(IndentUsingSpaces.ID),
activeTextEditorWidget.getAction(IndentUsingTabs.ID),
activeTextEditorWidget.getAction(DetectIndentation.ID),
@@ -1088,10 +1096,10 @@ class ChangeIndentationAction extends Action {
};
});
(<IPickOpenEntry>picks[0]).separator = { label: nls.localize('indentView', "change view") };
(<IPickOpenEntry>picks[3]).separator = { label: nls.localize('indentConvert', "convert file"), border: true };
picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") });
picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") });
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }).then(action => action && action.run());
return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }).then(action => action && action.run());
}
}
@@ -1104,19 +1112,19 @@ export class ChangeEOLAction extends Action {
actionId: string,
actionLabel: string,
@IEditorService private editorService: IEditorService,
@IQuickOpenService private quickOpenService: IQuickOpenService
@IQuickInputService private quickInputService: IQuickInputService
) {
super(actionId, actionLabel);
}
run(): TPromise<any> {
run(): Thenable<any> {
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
if (!activeTextEditorWidget) {
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
if (!isWritableCodeEditor(activeTextEditorWidget)) {
return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
}
const textModel = activeTextEditorWidget.getModel();
@@ -1128,7 +1136,7 @@ export class ChangeEOLAction extends Action {
const selectedIndex = (textModel && textModel.getEOL() === '\n') ? 0 : 1;
return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then(eol => {
return this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }).then(eol => {
if (eol) {
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget);
if (activeCodeEditor && isWritableCodeEditor(activeCodeEditor)) {
@@ -1149,28 +1157,26 @@ export class ChangeEncodingAction extends Action {
actionId: string,
actionLabel: string,
@IEditorService private editorService: IEditorService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IQuickInputService private quickInputService: IQuickInputService,
@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
@IFileService private fileService: IFileService
) {
super(actionId, actionLabel);
}
run(): TPromise<any> {
run(): Thenable<any> {
if (!getCodeEditor(this.editorService.activeTextEditorWidget)) {
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
let activeControl = this.editorService.activeControl;
let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input);
if (!encodingSupport) {
return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
}
let pickActionPromise: TPromise<IPickOpenEntry>;
let saveWithEncodingPick: IPickOpenEntry;
let reopenWithEncodingPick: IPickOpenEntry;
let saveWithEncodingPick: IQuickPickItem;
let reopenWithEncodingPick: IQuickPickItem;
if (language === LANGUAGE_DEFAULT) {
saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
@@ -1179,12 +1185,13 @@ export class ChangeEncodingAction extends Action {
reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' };
}
let pickActionPromise: Promise<IQuickPickItem>;
if (encodingSupport instanceof UntitledEditorInput) {
pickActionPromise = TPromise.as(saveWithEncodingPick);
pickActionPromise = Promise.resolve(saveWithEncodingPick);
} else if (!isWritableBaseEditor(activeControl)) {
pickActionPromise = TPromise.as(reopenWithEncodingPick);
pickActionPromise = Promise.resolve(reopenWithEncodingPick);
} else {
pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
}
return pickActionPromise.then(action => {
@@ -1194,10 +1201,10 @@ export class ChangeEncodingAction extends Action {
const resource = toResource(activeControl.input, { supportSideBySide: true });
return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */)
return timeout(50 /* quick open is sensitive to being opened so soon after another */)
.then(() => {
if (!resource || !this.fileService.canHandleResource(resource)) {
return TPromise.as(null); // encoding detection only possible for resources the file service can handle
return Promise.resolve(null); // encoding detection only possible for resources the file service can handle
}
return this.fileService.resolveContent(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null);
@@ -1211,7 +1218,7 @@ export class ChangeEncodingAction extends Action {
let aliasMatchIndex: number;
// All encodings are valid picks
const picks: IPickOpenEntry[] = Object.keys(SUPPORTED_ENCODINGS)
const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS)
.sort((k1, k2) => {
if (k1 === configuredEncoding) {
return -1;
@@ -1238,15 +1245,17 @@ export class ChangeEncodingAction extends Action {
return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
});
const items = picks.slice() as IQuickPickItem[];
// If we have a guessed encoding, show it first unless it matches the configured encoding
if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
picks[0].separator = { border: true };
picks.unshift({ type: 'separator' });
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
}
return this.quickOpenService.pick(picks, {
return this.quickInputService.pick(picks, {
placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
autoFocus: { autoFocusIndex: typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : void 0 }
activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
}).then(encoding => {
if (encoding) {
activeControl = this.editorService.activeControl;
@@ -1260,149 +1269,3 @@ export class ChangeEncodingAction extends Action {
});
}
}
class ScreenReaderDetectedExplanation extends Themable {
private container: HTMLElement;
private hrElement: HTMLHRElement;
private _visible: boolean;
constructor(
@IThemeService themeService: IThemeService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
) {
super(themeService);
}
get visible(): boolean {
return this._visible;
}
protected updateStyles(): void {
if (this.container) {
const background = this.getColor(editorWidgetBackground);
this.container.style.backgroundColor = background ? background.toString() : null;
const widgetShadowColor = this.getColor(widgetShadow);
this.container.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null;
const contrastBorderColor = this.getColor(contrastBorder);
this.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : null;
const foregroundColor = this.getColor(foreground);
this.hrElement.style.backgroundColor = foregroundColor ? foregroundColor.toString() : null;
}
}
show(anchorElement: HTMLElement): void {
this._visible = true;
this.contextViewService.showContextView({
getAnchor: () => {
const res = getDomNodePagePosition(anchorElement);
return {
x: res.left,
y: res.top - 9, /* above the status bar */
width: res.width,
height: res.height
} as IAnchor;
},
render: (container) => {
return this.renderContents(container);
},
onDOMEvent: (e, activeElement) => { },
onHide: () => {
this._visible = false;
}
});
}
hide(): void {
this.contextViewService.hideContextView();
}
protected renderContents(parent: HTMLElement): IDisposable {
const toDispose: IDisposable[] = [];
this.container = $('div.screen-reader-detected-explanation', {
'aria-hidden': 'true'
});
const title = $('h2.title', {}, nls.localize('screenReaderDetectedExplanation.title', "Screen Reader Optimized"));
this.container.appendChild(title);
const closeBtn = $('div.cancel');
toDispose.push(addDisposableListener(closeBtn, 'click', () => {
this.contextViewService.hideContextView();
}));
toDispose.push(addDisposableListener(closeBtn, 'mouseover', () => {
const theme = this.themeService.getTheme();
let darkenFactor: number;
switch (theme.type) {
case 'light':
darkenFactor = 0.1;
break;
case 'dark':
darkenFactor = 0.2;
break;
}
if (darkenFactor) {
closeBtn.style.backgroundColor = this.getColor(editorWidgetBackground, (color, theme) => darken(color, darkenFactor)(theme));
}
}));
toDispose.push(addDisposableListener(closeBtn, 'mouseout', () => {
closeBtn.style.backgroundColor = null;
}));
this.container.appendChild(closeBtn);
// {{SQL CARBON EDIT}}
const question = $('p.question', {}, nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio?"));
this.container.appendChild(question);
const buttonContainer = $('div.buttons');
this.container.appendChild(buttonContainer);
const yesBtn = new Button(buttonContainer);
yesBtn.label = nls.localize('screenReaderDetectedExplanation.answerYes', "Yes");
toDispose.push(attachButtonStyler(yesBtn, this.themeService));
toDispose.push(yesBtn.onDidClick(e => {
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
this.contextViewService.hideContextView();
}));
const noBtn = new Button(buttonContainer);
noBtn.label = nls.localize('screenReaderDetectedExplanation.answerNo', "No");
toDispose.push(attachButtonStyler(noBtn, this.themeService));
toDispose.push(noBtn.onDidClick(e => {
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
this.contextViewService.hideContextView();
}));
const clear = $('div');
clear.style.clear = 'both';
this.container.appendChild(clear);
const br = $('br');
this.container.appendChild(br);
this.hrElement = $('hr');
this.container.appendChild(this.hrElement);
// {{SQL CARBON EDIT}}
const explanation1 = $('p.body1', {}, nls.localize('screenReaderDetectedExplanation.body1', "Azure Data Studio is now optimized for usage with a screen reader."));
this.container.appendChild(explanation1);
const explanation2 = $('p.body2', {}, nls.localize('screenReaderDetectedExplanation.body2', "Some editor features will have different behaviour: e.g. word wrapping, folding, etc."));
this.container.appendChild(explanation2);
parent.appendChild(this.container);
this.updateStyles();
return {
dispose: () => dispose(toDispose)
};
}
}

View File

@@ -0,0 +1,179 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Widget } from 'vs/base/browser/ui/widget';
import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { Event, Emitter } from 'vs/base/common/event';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { $, append } from 'vs/base/browser/dom';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { Schemas } from 'vs/base/common/network';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { extname } from 'vs/base/common/paths';
import { Disposable, dispose } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { isEqual } from 'vs/base/common/resources';
export class FloatingClickWidget extends Widget implements IOverlayWidget {
private _onClick: Emitter<void> = this._register(new Emitter<void>());
get onClick(): Event<void> { return this._onClick.event; }
private _domNode: HTMLElement;
constructor(
private editor: ICodeEditor,
private label: string,
keyBindingAction: string,
@IKeybindingService keybindingService: IKeybindingService,
@IThemeService private themeService: IThemeService
) {
super();
if (keyBindingAction) {
const keybinding = keybindingService.lookupKeybinding(keyBindingAction);
if (keybinding) {
this.label += ` (${keybinding.getLabel()})`;
}
}
}
getId(): string {
return 'editor.overlayWidget.floatingClickWidget';
}
getDomNode(): HTMLElement {
return this._domNode;
}
getPosition(): IOverlayWidgetPosition {
return {
preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER
};
}
render() {
this._domNode = $('.floating-click-widget');
this._register(attachStylerCallback(this.themeService, { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder }, colors => {
const backgroundColor = colors.buttonBackground ? colors.buttonBackground : colors.editorBackground;
if (backgroundColor) {
this._domNode.style.backgroundColor = backgroundColor.toString();
}
const foregroundColor = colors.buttonForeground ? colors.buttonForeground : colors.editorForeground;
if (foregroundColor) {
this._domNode.style.color = foregroundColor.toString();
}
const borderColor = colors.contrastBorder ? colors.contrastBorder.toString() : null;
this._domNode.style.borderWidth = borderColor ? '1px' : null;
this._domNode.style.borderStyle = borderColor ? 'solid' : null;
this._domNode.style.borderColor = borderColor;
}));
append(this._domNode, $('')).textContent = this.label;
this.onclick(this._domNode, e => this._onClick.fire());
this.editor.addOverlayWidget(this);
}
dispose(): void {
this.editor.removeOverlayWidget(this);
super.dispose();
}
}
export class OpenWorkspaceButtonContribution extends Disposable implements IEditorContribution {
static get(editor: ICodeEditor): OpenWorkspaceButtonContribution {
return editor.getContribution<OpenWorkspaceButtonContribution>(OpenWorkspaceButtonContribution.ID);
}
private static readonly ID = 'editor.contrib.openWorkspaceButton';
private openWorkspaceButton: FloatingClickWidget;
constructor(
private editor: ICodeEditor,
@IInstantiationService private instantiationService: IInstantiationService,
@IWindowService private windowService: IWindowService,
@IWorkspaceContextService private contextService: IWorkspaceContextService
) {
super();
this.update();
this.registerListeners();
}
private registerListeners(): void {
this._register(this.editor.onDidChangeModel(e => this.update()));
}
getId(): string {
return OpenWorkspaceButtonContribution.ID;
}
private update(): void {
if (!this.shouldShowButton(this.editor)) {
this.disposeOpenWorkspaceWidgetRenderer();
return;
}
this.createOpenWorkspaceWidgetRenderer();
}
private shouldShowButton(editor: ICodeEditor): boolean {
const model = editor.getModel();
if (!model) {
return false; // we need a model
}
if (model.uri.scheme !== Schemas.file || extname(model.uri.fsPath) !== `.${WORKSPACE_EXTENSION}`) {
return false; // we need a local workspace file
}
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
const workspaceConfiguration = this.contextService.getWorkspace().configuration;
if (workspaceConfiguration && isEqual(workspaceConfiguration, model.uri)) {
return false; // already inside workspace
}
}
return true;
}
private createOpenWorkspaceWidgetRenderer(): void {
if (!this.openWorkspaceButton) {
this.openWorkspaceButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, localize('openWorkspace', "Open Workspace"), null);
this._register(this.openWorkspaceButton.onClick(() => {
const model = this.editor.getModel();
if (model) {
this.windowService.openWindow([model.uri]);
}
}));
this.openWorkspaceButton.render();
}
}
private disposeOpenWorkspaceWidgetRenderer(): void {
this.openWorkspaceButton = dispose(this.openWorkspaceButton);
}
dispose(): void {
this.disposeOpenWorkspaceWidgetRenderer();
super.dispose();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 381 B

View File

@@ -7,10 +7,6 @@
display: none;
}
.monaco-workbench>.part.editor>.content .editor-group-container:not(.active) .breadcrumbs-control {
opacity: .8;
}
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.selected .monaco-icon-label,
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.focused .monaco-icon-label {
text-decoration-line: underline;
@@ -23,6 +19,17 @@
/* todo@joh move somewhere else */
.monaco-workbench .monaco-breadcrumbs-picker .arrow {
position: absolute;
width: 0;
border-style: solid;
}
.monaco-workbench .monaco-breadcrumbs-picker .picker-item {
line-height: 22px;
flex: 1;
}
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree {
height: 100%;
overflow: hidden;

View File

@@ -44,15 +44,13 @@
.monaco-workbench > .part.editor > .content .editor-group-container > .title {
position: relative;
display: flex;
flex-wrap: nowrap;
box-sizing: border-box;
overflow: hidden;
justify-content: space-between;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs {
flex-wrap: wrap;
.monaco-workbench > .part.editor > .content .editor-group-container > .title:not(.tabs) {
display: flex; /* when tabs are not shown, use flex layout */
flex-wrap: nowrap;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.title-border-bottom::after {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 381 B

View File

@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Title Label */
.monaco-workbench > .part.editor > .content .editor-group-container > .title > .label-container {
display: flex;
justify-content: flex-start;
@@ -11,8 +13,6 @@
flex: auto;
}
/* Title Label */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label {
line-height: 35px;
overflow: hidden;
@@ -34,7 +34,7 @@
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control {
flex: 1 50%;
overflow: hidden;
padding: 0 6px;
margin-left: .45em;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
@@ -67,8 +67,8 @@
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::after {
/* use dot separator for workspace folder */
content: '';
padding: 0 4px;
content: '\00a0•\00a0';
padding: 0px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;opacity:0.5;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;opacity:0.5;}</style></defs><title>Paragraph_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13,1V4H12V16H6V9.973A4.5,4.5,0,0,1,6.5,1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M12,2V3H11V15H10V3H8V15H7V8.95A3.588,3.588,0,0,1,6.5,9a3.5,3.5,0,0,1,0-7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 576 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;opacity:0.5;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;opacity:0.5;}</style></defs><title>Paragraph_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13,1V4H12V16H6V9.973A4.5,4.5,0,0,1,6.5,1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M12,2V3H11V15H10V3H8V15H7V8.95A3.588,3.588,0,0,1,6.5,9a3.5,3.5,0,0,1,0-7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 576 B

View File

@@ -5,11 +5,15 @@
/* Title Container */
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .monaco-scrollable-element {
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container {
display: flex;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element {
flex: 1;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .monaco-scrollable-element .scrollbar {
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar {
z-index: 3; /* on top of tabs */
cursor: default;
}
@@ -92,28 +96,33 @@
display: none; /* hidden by default until a color is provided (see below) */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 6; /* over possible title border */
pointer-events: none;
background-color: var(--tab-border-top-color);
width: 100%;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
top: 0;
height: 1px;
background-color: var(--tab-border-top-color);
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container {
display: block;
position: absolute;
bottom: 0;
left: 0;
z-index: 6; /* over possible title border */
pointer-events: none;
background-color: var(--tab-border-bottom-color);
width: 100%;
height: 1px;
background-color: var(--tab-border-bottom-color);
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container {
top: 0;
height: 2px;
background-color: var(--tab-dirty-border-top-color);
}
/* Tab Label */
@@ -137,6 +146,10 @@
padding: 0;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:focus > .tab-label::after {
opacity: 0; /* when tab has the focus this shade breaks the tab border (fixes https://github.com/Microsoft/vscode/issues/57819) */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-description-container {
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
@@ -168,8 +181,9 @@
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.close-button-right.sizing-shrink > .tab-close,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close {
overflow: visible; /* ...but still show the close button on hover and when dirty */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close:focus-within {
overflow: visible; /* ...but still show the close button on hover, focus and when dirty */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close {
@@ -259,23 +273,28 @@
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control {
flex: 1 100%;
height: 25px;
height: 22px;
cursor: default;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label {
height: 22px;
line-height: 22px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label::before {
height: 18px; /* tweak the icon size of the editor labels when icons are enabled */
height: 22px; /* tweak the icon size of the editor labels when icons are enabled */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
max-width: 260px;
max-width: 80%;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
min-width: 16px;
height: 22px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
padding-right: 8px;
}
/* .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:not(:last-child):not(:hover):not(.focused):not(.file) {
min-width: 33px;
} */

View File

@@ -1,40 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-workbench .textdiff-editor-action.next {
background: url('next-diff.svg') center center no-repeat;
}
.vs .monaco-workbench .textdiff-editor-action.previous {
background: url('previous-diff.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.next,
.hc-black .monaco-workbench .textdiff-editor-action.next {
background: url('next-diff-inverse.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.previous,
.hc-black .monaco-workbench .textdiff-editor-action.previous {
background: url('previous-diff-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
opacity: 1;
background: url('paragraph.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace,
.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
opacity: 1;
background: url('paragraph-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked {
opacity: 0.5;
}
.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked,
.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked {
opacity: 0.5;
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/notabstitlecontrol';
import { toResource, Verbosity, IEditorInput } from 'vs/workbench/common/editor';
import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl';
@@ -15,11 +13,17 @@ import { addDisposableListener, EventType, addClass, EventHelper, removeClass, t
import { IEditorPartOptions, EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor';
import { IAction } from 'vs/base/common/actions';
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { Color } from 'vs/base/common/color';
interface IRenderedEditorLabel {
editor: IEditorInput;
pinned: boolean;
}
export class NoTabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private editorLabel: ResourceLabel;
private lastRenderedActiveEditor: IEditorInput;
private activeLabel: IRenderedEditorLabel = Object.create(null);
protected create(parent: HTMLElement): void {
this.titleContainer = parent;
@@ -40,7 +44,9 @@ export class NoTabsTitleControl extends TitleControl {
this._register(this.editorLabel.onClick(e => this.onTitleLabelClick(e)));
// Breadcrumbs
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, extraClasses: ['no-tabs-breadcrumbs'] });
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent });
toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl));
this.toDispose.push({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node
// Right Actions Container
const actionsContainer = document.createElement('div');
@@ -60,7 +66,7 @@ export class NoTabsTitleControl extends TitleControl {
this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
// Detect mouse click
this._register(addDisposableListener(this.titleContainer, EventType.CLICK, (e: MouseEvent) => this.onTitleClick(e)));
this._register(addDisposableListener(this.titleContainer, EventType.MOUSE_UP, (e: MouseEvent) => this.onTitleClick(e)));
// Detect touch
this._register(addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
@@ -87,6 +93,8 @@ export class NoTabsTitleControl extends TitleControl {
// Close editor on middle mouse click
if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) {
EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */);
this.group.closeEditor(this.group.activeEditor);
}
}
@@ -96,7 +104,10 @@ export class NoTabsTitleControl extends TitleControl {
}
openEditor(editor: IEditorInput): void {
this.ifActiveEditorChanged(() => this.redraw());
const activeEditorChanged = this.ifActiveEditorChanged(() => this.redraw());
if (!activeEditorChanged) {
this.ifActiveEditorPropertiesChanged(() => this.redraw());
}
}
closeEditor(editor: IEditorInput): void {
@@ -147,16 +158,36 @@ export class NoTabsTitleControl extends TitleControl {
this.redraw();
}
private ifActiveEditorChanged(fn: () => void): void {
protected handleBreadcrumbsEnablementChange(): void {
toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl));
this.redraw();
}
private ifActiveEditorChanged(fn: () => void): boolean {
if (
!this.lastRenderedActiveEditor && this.group.activeEditor || // active editor changed from null => editor
this.lastRenderedActiveEditor && !this.group.activeEditor || // active editor changed from editor => null
!this.group.isActive(this.lastRenderedActiveEditor) // active editor changed from editorA => editorB
!this.activeLabel.editor && this.group.activeEditor || // active editor changed from null => editor
this.activeLabel.editor && !this.group.activeEditor || // active editor changed from editor => null
!this.group.isActive(this.activeLabel.editor) // active editor changed from editorA => editorB
) {
fn();
return true;
}
return false;
}
private ifActiveEditorPropertiesChanged(fn: () => void): void {
if (!this.activeLabel.editor || !this.group.activeEditor) {
return; // need an active editor to check for properties changed
}
if (this.activeLabel.pinned !== this.group.isPinned(this.group.activeEditor)) {
fn(); // only run if pinned state has changed
}
}
private ifEditorIsActive(editor: IEditorInput, fn: () => void): void {
if (this.group.isActive(editor)) {
fn(); // only run if editor is current active
@@ -165,11 +196,12 @@ export class NoTabsTitleControl extends TitleControl {
private redraw(): void {
const editor = this.group.activeEditor;
this.lastRenderedActiveEditor = editor;
const isEditorPinned = this.group.isPinned(this.group.activeEditor);
const isGroupActive = this.accessor.activeGroup === this.group;
this.activeLabel = { editor, pinned: isEditorPinned };
// Update Breadcrumbs
if (this.breadcrumbsControl) {
if (isGroupActive) {
@@ -189,6 +221,7 @@ export class NoTabsTitleControl extends TitleControl {
// Otherwise render it
else {
// Dirty state
this.updateEditorDirty(editor);

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IRange } from 'vs/editor/common/core/range';
@@ -21,8 +21,8 @@ export interface IRangeHighlightDecoration {
export class RangeHighlightDecorations extends Disposable {
private rangeHighlightDecorationId: string = null;
private editor: ICodeEditor = null;
private rangeHighlightDecorationId: string | null = null;
private editor: ICodeEditor | null = null;
private editorDisposables: IDisposable[] = [];
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());

View File

@@ -6,20 +6,19 @@
import 'vs/css!./media/resourceviewer';
import * as nls from 'vs/nls';
import * as mimes from 'vs/base/common/mime';
import URI from 'vs/base/common/uri';
import { Builder, $ } from 'vs/base/browser/builder';
import { URI } from 'vs/base/common/uri';
import * as DOM from 'vs/base/browser/dom';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { LRUCache } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { clamp } from 'vs/base/common/numbers';
import { Themable } from 'vs/workbench/common/theme';
import { IStatusbarItem, StatusbarItemDescriptor, IStatusbarRegistry, Extensions, StatusbarAlignment } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IStatusbarItem, StatusbarItemDescriptor, IStatusbarRegistry, Extensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Registry } from 'vs/platform/registry/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { memoize } from 'vs/base/common/decorators';
@@ -61,8 +60,8 @@ class BinarySize {
}
}
export interface ResourceViewerContext {
layout(dimension: DOM.Dimension): void;
export interface ResourceViewerContext extends IDisposable {
layout?(dimension: DOM.Dimension): void;
}
/**
@@ -81,10 +80,10 @@ export class ResourceViewer {
openInternalClb: (uri: URI) => void,
openExternalClb: (uri: URI) => void,
metadataClb: (meta: string) => void
): ResourceViewerContext | null {
): ResourceViewerContext {
// Ensure CSS class
$(container).setClass('monaco-resource-viewer');
container.className = 'monaco-resource-viewer';
// Images
if (ResourceViewer.isImageResource(descriptor)) {
@@ -93,21 +92,20 @@ export class ResourceViewer {
// Large Files
if (descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) {
FileTooLargeFileView.create(container, descriptor, scrollbar, metadataClb);
return FileTooLargeFileView.create(container, descriptor, scrollbar, metadataClb);
}
// Seemingly Binary Files
else {
FileSeemsBinaryFileView.create(container, descriptor, scrollbar, openInternalClb, metadataClb);
return FileSeemsBinaryFileView.create(container, descriptor, scrollbar, openInternalClb, metadataClb);
}
return null;
}
private static isImageResource(descriptor: IResourceDescriptor) {
const mime = getMime(descriptor);
return mime.indexOf('image/') >= 0;
// Chrome does not support tiffs
return mime.indexOf('image/') >= 0 && mime !== 'image/tiff';
}
}
@@ -122,14 +120,12 @@ class ImageView {
scrollbar: DomScrollableElement,
openExternalClb: (uri: URI) => void,
metadataClb: (meta: string) => void
): ResourceViewerContext | null {
): ResourceViewerContext {
if (ImageView.shouldShowImageInline(descriptor)) {
return InlineImageView.create(container, descriptor, fileService, scrollbar, metadataClb);
}
LargeImageView.create(container, descriptor, openExternalClb);
return null;
return LargeImageView.create(container, descriptor, openExternalClb, metadataClb);
}
private static shouldShowImageInline(descriptor: IResourceDescriptor): boolean {
@@ -156,25 +152,29 @@ class LargeImageView {
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
openExternalClb: (uri: URI) => void
openExternalClb: (uri: URI) => void,
metadataClb: (meta: string) => void
) {
const size = BinarySize.formatSize(descriptor.size);
metadataClb(size);
const imageContainer = $(container)
.empty()
.p({
text: nls.localize('largeImageError', "The image is not displayed in the editor because it is too large ({0}).", size)
});
DOM.clearNode(container);
const disposables: IDisposable[] = [];
const label = document.createElement('p');
label.textContent = nls.localize('largeImageError', "The image is not displayed in the editor because it is too large ({0}).", size);
container.appendChild(label);
if (descriptor.resource.scheme !== Schemas.data) {
imageContainer.append($('a', {
role: 'button',
class: 'embedded-link',
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
}).on(DOM.EventType.CLICK, (e) => {
openExternalClb(descriptor.resource);
}));
const link = DOM.append(label, DOM.$('a.embedded-link'));
link.setAttribute('role', 'button');
link.textContent = nls.localize('resourceOpenExternalButton', "Open image using external program?");
disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternalClb(descriptor.resource)));
}
return combinedDisposable(disposables);
}
}
@@ -186,18 +186,17 @@ class FileTooLargeFileView {
metadataClb: (meta: string) => void
) {
const size = BinarySize.formatSize(descriptor.size);
metadataClb(size);
$(container)
.empty()
.span({
text: nls.localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size)
});
DOM.clearNode(container);
if (metadataClb) {
metadataClb(size);
}
const label = document.createElement('span');
label.textContent = nls.localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size);
container.appendChild(label);
scrollbar.scanDomNode();
return Disposable.None;
}
}
@@ -209,27 +208,27 @@ class FileSeemsBinaryFileView {
openInternalClb: (uri: URI) => void,
metadataClb: (meta: string) => void
) {
const binaryContainer = $(container)
.empty()
.p({
text: nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.")
});
metadataClb(typeof descriptor.size === 'number' ? BinarySize.formatSize(descriptor.size) : '');
DOM.clearNode(container);
const disposables: IDisposable[] = [];
const label = document.createElement('p');
label.textContent = nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.");
container.appendChild(label);
if (descriptor.resource.scheme !== Schemas.data) {
binaryContainer.append($('a', {
role: 'button',
class: 'embedded-link',
text: nls.localize('openAsText', "Do you want to open it anyway?")
}).on(DOM.EventType.CLICK, (e) => {
openInternalClb(descriptor.resource);
}));
}
const link = DOM.append(label, DOM.$('a.embedded-link'));
link.setAttribute('role', 'button');
link.textContent = nls.localize('openAsText', "Do you want to open it anyway?");
if (metadataClb) {
metadataClb(BinarySize.formatSize(descriptor.size));
disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openInternalClb(descriptor.resource)));
}
scrollbar.scanDomNode();
return combinedDisposable(disposables);
}
}
@@ -239,7 +238,7 @@ class ZoomStatusbarItem extends Themable implements IStatusbarItem {
static instance: ZoomStatusbarItem;
showTimeout: number;
showTimeout: any;
private statusBarItem: HTMLElement;
private onSelectScale?: (scale: Scale) => void;
@@ -276,16 +275,16 @@ class ZoomStatusbarItem extends Themable implements IStatusbarItem {
render(container: HTMLElement): IDisposable {
if (!this.statusBarItem && container) {
this.statusBarItem = $(container).a()
.addClass('.zoom-statusbar-item')
.on('click', () => {
this.contextMenuService.showContextMenu({
getAnchor: () => container,
getActions: () => TPromise.as(this.zoomActions)
});
})
.getHTMLElement();
this.statusBarItem = DOM.append(container, DOM.$('a.zoom-statusbar-item'));
this.statusBarItem.setAttribute('role', 'button');
this.statusBarItem.style.display = 'none';
DOM.addDisposableListener(this.statusBarItem, DOM.EventType.CLICK, () => {
this.contextMenuService.showContextMenu({
getAnchor: () => container,
getActions: () => this.zoomActions
});
});
}
return this;
@@ -304,7 +303,7 @@ class ZoomStatusbarItem extends Themable implements IStatusbarItem {
this.onSelectScale(scale);
}
return null;
return void 0;
}));
}
@@ -368,8 +367,11 @@ class InlineImageView {
scrollbar: DomScrollableElement,
metadataClb: (meta: string) => void
) {
const context = {
layout(dimension: DOM.Dimension) { }
const disposables: IDisposable[] = [];
const context: ResourceViewerContext = {
layout(dimension: DOM.Dimension) { },
dispose: () => combinedDisposable(disposables).dispose()
};
const cacheKey = descriptor.resource.toString();
@@ -379,41 +381,40 @@ class InlineImageView {
const initialState: ImageState = InlineImageView.imageStateCache.get(cacheKey) || { scale: 'fit', offsetX: 0, offsetY: 0 };
let scale = initialState.scale;
let img: Builder | null = null;
let imgElement: HTMLImageElement | null = null;
let image: HTMLImageElement | null = null;
function updateScale(newScale: Scale) {
if (!img || !imgElement.parentElement) {
if (!image || !image.parentElement) {
return;
}
if (newScale === 'fit') {
scale = 'fit';
img.addClass('scale-to-fit');
img.removeClass('pixelated');
img.style('min-width', 'auto');
img.style('width', 'auto');
DOM.addClass(image, 'scale-to-fit');
DOM.removeClass(image, 'pixelated');
image.style.minWidth = 'auto';
image.style.width = 'auto';
InlineImageView.imageStateCache.set(cacheKey, null);
} else {
const oldWidth = imgElement.width;
const oldHeight = imgElement.height;
const oldWidth = image.width;
const oldHeight = image.height;
scale = clamp(newScale, InlineImageView.MIN_SCALE, InlineImageView.MAX_SCALE);
if (scale >= InlineImageView.PIXELATION_THRESHOLD) {
img.addClass('pixelated');
DOM.addClass(image, 'pixelated');
} else {
img.removeClass('pixelated');
DOM.removeClass(image, 'pixelated');
}
const { scrollTop, scrollLeft } = imgElement.parentElement;
const dx = (scrollLeft + imgElement.parentElement.clientWidth / 2) / imgElement.parentElement.scrollWidth;
const dy = (scrollTop + imgElement.parentElement.clientHeight / 2) / imgElement.parentElement.scrollHeight;
const { scrollTop, scrollLeft } = image.parentElement;
const dx = (scrollLeft + image.parentElement.clientWidth / 2) / image.parentElement.scrollWidth;
const dy = (scrollTop + image.parentElement.clientHeight / 2) / image.parentElement.scrollHeight;
img.removeClass('scale-to-fit');
img.style('min-width', `${(imgElement.naturalWidth * scale)}px`);
img.style('width', `${(imgElement.naturalWidth * scale)}px`);
DOM.removeClass(image, 'scale-to-fit');
image.style.minWidth = `${(image.naturalWidth * scale)}px`;
image.style.width = `${(image.naturalWidth * scale)}px`;
const newWidth = imgElement.width;
const newWidth = image.width;
const scaleFactor = (newWidth - oldWidth) / oldWidth;
const newScrollLeft = ((oldWidth * scaleFactor * dx) + scrollLeft);
@@ -431,123 +432,131 @@ class InlineImageView {
}
function firstZoom() {
scale = imgElement.clientWidth / imgElement.naturalWidth;
scale = image.clientWidth / image.naturalWidth;
updateScale(scale);
}
$(container)
.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent, c) => {
if (!img) {
return;
}
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
disposables.push(DOM.addDisposableListener(window, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
if (!image) {
return;
}
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
if (platform.isMacintosh ? altPressed : ctrlPressed) {
c.removeClass('zoom-in').addClass('zoom-out');
}
})
.on(DOM.EventType.KEY_UP, (e: KeyboardEvent, c) => {
if (!img) {
return;
}
if (platform.isMacintosh ? altPressed : ctrlPressed) {
DOM.removeClass(container, 'zoom-in');
DOM.addClass(container, 'zoom-out');
}
}));
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
disposables.push(DOM.addDisposableListener(window, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
if (!image) {
return;
}
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) {
c.removeClass('zoom-out').addClass('zoom-in');
}
})
.on(DOM.EventType.CLICK, (e: MouseEvent) => {
if (!img) {
return;
}
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
if (e.button !== 0) {
return;
}
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) {
DOM.removeClass(container, 'zoom-out');
DOM.addClass(container, 'zoom-in');
}
}));
// left click
if (scale === 'fit') {
firstZoom();
}
disposables.push(DOM.addDisposableListener(container, DOM.EventType.CLICK, (e: MouseEvent) => {
if (!image) {
return;
}
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) { // zoom in
let i = 0;
for (; i < InlineImageView.zoomLevels.length; ++i) {
if (InlineImageView.zoomLevels[i] > scale) {
break;
}
if (e.button !== 0) {
return;
}
// left click
if (scale === 'fit') {
firstZoom();
}
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) { // zoom in
let i = 0;
for (; i < InlineImageView.zoomLevels.length; ++i) {
if (InlineImageView.zoomLevels[i] > scale) {
break;
}
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MAX_SCALE);
} else {
let i = InlineImageView.zoomLevels.length - 1;
for (; i >= 0; --i) {
if (InlineImageView.zoomLevels[i] < scale) {
break;
}
}
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MAX_SCALE);
} else {
let i = InlineImageView.zoomLevels.length - 1;
for (; i >= 0; --i) {
if (InlineImageView.zoomLevels[i] < scale) {
break;
}
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MIN_SCALE);
}
})
.on(DOM.EventType.WHEEL, (e: WheelEvent) => {
if (!img) {
return;
}
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MIN_SCALE);
}
}));
const isScrollWhellKeyPressed = platform.isMacintosh ? altPressed : ctrlPressed;
if (!isScrollWhellKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl
return;
}
disposables.push(DOM.addDisposableListener(container, DOM.EventType.WHEEL, (e: WheelEvent) => {
if (!image) {
return;
}
e.preventDefault();
e.stopPropagation();
const isScrollWhellKeyPressed = platform.isMacintosh ? altPressed : ctrlPressed;
if (!isScrollWhellKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl
return;
}
if (scale === 'fit') {
firstZoom();
}
e.preventDefault();
e.stopPropagation();
let delta = e.deltaY < 0 ? 1 : -1;
if (scale === 'fit') {
firstZoom();
}
// Pinching should increase the scale
if (e.ctrlKey && !isScrollWhellKeyPressed) {
delta *= -1;
}
updateScale(scale as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR));
})
.on(DOM.EventType.SCROLL, () => {
if (!imgElement || !imgElement.parentElement || scale === 'fit') {
return;
}
let delta = e.deltaY < 0 ? 1 : -1;
const entry = InlineImageView.imageStateCache.get(cacheKey);
if (entry) {
const { scrollTop, scrollLeft } = imgElement.parentElement;
InlineImageView.imageStateCache.set(cacheKey, { scale: entry.scale, offsetX: scrollLeft, offsetY: scrollTop });
}
});
// Pinching should increase the scale
if (e.ctrlKey && !isScrollWhellKeyPressed) {
delta *= -1;
}
updateScale(scale as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR));
}));
$(container)
.empty()
.addClass('image', 'zoom-in')
.img({})
.style('visibility', 'hidden')
.addClass('scale-to-fit')
.on(DOM.EventType.LOAD, (e, i) => {
img = i;
imgElement = img.getHTMLElement() as HTMLImageElement;
metadataClb(nls.localize('imgMeta', '{0}x{1} {2}', imgElement.naturalWidth, imgElement.naturalHeight, BinarySize.formatSize(descriptor.size)));
scrollbar.scanDomNode();
img.style('visibility', 'visible');
updateScale(scale);
if (initialState.scale !== 'fit') {
scrollbar.setScrollPosition({
scrollLeft: initialState.offsetX,
scrollTop: initialState.offsetY,
});
}
});
disposables.push(DOM.addDisposableListener(container, DOM.EventType.SCROLL, () => {
if (!image || !image.parentElement || scale === 'fit') {
return;
}
const entry = InlineImageView.imageStateCache.get(cacheKey);
if (entry) {
const { scrollTop, scrollLeft } = image.parentElement;
InlineImageView.imageStateCache.set(cacheKey, { scale: entry.scale, offsetX: scrollLeft, offsetY: scrollTop });
}
}));
DOM.clearNode(container);
DOM.addClasses(container, 'image', 'zoom-in');
image = DOM.append(container, DOM.$('img.scale-to-fit'));
image.style.visibility = 'hidden';
disposables.push(DOM.addDisposableListener(image, DOM.EventType.LOAD, e => {
if (typeof descriptor.size === 'number') {
metadataClb(nls.localize('imgMeta', '{0}x{1} {2}', image.naturalWidth, image.naturalHeight, BinarySize.formatSize(descriptor.size)));
} else {
metadataClb(nls.localize('imgMetaNoSize', '{0}x{1}', image.naturalWidth, image.naturalHeight));
}
scrollbar.scanDomNode();
image.style.visibility = 'visible';
updateScale(scale);
if (initialState.scale !== 'fit') {
scrollbar.setScrollPosition({
scrollLeft: initialState.offsetX,
scrollTop: initialState.offsetY,
});
}
}));
InlineImageView.imageSrc(descriptor, fileService).then(dataUri => {
const imgs = container.getElementsByTagName('img');
@@ -559,9 +568,9 @@ class InlineImageView {
return context;
}
private static imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): TPromise<string> {
private static imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): Thenable<string> {
if (descriptor.resource.scheme === Schemas.data) {
return TPromise.as(descriptor.resource.toString(true /* skip encoding */));
return Promise.resolve(descriptor.resource.toString(true /* skip encoding */));
}
return fileService.resolveContent(descriptor.resource, { encoding: 'base64' }).then(data => {
@@ -575,7 +584,8 @@ class InlineImageView {
function getMime(descriptor: IResourceDescriptor) {
let mime = descriptor.mime;
if (!mime && descriptor.resource.scheme !== Schemas.data) {
mime = mimes.getMediaMime(descriptor.resource.toString());
mime = mimes.getMediaMime(descriptor.resource.path);
}
return mime || mimes.MIME_BINARY;
}

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import * as DOM from 'vs/base/browser/dom';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditor } from 'vs/workbench/common/editor';
@@ -17,6 +16,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Event, Relay, anyEvent, mapEvent, Emitter } from 'vs/base/common/event';
import { IStorageService } from 'vs/platform/storage/common/storage';
export class SideBySideEditor extends BaseEditor {
@@ -59,9 +59,10 @@ export class SideBySideEditor extends BaseEditor {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(SideBySideEditor.ID, telemetryService, themeService);
super(SideBySideEditor.ID, telemetryService, themeService, storageService);
}
protected createEditor(parent: HTMLElement): void {
@@ -107,9 +108,11 @@ export class SideBySideEditor extends BaseEditor {
if (this.masterEditor) {
this.masterEditor.setVisible(visible, group);
}
if (this.detailsEditor) {
this.detailsEditor.setVisible(visible, group);
}
super.setEditorVisible(visible, group);
}
@@ -117,10 +120,13 @@ export class SideBySideEditor extends BaseEditor {
if (this.masterEditor) {
this.masterEditor.clearInput();
}
if (this.detailsEditor) {
this.detailsEditor.clearInput();
}
this.disposeEditors();
super.clearInput();
}
@@ -139,6 +145,7 @@ export class SideBySideEditor extends BaseEditor {
if (this.masterEditor) {
return this.masterEditor.getControl();
}
return null;
}
@@ -159,7 +166,10 @@ export class SideBySideEditor extends BaseEditor {
return this.setNewInput(newInput, options, token);
}
return TPromise.join([this.detailsEditor.setInput(newInput.details, null, token), this.masterEditor.setInput(newInput.master, options, token)]).then(() => void 0);
return Promise.all([
this.detailsEditor.setInput(newInput.details, null, token),
this.masterEditor.setInput(newInput.master, options, token)]
).then(() => void 0);
}
private setNewInput(newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
@@ -179,7 +189,7 @@ export class SideBySideEditor extends BaseEditor {
return editor;
}
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions, token: CancellationToken): TPromise<void> {
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
this.detailsEditor = details;
this.masterEditor = master;
@@ -190,7 +200,7 @@ export class SideBySideEditor extends BaseEditor {
this.onDidCreateEditors.fire();
return TPromise.join([this.detailsEditor.setInput(detailsInput, null, token), this.masterEditor.setInput(masterInput, options, token)]).then(() => this.focus());
return Promise.all([this.detailsEditor.setInput(detailsInput, null, token), this.masterEditor.setInput(masterInput, options, token)]).then(() => this.focus());
}
updateStyles(): void {

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/tabstitlecontrol';
import { isMacintosh } from 'vs/base/common/platform';
import { shorten } from 'vs/base/common/labels';
@@ -26,11 +24,10 @@ import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecyc
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { getOrSet } from 'vs/base/common/map';
// {{SQL CARBON EDIT}} -- Display the editor's tab color
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry';
import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { Color } from 'vs/base/common/color';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -43,9 +40,7 @@ import { IEditorGroupsAccessor, IEditorPartOptions, IEditorGroupView } from 'vs/
import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import * as QueryConstants from 'sql/parts/query/common/constants';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import { IFileService } from 'vs/platform/files/common/files';
// {{SQL CARBON EDIT}} -- Display the editor's tab color
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
@@ -53,6 +48,10 @@ import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/q
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { GlobalNewUntitledFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import * as QueryConstants from 'sql/parts/query/common/constants';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
// {{SQL CARBON EDIT}} -- End
interface IEditorInputLabel {
name: string;
@@ -67,7 +66,7 @@ export class TabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private tabsContainer: HTMLElement;
private editorToolbarContainer: HTMLElement;
private scrollbar: ScrollableElement;
private tabsScrollbar: ScrollableElement;
private closeOneEditorAction: CloseOneEditorAction;
private tabLabelWidgets: ResourceLabel[] = [];
@@ -94,19 +93,26 @@ export class TabsTitleControl extends TitleControl {
@IThemeService themeService: IThemeService,
@IExtensionService extensionService: IExtensionService,
@IConfigurationService configurationService: IConfigurationService,
@IFileService fileService: IFileService,
// {{SQL CARBON EDIT}} -- Display the editor's tab color
@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
@ICommandService private commandService: ICommandService,
@IConnectionManagementService private connectionService: IConnectionManagementService,
@IQueryEditorService private queryEditorService: IQueryEditorService,
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
@IObjectExplorerService private objectExplorerService: IObjectExplorerService,
// {{SQL CARBON EDIT}} -- End
) {
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService);
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService);
}
protected create(parent: HTMLElement): void {
this.titleContainer = parent;
// Tabs and Actions Container (are on a single row with flex side-by-side)
const tabsAndActionsContainer = document.createElement('div');
addClass(tabsAndActionsContainer, 'tabs-and-actions-container');
this.titleContainer.appendChild(tabsAndActionsContainer);
// Tabs Container
this.tabsContainer = document.createElement('div');
this.tabsContainer.setAttribute('role', 'tablist');
@@ -114,15 +120,16 @@ export class TabsTitleControl extends TitleControl {
addClass(this.tabsContainer, 'tabs-container');
// Tabs Container listeners
this.registerContainerListeners();
this.registerTabsContainerListeners();
// Scrollbar
this.createScrollbar();
// Tabs Scrollbar
this.tabsScrollbar = this.createTabsScrollbar(this.tabsContainer);
tabsAndActionsContainer.appendChild(this.tabsScrollbar.getDomNode());
// Editor Toolbar Container
this.editorToolbarContainer = document.createElement('div');
addClass(this.editorToolbarContainer, 'editor-actions');
this.titleContainer.appendChild(this.editorToolbarContainer);
tabsAndActionsContainer.appendChild(this.editorToolbarContainer);
// Editor Actions Toolbar
this.createEditorActionsToolBar(this.editorToolbarContainer);
@@ -130,17 +137,15 @@ export class TabsTitleControl extends TitleControl {
// Close Action
this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL));
// Breadcrumbs
// Breadcrumbs (are on a separate row below tabs and actions)
const breadcrumbsContainer = document.createElement('div');
addClass(breadcrumbsContainer, 'tabs-breadcrumbs');
this.titleContainer.appendChild(breadcrumbsContainer);
this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, extraClasses: [] });
this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: breadcrumbsBackground });
}
private createScrollbar(): void {
// Custom Scrollbar
this.scrollbar = new ScrollableElement(this.tabsContainer, {
private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement {
const tabsScrollbar = new ScrollableElement(scrollable, {
horizontal: ScrollbarVisibility.Auto,
vertical: ScrollbarVisibility.Hidden,
scrollYToX: true,
@@ -148,11 +153,11 @@ export class TabsTitleControl extends TitleControl {
horizontalScrollbarSize: 3
});
this.scrollbar.onScroll(e => {
this.tabsContainer.scrollLeft = e.scrollLeft;
tabsScrollbar.onScroll(e => {
scrollable.scrollLeft = e.scrollLeft;
});
this.titleContainer.appendChild(this.scrollbar.getDomNode());
return tabsScrollbar;
}
private updateBreadcrumbsControl(): void {
@@ -163,7 +168,12 @@ export class TabsTitleControl extends TitleControl {
}
}
private registerContainerListeners(): void {
protected handleBreadcrumbsEnablementChange(): void {
// relayout when breadcrumbs are enable/disabled
this.group.relayout();
}
private registerTabsContainerListeners(): void {
// Group dragging
this.enableGroupDragging(this.tabsContainer);
@@ -171,7 +181,7 @@ export class TabsTitleControl extends TitleControl {
// Forward scrolling inside the container to our custom scrollbar
this._register(addDisposableListener(this.tabsContainer, EventType.SCROLL, () => {
if (hasClass(this.tabsContainer, 'scroll')) {
this.scrollbar.setScrollPosition({
this.tabsScrollbar.setScrollPosition({
scrollLeft: this.tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar
});
}
@@ -182,7 +192,7 @@ export class TabsTitleControl extends TitleControl {
if (e.target === this.tabsContainer) {
EventHelper.stop(e);
// {{SQL CARBON EDIT}}
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).done(undefined, err => this.notificationService.warn(err));
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err));
}
}));
@@ -354,7 +364,7 @@ export class TabsTitleControl extends TitleControl {
// Activity has an impact on each tab
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => {
this.redrawEditorActive(isGroupActive, editor, tabContainer, tabLabelWidget);
this.redrawEditorActiveAndDirty(isGroupActive, editor, tabContainer, tabLabelWidget);
});
// Activity has an impact on the toolbar, so we need to update and layout
@@ -377,7 +387,7 @@ export class TabsTitleControl extends TitleControl {
}
updateEditorDirty(editor: IEditorInput): void {
this.withTab(editor, tabContainer => this.redrawEditorDirty(editor, tabContainer));
this.withTab(editor, (tabContainer, tabLabelWidget) => this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget));
}
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
@@ -393,7 +403,8 @@ export class TabsTitleControl extends TitleControl {
oldOptions.tabCloseButton !== newOptions.tabCloseButton ||
oldOptions.tabSizing !== newOptions.tabSizing ||
oldOptions.showIcons !== newOptions.showIcons ||
oldOptions.iconTheme !== newOptions.iconTheme
oldOptions.iconTheme !== newOptions.iconTheme ||
oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs
) {
this.redraw();
}
@@ -493,7 +504,7 @@ export class TabsTitleControl extends TitleControl {
// Touch Scroll Support
disposables.push(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
this.scrollbar.setScrollPosition({ scrollLeft: this.scrollbar.getScrollPosition().scrollLeft - e.translationX });
this.tabsScrollbar.setScrollPosition({ scrollLeft: this.tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
}));
// Close on mouse middle click
@@ -502,7 +513,9 @@ export class TabsTitleControl extends TitleControl {
tab.blur();
if (e.button === 1 /* Middle Button*/ && !this.originatesFromTabActionBar(e)) {
if (e.button === 1 /* Middle Button*/) {
e.stopPropagation(); // for https://github.com/Microsoft/vscode/issues/56715
this.blockRevealActiveTabOnce();
this.closeOneEditorAction.run({ groupId: this.group.id, editorIndex: index });
}
@@ -558,7 +571,7 @@ export class TabsTitleControl extends TitleControl {
}
// moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar
this.scrollbar.setScrollPosition({
this.tabsScrollbar.setScrollPosition({
scrollLeft: this.tabsContainer.scrollLeft
});
}));
@@ -838,9 +851,7 @@ export class TabsTitleControl extends TitleControl {
this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel);
// Borders / Outline
const borderLeftColor = (index !== 0) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
const borderRightColor = (index === this.group.count - 1) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
tabContainer.style.borderLeft = borderLeftColor ? `1px solid ${borderLeftColor}` : null;
const borderRightColor = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder));
tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : null;
tabContainer.style.outlineColor = this.getColor(activeContrastBorder);
@@ -863,12 +874,8 @@ export class TabsTitleControl extends TitleControl {
removeClass(tabContainer, 'has-icon-theme');
}
// Active state
this.redrawEditorActive(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget);
// Dirty State
this.redrawEditorDirty(editor, tabContainer);
// Active / dirty state
this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget);
// {{SQL CARBON EDIT}} -- Display the editor's tab color
this.setEditorTabColor(editor, tabContainer, this.group.isActive(editor));
}
@@ -883,15 +890,22 @@ export class TabsTitleControl extends TitleControl {
tabContainer.title = title;
// Label
// {{SQL CARBON EDIT}} -- add title in options passed
tabLabelWidget.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), title });
tabLabelWidget.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) });
// {{SQL CARBON EDIT}} -- Display the editor's tab color
const isTabActive = this.group.isActive(editor);
this.setEditorTabColor(editor, tabContainer, isTabActive);
}
private redrawEditorActive(isGroupActive: boolean, editor: IEditorInput, tabContainer: HTMLElement, tabLabelWidget: ResourceLabel): void {
private redrawEditorActiveAndDirty(isGroupActive: boolean, editor: IEditorInput, tabContainer: HTMLElement, tabLabelWidget: ResourceLabel): void {
const isTabActive = this.group.isActive(editor);
const hasModifiedBorderTop = this.doRedrawEditorDirty(isGroupActive, isTabActive, editor, tabContainer);
this.doRedrawEditorActive(isGroupActive, !hasModifiedBorderTop, editor, tabContainer, tabLabelWidget);
}
private doRedrawEditorActive(isGroupActive: boolean, allowBorderTop: boolean, editor: IEditorInput, tabContainer: HTMLElement, tabLabelWidget: ResourceLabel): void {
// Tab is active
if (this.group.isActive(editor)) {
@@ -910,7 +924,7 @@ export class TabsTitleControl extends TitleControl {
tabContainer.style.removeProperty('--tab-border-bottom-color');
}
const activeTabBorderColorTop = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER_TOP : TAB_UNFOCUSED_ACTIVE_BORDER_TOP);
const activeTabBorderColorTop = allowBorderTop ? this.getColor(isGroupActive ? TAB_ACTIVE_BORDER_TOP : TAB_UNFOCUSED_ACTIVE_BORDER_TOP) : void 0;
if (activeTabBorderColorTop) {
addClass(tabContainer, 'tab-border-top');
tabContainer.style.setProperty('--tab-border-top-color', activeTabBorderColorTop.toString());
@@ -926,7 +940,7 @@ export class TabsTitleControl extends TitleControl {
// Tab is inactive
else {
// Containr
// Container
removeClass(tabContainer, 'active');
tabContainer.setAttribute('aria-selected', 'false');
tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND);
@@ -937,22 +951,57 @@ export class TabsTitleControl extends TitleControl {
}
}
private redrawEditorDirty(editor: IEditorInput, tabContainer: HTMLElement): void {
private doRedrawEditorDirty(isGroupActive: boolean, isTabActive: boolean, editor: IEditorInput, tabContainer: HTMLElement): boolean {
let hasModifiedBorderColor = false;
// Tab: dirty
if (editor.isDirty()) {
addClass(tabContainer, 'dirty');
} else {
removeClass(tabContainer, 'dirty');
// Highlight modified tabs with a border if configured
if (this.accessor.partOptions.highlightModifiedTabs) {
let modifiedBorderColor: string;
if (isGroupActive && isTabActive) {
modifiedBorderColor = this.getColor(TAB_ACTIVE_MODIFIED_BORDER);
} else if (isGroupActive && !isTabActive) {
modifiedBorderColor = this.getColor(TAB_INACTIVE_MODIFIED_BORDER);
} else if (!isGroupActive && isTabActive) {
modifiedBorderColor = this.getColor(TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER);
} else {
modifiedBorderColor = this.getColor(TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER);
}
if (modifiedBorderColor) {
hasModifiedBorderColor = true;
addClass(tabContainer, 'dirty-border-top');
tabContainer.style.setProperty('--tab-dirty-border-top-color', modifiedBorderColor);
}
} else {
removeClass(tabContainer, 'dirty-border-top');
tabContainer.style.removeProperty('--tab-dirty-border-top-color');
}
}
// Tab: not dirty
else {
removeClass(tabContainer, 'dirty');
removeClass(tabContainer, 'dirty-border-top');
tabContainer.style.removeProperty('--tab-dirty-border-top-color');
}
return hasModifiedBorderColor;
}
layout(dimension: Dimension): void {
this.dimension = dimension;
const activeTab = this.getTab(this.group.activeEditor);
if (!activeTab || !dimension) {
if (!activeTab || !this.dimension) {
return;
}
this.dimension = dimension;
// The layout of tabs can be an expensive operation because we access DOM properties
// that can result in the browser doing a full page layout to validate them. To buffer
// this a little bit we try at least to schedule this work on the next animation frame.
@@ -972,7 +1021,7 @@ export class TabsTitleControl extends TitleControl {
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
this.breadcrumbsControl.layout({ width: dimension.width, height: BreadcrumbsControl.HEIGHT });
this.scrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
this.tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
}
const visibleContainerWidth = this.tabsContainer.offsetWidth;
@@ -987,7 +1036,7 @@ export class TabsTitleControl extends TitleControl {
}
// Update scrollbar
this.scrollbar.setScrollDimensions({
this.tabsScrollbar.setScrollDimensions({
width: visibleContainerWidth,
scrollWidth: totalContainerWidth
});
@@ -999,20 +1048,20 @@ export class TabsTitleControl extends TitleControl {
}
// Reveal the active one
const containerScrollPosX = this.scrollbar.getScrollPosition().scrollLeft;
const containerScrollPosX = this.tabsScrollbar.getScrollPosition().scrollLeft;
const activeTabFits = activeTabWidth <= visibleContainerWidth;
// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
// Note: only try to do this if we actually have enough width to give to show the tab fully!
if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) {
this.scrollbar.setScrollPosition({
this.tabsScrollbar.setScrollPosition({
scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
});
}
// Tab is overlflowng to the left or does not fit: Scroll it into view to the left
else if (containerScrollPosX > activeTabPosX || !activeTabFits) {
this.scrollbar.setScrollPosition({
this.tabsScrollbar.setScrollPosition({
scrollLeft: activeTabPosX
});
}
@@ -1247,7 +1296,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const adjustedColorDrag = editorDragAndDropBackground.flatten(adjustedTabDragBackground);
collector.addRule(`
.monaco-workbench > .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged) > .tab-label::after,
.monaco-workbench > .part.editor > .content.dragged-over .editor-group-container > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
.monaco-workbench > .part.editor > .content.dragged-over .editor-group-container:not(.active) > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important;
}
`);

View File

@@ -3,13 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/textdiffeditor';
import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import * as objects from 'vs/base/common/objects';
import { Action, IAction } from 'vs/base/common/actions';
import * as types from 'vs/base/common/types';
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
@@ -28,15 +23,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/common/editorCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { once } from 'vs/base/common/event';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IWindowService } from 'vs/platform/windows/common/windows';
/**
* The text editor that leverages the diff text editor for the editing experience.
@@ -47,31 +42,22 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
private diffNavigator: DiffNavigator;
private diffNavigatorDisposables: IDisposable[] = [];
private nextDiffAction: NavigateAction;
private previousDiffAction: NavigateAction;
private toggleIgnoreTrimWhitespaceAction: ToggleIgnoreTrimWhitespaceAction;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IConfigurationService private readonly _actualConfigurationService: IConfigurationService,
@IEditorService editorService: IEditorService,
@IThemeService themeService: IThemeService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@ITextFileService textFileService: ITextFileService,
@IWindowService windowService: IWindowService
) {
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService);
this._register(this._actualConfigurationService.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('diffEditor.ignoreTrimWhitespace')) {
this.updateIgnoreTrimWhitespaceAction();
}
}));
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService);
}
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted
}
@@ -84,13 +70,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
}
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
// Actions
this.nextDiffAction = new NavigateAction(this, true);
this.previousDiffAction = new NavigateAction(this, false);
this.toggleIgnoreTrimWhitespaceAction = new ToggleIgnoreTrimWhitespaceAction(this._actualConfigurationService);
this.updateIgnoreTrimWhitespaceAction();
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
}
@@ -139,14 +118,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
});
this.diffNavigatorDisposables.push(this.diffNavigator);
this.diffNavigatorDisposables.push(this.diffNavigator.onDidUpdate(() => {
this.nextDiffAction.updateEnablement();
this.previousDiffAction.updateEnablement();
}));
// Enablement of actions
this.updateIgnoreTrimWhitespaceAction();
// Readonly flag
diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() });
}, error => {
@@ -157,7 +128,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
}
// Otherwise make sure the error bubbles up
return TPromise.wrapError(error);
return Promise.reject(error);
});
});
}
@@ -185,13 +156,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
return false;
}
private updateIgnoreTrimWhitespaceAction(): void {
const ignoreTrimWhitespace = this.configurationService.getValue<boolean>(this.getResource(), 'diffEditor.ignoreTrimWhitespace');
if (this.toggleIgnoreTrimWhitespaceAction) {
this.toggleIgnoreTrimWhitespaceAction.updateClassName(ignoreTrimWhitespace);
}
}
private openAsBinary(input: EditorInput, options: EditorOptions): boolean {
if (input instanceof DiffEditorInput) {
const originalInput = input.originalInput;
@@ -232,6 +196,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
const options: IDiffEditorOptions = super.getConfigurationOverrides();
options.readOnly = this.isReadOnly();
options.lineDecorationsWidth = '2ch';
return options;
}
@@ -289,14 +254,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
return this.diffNavigator;
}
getActions(): IAction[] {
return [
this.toggleIgnoreTrimWhitespaceAction,
this.previousDiffAction,
this.nextDiffAction
];
}
getControl(): IDiffEditor {
return super.getControl() as IDiffEditor;
}
@@ -380,59 +337,3 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
super.dispose();
}
}
class NavigateAction extends Action {
static ID_NEXT = 'workbench.action.compareEditor.nextChange';
static ID_PREV = 'workbench.action.compareEditor.previousChange';
private editor: TextDiffEditor;
private next: boolean;
constructor(editor: TextDiffEditor, next: boolean) {
super(next ? NavigateAction.ID_NEXT : NavigateAction.ID_PREV);
this.editor = editor;
this.next = next;
this.label = this.next ? nls.localize('navigate.next.label', "Next Change") : nls.localize('navigate.prev.label', "Previous Change");
this.class = this.next ? 'textdiff-editor-action next' : 'textdiff-editor-action previous';
this.enabled = false;
}
run(): TPromise<any> {
if (this.next) {
this.editor.getDiffNavigator().next();
} else {
this.editor.getDiffNavigator().previous();
}
return null;
}
updateEnablement(): void {
this.enabled = this.editor.getDiffNavigator().canNavigate();
}
}
class ToggleIgnoreTrimWhitespaceAction extends Action {
static ID = 'workbench.action.compareEditor.toggleIgnoreTrimWhitespace';
private _isChecked: boolean;
constructor(
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
super(ToggleIgnoreTrimWhitespaceAction.ID);
this.label = nls.localize('toggleIgnoreTrimWhitespace.label', "Ignore Trim Whitespace");
}
updateClassName(ignoreTrimWhitespace: boolean): void {
this._isChecked = ignoreTrimWhitespace;
this.class = `textdiff-editor-action toggleIgnoreTrimWhitespace${this._isChecked ? ' is-checked' : ''}`;
}
run(): TPromise<any> {
this._configurationService.updateValue(`diffEditor.ignoreTrimWhitespace`, !this._isChecked);
return null;
}
}

View File

@@ -3,13 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import * as objects from 'vs/base/common/objects';
import * as types from 'vs/base/common/types';
import * as errors from 'vs/base/common/errors';
import * as DOM from 'vs/base/browser/dom';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor';
@@ -26,6 +23,7 @@ import { isDiffEditor, isCodeEditor, ICodeEditor, getCodeEditor } from 'vs/edito
import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWindowService } from 'vs/platform/windows/common/windows';
const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState';
@@ -55,10 +53,11 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
@ITextFileService private readonly _textFileService: ITextFileService,
@IEditorService protected editorService: IEditorService,
@IEditorGroupsService protected editorGroupService: IEditorGroupsService,
@IWindowService private windowService: IWindowService
) {
super(id, telemetryService, themeService);
super(id, telemetryService, themeService, storageService);
this.editorMemento = this.getEditorMemento<IEditorViewState>(storageService, editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this.editorMemento = this.getEditorMemento<IEditorViewState>(editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this._register(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
}
@@ -75,7 +74,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
return this._textFileService;
}
private handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void {
protected handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void {
if (this.isVisible()) {
this.updateEditorConfiguration(configuration);
} else {
@@ -148,15 +147,17 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
}
this._register(this.editorService.onDidActiveEditorChange(() => this.onEditorFocusLost()));
this._register(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost()));
this._register(this.windowService.onDidChangeFocus(focused => this.onWindowFocusChange(focused)));
}
private onEditorFocusLost(): void {
this.maybeTriggerSaveAll(SaveReason.FOCUS_CHANGE);
}
private onWindowFocusLost(): void {
this.maybeTriggerSaveAll(SaveReason.WINDOW_CHANGE);
private onWindowFocusChange(focused: boolean): void {
if (!focused) {
this.maybeTriggerSaveAll(SaveReason.WINDOW_CHANGE);
}
}
private maybeTriggerSaveAll(reason: SaveReason): void {
@@ -169,7 +170,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
(reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE)
) {
if (this.textFileService.isDirty()) {
this.textFileService.saveAll(void 0, { reason }).done(null, errors.onUnexpectedError);
this.textFileService.saveAll(void 0, { reason });
}
}
}
@@ -232,7 +233,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
return;
}
this.editorMemento.saveState(this.group, resource, editorViewState);
this.editorMemento.saveEditorState(this.group, resource, editorViewState);
}
protected retrieveTextEditorViewState(resource: URI): IEditorViewState {
@@ -257,9 +258,9 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
/**
* Clears the text editor view state for the given resources.
*/
protected clearTextEditorViewState(resources: URI[]): void {
protected clearTextEditorViewState(resources: URI[], group?: IEditorGroup): void {
resources.forEach(resource => {
this.editorMemento.clearState(resource);
this.editorMemento.clearEditorState(resource, group);
});
}
@@ -267,7 +268,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
* Loads the text editor view state for the given resource and returns it.
*/
protected loadTextEditorViewState(resource: URI): IEditorViewState {
return this.editorMemento.loadState(this.group, resource);
return this.editorMemento.loadEditorState(this.group, resource);
}
private updateEditorConfiguration(configuration = this.configurationService.getValue<IEditorConfiguration>(this.getResource())): void {

View File

@@ -2,9 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -25,6 +23,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWindowService } from 'vs/platform/windows/common/windows';
/**
* An editor implementation that is capable of showing the contents of resource inputs. Uses
@@ -41,9 +40,10 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
@IThemeService themeService: IThemeService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@ITextFileService textFileService: ITextFileService,
@IEditorService editorService: IEditorService
@IEditorService editorService: IEditorService,
@IWindowService windowService: IWindowService
) {
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService);
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService);
}
getTitle(): string {
@@ -70,7 +70,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
// Assert Model instance
if (!(resolvedModel instanceof BaseTextEditorModel)) {
return TPromise.wrapError<void>(new Error('Unable to open file as text'));
return Promise.reject(new Error('Unable to open file as text'));
}
// Set Editor Model
@@ -163,15 +163,14 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
super.clearInput();
}
shutdown(): void {
protected saveState(): void {
// Save View State (only for untitled)
if (this.input instanceof UntitledEditorInput) {
this.saveTextResourceEditorViewState(this.input);
}
// Call Super
super.shutdown();
super.saveState();
}
private saveTextResourceEditorViewState(input: EditorInput): void {
@@ -210,8 +209,9 @@ export class TextResourceEditor extends AbstractTextResourceEditor {
@IThemeService themeService: IThemeService,
@ITextFileService textFileService: ITextFileService,
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IWindowService windowService: IWindowService
) {
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService);
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService, windowService);
}
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { applyDragImage } from 'vs/base/browser/dnd';
import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
@@ -14,7 +12,6 @@ import { Action, IAction, IRunEvent } from 'vs/base/common/actions';
import * as arrays from 'vs/base/common/arrays';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import 'vs/css!./media/titlecontrol';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { localize } from 'vs/nls';
@@ -40,6 +37,8 @@ import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { Themable } from 'vs/workbench/common/theme';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { IFileService } from 'vs/platform/files/common/files';
export interface IToolbarActions {
primary: IAction[];
@@ -57,8 +56,6 @@ export abstract class TitleControl extends Themable {
private currentSecondaryEditorActionIds: string[] = [];
protected editorActionsToolbar: ToolBar;
private mapEditorToActions: Map<string, IToolbarActions> = new Map();
private resourceContext: ResourceContextKey;
private editorToolBarMenuDisposables: IDisposable[] = [];
@@ -79,11 +76,12 @@ export abstract class TitleControl extends Themable {
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@IThemeService themeService: IThemeService,
@IExtensionService private extensionService: IExtensionService,
@IConfigurationService protected configurationService: IConfigurationService
@IConfigurationService protected configurationService: IConfigurationService,
@IFileService private readonly fileService: IFileService,
) {
super(themeService);
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
this.resourceContext = this._register(instantiationService.createInstance(ResourceContextKey));
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
this.create(parent);
@@ -98,22 +96,31 @@ export abstract class TitleControl extends Themable {
protected createBreadcrumbsControl(container: HTMLElement, options: IBreadcrumbsControlOptions): void {
const config = this._register(BreadcrumbsConfig.IsEnabled.bindTo(this.configurationService));
config.onDidChange(value => {
this._register(config.onDidChange(() => {
const value = config.getValue();
if (!value && this.breadcrumbsControl) {
this.breadcrumbsControl.dispose();
this.breadcrumbsControl = undefined;
this.group.relayout();
this.handleBreadcrumbsEnablementChange();
} else if (value && !this.breadcrumbsControl) {
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
this.breadcrumbsControl.update();
this.group.relayout();
this.handleBreadcrumbsEnablementChange();
}
});
if (config.value) {
}));
if (config.getValue()) {
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
}
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => {
if (this.breadcrumbsControl && this.breadcrumbsControl.update()) {
this.handleBreadcrumbsEnablementChange();
}
}));
}
protected abstract handleBreadcrumbsEnablementChange(): void;
protected createEditorActionsToolBar(container: HTMLElement): void {
const context = { groupId: this.group.id } as IEditorCommandsContext;
@@ -122,7 +129,8 @@ export abstract class TitleControl extends Themable {
orientation: ActionsOrientation.HORIZONTAL,
ariaLabel: localize('araLabelEditorActions', "Editor actions"),
getKeyBinding: action => this.getKeybinding(action),
actionRunner: this._register(new EditorCommandsContextActionRunner(context))
actionRunner: this._register(new EditorCommandsContextActionRunner(context)),
anchorAlignmentProvider: () => AnchorAlignment.RIGHT
}));
// Context
@@ -215,17 +223,6 @@ export abstract class TitleControl extends Themable {
// Editor actions require the editor control to be there, so we retrieve it via service
const activeControl = this.group.activeControl;
if (activeControl instanceof BaseEditor) {
// Editor Control Actions
let editorActions = this.mapEditorToActions.get(activeControl.getId());
if (!editorActions) {
editorActions = { primary: activeControl.getActions(), secondary: activeControl.getSecondaryActions() };
this.mapEditorToActions.set(activeControl.getId(), editorActions);
}
primary.push(...editorActions.primary);
secondary.push(...editorActions.secondary);
// Contributed Actions
const codeEditor = getCodeEditor(activeControl.getControl());
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
@@ -302,7 +299,7 @@ export abstract class TitleControl extends Themable {
// Show it
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.as(actions),
getActions: () => actions,
getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) } as IEditorCommandsContext),
getKeyBinding: (action) => this.getKeybinding(action),
onHide: () => {
@@ -316,7 +313,7 @@ export abstract class TitleControl extends Themable {
});
}
protected getKeybinding(action: IAction): ResolvedKeybinding {
private getKeybinding(action: IAction): ResolvedKeybinding {
return this.keybindingService.lookupKeybinding(action.id);
}