SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,305 @@
/*---------------------------------------------------------------------------------------------
* 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 types = require('vs/base/common/types');
import { Builder } from 'vs/base/browser/builder';
import { Registry } from 'vs/platform/registry/common/platform';
import { Panel } from 'vs/workbench/browser/panel';
import { EditorInput, EditorOptions, IEditorDescriptor, IEditorInputFactory, IEditorRegistry, Extensions, IFileInputFactory } from 'vs/workbench/common/editor';
import { IEditor, Position } from 'vs/platform/editor/common/editor';
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
import { SyncDescriptor, AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
/**
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
* Editors are layed out in the editor part of the workbench. Only one editor can be open at a time.
* Each editor has a minimized representation that is good enough to provide some information about the
* state of the editor data.
* The workbench will keep an editor alive after it has been created and show/hide it based on
* user interaction. The lifecycle of a editor goes in the order create(), setVisible(true|false),
* layout(), setInput(), focus(), dispose(). During use of the workbench, a editor will often receive a
* clearInput, setVisible, layout and focus call, but only one create and dispose call.
*
* This class is only intended to be subclassed and not instantiated.
*/
export abstract class BaseEditor extends Panel implements IEditor {
protected _input: EditorInput;
private _options: EditorOptions;
private _position: Position;
constructor(id: string, telemetryService: ITelemetryService, themeService: IThemeService) {
super(id, telemetryService, themeService);
}
public get input(): EditorInput {
return this._input;
}
public get options(): EditorOptions {
return this._options;
}
/**
* Note: Clients should not call this method, the workbench calls this
* method. Calling it otherwise may result in unexpected behavior.
*
* Sets the given input with the options to the part. An editor has to deal with the
* situation that the same input is being set with different options.
*/
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
this._input = input;
this._options = options;
return TPromise.as<void>(null);
}
/**
* Called to indicate to the editor that the input should be cleared and resources associated with the
* input should be freed.
*/
public clearInput(): void {
this._input = null;
this._options = null;
}
public create(parent: Builder): void; // create is sync for editors
public create(parent: Builder): TPromise<void>;
public create(parent: Builder): TPromise<void> {
const res = super.create(parent);
// Create Editor
this.createEditor(parent);
return res;
}
/**
* Called to create the editor in the parent builder.
*/
protected abstract createEditor(parent: Builder): void;
/**
* Overload this function to allow for passing in a position argument.
*/
public setVisible(visible: boolean, position?: Position): void; // setVisible is sync for editors
public setVisible(visible: boolean, position?: Position): TPromise<void>;
public setVisible(visible: boolean, position: Position = null): TPromise<void> {
const promise = super.setVisible(visible);
// Propagate to Editor
this.setEditorVisible(visible, position);
return promise;
}
protected setEditorVisible(visible: boolean, position: Position = null): void {
this._position = position;
}
/**
* Called when the position of the editor changes while it is visible.
*/
public changePosition(position: Position): void {
this._position = position;
}
/**
* The position this editor is showing in or null if none.
*/
public get position(): Position {
return this._position;
}
public dispose(): void {
this._input = null;
this._options = null;
// Super Dispose
super.dispose();
}
}
/**
* A lightweight descriptor of an editor. The descriptor is deferred so that heavy editors
* can load lazily in the workbench.
*/
export class EditorDescriptor extends AsyncDescriptor<BaseEditor> implements IEditorDescriptor {
private id: string;
private name: string;
constructor(id: string, name: string, moduleId: string, ctorName: string) {
super(moduleId, ctorName);
this.id = id;
this.name = name;
}
public getId(): string {
return this.id;
}
public getName(): string {
return this.name;
}
public describes(obj: any): boolean {
return obj instanceof BaseEditor && (<BaseEditor>obj).getId() === this.id;
}
}
const INPUT_DESCRIPTORS_PROPERTY = '__$inputDescriptors';
class EditorRegistry implements IEditorRegistry {
private editors: EditorDescriptor[];
private instantiationService: IInstantiationService;
private fileInputFactory: IFileInputFactory;
private editorInputFactoryConstructors: { [editorInputId: string]: IConstructorSignature0<IEditorInputFactory> } = Object.create(null);
private editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null);
constructor() {
this.editors = [];
}
public setInstantiationService(service: IInstantiationService): void {
this.instantiationService = service;
for (let key in this.editorInputFactoryConstructors) {
const element = this.editorInputFactoryConstructors[key];
this.createEditorInputFactory(key, element);
}
this.editorInputFactoryConstructors = {};
}
private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
const instance = this.instantiationService.createInstance(ctor);
this.editorInputFactoryInstances[editorInputId] = instance;
}
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>): void;
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>[]): void;
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: any): void {
// Support both non-array and array parameter
let inputDescriptors: SyncDescriptor<EditorInput>[] = [];
if (!types.isArray(editorInputDescriptor)) {
inputDescriptors.push(editorInputDescriptor);
} else {
inputDescriptors = editorInputDescriptor;
}
// Register (Support multiple Editors per Input)
descriptor[INPUT_DESCRIPTORS_PROPERTY] = inputDescriptors;
this.editors.push(descriptor);
}
public getEditor(input: EditorInput): EditorDescriptor {
const findEditorDescriptors = (input: EditorInput, byInstanceOf?: boolean): EditorDescriptor[] => {
const matchingDescriptors: EditorDescriptor[] = [];
for (let i = 0; i < this.editors.length; i++) {
const editor = this.editors[i];
const inputDescriptors = <SyncDescriptor<EditorInput>[]>editor[INPUT_DESCRIPTORS_PROPERTY];
for (let j = 0; j < inputDescriptors.length; j++) {
const inputClass = inputDescriptors[j].ctor;
// Direct check on constructor type (ignores prototype chain)
if (!byInstanceOf && input.constructor === inputClass) {
matchingDescriptors.push(editor);
break;
}
// Normal instanceof check
else if (byInstanceOf && input instanceof inputClass) {
matchingDescriptors.push(editor);
break;
}
}
}
// If no descriptors found, continue search using instanceof and prototype chain
if (!byInstanceOf && matchingDescriptors.length === 0) {
return findEditorDescriptors(input, true);
}
if (byInstanceOf) {
return matchingDescriptors;
}
return matchingDescriptors;
};
const descriptors = findEditorDescriptors(input);
if (descriptors && descriptors.length > 0) {
// Ask the input for its preferred Editor
const preferredEditorId = input.getPreferredEditorId(descriptors.map(d => d.getId()));
if (preferredEditorId) {
return this.getEditorById(preferredEditorId);
}
// Otherwise, first come first serve
return descriptors[0];
}
return null;
}
public getEditorById(editorId: string): EditorDescriptor {
for (let i = 0; i < this.editors.length; i++) {
const editor = this.editors[i];
if (editor.getId() === editorId) {
return editor;
}
}
return null;
}
public getEditors(): EditorDescriptor[] {
return this.editors.slice(0);
}
public setEditors(editorsToSet: EditorDescriptor[]): void {
this.editors = editorsToSet;
}
public getEditorInputs(): any[] {
const inputClasses: any[] = [];
for (let i = 0; i < this.editors.length; i++) {
const editor = this.editors[i];
const editorInputDescriptors = <SyncDescriptor<EditorInput>[]>editor[INPUT_DESCRIPTORS_PROPERTY];
inputClasses.push(...editorInputDescriptors.map(descriptor => descriptor.ctor));
}
return inputClasses;
}
public registerFileInputFactory(factory: IFileInputFactory): void {
this.fileInputFactory = factory;
}
public getFileInputFactory(): IFileInputFactory {
return this.fileInputFactory;
}
public registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
if (!this.instantiationService) {
this.editorInputFactoryConstructors[editorInputId] = ctor;
} else {
this.createEditorInputFactory(editorInputId, ctor);
}
}
public getEditorInputFactory(editorInputId: string): IEditorInputFactory {
return this.editorInputFactoryInstances[editorInputId];
}
}
Registry.add(Extensions.Editors, new EditorRegistry());

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* 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 nls = require('vs/nls');
import { BINARY_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
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';
/**
* An implementation of editor for diffing binary files like images or videos.
*/
export class BinaryResourceDiffEditor extends SideBySideEditor {
public static ID = BINARY_DIFF_EDITOR_ID;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
) {
super(telemetryService, instantiationService, themeService);
}
public getMetadata(): string {
const master = this.masterEditor;
const details = this.detailsEditor;
if (master instanceof BaseBinaryResourceEditor && details instanceof BaseBinaryResourceEditor) {
return nls.localize('metadataDiff', "{0} ↔ {1}", details.getMetadata(), master.getMetadata());
}
return null;
}
}

View File

@@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* 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 nls = require('vs/nls');
import Event, { Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
import { ResourceViewer } from 'vs/base/browser/ui/resourceviewer/resourceViewer';
import { EditorModel, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWindowsService } from 'vs/platform/windows/common/windows';
/*
* This class is only intended to be subclassed and not instantiated.
*/
export abstract class BaseBinaryResourceEditor extends BaseEditor {
private _onMetadataChanged: Emitter<void>;
private metadata: string;
private binaryContainer: Builder;
private scrollbar: DomScrollableElement;
constructor(
id: string,
telemetryService: ITelemetryService,
themeService: IThemeService,
private windowsService: IWindowsService
) {
super(id, telemetryService, themeService);
this._onMetadataChanged = new Emitter<void>();
}
public get onMetadataChanged(): Event<void> {
return this._onMetadataChanged.event;
}
public getTitle(): string {
return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer");
}
protected createEditor(parent: Builder): 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)
// Custom Scrollbars
this.scrollbar = new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto });
parent.getHTMLElement().appendChild(this.scrollbar.getDomNode());
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
const oldInput = this.input;
super.setInput(input, options);
// Detect options
const forceOpen = options && options.forceOpen;
// Same Input
if (!forceOpen && input.matches(oldInput)) {
return TPromise.as<void>(null);
}
// Different Input (Reload)
return input.resolve(true).then((resolvedModel: EditorModel) => {
// Assert Model instance
if (!(resolvedModel instanceof BinaryEditorModel)) {
return TPromise.wrapError<void>(new Error('Unable to open file as binary'));
}
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
if (!this.input || this.input !== input) {
return null;
}
// Render Input
const model = <BinaryEditorModel>resolvedModel;
ResourceViewer.show(
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag() },
this.binaryContainer,
this.scrollbar,
(resource: URI) => {
this.windowsService.openExternal(resource.toString()).then(didOpen => {
if (!didOpen) {
return this.windowsService.showItemInFolder(resource.fsPath);
}
return void 0;
});
},
(meta) => this.handleMetadataChanged(meta));
return TPromise.as<void>(null);
});
}
private handleMetadataChanged(meta: string): void {
this.metadata = meta;
this._onMetadataChanged.fire();
}
public getMetadata(): string {
return this.metadata;
}
public clearInput(): void {
// Clear Meta
this.handleMetadataChanged(null);
// Empty HTML Container
$(this.binaryContainer).empty();
super.clearInput();
}
public layout(dimension: Dimension): void {
// Pass on to Binary Container
this.binaryContainer.size(dimension.width, dimension.height);
this.scrollbar.scanDomNode();
}
public focus(): void {
this.binaryContainer.domFocus();
}
public dispose(): void {
// Destroy Container
this.binaryContainer.destroy();
this.scrollbar.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,408 @@
/*---------------------------------------------------------------------------------------------
* 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 nls = require('vs/nls');
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 { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorInput, IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory, SideBySideEditorInput } 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';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import {
CloseEditorsInGroupAction, CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, KeepEditorAction, CloseOtherEditorsInGroupAction, OpenToSideAction, RevertAndCloseEditorAction,
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction,
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, NAVIGATE_IN_GROUP_ONE_PREFIX,
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
NAVIGATE_IN_GROUP_TWO_PREFIX, ShowEditorsInGroupThreeAction, NAVIGATE_IN_GROUP_THREE_PREFIX, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction
} from 'vs/workbench/browser/parts/editor/editorActions';
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
// Register String Editor
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
new EditorDescriptor(
TextResourceEditor.ID,
nls.localize('textEditor', "Text Editor"),
'vs/workbench/browser/parts/editor/textResourceEditor',
'TextResourceEditor'
),
[
new SyncDescriptor(UntitledEditorInput),
new SyncDescriptor(ResourceEditorInput)
]
);
// Register Text Diff Editor
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
new EditorDescriptor(
TextDiffEditor.ID,
nls.localize('textDiffEditor', "Text Diff Editor"),
'vs/workbench/browser/parts/editor/textDiffEditor',
'TextDiffEditor'
),
[
new SyncDescriptor(DiffEditorInput)
]
);
// Register Binary Resource Diff Editor
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
new EditorDescriptor(
BinaryResourceDiffEditor.ID,
nls.localize('binaryDiffEditor', "Binary Diff Editor"),
'vs/workbench/browser/parts/editor/binaryDiffEditor',
'BinaryResourceDiffEditor'
),
[
new SyncDescriptor(DiffEditorInput)
]
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
new EditorDescriptor(
SideBySideEditor.ID,
nls.localize('sideBySideEditor', "Side by Side Editor"),
'vs/workbench/browser/parts/editor/sideBySideEditor',
'SideBySideEditor'
),
[
new SyncDescriptor(SideBySideEditorInput)
]
);
interface ISerializedUntitledEditorInput {
resource: string;
resourceJSON: object;
modeId: string;
encoding: string;
}
// Register Editor Input Factory
class UntitledEditorInputFactory implements IEditorInputFactory {
constructor(
@ITextFileService private textFileService: ITextFileService
) {
}
public serialize(editorInput: EditorInput): string {
if (!this.textFileService.isHotExitEnabled) {
return null; // never restore untitled unless hot exit is enabled
}
const untitledEditorInput = <UntitledEditorInput>editorInput;
// {{SQL CARBON EDIT}}
if (!untitledEditorInput.getResource) {
return null;
}
let resource = untitledEditorInput.getResource();
if (untitledEditorInput.hasAssociatedFilePath) {
resource = URI.file(resource.fsPath); // untitled with associated file path use the file schema
}
const serialized: ISerializedUntitledEditorInput = {
resource: resource.toString(), // Keep for backwards compatibility
resourceJSON: resource.toJSON(),
modeId: untitledEditorInput.getModeId(),
encoding: untitledEditorInput.getEncoding()
};
return JSON.stringify(serialized);
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput {
return instantiationService.invokeFunction<UntitledEditorInput>(accessor => {
const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput);
const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource);
const filePath = resource.scheme === 'file' ? resource.fsPath : void 0;
const language = deserialized.modeId;
const encoding = deserialized.encoding;
return accessor.get(IWorkbenchEditorService).createInput({ resource, filePath, language, encoding }) as UntitledEditorInput;
});
}
}
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(UntitledEditorInput.ID, UntitledEditorInputFactory);
interface ISerializedSideBySideEditorInput {
name: string;
description: string;
detailsSerialized: string;
masterSerialized: string;
detailsTypeId: string;
masterTypeId: string;
}
// Register Side by Side Editor Input Factory
class SideBySideEditorInputFactory implements IEditorInputFactory {
public serialize(editorInput: EditorInput): string {
const input = <SideBySideEditorInput>editorInput;
if (input.details && input.master) {
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId());
const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId());
if (detailsInputFactory && masterInputFactory) {
const detailsSerialized = detailsInputFactory.serialize(input.details);
const masterSerialized = masterInputFactory.serialize(input.master);
if (detailsSerialized && masterSerialized) {
return JSON.stringify(<ISerializedSideBySideEditorInput>{
name: input.getName(),
description: input.getDescription(),
detailsSerialized,
masterSerialized,
detailsTypeId: input.details.getTypeId(),
masterTypeId: input.master.getTypeId()
});
}
}
}
return null;
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
const detailsInputFactory = registry.getEditorInputFactory(deserialized.detailsTypeId);
const masterInputFactory = registry.getEditorInputFactory(deserialized.masterTypeId);
if (detailsInputFactory && masterInputFactory) {
const detailsInput = detailsInputFactory.deserialize(instantiationService, deserialized.detailsSerialized);
const masterInput = masterInputFactory.deserialize(instantiationService, deserialized.masterSerialized);
if (detailsInput && masterInput) {
return new SideBySideEditorInput(deserialized.name, deserialized.description, detailsInput, masterInput);
}
}
return null;
}
}
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
// Register Editor Status
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* High Priority */));
// Register Status Actions
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode');
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence');
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding');
export class QuickOpenActionContributor extends ActionBarContributor {
private openToSideActionInstance: OpenToSideAction;
constructor( @IInstantiationService private instantiationService: IInstantiationService) {
super();
}
public hasActions(context: any): boolean {
const entry = this.getEntry(context);
return !!entry;
}
public getActions(context: any): IAction[] {
const actions: Action[] = [];
const entry = this.getEntry(context);
if (entry) {
if (!this.openToSideActionInstance) {
this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideAction);
} else {
this.openToSideActionInstance.updateClass();
}
actions.push(this.openToSideActionInstance);
}
return actions;
}
private getEntry(context: any): IEditorQuickOpenEntry {
if (!context || !context.element) {
return null;
}
return toEditorQuickOpenEntry(context.element);
}
}
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor);
const editorPickerContextKey = 'inEditorsPicker';
const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(editorPickerContextKey));
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/browser/parts/editor/editorPicker',
'GroupOnePicker',
NAVIGATE_IN_GROUP_ONE_PREFIX,
editorPickerContextKey,
[
{
prefix: NAVIGATE_IN_GROUP_ONE_PREFIX,
needsEditor: false,
description: nls.localize('groupOnePicker', "Show Editors in First Group")
},
{
prefix: NAVIGATE_IN_GROUP_TWO_PREFIX,
needsEditor: false,
description: nls.localize('groupTwoPicker', "Show Editors in Second Group")
},
{
prefix: NAVIGATE_IN_GROUP_THREE_PREFIX,
needsEditor: false,
description: nls.localize('groupThreePicker', "Show Editors in Third Group")
}
]
)
);
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/browser/parts/editor/editorPicker',
'GroupTwoPicker',
NAVIGATE_IN_GROUP_TWO_PREFIX,
editorPickerContextKey,
[]
)
);
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/browser/parts/editor/editorPicker',
'GroupThreePicker',
NAVIGATE_IN_GROUP_THREE_PREFIX,
editorPickerContextKey,
[]
)
);
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/browser/parts/editor/editorPicker',
'AllEditorsPicker',
NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
editorPickerContextKey,
[
{
prefix: NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
needsEditor: false,
description: nls.localize('allEditorsPicker', "Show All Opened Editors")
}
]
)
);
// Register Editor Actions
const category = nls.localize('view', "View");
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupOneAction, ShowEditorsInGroupOneAction.ID, ShowEditorsInGroupOneAction.LABEL), 'View: Show Editors in First Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupTwoAction, ShowEditorsInGroupTwoAction.ID, ShowEditorsInGroupTwoAction.LABEL), 'View: Show Editors in Second Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupThreeAction, ShowEditorsInGroupThreeAction.ID, ShowEditorsInGroupThreeAction.LABEL), 'View: Show Editors in Third Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'View: Clear Recently Opened', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(KeepEditorAction, KeepEditorAction.ID, KeepEditorAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter) }), 'View: Keep Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, CloseRightEditorsInGroupAction.LABEL), 'View: Close Editors to the Right', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, CloseUnmodifiedEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U) }), 'View: Close Unmodified Editors in Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, CloseEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W) }), 'View: Close All Editors in Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, CloseOtherEditorsInGroupAction.LABEL, { primary: null, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T } }), 'View: Close Other Editors', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other 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(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editors of Two Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSecondGroupAction, FocusSecondGroupAction.ID, FocusSecondGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_2 }), 'View: Focus Second Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusThirdGroupAction, FocusThirdGroupAction.ID, FocusThirdGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_3 }), 'View: Focus Third Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLastEditorInStackAction, FocusLastEditorInStackAction.ID, FocusLastEditorInStackAction.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0 } }), 'View: Open Last Editor in Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EvenGroupWidthsAction, EvenGroupWidthsAction.ID, EvenGroupWidthsAction.LABEL), 'View: Even Editor Group Widths', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Sidebar', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Minimize Other Editor Groups', 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);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, MoveGroupLeftAction.ID, MoveGroupLeftAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.LeftArrow) }), 'View: Move Editor Group Left', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Previous Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Next Group', 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(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History');
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History');
registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category);
// Register Editor Picker Actions including quick navigate support
const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } };
const openPreviousEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } };
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'Open Next Recently Used Editor in Group');
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'Open Previous Recently Used Editor in Group');
const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickOpenNavigateNextInEditorPickerId,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true),
when: editorPickerContext,
primary: openNextEditorKeybinding.primary,
mac: openNextEditorKeybinding.mac
});
const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickOpenNavigatePreviousInEditorPickerId,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false),
when: editorPickerContext,
primary: openPreviousEditorKeybinding.primary,
mac: openPreviousEditorKeybinding.mac
});
// Editor Commands
editorCommands.setup();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,275 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible } from 'vs/workbench/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditor, Position, POSITIONS } from 'vs/platform/editor/common/editor';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
import { EditorStacksModel } from 'vs/workbench/common/editor/editorStacksModel';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IMessageService, Severity, CloseAction } from 'vs/platform/message/common/message';
import { Action } from 'vs/base/common/actions';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
export function setup(): void {
registerActiveEditorMoveCommand();
registerDiffEditorCommands();
registerOpenEditorAtIndexCommands();
handleCommandDeprecations();
}
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
if (!types.isObject(arg)) {
return false;
}
const activeEditorMoveArg: ActiveEditorMoveArguments = arg;
if (!types.isString(activeEditorMoveArg.to)) {
return false;
}
if (!types.isUndefined(activeEditorMoveArg.by) && !types.isString(activeEditorMoveArg.by)) {
return false;
}
if (!types.isUndefined(activeEditorMoveArg.value) && !types.isNumber(activeEditorMoveArg.value)) {
return false;
}
return true;
};
function registerActiveEditorMoveCommand(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: EditorCommands.MoveActiveEditor,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: EditorContextKeys.textFocus,
primary: null,
handler: (accessor, args: any) => moveActiveEditor(args, accessor),
description: {
description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
args: [
{
name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
description: nls.localize('editorCommand.activeEditorMove.arg.description', `Argument Properties:
* 'to': String value providing where to move.
* 'by': String value providing the unit for move. By tab or by group.
* 'value': Number value providing how many positions or an absolute position to move.
`),
constraint: isActiveEditorMoveArg
}
]
}
});
}
function moveActiveEditor(args: ActiveEditorMoveArguments = {}, accessor: ServicesAccessor): void {
const showTabs = accessor.get(IEditorGroupService).getTabOptions().showTabs;
args.to = args.to || ActiveEditorMovePositioning.RIGHT;
args.by = showTabs ? args.by || ActiveEditorMovePositioningBy.TAB : ActiveEditorMovePositioningBy.GROUP;
args.value = types.isUndefined(args.value) ? 1 : args.value;
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
if (activeEditor) {
switch (args.by) {
case ActiveEditorMovePositioningBy.TAB:
return moveActiveTab(args, activeEditor, accessor);
case ActiveEditorMovePositioningBy.GROUP:
return moveActiveEditorToGroup(args, activeEditor, accessor);
}
}
}
function moveActiveTab(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
const editorGroupsService: IEditorGroupService = accessor.get(IEditorGroupService);
const editorGroup = editorGroupsService.getStacksModel().groupAt(activeEditor.position);
let index = editorGroup.indexOf(activeEditor.input);
switch (args.to) {
case ActiveEditorMovePositioning.FIRST:
index = 0;
break;
case ActiveEditorMovePositioning.LAST:
index = editorGroup.count - 1;
break;
case ActiveEditorMovePositioning.LEFT:
index = index - args.value;
break;
case ActiveEditorMovePositioning.RIGHT:
index = index + args.value;
break;
case ActiveEditorMovePositioning.CENTER:
index = Math.round(editorGroup.count / 2) - 1;
break;
case ActiveEditorMovePositioning.POSITION:
index = args.value - 1;
break;
}
index = index < 0 ? 0 : index >= editorGroup.count ? editorGroup.count - 1 : index;
editorGroupsService.moveEditor(activeEditor.input, editorGroup, editorGroup, { index });
}
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
let newPosition = activeEditor.position;
switch (args.to) {
case ActiveEditorMovePositioning.LEFT:
newPosition = newPosition - 1;
break;
case ActiveEditorMovePositioning.RIGHT:
newPosition = newPosition + 1;
break;
case ActiveEditorMovePositioning.FIRST:
newPosition = Position.ONE;
break;
case ActiveEditorMovePositioning.LAST:
newPosition = Position.THREE;
break;
case ActiveEditorMovePositioning.CENTER:
newPosition = Position.TWO;
break;
case ActiveEditorMovePositioning.POSITION:
newPosition = args.value - 1;
break;
}
newPosition = POSITIONS.indexOf(newPosition) !== -1 ? newPosition : activeEditor.position;
accessor.get(IEditorGroupService).moveEditor(activeEditor.input, activeEditor.position, newPosition);
}
function registerDiffEditorCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.compareEditor.nextChange',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: TextCompareEditorVisible,
primary: null,
handler: accessor => navigateInDiffEditor(accessor, true)
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.compareEditor.previousChange',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: TextCompareEditorVisible,
primary: null,
handler: accessor => navigateInDiffEditor(accessor, false)
});
function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
let editorService = accessor.get(IWorkbenchEditorService);
const candidates = [editorService.getActiveEditor(), ...editorService.getVisibleEditors()].filter(e => e instanceof TextDiffEditor);
if (candidates.length > 0) {
next ? (<TextDiffEditor>candidates[0]).getDiffNavigator().next() : (<TextDiffEditor>candidates[0]).getDiffNavigator().previous();
}
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.printStacksModel',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor) {
console.log(`${accessor.get(IEditorGroupService).getStacksModel().toString()}\n\n`);
},
when: undefined,
primary: undefined
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.validateStacksModel',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor) {
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
},
when: undefined,
primary: undefined
});
}
function handleCommandDeprecations(): void {
const mapDeprecatedCommands = {
'workbench.action.files.newFile': 'explorer.newFile',
'workbench.action.files.newFolder': 'explorer.newFolder'
};
Object.keys(mapDeprecatedCommands).forEach(deprecatedCommandId => {
const newCommandId = mapDeprecatedCommands[deprecatedCommandId];
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: deprecatedCommandId,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor) {
const messageService = accessor.get(IMessageService);
const commandService = accessor.get(ICommandService);
messageService.show(Severity.Warning, {
message: nls.localize('commandDeprecated', "Command **{0}** has been removed. You can use **{1}** instead", deprecatedCommandId, newCommandId),
actions: [
new Action('openKeybindings', nls.localize('openKeybindings', "Configure Keyboard Shortcuts"), null, true, () => {
return commandService.executeCommand('workbench.action.openGlobalKeybindings');
}),
CloseAction
]
});
},
when: undefined,
primary: undefined
});
});
}
function registerOpenEditorAtIndexCommands(): void {
// Keybindings to focus a specific index in the tab folder if tabs are enabled
for (let i = 0; i < 9; i++) {
const editorIndex = i;
const visibleIndex = i + 1;
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.openEditorAtIndex' + visibleIndex,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyMod.Alt | toKeyCode(visibleIndex),
mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
handler: accessor => {
const editorService = accessor.get(IWorkbenchEditorService);
const editorGroupService = accessor.get(IEditorGroupService);
const active = editorService.getActiveEditor();
if (active) {
const group = editorGroupService.getStacksModel().groupAt(active.position);
const editor = group.getEditor(editorIndex);
if (editor) {
return editorService.openEditor(editor);
}
}
return void 0;
}
});
}
function toKeyCode(index: number): KeyCode {
switch (index) {
case 0: return KeyCode.KEY_0;
case 1: return KeyCode.KEY_1;
case 2: return KeyCode.KEY_2;
case 3: return KeyCode.KEY_3;
case 4: return KeyCode.KEY_4;
case 5: return KeyCode.KEY_5;
case 6: return KeyCode.KEY_6;
case 7: return KeyCode.KEY_7;
case 8: return KeyCode.KEY_8;
case 9: return KeyCode.KEY_9;
}
return void 0;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
/*---------------------------------------------------------------------------------------------
* 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 nls = require('vs/nls');
import labels = require('vs/base/common/labels');
import URI from 'vs/base/common/uri';
import errors = require('vs/base/common/errors');
import strings = require('vs/base/common/strings');
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import scorer = require('vs/base/common/scorer');
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/workbench/browser/labels';
import { IModelService } from 'vs/editor/common/services/modelService';
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { Position } from 'vs/platform/editor/common/editor';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { EditorInput, toResource, IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor';
export class EditorPickerEntry extends QuickOpenEntryGroup {
private stacks: IEditorStacksModel;
constructor(
private editor: EditorInput,
private _group: IEditorGroup,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IModeService private modeService: IModeService,
@IModelService private modelService: IModelService,
@IEditorGroupService editorGroupService: IEditorGroupService
) {
super();
this.stacks = editorGroupService.getStacksModel();
}
public getLabelOptions(): IIconLabelOptions {
return {
extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()),
italic: this._group.isPreview(this.editor)
};
}
public getLabel(): string {
return this.editor.getName();
}
public getIcon(): string {
return this.editor.isDirty() ? 'dirty' : '';
}
public get group(): IEditorGroup {
return this._group;
}
public getResource(): URI {
return toResource(this.editor, { supportSideBySide: true });
}
public getAriaLabel(): string {
return nls.localize('entryAriaLabel', "{0}, editor group picker", this.getLabel());
}
public getDescription(): string {
return this.editor.getDescription();
}
public run(mode: Mode, context: IEntryRunContext): boolean {
if (mode === Mode.OPEN) {
return this.runOpen(context);
}
return super.run(mode, context);
}
private runOpen(context: IEntryRunContext): boolean {
this.editorService.openEditor(this.editor, null, this.stacks.positionOfGroup(this.group)).done(null, errors.onUnexpectedError);
return true;
}
}
export abstract class BaseEditorPicker extends QuickOpenHandler {
private scorerCache: { [key: string]: number };
constructor(
@IInstantiationService protected instantiationService: IInstantiationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IEditorGroupService protected editorGroupService: IEditorGroupService
) {
super();
this.scorerCache = Object.create(null);
}
public getResults(searchValue: string): TPromise<QuickOpenModel> {
searchValue = searchValue.trim();
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
const editorEntries = this.getEditorEntries();
if (!editorEntries.length) {
return TPromise.as(null);
}
const stacks = this.editorGroupService.getStacksModel();
const entries = editorEntries.filter(e => {
if (!searchValue) {
return true;
}
const resource = e.getResource();
const targetToMatch = resource ? labels.getPathLabel(e.getResource(), this.contextService) : e.getLabel();
if (!scorer.matches(targetToMatch, normalizedSearchValueLowercase)) {
return false;
}
const { labelHighlights, descriptionHighlights } = QuickOpenEntry.highlight(e, searchValue, true /* fuzzy highlight */);
e.setHighlights(labelHighlights, descriptionHighlights);
return true;
});
// Sorting
if (searchValue) {
entries.sort((e1, e2) => {
if (e1.group !== e2.group) {
return stacks.positionOfGroup(e1.group) - stacks.positionOfGroup(e2.group);
}
return QuickOpenEntry.compareByScore(e1, e2, searchValue, normalizedSearchValueLowercase, this.scorerCache);
});
}
// Grouping (for more than one group)
if (stacks.groups.length > 1) {
let lastGroup: IEditorGroup;
entries.forEach(e => {
if (!lastGroup || lastGroup !== e.group) {
e.setGroupLabel(nls.localize('groupLabel', "Group: {0}", e.group.label));
e.setShowBorder(!!lastGroup);
lastGroup = e.group;
}
});
}
return TPromise.as(new QuickOpenModel(entries));
}
public onClose(canceled: boolean): void {
this.scorerCache = Object.create(null);
}
protected abstract getEditorEntries(): EditorPickerEntry[];
}
export abstract class EditorGroupPicker extends BaseEditorPicker {
protected getEditorEntries(): EditorPickerEntry[] {
const stacks = this.editorGroupService.getStacksModel();
const group = stacks.groupAt(this.getPosition());
if (!group) {
return [];
}
return group.getEditors(true).map((editor, index) => this.instantiationService.createInstance(EditorPickerEntry, editor, group));
}
protected abstract getPosition(): Position;
public getEmptyLabel(searchString: string): string {
if (searchString) {
return nls.localize('noResultsFoundInGroup', "No matching opened editor found in group");
}
return nls.localize('noOpenedEditors', "List of opened editors is currently empty in group");
}
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
if (searchValue || !context.quickNavigateConfiguration) {
return {
autoFocusFirstEntry: true
};
}
const stacks = this.editorGroupService.getStacksModel();
const group = stacks.groupAt(this.getPosition());
if (!group) {
return super.getAutoFocus(searchValue, context);
}
const isShiftNavigate = (context.quickNavigateConfiguration && context.quickNavigateConfiguration.keybindings.some(k => {
const [firstPart, chordPart] = k.getParts();
if (chordPart) {
return false;
}
return firstPart.shiftKey;
}));
if (isShiftNavigate) {
return {
autoFocusLastEntry: true
};
}
return {
autoFocusFirstEntry: group.count === 1,
autoFocusSecondEntry: group.count > 1
};
}
}
export class GroupOnePicker extends EditorGroupPicker {
protected getPosition(): Position {
return Position.ONE;
}
}
export class GroupTwoPicker extends EditorGroupPicker {
protected getPosition(): Position {
return Position.TWO;
}
}
export class GroupThreePicker extends EditorGroupPicker {
protected getPosition(): Position {
return Position.THREE;
}
}
export class AllEditorsPicker extends BaseEditorPicker {
protected getEditorEntries(): EditorPickerEntry[] {
const entries: EditorPickerEntry[] = [];
const stacks = this.editorGroupService.getStacksModel();
stacks.groups.forEach((group, position) => {
group.getEditors().forEach((editor, index) => {
entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group));
});
});
return entries;
}
public getEmptyLabel(searchString: string): string {
if (searchString) {
return nls.localize('noResultsFound', "No matching opened editor found");
}
return nls.localize('noOpenedEditorsAllGroups', "List of opened editors is currently empty");
}
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
if (searchValue) {
return {
autoFocusFirstEntry: true
};
}
return super.getAutoFocus(searchValue, context);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#C5C5C5" cx="8" cy="8" r="4"/></svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#424242" cx="8" cy="8" r="4"/></svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench > .editor > .content.dragging > .monaco-sash {
display: none; /* hide sashes while dragging editors around */
}
#monaco-workbench-editor-move-overlay,
#monaco-workbench-editor-drop-overlay {
position: absolute;
left: 0;
width: 100%;
z-index: 10000;
}
#monaco-workbench-editor-drop-overlay {
opacity: 0; /* initially not visible until moving around */
}
.monaco-workbench > .editor > .content > .one-editor-silo {
position: absolute;
box-sizing: border-box; /* use border box to be able to draw a border as separator between editors */
}
.monaco-workbench > .editor > .content > .one-editor-silo.editor-one {
left: 0;
top: 0;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
right: 0;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
bottom: 0;
}
.monaco-workbench > .editor > .content > .one-editor-silo.dragging {
z-index: 70;
box-sizing: content-box;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
border-left: 1px solid;
border-right: 1px solid;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
border-top: 1px solid;
border-bottom: 1px solid;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
border-left: 1px solid;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
border-top: 1px solid;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.draggedunder {
transition: left 200ms ease-out;
}
.monaco-workbench > .editor > .content.vertical-layout > .editor-three.draggedunder {
transition-property: right;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.draggedunder {
transition: top 200ms ease-out;
}
.monaco-workbench > .editor > .content.horizontal-layout > .editor-three.draggedunder {
transition-property: bottom;
}
.monaco-workbench > .editor > .content > .one-editor-silo > .container {
height: 100%;
}
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
height: calc(100% - 35px); /* Editor is below editor title */
}

View File

@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/** Letter press styling for empty editor */
.monaco-workbench > .part.editor.empty {
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: 260px 260px;
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry.editor-preview {
font-style: italic;
}

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .editor-statusbar-item > a:not(:first-child) {
margin-left: 5px;
}
.monaco-workbench .editor-statusbar-item > .editor-status-mode,
.monaco-workbench .editor-statusbar-item > .editor-status-encoding,
.monaco-workbench .editor-statusbar-item > .editor-status-eol,
.monaco-workbench .editor-statusbar-item > .editor-status-selection,
.monaco-workbench .editor-statusbar-item > .editor-status-indentation,
.monaco-workbench .editor-statusbar-item > .editor-status-metadata,
.monaco-workbench .editor-statusbar-item > .editor-status-tabfocusmode,
.monaco-workbench .editor-statusbar-item > .editor-status-screenreadermode {
padding: 0 5px 0 5px;
}
.monaco-workbench .editor-statusbar-item > .editor-status-metadata,
.monaco-workbench > .part.statusbar > .statusbar-item > .editor-statusbar-item > a.editor-status-screenreadermode {
cursor: default !important;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><path fill="#C5C5C5" d="M1 4h7l-3-3h3l4 4-4 4h-3l3-3h-7v-2z"/></svg>

After

Width:  |  Height:  |  Size: 189 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><path fill="#656565" d="M1 4h7l-3-3h3l4 4-4 4h-3l3-3h-7v-2z"/></svg>

After

Width:  |  Height:  |  Size: 189 B

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Title Label */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label {
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
position: relative;
padding-left: 20px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before {
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
}
/* Title Actions */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions {
display: flex;
flex: initial;
opacity: 0.5;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-actions {
opacity: 1;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
background: url('close-dirty.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
background: url('close-dirty-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
background: url('close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
background: url('close-inverse.svg') center center no-repeat;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><polygon fill="#C5C5C5" points="13,4 6,4 9,1 6,1 2,5 6,9 9,9 6,6 13,6"/></svg>

After

Width:  |  Height:  |  Size: 199 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><polygon fill="#656565" points="13,4 6,4 9,1 6,1 2,5 6,9 9,9 6,6 13,6"/></svg>

After

Width:  |  Height:  |  Size: 199 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#C5C5C5" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#656565" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#C5C5C5;}
</style>
<g id="outline">
</g>
<g id="icon_x5F_bg">
<path class="st0" d="M7.6,5H3c0,0-1,0-1,1c0,0.8,0,5.4,0,8c0,1,1,1,1,1s1.5,0,3,0s3,0,3,0s1,0,1-1c0-2.6,0-6.9,0-6.9L7.6,5z M9,14
H3V6h4v2h2V14z"/>
<path class="st0" d="M9.6,3H5c0,0-1,0-1,1h5v0.9L10.2,6H11v7c1,0,1-1,1-1V5.1L9.6,3z"/>
<path class="st0" d="M11.6,1H7c0,0-1,0-1,1h5v0.9L12.2,4H13v7c1,0,1-1,1-1V3.1L11.6,1z"/>
</g>
<g id="color_x5F_action">
</g>
<g id="icon_x5F_fg">
</g>
</svg>

After

Width:  |  Height:  |  Size: 822 B

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#656565;}
</style>
<g id="outline">
</g>
<g id="icon_x5F_bg">
<path class="st0" d="M7.6,5H3c0,0-1,0-1,1c0,0.8,0,5.4,0,8c0,1,1,1,1,1s1.5,0,3,0s3,0,3,0s1,0,1-1c0-2.6,0-6.9,0-6.9L7.6,5z M9,14
H3V6h4v2h2V14z"/>
<path class="st0" d="M9.6,3H5c0,0-1,0-1,1h5v0.9L10.2,6H11v7c1,0,1-1,1-1V5.1L9.6,3z"/>
<path class="st0" d="M11.6,1H7c0,0-1,0-1,1h5v0.9L12.2,4H13v7c1,0,1-1,1-1V3.1L11.6,1z"/>
</g>
<g id="color_x5F_action">
</g>
<g id="icon_x5F_fg">
</g>
</svg>

After

Width:  |  Height:  |  Size: 822 B

View File

@@ -0,0 +1,156 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Title Container */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.empty {
background: inherit !important; /* prevents some ugly flickering when opening first tab */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element {
flex: 1;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element .scrollbar {
z-index: 3; /* on top of tabs */
cursor: default;
}
/* Tabs Container */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container {
display: flex;
height: 35px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.scroll {
overflow: scroll !important;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container::-webkit-scrollbar {
display: none;
}
/* Tab */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
display: flex;
width: 120px;
min-width: fit-content;
white-space: nowrap;
cursor: pointer;
height: 35px;
box-sizing: border-box;
border: 1px solid transparent;
padding-left: 10px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-left {
flex-direction: row-reverse;
padding-left: 0;
padding-right: 10px;
}
/* Tab Label */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
margin-top: auto;
margin-bottom: auto;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label {
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before {
height: 16px; /* tweak the icon size of the editor labels when icons are enabled */
}
/* Tab Close */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab > .tab-close {
margin-top: auto;
margin-bottom: auto;
width: 28px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off > .tab-close {
display: none; /* hide the close action bar when we are configured to hide it */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */
opacity: 1;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
opacity: 0.5;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab > .tab-close .action-label {
opacity: 0;
display: block;
height: 16px;
width: 16px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
margin-right: 0.5em;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
background: url('close-dirty.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
background: url('close-dirty-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
background: url('close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
background: url('close-inverse.svg') center center no-repeat;
}
/* No Tab Close Button */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off {
padding-right: 12px;
transition: padding-right ease-in-out 100ms;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
background-repeat: no-repeat;
background-position-y: center;
background-position-x: calc(100% - 6px); /* to the right of the tab label */
padding-right: 28px; /* make room for dirty indication when we are running without close button */
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
background-image: url('close-dirty.svg');
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
background-image: url('close-dirty-inverse.svg');
}
/* Editor Actions */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions {
cursor: default;
flex: initial;
padding-left: 4px;
}

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* 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;
}

View File

@@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Editor Label */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
white-space: nowrap;
flex: 1;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
text-decoration: none;
font-size: 13px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label span,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label span {
cursor: pointer;
}
/* Title Actions */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
display: block;
height: 35px;
line-height: 35px;
min-width: 28px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
line-height: initial;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label .label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label .label {
display: none;
}
/* Drag Cursor */
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title.tabs .scrollbar .slider,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .title-label span {
cursor: -webkit-grab;
}
#monaco-workbench-editor-move-overlay,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title.tabs .scrollbar .slider,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .title-label span {
cursor: -webkit-grabbing;
}
/* Actions */
.monaco-workbench .close-editor-action {
background: url('close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .close-editor-action,
.hc-black .monaco-workbench .close-editor-action {
background: url('close-inverse.svg') center center no-repeat;
}
.monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-vertical.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action,
.hc-black .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-vertical-inverse.svg') center center no-repeat;
}
.monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-horizontal.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action,
.hc-black .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-horizontal-inverse.svg') center center no-repeat;
}
.monaco-workbench .show-group-editors-action {
background: url('stackview.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .show-group-editors-action,
.hc-black .monaco-workbench .show-group-editors-action {
background: url('stackview-inverse.svg') center center no-repeat;
}

View File

@@ -0,0 +1,138 @@
/*---------------------------------------------------------------------------------------------
* 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/notabstitle';
import errors = require('vs/base/common/errors');
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
import DOM = require('vs/base/browser/dom');
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { EditorLabel } from 'vs/workbench/browser/labels';
import { Verbosity } from 'vs/platform/editor/common/editor';
import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
export class NoTabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private editorLabel: EditorLabel;
public setContext(group: IEditorGroup): void {
super.setContext(group);
this.editorActionsToolbar.context = { group };
}
public create(parent: HTMLElement): void {
super.create(parent);
this.titleContainer = parent;
// Pin on double click
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
// Detect mouse click
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleClick(e)));
// Editor Label
this.editorLabel = this.instantiationService.createInstance(EditorLabel, this.titleContainer, void 0);
this.toUnbind.push(this.editorLabel);
this.toUnbind.push(DOM.addDisposableListener(this.editorLabel.labelElement, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleLabelClick(e)));
this.toUnbind.push(DOM.addDisposableListener(this.editorLabel.descriptionElement, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleLabelClick(e)));
// Right Actions Container
const actionsContainer = document.createElement('div');
DOM.addClass(actionsContainer, 'title-actions');
this.titleContainer.appendChild(actionsContainer);
// Editor actions toolbar
this.createEditorActionsToolBar(actionsContainer);
// Context Menu
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.CONTEXT_MENU, (e: Event) => this.onContextMenu({ group: this.context, editor: this.context.activeEditor }, e, this.titleContainer)));
}
private onTitleLabelClick(e: MouseEvent): void {
DOM.EventHelper.stop(e, false);
if (!this.dragged) {
setTimeout(() => this.quickOpenService.show()); // delayed to let the onTitleClick() come first which can cause a focus change which can close quick open
}
}
private onTitleDoubleClick(e: MouseEvent): void {
DOM.EventHelper.stop(e);
if (!this.context) {
return;
}
const group = this.context;
this.editorGroupService.pinEditor(group, group.activeEditor);
}
private onTitleClick(e: MouseEvent): void {
if (!this.context) {
return;
}
const group = this.context;
// Close editor on middle mouse click
if (e.button === 1 /* Middle Button */) {
this.closeEditorAction.run({ group, editor: group.activeEditor }).done(null, errors.onUnexpectedError);
}
// Focus editor group unless click on toolbar
else if (this.stacks.groups.length === 1 && !DOM.isAncestor((e.target || e.srcElement) as HTMLElement, this.editorActionsToolbar.getContainer().getHTMLElement())) {
this.editorGroupService.focusGroup(group);
}
}
protected doRefresh(): void {
const group = this.context;
const editor = group && group.activeEditor;
if (!editor) {
this.editorLabel.clear();
this.clearEditorActionsToolbar();
return; // return early if we are being closed
}
const isPinned = group.isPinned(group.activeEditor);
const isActive = this.stacks.isActive(group);
// Activity state
if (isActive) {
DOM.addClass(this.titleContainer, 'active');
} else {
DOM.removeClass(this.titleContainer, 'active');
}
// Dirty state
if (editor.isDirty()) {
DOM.addClass(this.titleContainer, 'dirty');
} else {
DOM.removeClass(this.titleContainer, 'dirty');
}
// Editor Label
const resource = toResource(editor, { supportSideBySide: true });
const name = editor.getName() || '';
const description = isActive ? (editor.getDescription() || '') : '';
let title = editor.getTitle(Verbosity.LONG);
if (description === title) {
title = ''; // dont repeat what is already shown
}
this.editorLabel.setLabel({ name, description, resource }, { title, italic: !isPinned, extraClasses: ['title-label'] });
if (isActive) {
this.editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND);
} else {
this.editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND);
}
// Update Editor Actions Toolbar
this.updateEditorActionsToolbar();
}
}

View File

@@ -0,0 +1,222 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 strings from 'vs/base/common/strings';
import * as DOM from 'vs/base/browser/dom';
import { Dimension, Builder } from 'vs/base/browser/builder';
import { Registry } from 'vs/platform/registry/common/platform';
import { IEditorRegistry, Extensions as EditorExtensions, EditorInput, EditorOptions, SideBySideEditorInput } from 'vs/workbench/common/editor';
import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorControl, Position, IEditor } from 'vs/platform/editor/common/editor';
import { VSash } from 'vs/base/browser/ui/sash/sash';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
export class SideBySideEditor extends BaseEditor {
public static ID: string = 'workbench.editor.sidebysideEditor';
private dimension: Dimension;
protected masterEditor: BaseEditor;
private masterEditorContainer: HTMLElement;
protected detailsEditor: BaseEditor;
private detailsEditorContainer: HTMLElement;
private sash: VSash;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
) {
super(SideBySideEditor.ID, telemetryService, themeService);
}
protected createEditor(parent: Builder): void {
const parentElement = parent.getHTMLElement();
DOM.addClass(parentElement, 'side-by-side-editor');
this.createSash(parentElement);
}
public setInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
const oldInput = <SideBySideEditorInput>this.input;
return super.setInput(newInput, options)
.then(() => this.updateInput(oldInput, newInput, options));
}
protected setEditorVisible(visible: boolean, position: Position): void {
if (this.masterEditor) {
this.masterEditor.setVisible(visible, position);
}
if (this.detailsEditor) {
this.detailsEditor.setVisible(visible, position);
}
super.setEditorVisible(visible, position);
}
public changePosition(position: Position): void {
if (this.masterEditor) {
this.masterEditor.changePosition(position);
}
if (this.detailsEditor) {
this.detailsEditor.changePosition(position);
}
super.changePosition(position);
}
public clearInput(): void {
if (this.masterEditor) {
this.masterEditor.clearInput();
}
if (this.detailsEditor) {
this.detailsEditor.clearInput();
}
this.disposeEditors();
super.clearInput();
}
public focus(): void {
if (this.masterEditor) {
this.masterEditor.focus();
}
}
public layout(dimension: Dimension): void {
this.dimension = dimension;
this.sash.setDimenesion(this.dimension);
}
public getControl(): IEditorControl {
if (this.masterEditor) {
return this.masterEditor.getControl();
}
return null;
}
public getMasterEditor(): IEditor {
return this.masterEditor;
}
public getDetailsEditor(): IEditor {
return this.detailsEditor;
}
private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
if (!newInput.matches(oldInput)) {
if (oldInput) {
this.disposeEditors();
}
this.createEditorContainers();
return this.setNewInput(newInput, options);
} else {
this.detailsEditor.setInput(newInput.details);
this.masterEditor.setInput(newInput.master, options);
return void 0;
}
}
private setNewInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
return TPromise.join([
this._createEditor(<EditorInput>newInput.details, this.detailsEditorContainer),
this._createEditor(<EditorInput>newInput.master, this.masterEditorContainer)
]).then(result => this.onEditorsCreated(result[0], result[1], newInput.details, newInput.master, options));
}
private _createEditor(editorInput: EditorInput, container: HTMLElement): TPromise<BaseEditor> {
const descriptor = Registry.as<IEditorRegistry>(EditorExtensions.Editors).getEditor(editorInput);
if (!descriptor) {
return TPromise.wrapError<BaseEditor>(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput)));
}
return this.instantiationService.createInstance(<EditorDescriptor>descriptor)
.then((editor: BaseEditor) => {
editor.create(new Builder(container));
editor.setVisible(this.isVisible(), this.position);
return editor;
});
}
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions): TPromise<void> {
this.detailsEditor = details;
this.masterEditor = master;
this.dolayout(this.sash.getVerticalSashLeft());
return TPromise.join([this.detailsEditor.setInput(detailsInput), this.masterEditor.setInput(masterInput, options)]).then(() => this.focus());
}
private createEditorContainers(): void {
const parentElement = this.getContainer().getHTMLElement();
this.detailsEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container'));
this.detailsEditorContainer.style.position = 'absolute';
this.masterEditorContainer = DOM.append(parentElement, DOM.$('.master-editor-container'));
this.masterEditorContainer.style.position = 'absolute';
this.updateStyles();
}
public updateStyles(): void {
super.updateStyles();
if (this.masterEditorContainer) {
this.masterEditorContainer.style.boxShadow = `-6px 0 5px -5px ${this.getColor(scrollbarShadow)}`;
}
}
private createSash(parentElement: HTMLElement): void {
this.sash = this._register(new VSash(parentElement, 220));
this._register(this.sash.onPositionChange(position => this.dolayout(position)));
}
private dolayout(splitPoint: number): void {
if (!this.detailsEditor || !this.masterEditor || !this.dimension) {
return;
}
const masterEditorWidth = this.dimension.width - splitPoint;
const detailsEditorWidth = this.dimension.width - masterEditorWidth;
this.detailsEditorContainer.style.width = `${detailsEditorWidth}px`;
this.detailsEditorContainer.style.height = `${this.dimension.height}px`;
this.detailsEditorContainer.style.left = '0px';
this.masterEditorContainer.style.width = `${masterEditorWidth}px`;
this.masterEditorContainer.style.height = `${this.dimension.height}px`;
this.masterEditorContainer.style.left = `${splitPoint}px`;
this.detailsEditor.layout(new Dimension(detailsEditorWidth, this.dimension.height));
this.masterEditor.layout(new Dimension(masterEditorWidth, this.dimension.height));
}
private disposeEditors(): void {
const parentContainer = this.getContainer().getHTMLElement();
if (this.detailsEditor) {
this.detailsEditor.dispose();
this.detailsEditor = null;
}
if (this.masterEditor) {
this.masterEditor.dispose();
this.masterEditor = null;
}
if (this.detailsEditorContainer) {
parentContainer.removeChild(this.detailsEditorContainer);
this.detailsEditorContainer = null;
}
if (this.masterEditorContainer) {
parentContainer.removeChild(this.masterEditorContainer);
this.masterEditorContainer = null;
}
}
public dispose(): void {
this.disposeEditors();
super.dispose();
}
}

View File

@@ -0,0 +1,788 @@
/*---------------------------------------------------------------------------------------------
* 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/tabstitle';
import nls = require('vs/nls');
import { TPromise } from 'vs/base/common/winjs.base';
import errors = require('vs/base/common/errors');
import DOM = require('vs/base/browser/dom');
import { isMacintosh } from 'vs/base/common/platform';
import { MIME_BINARY } from 'vs/base/common/mime';
import { shorten, getPathLabel } from 'vs/base/common/labels';
import { ActionRunner, IAction } from 'vs/base/common/actions';
import { Position, IEditorInput, Verbosity, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { EditorLabel } from 'vs/workbench/browser/labels';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IMessageService } from 'vs/platform/message/common/message';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { TitleControl, handleWorkspaceExternalDrop } from 'vs/workbench/browser/parts/editor/titleControl';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { extractResources } from 'vs/base/browser/dnd';
import { getOrSet } from 'vs/base/common/map';
import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } 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 } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
interface IEditorInputLabel {
name: string;
hasAmbiguousName?: boolean;
description?: string;
title?: string;
}
export class TabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private tabsContainer: HTMLElement;
private activeTab: HTMLElement;
private editorLabels: EditorLabel[];
private scrollbar: ScrollableElement;
private tabDisposeables: IDisposable[];
private blockRevealActiveTab: boolean;
constructor(
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService keybindingService: IKeybindingService,
@ITelemetryService telemetryService: ITelemetryService,
@IMessageService messageService: IMessageService,
@IMenuService menuService: IMenuService,
@IQuickOpenService quickOpenService: IQuickOpenService,
@IWindowService private windowService: IWindowService,
@IWindowsService private windowsService: IWindowsService,
@IThemeService themeService: IThemeService,
@IFileService private fileService: IFileService,
@IWorkspacesService private workspacesService: IWorkspacesService
) {
super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService, themeService);
this.tabDisposeables = [];
this.editorLabels = [];
}
protected initActions(services: IInstantiationService): void {
super.initActions(this.createScopedInstantiationService());
}
private createScopedInstantiationService(): IInstantiationService {
const stacks = this.editorGroupService.getStacksModel();
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
// We create a scoped instantiation service to override the behaviour when closing an inactive editor
// Specifically we want to move focus back to the editor when an inactive editor is closed from anywhere
// in the tabs title control (e.g. mouse middle click, context menu on tab). This is only needed for
// the inactive editors because closing the active one will always cause a tab switch that sets focus.
// We also want to block the tabs container to reveal the currently active tab because that makes it very
// hard to close multiple inactive tabs next to each other.
delegatingEditorService.setEditorCloseHandler((position, editor) => {
const group = stacks.groupAt(position);
if (group && stacks.isActive(group) && !group.isActive(editor)) {
this.editorGroupService.focusGroup(group);
}
this.blockRevealActiveTab = true;
return TPromise.as(void 0);
});
return this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
}
public setContext(group: IEditorGroup): void {
super.setContext(group);
this.editorActionsToolbar.context = { group };
}
public create(parent: HTMLElement): void {
super.create(parent);
this.titleContainer = parent;
// Tabs Container
this.tabsContainer = document.createElement('div');
this.tabsContainer.setAttribute('role', 'tablist');
DOM.addClass(this.tabsContainer, 'tabs-container');
// Forward scrolling inside the container to our custom scrollbar
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.SCROLL, e => {
if (DOM.hasClass(this.tabsContainer, 'scroll')) {
this.scrollbar.setScrollPosition({
scrollLeft: this.tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar
});
}
}));
// New file when double clicking on tabs container (but not tabs)
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DBLCLICK, e => {
const target = e.target;
if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
DOM.EventHelper.stop(e);
const group = this.context;
if (group) {
this.editorService.openEditor({ options: { pinned: true, index: group.count /* always at the end */ } } as IUntitledResourceInput).done(null, errors.onUnexpectedError); // untitled are always pinned
}
}
}));
// Custom Scrollbar
this.scrollbar = new ScrollableElement(this.tabsContainer, {
horizontal: ScrollbarVisibility.Auto,
vertical: ScrollbarVisibility.Hidden,
scrollYToX: true,
useShadows: false,
horizontalScrollbarSize: 3
});
this.scrollbar.onScroll(e => {
this.tabsContainer.scrollLeft = e.scrollLeft;
});
this.titleContainer.appendChild(this.scrollbar.getDomNode());
// Drag over
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_OVER, (e: DragEvent) => {
// update the dropEffect, otherwise it would look like a "move" operation. but only if we are
// not dragging a tab actually because there we support both moving as well as copying
if (!TabsTitleControl.getDraggedEditor()) {
e.dataTransfer.dropEffect = 'copy';
}
DOM.addClass(this.tabsContainer, 'scroll'); // enable support to scroll while dragging
const target = e.target;
if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
this.updateDropFeedback(this.tabsContainer, true);
}
}));
// Drag leave
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
this.updateDropFeedback(this.tabsContainer, false);
DOM.removeClass(this.tabsContainer, 'scroll');
}));
// Drag end
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_END, (e: DragEvent) => {
this.updateDropFeedback(this.tabsContainer, false);
DOM.removeClass(this.tabsContainer, 'scroll');
}));
// Drop onto tabs container
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DROP, (e: DragEvent) => {
this.updateDropFeedback(this.tabsContainer, false);
DOM.removeClass(this.tabsContainer, 'scroll');
const target = e.target;
if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
const group = this.context;
if (group) {
const targetPosition = this.stacks.positionOfGroup(group);
const targetIndex = group.count;
this.onDrop(e, group, targetPosition, targetIndex);
}
}
}));
// Editor Actions Container
const editorActionsContainer = document.createElement('div');
DOM.addClass(editorActionsContainer, 'editor-actions');
this.titleContainer.appendChild(editorActionsContainer);
// Editor Actions Toolbar
this.createEditorActionsToolBar(editorActionsContainer);
}
private updateDropFeedback(element: HTMLElement, isDND: boolean, index?: number): void {
const isTab = (typeof index === 'number');
const isActiveTab = isTab && this.context && this.context.isActive(this.context.getEditor(index));
// Background
const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : null;
element.style.backgroundColor = isDND ? this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) : noDNDBackgroundColor;
// Outline
const activeContrastBorderColor = this.getColor(activeContrastBorder);
if (activeContrastBorderColor && isDND) {
element.style.outlineWidth = '2px';
element.style.outlineStyle = 'dashed';
element.style.outlineColor = activeContrastBorderColor;
element.style.outlineOffset = isTab ? '-5px' : '-3px';
} else {
element.style.outlineWidth = null;
element.style.outlineStyle = null;
element.style.outlineColor = activeContrastBorderColor;
element.style.outlineOffset = null;
}
}
public allowDragging(element: HTMLElement): boolean {
return (element.className === 'tabs-container');
}
protected doUpdate(): void {
if (!this.context) {
return;
}
const group = this.context;
// Tabs container activity state
const isGroupActive = this.stacks.isActive(group);
if (isGroupActive) {
DOM.addClass(this.titleContainer, 'active');
} else {
DOM.removeClass(this.titleContainer, 'active');
}
// Compute labels and protect against duplicates
const editorsOfGroup = this.context.getEditors();
const labels = this.getUniqueTabLabels(editorsOfGroup);
// Tab label and styles
editorsOfGroup.forEach((editor, index) => {
const tabContainer = this.tabsContainer.children[index];
if (tabContainer instanceof HTMLElement) {
const isPinned = group.isPinned(index);
const isTabActive = group.isActive(editor);
const isDirty = editor.isDirty();
const label = labels[index];
const name = label.name;
const description = label.hasAmbiguousName && label.description ? label.description : '';
const title = label.title || '';
// Container
tabContainer.setAttribute('aria-label', `${name}, tab`);
tabContainer.title = title;
tabContainer.style.borderLeftColor = (index !== 0) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
tabContainer.style.borderRightColor = (index === editorsOfGroup.length - 1) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
tabContainer.style.outlineColor = this.getColor(activeContrastBorder);
const tabOptions = this.editorGroupService.getTabOptions();
['off', 'left'].forEach(option => {
const domAction = tabOptions.tabCloseButton === option ? DOM.addClass : DOM.removeClass;
domAction(tabContainer, `close-button-${option}`);
});
// Label
const tabLabel = this.editorLabels[index];
tabLabel.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { extraClasses: ['tab-label'], italic: !isPinned });
// Active state
if (isTabActive) {
DOM.addClass(tabContainer, 'active');
tabContainer.setAttribute('aria-selected', 'true');
tabContainer.style.backgroundColor = this.getColor(TAB_ACTIVE_BACKGROUND);
tabLabel.element.style.color = this.getColor(isGroupActive ? TAB_ACTIVE_FOREGROUND : TAB_UNFOCUSED_ACTIVE_FOREGROUND);
// Use boxShadow for the active tab border because if we also have a editor group header
// color, the two colors would collide and the tab border never shows up.
// see https://github.com/Microsoft/vscode/issues/33111
const activeTabBorderColor = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER : TAB_UNFOCUSED_ACTIVE_BORDER);
if (activeTabBorderColor) {
tabContainer.style.boxShadow = `${activeTabBorderColor} 0 -1px inset`;
} else {
tabContainer.style.boxShadow = null;
}
this.activeTab = tabContainer;
} else {
DOM.removeClass(tabContainer, 'active');
tabContainer.setAttribute('aria-selected', 'false');
tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND);
tabLabel.element.style.color = this.getColor(isGroupActive ? TAB_INACTIVE_FOREGROUND : TAB_UNFOCUSED_INACTIVE_FOREGROUND);
tabContainer.style.boxShadow = null;
}
// Dirty State
if (isDirty) {
DOM.addClass(tabContainer, 'dirty');
} else {
DOM.removeClass(tabContainer, 'dirty');
}
}
});
// Update Editor Actions Toolbar
this.updateEditorActionsToolbar();
// Ensure the active tab is always revealed
this.layout();
}
private getUniqueTabLabels(editors: IEditorInput[]): IEditorInputLabel[] {
const labels: IEditorInputLabel[] = [];
const mapLabelToDuplicates = new Map<string, IEditorInputLabel[]>();
const mapLabelAndDescriptionToDuplicates = new Map<string, IEditorInputLabel[]>();
// Build labels and descriptions for each editor
editors.forEach(editor => {
const name = editor.getName();
let description = editor.getDescription();
if (mapLabelAndDescriptionToDuplicates.has(`${name}${description}`)) {
description = editor.getDescription(true); // try verbose description if name+description already exists
}
const item: IEditorInputLabel = {
name,
description,
title: editor.getTitle(Verbosity.LONG)
};
labels.push(item);
getOrSet(mapLabelToDuplicates, item.name, []).push(item);
if (typeof description === 'string') {
getOrSet(mapLabelAndDescriptionToDuplicates, `${item.name}${item.description}`, []).push(item);
}
});
// Mark duplicates and shorten their descriptions
mapLabelToDuplicates.forEach(duplicates => {
if (duplicates.length > 1) {
duplicates = duplicates.filter(d => {
// we could have items with equal label and description. in that case it does not make much
// sense to produce a shortened version of the label, so we ignore those kind of items
return typeof d.description === 'string' && mapLabelAndDescriptionToDuplicates.get(`${d.name}${d.description}`).length === 1;
});
if (duplicates.length > 1) {
const shortenedDescriptions = shorten(duplicates.map(duplicate => duplicate.description));
duplicates.forEach((duplicate, i) => {
duplicate.description = shortenedDescriptions[i];
duplicate.hasAmbiguousName = true;
});
}
}
});
return labels;
}
protected doRefresh(): void {
const group = this.context;
const editor = group && group.activeEditor;
if (!editor) {
this.clearTabs();
this.clearEditorActionsToolbar();
return; // return early if we are being closed
}
// Handle Tabs
this.handleTabs(group.count);
DOM.removeClass(this.titleContainer, 'empty');
// Update Tabs
this.doUpdate();
}
private clearTabs(): void {
DOM.clearNode(this.tabsContainer);
this.tabDisposeables = dispose(this.tabDisposeables);
this.editorLabels = [];
DOM.addClass(this.titleContainer, 'empty');
}
private handleTabs(tabsNeeded: number): void {
const tabs = this.tabsContainer.children;
const tabsCount = tabs.length;
// Nothing to do if count did not change
if (tabsCount === tabsNeeded) {
return;
}
// We need more tabs: create new ones
if (tabsCount < tabsNeeded) {
for (let i = tabsCount; i < tabsNeeded; i++) {
this.tabsContainer.appendChild(this.createTab(i));
}
}
// We need less tabs: delete the ones we do not need
else {
for (let i = 0; i < tabsCount - tabsNeeded; i++) {
(this.tabsContainer.lastChild as HTMLElement).remove();
this.editorLabels.pop();
this.tabDisposeables.pop().dispose();
}
}
}
private createTab(index: number): HTMLElement {
// Tab Container
const tabContainer = document.createElement('div');
tabContainer.draggable = true;
tabContainer.tabIndex = 0;
tabContainer.setAttribute('role', 'presentation'); // cannot use role "tab" here due to https://github.com/Microsoft/vscode/issues/8659
DOM.addClass(tabContainer, 'tab');
// Tab Editor Label
const editorLabel = this.instantiationService.createInstance(EditorLabel, tabContainer, void 0);
this.editorLabels.push(editorLabel);
// Tab Close
const tabCloseContainer = document.createElement('div');
DOM.addClass(tabCloseContainer, 'tab-close');
tabContainer.appendChild(tabCloseContainer);
const bar = new ActionBar(tabCloseContainer, { ariaLabel: nls.localize('araLabelTabActions', "Tab actions"), actionRunner: new TabActionRunner(() => this.context, index) });
bar.push(this.closeEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeEditorAction) });
// Eventing
const disposable = this.hookTabListeners(tabContainer, index);
this.tabDisposeables.push(combinedDisposable([disposable, bar, editorLabel]));
return tabContainer;
}
public layout(): void {
if (!this.activeTab) {
return;
}
const visibleContainerWidth = this.tabsContainer.offsetWidth;
const totalContainerWidth = this.tabsContainer.scrollWidth;
// Update scrollbar
this.scrollbar.setScrollDimensions({
width: visibleContainerWidth,
scrollWidth: totalContainerWidth
});
// Return now if we are blocked to reveal the active tab and clear flag
if (this.blockRevealActiveTab) {
this.blockRevealActiveTab = false;
return;
}
// Reveal the active one
const containerScrollPosX = this.tabsContainer.scrollLeft;
const activeTabPosX = this.activeTab.offsetLeft;
const activeTabWidth = this.activeTab.offsetWidth;
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({
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({
scrollLeft: this.activeTab.offsetLeft
});
}
}
private hookTabListeners(tab: HTMLElement, index: number): IDisposable {
const disposables: IDisposable[] = [];
// Open on Click
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
tab.blur();
const { editor, position } = this.toTabContext(index);
if (e.button === 0 /* Left Button */ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
setTimeout(() => this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError)); // timeout to keep focus in editor after mouse up
}
}));
// Close on mouse middle click
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_UP, (e: MouseEvent) => {
DOM.EventHelper.stop(e);
tab.blur();
if (e.button === 1 /* Middle Button*/ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
this.closeEditorAction.run(this.toTabContext(index)).done(null, errors.onUnexpectedError);
}
}));
// Context menu on Shift+F10
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
if (event.shiftKey && event.keyCode === KeyCode.F10) {
DOM.EventHelper.stop(e);
const { group, editor } = this.toTabContext(index);
this.onContextMenu({ group, editor }, e, tab);
}
}));
// Keyboard accessibility
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
let handled = false;
const { group, position, editor } = this.toTabContext(index);
// Run action on Enter/Space
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
handled = true;
this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError);
}
// Navigate in editors
else if ([KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.Home, KeyCode.End].some(kb => event.equals(kb))) {
let targetIndex: number;
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.UpArrow)) {
targetIndex = index - 1;
} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.DownArrow)) {
targetIndex = index + 1;
} else if (event.equals(KeyCode.Home)) {
targetIndex = 0;
} else {
targetIndex = group.count - 1;
}
const target = group.getEditor(targetIndex);
if (target) {
handled = true;
this.editorService.openEditor(target, { preserveFocus: true }, position).done(null, errors.onUnexpectedError);
(<HTMLElement>this.tabsContainer.childNodes[targetIndex]).focus();
}
}
if (handled) {
DOM.EventHelper.stop(e, true);
}
// moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar
this.scrollbar.setScrollPosition({
scrollLeft: this.tabsContainer.scrollLeft
});
}));
// Pin on double click
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DBLCLICK, (e: MouseEvent) => {
DOM.EventHelper.stop(e);
const { group, editor } = this.toTabContext(index);
this.editorGroupService.pinEditor(group, editor);
}));
// Context menu
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.CONTEXT_MENU, (e: Event) => {
DOM.EventHelper.stop(e, true);
const { group, editor } = this.toTabContext(index);
this.onContextMenu({ group, editor }, e, tab);
}, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */));
// Drag start
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_START, (e: DragEvent) => {
const { group, editor } = this.toTabContext(index);
this.onEditorDragStart({ editor, group });
e.dataTransfer.effectAllowed = 'copyMove';
// Insert transfer accordingly
const fileResource = toResource(editor, { supportSideBySide: true, filter: 'file' });
if (fileResource) {
const resource = fileResource.toString();
e.dataTransfer.setData('URL', resource); // enables cross window DND of tabs
e.dataTransfer.setData('DownloadURL', [MIME_BINARY, editor.getName(), resource].join(':')); // enables support to drag a tab as file to desktop
e.dataTransfer.setData('text/plain', getPathLabel(resource)); // enables dropping tab resource path into text controls
}
}));
// We need to keep track of DRAG_ENTER and DRAG_LEAVE events because a tab is not just a div without children,
// it contains a label and a close button. HTML gives us DRAG_ENTER and DRAG_LEAVE events when hovering over
// these children and this can cause flicker of the drop feedback. The workaround is to count the events and only
// remove the drop feedback when the counter is 0 (see https://github.com/Microsoft/vscode/issues/14470)
let counter = 0;
// Drag over
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
counter++;
this.updateDropFeedback(tab, true, index);
}));
// Drag leave
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
counter--;
if (counter === 0) {
this.updateDropFeedback(tab, false, index);
}
}));
// Drag end
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_END, (e: DragEvent) => {
counter = 0;
this.updateDropFeedback(tab, false, index);
this.onEditorDragEnd();
}));
// Drop
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DROP, (e: DragEvent) => {
counter = 0;
this.updateDropFeedback(tab, false, index);
const { group, position } = this.toTabContext(index);
this.onDrop(e, group, position, index);
}));
return combinedDisposable(disposables);
}
private isTabActionBar(element: HTMLElement): boolean {
return !!DOM.findParentWithClass(element, 'monaco-action-bar', 'tab');
}
private toTabContext(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
const group = this.context;
const position = this.stacks.positionOfGroup(group);
const editor = group.getEditor(index);
return { group, position, editor };
}
private onDrop(e: DragEvent, group: IEditorGroup, targetPosition: Position, targetIndex: number): void {
this.updateDropFeedback(this.tabsContainer, false);
DOM.removeClass(this.tabsContainer, 'scroll');
// Local DND
const draggedEditor = TabsTitleControl.getDraggedEditor();
if (draggedEditor) {
DOM.EventHelper.stop(e, true);
// Move editor to target position and index
if (this.isMoveOperation(e, draggedEditor.group, group)) {
this.editorGroupService.moveEditor(draggedEditor.editor, draggedEditor.group, group, { index: targetIndex });
}
// Copy: just open editor at target index
else {
this.editorService.openEditor(draggedEditor.editor, { pinned: true, index: targetIndex }, targetPosition).done(null, errors.onUnexpectedError);
}
this.onEditorDragEnd();
}
// External DND
else {
this.handleExternalDrop(e, targetPosition, targetIndex);
}
}
private handleExternalDrop(e: DragEvent, targetPosition: Position, targetIndex: number): void {
const droppedResources = extractResources(e).filter(r => r.resource.scheme === 'file' || r.resource.scheme === 'untitled');
if (droppedResources.length) {
DOM.EventHelper.stop(e, true);
handleWorkspaceExternalDrop(droppedResources, this.fileService, this.messageService, this.windowsService, this.windowService, this.workspacesService).then(handled => {
if (handled) {
return;
}
// Add external ones to recently open list
const externalResources = droppedResources.filter(d => d.isExternal).map(d => d.resource);
if (externalResources.length) {
this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath));
}
// Open in Editor
this.windowService.focusWindow()
.then(() => this.editorService.openEditors(droppedResources.map(d => {
return {
input: { resource: d.resource, options: { pinned: true, index: targetIndex } },
position: targetPosition
};
}))).then(() => {
this.editorGroupService.focusGroup(targetPosition);
}).done(null, errors.onUnexpectedError);
});
}
}
private isMoveOperation(e: DragEvent, source: IEditorGroup, target: IEditorGroup) {
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
return !isCopy || source.id === target.id;
}
}
class TabActionRunner extends ActionRunner {
constructor(private group: () => IEditorGroup, private index: number) {
super();
}
public run(action: IAction, context?: any): TPromise<void> {
const group = this.group();
if (!group) {
return TPromise.as(void 0);
}
return super.run(action, { group, editor: group.getEditor(this.index) });
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
// Styling with Outline color (e.g. high contrast theme)
const activeContrastBorderColor = theme.getColor(activeContrastBorder);
if (activeContrastBorderColor) {
collector.addRule(`
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover {
outline: 1px solid;
outline-offset: -5px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover {
outline: 1px dashed;
outline-offset: -5px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active > .tab-close .action-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover > .tab-close .action-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty > .tab-close .action-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover > .tab-close .action-label {
opacity: 1 !important;
}
`);
}
});

View File

@@ -0,0 +1,377 @@
/*---------------------------------------------------------------------------------------------
* 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/textdiffeditor';
import { TPromise } from 'vs/base/common/winjs.base';
import nls = require('vs/nls');
import objects = require('vs/base/common/objects');
import { Builder } from 'vs/base/browser/builder';
import { Action, IAction } from 'vs/base/common/actions';
import { onUnexpectedError } from 'vs/base/common/errors';
import types = require('vs/base/common/types');
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IFileEditorInput } from 'vs/workbench/common/editor';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService';
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorInput } from 'vs/platform/editor/common/editor';
import { ScrollType } from 'vs/editor/common/editorCommon';
/**
* The text editor that leverages the diff text editor for the editing experience.
*/
export class TextDiffEditor extends BaseTextEditor {
public static ID = TEXT_DIFF_EDITOR_ID;
private diffNavigator: DiffNavigator;
private nextDiffAction: NavigateAction;
private previousDiffAction: NavigateAction;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IThemeService themeService: IThemeService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IModeService modeService: IModeService,
@ITextFileService textFileService: ITextFileService
) {
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, modeService, textFileService, editorGroupService);
}
public getTitle(): string {
if (this.input) {
return this.input.getName();
}
return nls.localize('textDiffEditor', "Text Diff Editor");
}
public createEditorControl(parent: Builder, configuration: IEditorOptions): IDiffEditor {
// Actions
this.nextDiffAction = new NavigateAction(this, true);
this.previousDiffAction = new NavigateAction(this, false);
// Support navigation within the diff editor by overriding the editor service within
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
delegatingEditorService.setEditorOpenHandler((input: EditorInput, options?: EditorOptions, arg3?: any) => {
// Check if arg4 is a position argument that differs from this editors position
if (types.isUndefinedOrNull(arg3) || arg3 === false || arg3 === this.position) {
const activeDiffInput = <DiffEditorInput>this.input;
if (input && options && activeDiffInput) {
// Input matches modified side of the diff editor: perform the action on modified side
if (input.matches(activeDiffInput.modifiedInput)) {
return this.setInput(this.input, options).then(() => this);
}
// Input matches original side of the diff editor: perform the action on original side
else if (input.matches(activeDiffInput.originalInput)) {
const originalEditor = this.getControl().getOriginalEditor();
if (options instanceof TextEditorOptions) {
(<TextEditorOptions>options).apply(originalEditor, ScrollType.Smooth);
return TPromise.as(this);
}
}
}
}
return TPromise.as(null);
});
// Create a special child of instantiator that will delegate all calls to openEditor() to the same diff editor if the input matches with the modified one
const diffEditorInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
return diffEditorInstantiator.createInstance(DiffEditorWidget, parent.getHTMLElement(), configuration);
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
const oldInput = this.input;
super.setInput(input, options);
// Detect options
const forceOpen = options && options.forceOpen;
// Same Input
if (!forceOpen && input.matches(oldInput)) {
// TextOptions (avoiding instanceof here for a reason, do not change!)
const textOptions = <TextEditorOptions>options;
if (textOptions && types.isFunction(textOptions.apply)) {
textOptions.apply(<IDiffEditor>this.getControl(), ScrollType.Smooth);
}
return TPromise.as<void>(null);
}
// Dispose previous diff navigator
if (this.diffNavigator) {
this.diffNavigator.dispose();
}
// Different Input (Reload)
return input.resolve(true).then(resolvedModel => {
// Assert Model Instance
if (!(resolvedModel instanceof TextDiffEditorModel) && this.openAsBinary(input, options)) {
return null;
}
// Assert that the current input is still the one we expect. This prevents a race condition when loading a diff takes long and another input was set meanwhile
if (!this.input || this.input !== input) {
return null;
}
// Editor
const diffEditor = <IDiffEditor>this.getControl();
diffEditor.setModel((<TextDiffEditorModel>resolvedModel).textDiffEditorModel);
// Handle TextOptions
let alwaysRevealFirst = true;
if (options && types.isFunction((<TextEditorOptions>options).apply)) {
const hadOptions = (<TextEditorOptions>options).apply(<IDiffEditor>diffEditor, ScrollType.Immediate);
if (hadOptions) {
alwaysRevealFirst = false; // Do not reveal if we are instructed to open specific line/col
}
}
// Listen on diff updated changes to reveal the first change
this.diffNavigator = new DiffNavigator(diffEditor, {
alwaysRevealFirst
});
this.diffNavigator.addListener(DiffNavigator.Events.UPDATED, () => {
this.nextDiffAction.updateEnablement();
this.previousDiffAction.updateEnablement();
});
}, error => {
// In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff.
if (this.isFileBinaryError(error) && this.openAsBinary(input, options)) {
return null;
}
// Otherwise make sure the error bubbles up
return TPromise.wrapError(error);
});
}
private openAsBinary(input: EditorInput, options: EditorOptions): boolean {
if (input instanceof DiffEditorInput) {
const originalInput = input.originalInput;
const modifiedInput = input.modifiedInput;
const binaryDiffInput = new DiffEditorInput(input.getName(), input.getDescription(), originalInput, modifiedInput, true);
// Forward binary flag to input if supported
if (types.isFunction(((originalInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary)) {
((originalInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary();
}
if (types.isFunction(((modifiedInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary)) {
((modifiedInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary();
}
this.editorService.openEditor(binaryDiffInput, options, this.position).done(null, onUnexpectedError);
return true;
}
return false;
}
protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions {
const editorConfiguration = super.computeConfiguration(configuration);
// Handle diff editor specially by merging in diffEditor configuration
if (types.isObject(configuration.diffEditor)) {
objects.mixin(editorConfiguration, configuration.diffEditor);
}
return editorConfiguration;
}
protected getConfigurationOverrides(): IEditorOptions {
const options: IDiffEditorOptions = super.getConfigurationOverrides();
options.readOnly = this.isReadOnly();
return options;
}
protected getAriaLabel(): string {
let ariaLabel: string;
const inputName = this.input && this.input.getName();
if (this.isReadOnly()) {
ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text compare editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text compare editor.");
} else {
ariaLabel = inputName ? nls.localize('editableEditorWithInputAriaLabel', "{0}. Text file compare editor.", inputName) : nls.localize('editableEditorAriaLabel', "Text file compare editor.");
}
return ariaLabel;
}
private isReadOnly(): boolean {
const input = this.input;
if (input instanceof DiffEditorInput) {
const modifiedInput = input.modifiedInput;
return modifiedInput instanceof ResourceEditorInput;
}
return false;
}
private isFileBinaryError(error: Error[]): boolean;
private isFileBinaryError(error: Error): boolean;
private isFileBinaryError(error: any): boolean {
if (types.isArray(error)) {
const errors = <Error[]>error;
return errors.some(e => this.isFileBinaryError(e));
}
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY;
}
public clearInput(): void {
// Dispose previous diff navigator
if (this.diffNavigator) {
this.diffNavigator.dispose();
}
// Clear Model
this.getControl().setModel(null);
// Pass to super
super.clearInput();
}
public getDiffNavigator(): DiffNavigator {
return this.diffNavigator;
}
public getActions(): IAction[] {
return [
this.previousDiffAction,
this.nextDiffAction
];
}
public getSecondaryActions(): IAction[] {
const actions = super.getSecondaryActions();
// Action to toggle editor mode from inline to side by side
const toggleEditorModeAction = new ToggleEditorModeAction(this);
toggleEditorModeAction.order = 50; // Closer to the end
actions.push(...[
toggleEditorModeAction
]);
return actions;
}
public getControl(): IDiffEditor {
return super.getControl() as IDiffEditor;
}
public dispose(): void {
// Dispose previous diff navigator
if (this.diffNavigator) {
this.diffNavigator.dispose();
}
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;
}
public run(): TPromise<any> {
if (this.next) {
this.editor.getDiffNavigator().next();
} else {
this.editor.getDiffNavigator().previous();
}
return null;
}
public updateEnablement(): void {
this.enabled = this.editor.getDiffNavigator().canNavigate();
}
}
class ToggleEditorModeAction extends Action {
private static ID = 'toggle.diff.editorMode';
private static INLINE_LABEL = nls.localize('inlineDiffLabel', "Switch to Inline View");
private static SIDEBYSIDE_LABEL = nls.localize('sideBySideDiffLabel', "Switch to Side by Side View");
constructor(private editor: TextDiffEditor) {
super(ToggleEditorModeAction.ID);
}
public get label(): string {
return ToggleEditorModeAction.isInlineMode(this.editor) ? ToggleEditorModeAction.SIDEBYSIDE_LABEL : ToggleEditorModeAction.INLINE_LABEL;
}
public run(): TPromise<boolean> {
const inlineModeActive = ToggleEditorModeAction.isInlineMode(this.editor);
const control = this.editor.getControl();
control.updateOptions(<IDiffEditorOptions>{
renderSideBySide: inlineModeActive
});
return TPromise.as(true);
}
private static isInlineMode(editor: TextDiffEditor): boolean {
const control = editor.getControl();
return control && !control.renderSideBySide;
}
}

View File

@@ -0,0 +1,334 @@
/*---------------------------------------------------------------------------------------------
* 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 nls = require('vs/nls');
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { Dimension, Builder } from 'vs/base/browser/builder';
import objects = require('vs/base/common/objects');
import types = require('vs/base/common/types');
import errors = require('vs/base/common/errors');
import DOM = require('vs/base/browser/dom');
import { CodeEditor } from 'vs/editor/browser/codeEditor';
import { EditorInput, EditorOptions, toResource } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorViewState, IEditor, isCommonCodeEditor, isCommonDiffEditor } from 'vs/editor/common/editorCommon';
import { Position } from 'vs/platform/editor/common/editor';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Scope } from 'vs/workbench/common/memento';
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState';
interface ITextEditorViewState {
0?: IEditorViewState;
1?: IEditorViewState;
2?: IEditorViewState;
}
export interface IEditorConfiguration {
editor: object;
diffEditor: object;
}
/**
* The base class of editors that leverage the text editor for the editing experience. This class is only intended to
* be subclassed and not instantiated.
*/
export abstract class BaseTextEditor extends BaseEditor {
private editorControl: IEditor;
private _editorContainer: Builder;
private hasPendingConfigurationChange: boolean;
private lastAppliedEditorOptions: IEditorOptions;
constructor(
id: string,
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IStorageService private storageService: IStorageService,
@ITextResourceConfigurationService private _configurationService: ITextResourceConfigurationService,
@IThemeService protected themeService: IThemeService,
@IModeService private modeService: IModeService,
@ITextFileService private _textFileService: ITextFileService,
@IEditorGroupService protected editorGroupService: IEditorGroupService
) {
super(id, telemetryService, themeService);
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getConfiguration<IEditorConfiguration>(this.getResource()))));
}
protected get instantiationService(): IInstantiationService {
return this._instantiationService;
}
protected get configurationService(): ITextResourceConfigurationService {
return this._configurationService;
}
protected get textFileService(): ITextFileService {
return this._textFileService;
}
private handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void {
if (this.isVisible()) {
this.updateEditorConfiguration(configuration);
} else {
this.hasPendingConfigurationChange = true;
}
}
private consumePendingConfigurationChangeEvent(): void {
if (this.hasPendingConfigurationChange) {
this.updateEditorConfiguration();
this.hasPendingConfigurationChange = false;
}
}
protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions {
// Specific editor options always overwrite user configuration
const editorConfiguration: IEditorOptions = types.isObject(configuration.editor) ? objects.clone(configuration.editor) : Object.create(null);
objects.assign(editorConfiguration, this.getConfigurationOverrides());
// ARIA label
editorConfiguration.ariaLabel = this.computeAriaLabel();
return editorConfiguration;
}
private computeAriaLabel(): string {
let ariaLabel = this.getAriaLabel();
// Apply group information to help identify in which group we are
if (ariaLabel && typeof this.position === 'number') {
ariaLabel = nls.localize('editorLabelWithGroup', "{0}, Group {1}.", ariaLabel, this.position + 1);
}
return ariaLabel;
}
protected getConfigurationOverrides(): IEditorOptions {
const overrides = {};
objects.assign(overrides, {
overviewRulerLanes: 3,
lineNumbersMinChars: 3,
fixedOverflowWidgets: true
});
return overrides;
}
protected createEditor(parent: Builder): void {
// Editor for Text
this._editorContainer = parent;
this.editorControl = this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getConfiguration<IEditorConfiguration>(this.getResource())));
// Model & Language changes
const codeEditor = getCodeEditor(this);
if (codeEditor) {
this.toUnbind.push(codeEditor.onDidChangeModelLanguage(e => this.updateEditorConfiguration()));
this.toUnbind.push(codeEditor.onDidChangeModel(e => this.updateEditorConfiguration()));
}
// Application & Editor focus change to respect auto save settings
if (isCommonCodeEditor(this.editorControl)) {
this.toUnbind.push(this.editorControl.onDidBlurEditor(() => this.onEditorFocusLost()));
} else if (isCommonDiffEditor(this.editorControl)) {
this.toUnbind.push(this.editorControl.getOriginalEditor().onDidBlurEditor(() => this.onEditorFocusLost()));
this.toUnbind.push(this.editorControl.getModifiedEditor().onDidBlurEditor(() => this.onEditorFocusLost()));
}
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusLost()));
this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost()));
}
private onEditorFocusLost(): void {
this.maybeTriggerSaveAll(SaveReason.FOCUS_CHANGE);
}
private onWindowFocusLost(): void {
this.maybeTriggerSaveAll(SaveReason.WINDOW_CHANGE);
}
private maybeTriggerSaveAll(reason: SaveReason): void {
const mode = this.textFileService.getAutoSaveMode();
// Determine if we need to save all. In case of a window focus change we also save if auto save mode
// is configured to be ON_FOCUS_CHANGE (editor focus change)
if (
(reason === SaveReason.WINDOW_CHANGE && (mode === AutoSaveMode.ON_FOCUS_CHANGE || mode === AutoSaveMode.ON_WINDOW_CHANGE)) ||
(reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE)
) {
if (this.textFileService.isDirty()) {
this.textFileService.saveAll(void 0, { reason }).done(null, errors.onUnexpectedError);
}
}
}
/**
* This method creates and returns the text editor control to be used. Subclasses can override to
* provide their own editor control that should be used (e.g. a DiffEditor).
*
* The passed in configuration object should be passed to the editor control when creating it.
*/
protected createEditorControl(parent: Builder, configuration: IEditorOptions): IEditor {
// Use a getter for the instantiation service since some subclasses might use scoped instantiation services
return this.instantiationService.createInstance(CodeEditor, parent.getHTMLElement(), configuration);
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
return super.setInput(input, options).then(() => {
// Update editor options after having set the input. We do this because there can be
// editor input specific options (e.g. an ARIA label depending on the input showing)
this.updateEditorConfiguration();
});
}
public changePosition(position: Position): void {
super.changePosition(position);
// Make sure to update ARIA label if the position of this editor changed
if (this.editorControl) {
this.editorControl.updateOptions({ ariaLabel: this.computeAriaLabel() });
}
}
protected setEditorVisible(visible: boolean, position: Position = null): void {
// Pass on to Editor
if (visible) {
this.consumePendingConfigurationChangeEvent();
this.editorControl.onVisible();
} else {
this.editorControl.onHide();
}
super.setEditorVisible(visible, position);
}
public focus(): void {
this.editorControl.focus();
}
public layout(dimension: Dimension): void {
// Pass on to Editor
this.editorControl.layout(dimension);
}
public getControl(): IEditor {
return this.editorControl;
}
/**
* Saves the text editor view state under the given key.
*/
protected saveTextEditorViewState(key: string): void {
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
let textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
if (!textEditorViewStateMemento) {
textEditorViewStateMemento = Object.create(null);
memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY] = textEditorViewStateMemento;
}
const editorViewState = this.getControl().saveViewState();
let lastKnownViewState: ITextEditorViewState = textEditorViewStateMemento[key];
if (!lastKnownViewState) {
lastKnownViewState = Object.create(null);
textEditorViewStateMemento[key] = lastKnownViewState;
}
if (typeof this.position === 'number') {
lastKnownViewState[this.position] = editorViewState;
}
}
/**
* Clears the text editor view state under the given key.
*/
protected clearTextEditorViewState(keys: string[]): void {
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
const textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
if (textEditorViewStateMemento) {
keys.forEach(key => delete textEditorViewStateMemento[key]);
}
}
/**
* Loads the text editor view state for the given key and returns it.
*/
protected loadTextEditorViewState(key: string): IEditorViewState {
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
const textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
if (textEditorViewStateMemento) {
const viewState: ITextEditorViewState = textEditorViewStateMemento[key];
if (viewState) {
return viewState[this.position];
}
}
return null;
}
private updateEditorConfiguration(configuration = this.configurationService.getConfiguration<IEditorConfiguration>(this.getResource())): void {
if (!this.editorControl) {
return;
}
const editorConfiguration = this.computeConfiguration(configuration);
// Try to figure out the actual editor options that changed from the last time we updated the editor.
// We do this so that we are not overwriting some dynamic editor settings (e.g. word wrap) that might
// have been applied to the editor directly.
let editorSettingsToApply = editorConfiguration;
if (this.lastAppliedEditorOptions) {
editorSettingsToApply = objects.distinct(this.lastAppliedEditorOptions, editorSettingsToApply);
}
if (Object.keys(editorSettingsToApply).length > 0) {
this.lastAppliedEditorOptions = editorConfiguration;
this.editorControl.updateOptions(editorSettingsToApply);
}
}
protected getResource(): URI {
const codeEditor = getCodeEditor(this);
if (codeEditor) {
const model = codeEditor.getModel();
if (model) {
return model.uri;
}
}
if (this.input) {
return toResource(this.input);
}
return null;
}
protected abstract getAriaLabel(): string;
public dispose(): void {
this.lastAppliedEditorOptions = void 0;
this.editorControl.destroy();
super.dispose();
}
}

View File

@@ -0,0 +1,202 @@
/*---------------------------------------------------------------------------------------------
* 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 nls = require('vs/nls');
import types = require('vs/base/common/types');
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { TextEditorOptions, EditorModel, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { once } from 'vs/base/common/event';
import { ScrollType } from 'vs/editor/common/editorCommon';
/**
* An editor implementation that is capable of showing the contents of resource inputs. Uses
* the TextEditor widget to show the contents.
*/
export class TextResourceEditor extends BaseTextEditor {
public static ID = 'workbench.editors.textResourceEditor';
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IThemeService themeService: IThemeService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IModeService modeService: IModeService,
@ITextFileService textFileService: ITextFileService
) {
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, modeService, textFileService, editorGroupService);
}
public getTitle(): string {
if (this.input) {
return this.input.getName();
}
return nls.localize('textEditor', "Text Editor");
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
const oldInput = this.input;
super.setInput(input, options);
// Detect options
const forceOpen = options && options.forceOpen;
// Same Input
if (!forceOpen && input.matches(oldInput)) {
// TextOptions (avoiding instanceof here for a reason, do not change!)
const textOptions = <TextEditorOptions>options;
if (textOptions && types.isFunction(textOptions.apply)) {
textOptions.apply(this.getControl(), ScrollType.Smooth);
}
return TPromise.as<void>(null);
}
// Remember view settings if input changes
this.saveTextEditorViewStateForInput(oldInput);
// Different Input (Reload)
return input.resolve(true).then((resolvedModel: EditorModel) => {
// Assert Model instance
if (!(resolvedModel instanceof BaseTextEditorModel)) {
return TPromise.wrapError<void>(new Error('Unable to open file as text'));
}
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
if (!this.input || this.input !== input) {
return null;
}
// Set Editor Model
const textEditor = this.getControl();
const textEditorModel = resolvedModel.textEditorModel;
textEditor.setModel(textEditorModel);
// Apply Options from TextOptions
let optionsGotApplied = false;
const textOptions = <TextEditorOptions>options;
if (textOptions && types.isFunction(textOptions.apply)) {
optionsGotApplied = textOptions.apply(textEditor, ScrollType.Immediate);
}
// Otherwise restore View State
if (!optionsGotApplied) {
this.restoreViewState(input);
}
return void 0;
});
}
protected restoreViewState(input: EditorInput) {
if (input instanceof UntitledEditorInput || input instanceof ResourceEditorInput) {
const viewState = this.loadTextEditorViewState(input.getResource().toString());
if (viewState) {
this.getControl().restoreViewState(viewState);
}
}
}
protected getConfigurationOverrides(): IEditorOptions {
const options = super.getConfigurationOverrides();
options.readOnly = !(this.input instanceof UntitledEditorInput); // all resource editors are readonly except for the untitled one;
return options;
}
protected getAriaLabel(): string {
const input = this.input;
const isReadonly = !(this.input instanceof UntitledEditorInput);
let ariaLabel: string;
const inputName = input && input.getName();
if (isReadonly) {
ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text editor.");
} else {
ariaLabel = inputName ? nls.localize('untitledFileEditorWithInputAriaLabel', "{0}. Untitled file text editor.", inputName) : nls.localize('untitledFileEditorAriaLabel', "Untitled file text editor.");
}
return ariaLabel;
}
/**
* Reveals the last line of this editor if it has a model set.
*/
public revealLastLine(): void {
const codeEditor = <ICodeEditor>this.getControl();
const model = codeEditor.getModel();
if (model) {
const lastLine = model.getLineCount();
codeEditor.revealPosition({ lineNumber: lastLine, column: model.getLineMaxColumn(lastLine) }, ScrollType.Smooth);
}
}
public clearInput(): void {
// Keep editor view state in settings to restore when coming back
this.saveTextEditorViewStateForInput(this.input);
// Clear Model
this.getControl().setModel(null);
super.clearInput();
}
public shutdown(): void {
// Save View State (only for untitled)
if (this.input instanceof UntitledEditorInput) {
this.saveTextEditorViewStateForInput(this.input);
}
// Call Super
super.shutdown();
}
protected saveTextEditorViewStateForInput(input: EditorInput): void {
if (!(input instanceof UntitledEditorInput) && !(input instanceof ResourceEditorInput)) {
return; // only enabled for untitled and resource inputs
}
const key = input.getResource().toString();
// Clear view state if input is disposed
if (input.isDisposed()) {
super.clearTextEditorViewState([key]);
}
// Otherwise save it
else {
super.saveTextEditorViewState(key);
// Make sure to clean up when the input gets disposed
once(input.onDispose)(() => {
super.clearTextEditorViewState([key]);
});
}
}
}

View File

@@ -0,0 +1,556 @@
/*---------------------------------------------------------------------------------------------
* 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/titlecontrol';
import nls = require('vs/nls');
import { Registry } from 'vs/platform/registry/common/platform';
import { Scope, IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actions';
import { IAction, Action } from 'vs/base/common/actions';
import errors = require('vs/base/common/errors');
import DOM = require('vs/base/browser/dom');
import { TPromise } from 'vs/base/common/winjs.base';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { RunOnceScheduler } from 'vs/base/common/async';
import { isCommonCodeEditor, isCommonDiffEditor } from 'vs/editor/common/editorCommon';
import arrays = require('vs/base/common/arrays');
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource } from 'vs/workbench/common/editor';
import { EventType as BaseEventType } from 'vs/base/common/events';
import { IActionItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CloseEditorsInGroupAction, SplitEditorAction, CloseEditorAction, KeepEditorAction, CloseOtherEditorsInGroupAction, CloseRightEditorsInGroupAction, ShowEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IMenuService, MenuId, IMenu, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Themable } from 'vs/workbench/common/theme';
import { IDraggedResource } from 'vs/base/browser/dnd';
import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { extname } from 'vs/base/common/paths';
import { IFileService } from 'vs/platform/files/common/files';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import URI from 'vs/base/common/uri';
export interface IToolbarActions {
primary: IAction[];
secondary: IAction[];
}
export interface ITitleAreaControl {
setContext(group: IEditorGroup): void;
hasContext(): boolean;
allowDragging(element: HTMLElement): boolean;
setDragged(dragged: boolean): void;
create(parent: HTMLElement): void;
getContainer(): HTMLElement;
refresh(instant?: boolean): void;
update(instant?: boolean): void;
layout(): void;
dispose(): void;
}
export abstract class TitleControl extends Themable implements ITitleAreaControl {
private static draggedEditor: IEditorIdentifier;
protected stacks: IEditorStacksModel;
protected context: IEditorGroup;
protected dragged: boolean;
protected closeEditorAction: CloseEditorAction;
protected pinEditorAction: KeepEditorAction;
protected closeOtherEditorsAction: CloseOtherEditorsInGroupAction;
protected closeRightEditorsAction: CloseRightEditorsInGroupAction;
protected closeUnmodifiedEditorsInGroupAction: CloseUnmodifiedEditorsInGroupAction;
protected closeEditorsInGroupAction: CloseEditorsInGroupAction;
protected splitEditorAction: SplitEditorAction;
protected showEditorsInGroupAction: ShowEditorsInGroupAction;
private parent: HTMLElement;
private currentPrimaryEditorActionIds: string[] = [];
private currentSecondaryEditorActionIds: string[] = [];
protected editorActionsToolbar: ToolBar;
private mapActionsToEditors: { [editorId: string]: IToolbarActions; };
private scheduler: RunOnceScheduler;
private refreshScheduled: boolean;
private resourceContext: ResourceContextKey;
private disposeOnEditorActions: IDisposable[] = [];
private contextMenu: IMenu;
constructor(
@IContextMenuService protected contextMenuService: IContextMenuService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IEditorGroupService protected editorGroupService: IEditorGroupService,
@IContextKeyService protected contextKeyService: IContextKeyService,
@IKeybindingService protected keybindingService: IKeybindingService,
@ITelemetryService protected telemetryService: ITelemetryService,
@IMessageService protected messageService: IMessageService,
@IMenuService protected menuService: IMenuService,
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@IThemeService protected themeService: IThemeService
) {
super(themeService);
this.stacks = editorGroupService.getStacksModel();
this.mapActionsToEditors = Object.create(null);
this.scheduler = new RunOnceScheduler(() => this.onSchedule(), 0);
this.toUnbind.push(this.scheduler);
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
this.contextMenu = this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService);
this.toUnbind.push(this.contextMenu);
this.initActions(this.instantiationService);
this.registerListeners();
}
public static getDraggedEditor(): IEditorIdentifier {
return TitleControl.draggedEditor;
}
public setDragged(dragged: boolean): void {
this.dragged = dragged;
}
protected onEditorDragStart(editor: IEditorIdentifier): void {
TitleControl.draggedEditor = editor;
}
protected onEditorDragEnd(): void {
TitleControl.draggedEditor = void 0;
}
private registerListeners(): void {
this.toUnbind.push(this.stacks.onModelChanged(e => this.onStacksChanged(e)));
}
private onStacksChanged(e: IStacksModelChangeEvent): void {
if (e.structural) {
this.updateSplitActionEnablement();
}
}
private updateSplitActionEnablement(): void {
if (!this.context) {
return;
}
const groupCount = this.stacks.groups.length;
// Split editor
this.splitEditorAction.enabled = groupCount < 3;
}
protected updateStyles(): void {
super.updateStyles();
this.update(true); // run an update when the theme changes to new styles
}
private onSchedule(): void {
if (this.refreshScheduled) {
this.doRefresh();
} else {
this.doUpdate();
}
this.refreshScheduled = false;
}
public setContext(group: IEditorGroup): void {
this.context = group;
}
public hasContext(): boolean {
return !!this.context;
}
public update(instant?: boolean): void {
if (instant) {
this.scheduler.cancel();
this.onSchedule();
} else {
this.scheduler.schedule();
}
}
public refresh(instant?: boolean) {
this.refreshScheduled = true;
if (instant) {
this.scheduler.cancel();
this.onSchedule();
} else {
this.scheduler.schedule();
}
}
public create(parent: HTMLElement): void {
this.parent = parent;
}
public getContainer(): HTMLElement {
return this.parent;
}
protected abstract doRefresh(): void;
protected doUpdate(): void {
this.doRefresh();
}
public layout(): void {
// Subclasses can opt in to react on layout
}
public allowDragging(element: HTMLElement): boolean {
return !DOM.findParentWithClass(element, 'monaco-action-bar', 'one-editor-silo');
}
protected initActions(services: IInstantiationService): void {
this.closeEditorAction = services.createInstance(CloseEditorAction, CloseEditorAction.ID, nls.localize('close', "Close"));
this.closeOtherEditorsAction = services.createInstance(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, nls.localize('closeOthers', "Close Others"));
this.closeRightEditorsAction = services.createInstance(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, nls.localize('closeRight', "Close to the Right"));
this.closeEditorsInGroupAction = services.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"));
this.closeUnmodifiedEditorsInGroupAction = services.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"));
this.pinEditorAction = services.createInstance(KeepEditorAction, KeepEditorAction.ID, nls.localize('keepOpen', "Keep Open"));
this.showEditorsInGroupAction = services.createInstance(ShowEditorsInGroupAction, ShowEditorsInGroupAction.ID, nls.localize('showOpenedEditors', "Show Opened Editors"));
this.splitEditorAction = services.createInstance(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL);
}
protected createEditorActionsToolBar(container: HTMLElement): void {
this.editorActionsToolbar = new ToolBar(container, this.contextMenuService, {
actionItemProvider: (action: Action) => this.actionItemProvider(action),
orientation: ActionsOrientation.HORIZONTAL,
ariaLabel: nls.localize('araLabelEditorActions', "Editor actions"),
getKeyBinding: (action) => this.getKeybinding(action)
});
// Action Run Handling
this.toUnbind.push(this.editorActionsToolbar.actionRunner.addListener(BaseEventType.RUN, (e: any) => {
// Check for Error
if (e.error && !errors.isPromiseCanceledError(e.error)) {
this.messageService.show(Severity.Error, e.error);
}
// Log in telemetry
if (this.telemetryService) {
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'editorPart' });
}
}));
}
protected actionItemProvider(action: Action): IActionItem {
if (!this.context) {
return null;
}
const group = this.context;
const position = this.stacks.positionOfGroup(group);
const editor = this.editorService.getVisibleEditors()[position];
let actionItem: IActionItem;
// Check Active Editor
if (editor instanceof BaseEditor) {
actionItem = editor.getActionItem(action);
}
// Check Registry
if (!actionItem) {
const actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
actionItem = actionBarRegistry.getActionItemForContext(Scope.EDITOR, { input: editor && editor.input, editor, position }, action);
}
// Check extensions
if (!actionItem) {
actionItem = createActionItem(action, this.keybindingService, this.messageService);
}
return actionItem;
}
protected getEditorActions(identifier: IEditorIdentifier): IToolbarActions {
const primary: IAction[] = [];
const secondary: IAction[] = [];
const { group } = identifier;
const position = this.stacks.positionOfGroup(group);
// Update the resource context
this.resourceContext.set(group && toResource(group.activeEditor, { supportSideBySide: true }));
// Editor actions require the editor control to be there, so we retrieve it via service
const control = this.editorService.getVisibleEditors()[position];
if (control instanceof BaseEditor && control.input && typeof control.position === 'number') {
// Editor Control Actions
let editorActions = this.mapActionsToEditors[control.getId()];
if (!editorActions) {
editorActions = { primary: control.getActions(), secondary: control.getSecondaryActions() };
this.mapActionsToEditors[control.getId()] = editorActions;
}
primary.push(...editorActions.primary);
secondary.push(...editorActions.secondary);
// MenuItems
// TODO This isn't very proper but needed as we have failed to
// use the correct context key service per editor only once. Don't
// take this code as sample of how to work with menus
this.disposeOnEditorActions = dispose(this.disposeOnEditorActions);
const widget = control.getControl();
const codeEditor = isCommonCodeEditor(widget) && widget || isCommonDiffEditor(widget) && widget.getModifiedEditor();
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(_ => this.update()));
fillInActions(titleBarMenu, { arg: this.resourceContext.get() }, { primary, secondary });
}
return { primary, secondary };
}
protected updateEditorActionsToolbar(): void {
const group = this.context;
if (!group) {
return;
}
const editor = group && group.activeEditor;
const isActive = this.stacks.isActive(group);
// Update Editor Actions Toolbar
let primaryEditorActions: IAction[] = [];
let secondaryEditorActions: IAction[] = [];
if (isActive) {
const editorActions = this.getEditorActions({ group, editor });
primaryEditorActions = prepareActions(editorActions.primary);
if (isActive && editor instanceof EditorInput && editor.supportsSplitEditor()) {
this.updateSplitActionEnablement();
primaryEditorActions.push(this.splitEditorAction);
}
secondaryEditorActions = prepareActions(editorActions.secondary);
}
const tabOptions = this.editorGroupService.getTabOptions();
if (tabOptions.showTabs) {
if (secondaryEditorActions.length > 0) {
secondaryEditorActions.push(new Separator());
}
secondaryEditorActions.push(this.showEditorsInGroupAction);
secondaryEditorActions.push(new Separator());
secondaryEditorActions.push(this.closeUnmodifiedEditorsInGroupAction);
secondaryEditorActions.push(this.closeEditorsInGroupAction);
}
const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
if (!tabOptions.showTabs) {
primaryEditorActionIds.push(this.closeEditorAction.id); // always show "Close" when tabs are disabled
}
const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id);
if (
!arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) ||
!arrays.equals(secondaryEditorActionIds, this.currentSecondaryEditorActionIds) ||
primaryEditorActions.some(action => action instanceof ExecuteCommandAction) || // execute command actions can have the same ID but different arguments
secondaryEditorActions.some(action => action instanceof ExecuteCommandAction) // see also https://github.com/Microsoft/vscode/issues/16298
) {
this.editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();
if (!tabOptions.showTabs) {
this.editorActionsToolbar.addPrimaryAction(this.closeEditorAction)();
}
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
}
}
protected clearEditorActionsToolbar(): void {
this.editorActionsToolbar.setActions([], [])();
this.currentPrimaryEditorActionIds = [];
this.currentSecondaryEditorActionIds = [];
}
protected onContextMenu(identifier: IEditorIdentifier, e: Event, node: HTMLElement): void {
// Update the resource context
const currentContext = this.resourceContext.get();
this.resourceContext.set(toResource(identifier.editor, { supportSideBySide: true }));
// Find target anchor
let anchor: HTMLElement | { x: number, y: number } = node;
if (e instanceof MouseEvent) {
const event = new StandardMouseEvent(e);
anchor = { x: event.posx, y: event.posy };
}
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.as(this.getContextMenuActions(identifier)),
getActionsContext: () => identifier,
getKeyBinding: (action) => this.getKeybinding(action),
onHide: (cancel) => this.resourceContext.set(currentContext) // restore previous context
});
}
protected getKeybinding(action: IAction): ResolvedKeybinding {
return this.keybindingService.lookupKeybinding(action.id);
}
protected getKeybindingLabel(action: IAction): string {
const keybinding = this.getKeybinding(action);
return keybinding ? keybinding.getLabel() : void 0;
}
protected getContextMenuActions(identifier: IEditorIdentifier): IAction[] {
const { editor, group } = identifier;
// Enablement
this.closeOtherEditorsAction.enabled = group.count > 1;
this.pinEditorAction.enabled = !group.isPinned(editor);
this.closeRightEditorsAction.enabled = group.indexOf(editor) !== group.count - 1;
// Actions: For all editors
const actions: IAction[] = [
this.closeEditorAction,
this.closeOtherEditorsAction
];
const tabOptions = this.editorGroupService.getTabOptions();
if (tabOptions.showTabs) {
actions.push(this.closeRightEditorsAction);
}
actions.push(this.closeUnmodifiedEditorsInGroupAction);
actions.push(this.closeEditorsInGroupAction);
if (tabOptions.previewEditors) {
actions.push(new Separator(), this.pinEditorAction);
}
// Fill in contributed actions
fillInActions(this.contextMenu, { arg: this.resourceContext.get() }, actions);
return actions;
}
public dispose(): void {
super.dispose();
// Actions
[
this.splitEditorAction,
this.showEditorsInGroupAction,
this.closeEditorAction,
this.closeRightEditorsAction,
this.closeUnmodifiedEditorsInGroupAction,
this.closeOtherEditorsAction,
this.closeEditorsInGroupAction,
this.pinEditorAction
].forEach((action) => {
action.dispose();
});
// Toolbar
this.editorActionsToolbar.dispose();
}
}
/**
* Shared function across some editor components to handle drag & drop of folders and workspace files
* to open them in the window instead of the editor.
*/
export function handleWorkspaceExternalDrop(
resources: IDraggedResource[],
fileService: IFileService,
messageService: IMessageService,
windowsService: IWindowsService,
windowService: IWindowService,
workspacesService: IWorkspacesService
): TPromise<boolean /* handled */> {
// Return early if there are no external resources
const externalResources = resources.filter(d => d.isExternal).map(d => d.resource);
if (!externalResources.length) {
return TPromise.as(false);
}
const externalWorkspaceResources: { workspaces: URI[], folders: URI[] } = {
workspaces: [],
folders: []
};
return TPromise.join(externalResources.map(resource => {
// Check for Workspace
if (extname(resource.fsPath) === `.${WORKSPACE_EXTENSION}`) {
externalWorkspaceResources.workspaces.push(resource);
return void 0;
}
// Check for Folder
return fileService.resolveFile(resource).then(stat => {
if (stat.isDirectory) {
externalWorkspaceResources.folders.push(stat.resource);
}
}, error => void 0);
})).then(_ => {
const { workspaces, folders } = externalWorkspaceResources;
// Return early if no external resource is a folder or workspace
if (workspaces.length === 0 && folders.length === 0) {
return false;
}
// Pass focus to window
windowService.focusWindow();
let workspacesToOpen: TPromise<string[]>;
// Open in separate windows if we drop workspaces or just one folder
if (workspaces.length > 0 || folders.length === 1) {
workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources.fsPath));
}
// Multiple folders: Create new workspace with folders and open
else if (folders.length > 1) {
workspacesToOpen = workspacesService.createWorkspace([...folders].map(folder => folder.fsPath)).then(workspace => [workspace.configPath]);
}
// Open
workspacesToOpen.then(workspaces => {
windowsService.openWindow(workspaces, { forceReuseWindow: true });
});
return true;
});
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import URI from 'vs/base/common/uri';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Scope } from 'vs/workbench/common/memento';
export interface HtmlPreviewEditorViewState {
scrollYPercentage: number;
}
interface HtmlPreviewEditorViewStates {
0?: HtmlPreviewEditorViewState;
1?: HtmlPreviewEditorViewState;
2?: HtmlPreviewEditorViewState;
}
/**
* This class is only intended to be subclassed and not instantiated.
*/
export abstract class BaseWebviewEditor extends BaseEditor {
constructor(
id: string,
telemetryService: ITelemetryService,
themeService: IThemeService,
private storageService: IStorageService
) {
super(id, telemetryService, themeService);
}
private get viewStateStorageKey(): string {
return this.getId() + '.editorViewState';
}
protected saveViewState(resource: URI | string, editorViewState: HtmlPreviewEditorViewState): void {
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
let editorViewStateMemento = memento[this.viewStateStorageKey];
if (!editorViewStateMemento) {
editorViewStateMemento = Object.create(null);
memento[this.viewStateStorageKey] = editorViewStateMemento;
}
let fileViewState: HtmlPreviewEditorViewStates = editorViewStateMemento[resource.toString()];
if (!fileViewState) {
fileViewState = Object.create(null);
editorViewStateMemento[resource.toString()] = fileViewState;
}
if (typeof this.position === 'number') {
fileViewState[this.position] = editorViewState;
}
}
protected loadViewState(resource: URI | string): HtmlPreviewEditorViewState | null {
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
const editorViewStateMemento = memento[this.viewStateStorageKey];
if (editorViewStateMemento) {
const fileViewState: HtmlPreviewEditorViewStates = editorViewStateMemento[resource.toString()];
if (fileViewState) {
return fileViewState[this.position];
}
}
return null;
}
}