Merge VS Code 1.21 source code (#1067)

* Initial VS Code 1.21 file copy with patches

* A few more merges

* Post npm install

* Fix batch of build breaks

* Fix more build breaks

* Fix more build errors

* Fix more build breaks

* Runtime fixes 1

* Get connection dialog working with some todos

* Fix a few packaging issues

* Copy several node_modules to package build to fix loader issues

* Fix breaks from master

* A few more fixes

* Make tests pass

* First pass of license header updates

* Second pass of license header updates

* Fix restore dialog issues

* Remove add additional themes menu items

* fix select box issues where the list doesn't show up

* formatting

* Fix editor dispose issue

* Copy over node modules to correct location on all platforms
This commit is contained in:
Karl Burtram
2018-04-04 15:27:51 -07:00
committed by GitHub
parent 5fba3e31b4
commit dafb780987
9412 changed files with 141255 additions and 98813 deletions

View File

@@ -10,7 +10,6 @@ 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';
@@ -19,6 +18,7 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer';
/*
* This class is only intended to be subclassed and not instantiated.
@@ -29,6 +29,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
private binaryContainer: Builder;
private scrollbar: DomScrollableElement;
private resourceViewerContext: ResourceViewerContext;
constructor(
id: string,
@@ -87,7 +88,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
// Render Input
const model = <BinaryEditorModel>resolvedModel;
ResourceViewer.show(
this.resourceViewerContext = ResourceViewer.show(
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() },
this.binaryContainer,
this.scrollbar,
@@ -132,6 +133,9 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
// Pass on to Binary Container
this.binaryContainer.size(dimension.width, dimension.height);
this.scrollbar.scanDomNode();
if (this.resourceViewerContext) {
this.resourceViewerContext.layout(dimension);
}
}
public focus(): void {
@@ -146,4 +150,4 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
super.dispose();
}
}
}

View File

@@ -28,11 +28,11 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/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,
CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideAction, RevertAndCloseEditorAction,
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction,
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, 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, OpenLastEditorInGroup
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction,
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
ShowEditorsInGroupThreeAction, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorToSecondGroupAction, MoveEditorToThirdGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup
} 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';
@@ -41,6 +41,7 @@ import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRe
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { isMacintosh } from 'vs/base/common/platform';
import { GroupOnePicker, GroupTwoPicker, GroupThreePicker, AllEditorsPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
import { Schemas } from 'vs/base/common/network';
// Register String Editor
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
@@ -136,7 +137,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
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 filePath = resource.scheme === Schemas.file ? resource.fsPath : void 0;
const language = deserialized.modeId;
const encoding = deserialized.encoding;
@@ -213,7 +214,7 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
// Register Editor Status
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* High Priority */));
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* towards the left of the right hand side */));
// Register Status Actions
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
@@ -270,21 +271,21 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
new QuickOpenHandlerDescriptor(
GroupOnePicker,
GroupOnePicker.ID,
NAVIGATE_IN_GROUP_ONE_PREFIX,
editorCommands.NAVIGATE_IN_GROUP_ONE_PREFIX,
editorPickerContextKey,
[
{
prefix: NAVIGATE_IN_GROUP_ONE_PREFIX,
prefix: editorCommands.NAVIGATE_IN_GROUP_ONE_PREFIX,
needsEditor: false,
description: nls.localize('groupOnePicker', "Show Editors in First Group")
},
{
prefix: NAVIGATE_IN_GROUP_TWO_PREFIX,
prefix: editorCommands.NAVIGATE_IN_GROUP_TWO_PREFIX,
needsEditor: false,
description: nls.localize('groupTwoPicker', "Show Editors in Second Group")
},
{
prefix: NAVIGATE_IN_GROUP_THREE_PREFIX,
prefix: editorCommands.NAVIGATE_IN_GROUP_THREE_PREFIX,
needsEditor: false,
description: nls.localize('groupThreePicker', "Show Editors in Third Group")
}
@@ -296,7 +297,7 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
new QuickOpenHandlerDescriptor(
GroupTwoPicker,
GroupTwoPicker.ID,
NAVIGATE_IN_GROUP_TWO_PREFIX,
editorCommands.NAVIGATE_IN_GROUP_TWO_PREFIX,
editorPickerContextKey,
[]
)
@@ -306,7 +307,7 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
new QuickOpenHandlerDescriptor(
GroupThreePicker,
GroupThreePicker.ID,
NAVIGATE_IN_GROUP_THREE_PREFIX,
editorCommands.NAVIGATE_IN_GROUP_THREE_PREFIX,
editorPickerContextKey,
[]
)
@@ -316,11 +317,11 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
new QuickOpenHandlerDescriptor(
AllEditorsPicker,
AllEditorsPicker.ID,
NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
editorCommands.NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
editorPickerContextKey,
[
{
prefix: NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
prefix: editorCommands.NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
needsEditor: false,
description: nls.localize('allEditorsPicker', "Show All Opened Editors")
}
@@ -343,13 +344,8 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNe
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), 'File: Clear Recently Opened', nls.localize('file', "File"));
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);
@@ -368,6 +364,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, M
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(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToSecondGroupAction, MoveEditorToSecondGroupAction.ID, MoveEditorToSecondGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_2, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_2 } }), 'View: Move Editor into Second Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToThirdGroupAction, MoveEditorToThirdGroupAction.ID, MoveEditorToThirdGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_3, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_3 } }), 'View: Move Editor into Third 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');
@@ -410,12 +409,33 @@ editorCommands.setup();
// Touch Bar
if (isMacintosh) {
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath },
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath } },
group: 'navigation'
});
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath },
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath } },
group: 'navigation'
});
}
}
// Editor Title Context Menu
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close") }, group: '1_close', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others") }, group: '1_close', order: 20 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRight', "Close to the Right") }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open") }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
// Editor Title Menu
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_INLINE_MODE, title: nls.localize('toggleInlineView', "Toggle Inline View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.SHOW_EDITORS_IN_GROUP, title: nls.localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
// Editor Commands for Command Palette
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepEditor', "Keep Editor"), category }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOtherEditors', "Close Other Editors"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right"), category } });

View File

@@ -9,19 +9,20 @@ import nls = require('vs/nls');
import { Action } from 'vs/base/common/actions';
import { mixin } from 'vs/base/common/objects';
import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
import { EditorInput, TextEditorOptions, EditorOptions, IEditorIdentifier, IEditorContext, ActiveEditorMoveArguments, ActiveEditorMovePositioning, EditorCommands, ConfirmResult } from 'vs/workbench/common/editor';
import { EditorInput, TextEditorOptions, EditorOptions, IEditorIdentifier, ActiveEditorMoveArguments, ActiveEditorMovePositioning, EditorCommands, ConfirmResult, IEditorCommandsContext } from 'vs/workbench/common/editor';
import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { Position, IEditor, Direction, IResourceInput, IEditorInput } from 'vs/platform/editor/common/editor';
import { Position, IEditor, Direction, IResourceInput, IEditorInput, POSITIONS } from 'vs/platform/editor/common/editor';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IEditorGroupService, GroupArrangement } from 'vs/workbench/services/group/common/groupService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_IN_GROUP_ONE_PREFIX, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, NAVIGATE_IN_GROUP_THREE_PREFIX, NAVIGATE_IN_GROUP_TWO_PREFIX } from 'vs/workbench/browser/parts/editor/editorCommands';
export class SplitEditorAction extends Action {
@@ -37,10 +38,11 @@ export class SplitEditorAction extends Action {
super(id, label, 'split-editor-action');
}
public run(context?: IEditorContext): TPromise<any> {
public run(context?: IEditorCommandsContext): TPromise<any> {
let editorToSplit: IEditor;
if (context) {
editorToSplit = this.editorService.getVisibleEditors()[this.editorGroupService.getStacksModel().positionOfGroup(context.group)];
const stacks = this.editorGroupService.getStacksModel();
editorToSplit = this.editorService.getVisibleEditors()[stacks.positionOfGroup(stacks.getGroup(context.groupId))];
} else {
editorToSplit = this.editorService.getActiveEditor();
}
@@ -118,7 +120,7 @@ export class JoinTwoGroupsAction extends Action {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
public run(context?: IEditorIdentifier): TPromise<any> {
const editorStacksModel = this.editorGroupService.getStacksModel();
@@ -531,38 +533,13 @@ export class CloseEditorAction extends Action {
constructor(
id: string,
label: string,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
@ICommandService private commandService: ICommandService
) {
super(id, label, 'close-editor-action');
}
public run(context?: IEditorContext): TPromise<any> {
const position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
// Close Active Editor
if (typeof position !== 'number') {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor) {
return this.editorService.closeEditor(activeEditor.position, activeEditor.input);
}
}
let input = context ? context.editor : null;
if (!input) {
// Get Top Editor at Position
const visibleEditors = this.editorService.getVisibleEditors();
if (visibleEditors[position]) {
input = visibleEditors[position].input;
}
}
if (input) {
return this.editorService.closeEditor(position, input);
}
return TPromise.as(false);
public run(context?: IEditorCommandsContext): TPromise<any> {
return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, void 0, context);
}
}
@@ -585,9 +562,14 @@ export class RevertAndCloseEditorAction extends Action {
const input = activeEditor.input;
const position = activeEditor.position;
return activeEditor.input.revert().then(ok =>
this.editorService.closeEditor(position, input)
);
// first try a normal revert where the contents of the editor are restored
return activeEditor.input.revert().then(() => this.editorService.closeEditor(position, input), error => {
// if that fails, since we are about to close the editor, we accept that
// the editor cannot be reverted and instead do a soft revert that just
// enables us to close the editor. With this, a user can always close a
// dirty editor even when reverting fails.
return activeEditor.input.revert({ soft: true }).then(() => this.editorService.closeEditor(position, input));
});
}
return TPromise.as(false);
@@ -608,7 +590,7 @@ export class CloseLeftEditorsInGroupAction extends Action {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
public run(context?: IEditorIdentifier): TPromise<any> {
const editor = getTarget(this.editorService, this.groupService, context);
if (editor) {
return this.editorService.closeEditors(editor.position, { except: editor.input, direction: Direction.LEFT });
@@ -618,30 +600,6 @@ export class CloseLeftEditorsInGroupAction extends Action {
}
}
export class CloseRightEditorsInGroupAction extends Action {
public static readonly ID = 'workbench.action.closeEditorsToTheRight';
public static readonly LABEL = nls.localize('closeEditorsToTheRight', "Close Editors to the Right");
constructor(
id: string,
label: string,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupService private groupService: IEditorGroupService
) {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
const editor = getTarget(this.editorService, this.groupService, context);
if (editor) {
return this.editorService.closeEditors(editor.position, { except: editor.input, direction: Direction.RIGHT });
}
return TPromise.as(false);
}
}
export class CloseAllEditorsAction extends Action {
public static readonly ID = 'workbench.action.closeAllEditors';
@@ -660,65 +618,33 @@ export class CloseAllEditorsAction extends Action {
// Just close all if there are no or one dirty editor
if (this.textFileService.getDirty().length < 2) {
return this.editorService.closeAllEditors();
return this.editorService.closeEditors();
}
// Otherwise ask for combined confirmation
const confirm = this.textFileService.confirmSave();
if (confirm === ConfirmResult.CANCEL) {
return void 0;
}
let saveOrRevertPromise: TPromise<boolean>;
if (confirm === ConfirmResult.DONT_SAVE) {
saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true);
} else {
saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => r.success));
}
return saveOrRevertPromise.then(success => {
if (success) {
return this.editorService.closeAllEditors();
return this.textFileService.confirmSave().then(confirm => {
if (confirm === ConfirmResult.CANCEL) {
return void 0;
}
return void 0;
let saveOrRevertPromise: TPromise<boolean>;
if (confirm === ConfirmResult.DONT_SAVE) {
saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true);
} else {
saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => r.success));
}
return saveOrRevertPromise.then(success => {
if (success) {
return this.editorService.closeEditors();
}
return void 0;
});
});
}
}
export class CloseUnmodifiedEditorsInGroupAction extends Action {
public static readonly ID = 'workbench.action.closeUnmodifiedEditors';
public static readonly LABEL = nls.localize('closeUnmodifiedEditors', "Close Unmodified Editors in Group");
constructor(
id: string,
label: string,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
) {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
// If position is not passed in take the position of the active editor.
if (typeof position !== 'number') {
const active = this.editorService.getActiveEditor();
if (active) {
position = active.position;
}
}
if (typeof position === 'number') {
return this.editorService.closeEditors(position, { unmodifiedOnly: true });
}
return TPromise.as(false);
}
}
export class CloseEditorsInOtherGroupsAction extends Action {
public static readonly ID = 'workbench.action.closeEditorsInOtherGroups';
@@ -733,7 +659,7 @@ export class CloseEditorsInOtherGroupsAction extends Action {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
public run(context?: IEditorIdentifier): TPromise<any> {
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
if (typeof position !== 'number') {
const activeEditor = this.editorService.getActiveEditor();
@@ -743,71 +669,7 @@ export class CloseEditorsInOtherGroupsAction extends Action {
}
if (typeof position === 'number') {
return this.editorService.closeAllEditors(position);
}
return TPromise.as(false);
}
}
export class CloseOtherEditorsInGroupAction extends Action {
public static readonly ID = 'workbench.action.closeOtherEditors';
public static readonly LABEL = nls.localize('closeOtherEditorsInGroup', "Close Other Editors");
constructor(
id: string,
label: string,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
) {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
let input = context ? context.editor : null;
// If position or input are not passed in take the position and input of the active editor.
const active = this.editorService.getActiveEditor();
if (active) {
position = typeof position === 'number' ? position : active.position;
input = input ? input : <EditorInput>active.input;
}
if (typeof position === 'number' && input) {
return this.editorService.closeEditors(position, { except: input });
}
return TPromise.as(false);
}
}
export class CloseEditorsInGroupAction extends Action {
public static readonly ID = 'workbench.action.closeEditorsInGroup';
public static readonly LABEL = nls.localize('closeEditorsInGroup', "Close All Editors in Group");
constructor(
id: string,
label: string,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
) {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
if (typeof position !== 'number') {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor) {
position = activeEditor.position;
}
}
if (typeof position === 'number') {
return this.editorService.closeEditors(position);
return this.editorService.closeEditors(POSITIONS.filter(p => p !== position));
}
return TPromise.as(false);
@@ -828,7 +690,7 @@ export class MoveGroupLeftAction extends Action {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
public run(context?: IEditorIdentifier): TPromise<any> {
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
if (typeof position !== 'number') {
const activeEditor = this.editorService.getActiveEditor();
@@ -862,7 +724,7 @@ export class MoveGroupRightAction extends Action {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
public run(context?: IEditorIdentifier): TPromise<any> {
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
if (typeof position !== 'number') {
const activeEditor = this.editorService.getActiveEditor();
@@ -941,31 +803,7 @@ export class MaximizeGroupAction extends Action {
}
}
export class KeepEditorAction extends Action {
public static readonly ID = 'workbench.action.keepEditor';
public static readonly LABEL = nls.localize('keepEditor', "Keep Editor");
constructor(
id: string,
label: string,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
) {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
const target = getTarget(this.editorService, this.editorGroupService, context);
if (target) {
this.editorGroupService.pinEditor(target.position, target.input);
}
return TPromise.as(true);
}
}
function getTarget(editorService: IWorkbenchEditorService, editorGroupService: IEditorGroupService, context?: IEditorContext): { input: IEditorInput, position: Position } {
function getTarget(editorService: IWorkbenchEditorService, editorGroupService: IEditorGroupService, context?: IEditorIdentifier): { input: IEditorInput, position: Position } {
if (context) {
return { input: context.editor, position: editorGroupService.getStacksModel().positionOfGroup(context.group) };
}
@@ -1185,8 +1023,6 @@ export class ClearRecentFilesAction extends Action {
}
}
export const NAVIGATE_IN_GROUP_ONE_PREFIX = 'edt one ';
export class ShowEditorsInGroupOneAction extends QuickOpenAction {
public static readonly ID = 'workbench.action.showEditorsInFirstGroup';
@@ -1203,8 +1039,6 @@ export class ShowEditorsInGroupOneAction extends QuickOpenAction {
}
}
export const NAVIGATE_IN_GROUP_TWO_PREFIX = 'edt two ';
export class ShowEditorsInGroupTwoAction extends QuickOpenAction {
public static readonly ID = 'workbench.action.showEditorsInSecondGroup';
@@ -1221,8 +1055,6 @@ export class ShowEditorsInGroupTwoAction extends QuickOpenAction {
}
}
export const NAVIGATE_IN_GROUP_THREE_PREFIX = 'edt three ';
export class ShowEditorsInGroupThreeAction extends QuickOpenAction {
public static readonly ID = 'workbench.action.showEditorsInThirdGroup';
@@ -1239,40 +1071,6 @@ export class ShowEditorsInGroupThreeAction extends QuickOpenAction {
}
}
export class ShowEditorsInGroupAction extends Action {
public static readonly ID = 'workbench.action.showEditorsInGroup';
public static readonly LABEL = nls.localize('showEditorsInGroup', "Show Editors in Group");
constructor(
id: string,
label: string,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IEditorGroupService private editorGroupService: IEditorGroupService
) {
super(id, label);
}
public run(context?: IEditorContext): TPromise<any> {
const stacks = this.editorGroupService.getStacksModel();
const groupCount = stacks.groups.length;
if (groupCount <= 1 || !context) {
return this.quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
}
switch (stacks.positionOfGroup(context.group)) {
case Position.TWO:
return this.quickOpenService.show(NAVIGATE_IN_GROUP_TWO_PREFIX);
case Position.THREE:
return this.quickOpenService.show(NAVIGATE_IN_GROUP_THREE_PREFIX);
}
return this.quickOpenService.show(NAVIGATE_IN_GROUP_ONE_PREFIX);
}
}
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
export class ShowAllEditorsAction extends QuickOpenAction {
public static readonly ID = 'workbench.action.showAllEditors';
@@ -1547,3 +1345,70 @@ export class MoveEditorToNextGroupAction extends Action {
return TPromise.as(true);
}
}
export abstract class MoveEditorToSpecificGroup extends Action {
constructor(
id: string,
label: string,
private position: Position,
private editorGroupService: IEditorGroupService,
private editorService: IWorkbenchEditorService
) {
super(id, label);
}
public run(): TPromise<any> {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor && activeEditor.position !== this.position) {
this.editorGroupService.moveEditor(activeEditor.input, activeEditor.position, this.position);
}
return TPromise.as(true);
}
}
export class MoveEditorToFirstGroupAction extends MoveEditorToSpecificGroup {
public static readonly ID = 'workbench.action.moveEditorToFirstGroup';
public static readonly LABEL = nls.localize('moveEditorToFirstGroup', "Move Editor into First Group");
constructor(
id: string,
label: string,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService
) {
super(id, label, Position.ONE, editorGroupService, editorService);
}
}
export class MoveEditorToSecondGroupAction extends MoveEditorToSpecificGroup {
public static readonly ID = 'workbench.action.moveEditorToSecondGroup';
public static readonly LABEL = nls.localize('moveEditorToSecondGroup', "Move Editor into Second Group");
constructor(
id: string,
label: string,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService
) {
super(id, label, Position.TWO, editorGroupService, editorService);
}
}
export class MoveEditorToThirdGroupAction extends MoveEditorToSpecificGroup {
public static readonly ID = 'workbench.action.moveEditorToThirdGroup';
public static readonly LABEL = nls.localize('moveEditorToThirdGroup', "Move Editor into Third Group");
constructor(
id: string,
label: string,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService
) {
super(id, label, Position.THREE, editorGroupService, editorService);
}
}

View File

@@ -8,22 +8,40 @@ 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 { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible, EditorInput, IEditorIdentifier, IEditorCommandsContext } 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 { IEditor, Position, POSITIONS, Direction, IEditorInput } 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';
import { EditorStacksModel, EditorGroup } from 'vs/workbench/common/editor/editorStacksModel';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IListService } from 'vs/platform/list/browser/listService';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { distinct } from 'vs/base/common/arrays';
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
export const CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID = 'workbench.action.closeEditorsToTheRight';
export const CLOSE_EDITOR_COMMAND_ID = 'workbench.action.closeActiveEditor';
export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOtherEditors';
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
export const TOGGLE_DIFF_INLINE_MODE = 'toggle.diff.editorMode';
export const NAVIGATE_IN_GROUP_ONE_PREFIX = 'edt one ';
export const NAVIGATE_IN_GROUP_TWO_PREFIX = 'edt two ';
export const NAVIGATE_IN_GROUP_THREE_PREFIX = 'edt three ';
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
export function setup(): void {
registerActiveEditorMoveCommand();
registerDiffEditorCommands();
registerOpenEditorAtIndexCommands();
handleCommandDeprecations();
registerEditorCommands();
}
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
@@ -69,9 +87,8 @@ function registerActiveEditorMoveCommand(): void {
}
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.by = args.by || ActiveEditorMovePositioningBy.TAB;
args.value = types.isUndefined(args.value) ? 1 : args.value;
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
@@ -168,55 +185,30 @@ function registerDiffEditorCommands(): void {
}
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
});
id: TOGGLE_DIFF_INLINE_MODE,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
handler: (accessor, resource, context: IEditorCommandsContext) => {
const editorService = accessor.get(IWorkbenchEditorService);
const editorGroupService = accessor.get(IEditorGroupService);
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.validateStacksModel',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor) {
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
},
when: undefined,
primary: undefined
});
}
let editor: IEditor;
if (context) {
const position = positionAndInput(editorGroupService, editorService, context).position;
editor = editorService.getVisibleEditors()[position];
} else {
editor = editorService.getActiveEditor();
}
function handleCommandDeprecations(): void {
const mapDeprecatedCommands = {
'workbench.action.files.newFile': 'explorer.newFile',
'workbench.action.files.newFolder': 'explorer.newFolder'
};
Object.keys(mapDeprecatedCommands).forEach(deprecatedCommandId => {
const newCommandId: string = 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
]
if (editor instanceof TextDiffEditor) {
const control = editor.getControl();
const isInlineMode = !control.renderSideBySide;
control.updateOptions(<IDiffEditorOptions>{
renderSideBySide: isInlineMode
});
},
when: undefined,
primary: undefined
});
}
}
});
}
@@ -243,7 +235,7 @@ function registerOpenEditorAtIndexCommands(): void {
const editor = group.getEditor(editorIndex);
if (editor) {
return editorService.openEditor(editor);
return editorService.openEditor(editor).then(() => void 0);
}
}
@@ -268,4 +260,287 @@ function registerOpenEditorAtIndexCommands(): void {
return void 0;
}
}
}
function registerEditorCommands() {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_SAVED_EDITORS_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const model = editorGroupService.getStacksModel();
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
if (contexts.length === 0 && model.activeGroup) {
// If command is triggered from the command palette use the active group
contexts.push({ groupId: model.activeGroup.id });
}
let positionOne: { savedOnly: boolean } = void 0;
let positionTwo: { savedOnly: boolean } = void 0;
let positionThree: { savedOnly: boolean } = void 0;
contexts.forEach(c => {
switch (model.positionOfGroup(model.getGroup(c.groupId))) {
case Position.ONE: positionOne = { savedOnly: true }; break;
case Position.TWO: positionTwo = { savedOnly: true }; break;
case Position.THREE: positionThree = { savedOnly: true }; break;
}
});
return editorService.closeEditors({ positionOne, positionTwo, positionThree });
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const distinctGroupIds = distinct(contexts.map(c => c.groupId));
const model = editorGroupService.getStacksModel();
if (distinctGroupIds.length) {
return editorService.closeEditors(distinctGroupIds.map(gid => model.positionOfGroup(model.getGroup(gid))));
}
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
return editorService.closeEditors(activeEditor.position);
}
return TPromise.as(false);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_EDITOR_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const groupIds = distinct(contexts.map(context => context.groupId));
const model = editorGroupService.getStacksModel();
const editorsToClose = new Map<Position, IEditorInput[]>();
groupIds.forEach(groupId => {
const group = model.getGroup(groupId);
const position = model.positionOfGroup(group);
if (position >= 0) {
const inputs = contexts.map(c => {
if (c && groupId === c.groupId && types.isNumber(c.editorIndex)) {
return group.getEditor(c.editorIndex);
}
return group.activeEditor;
}).filter(input => !!input);
if (inputs.length) {
editorsToClose.set(position, inputs);
}
}
});
if (editorsToClose.size === 0) {
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
return editorService.closeEditor(activeEditor.position, activeEditor.input);
}
}
return editorService.closeEditors({
positionOne: editorsToClose.get(Position.ONE),
positionTwo: editorsToClose.get(Position.TWO),
positionThree: editorsToClose.get(Position.THREE)
});
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const model = editorGroupService.getStacksModel();
if (contexts.length === 0) {
// Cover the case when run from command palette
const activeGroup = model.activeGroup;
const activeEditor = editorService.getActiveEditorInput();
if (activeGroup && activeEditor) {
contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.indexOf(activeEditor) });
}
}
const groupIds = distinct(contexts.map(context => context.groupId));
const editorsToClose = new Map<Position, IEditorInput[]>();
groupIds.forEach(groupId => {
const group = model.getGroup(groupId);
const inputsToSkip = contexts.map(c => {
if (c.groupId === groupId && types.isNumber(c.editorIndex)) {
return group.getEditor(c.editorIndex);
}
return void 0;
}).filter(input => !!input);
const toClose = group.getEditors().filter(input => inputsToSkip.indexOf(input) === -1);
editorsToClose.set(model.positionOfGroup(group), toClose);
});
return editorService.closeEditors({
positionOne: editorsToClose.get(Position.ONE),
positionTwo: editorsToClose.get(Position.TWO),
positionThree: editorsToClose.get(Position.THREE)
});
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const { position, input } = positionAndInput(editorGroupService, editorService, context);
if (typeof position === 'number' && input) {
return editorService.closeEditors(position, { except: input, direction: Direction.RIGHT });
}
return TPromise.as(false);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: KEEP_EDITOR_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const { position, input } = positionAndInput(editorGroupService, editorService, context);
if (typeof position === 'number' && input) {
return editorGroupService.pinEditor(position, input);
}
return TPromise.as(false);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: SHOW_EDITORS_IN_GROUP,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const quickOpenService = accessor.get(IQuickOpenService);
const stacks = editorGroupService.getStacksModel();
const groupCount = stacks.groups.length;
if (groupCount <= 1) {
return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
}
const { position } = positionAndInput(editorGroupService, editorService, context);
switch (position) {
case Position.TWO:
return quickOpenService.show(NAVIGATE_IN_GROUP_TWO_PREFIX);
case Position.THREE:
return quickOpenService.show(NAVIGATE_IN_GROUP_THREE_PREFIX);
}
return quickOpenService.show(NAVIGATE_IN_GROUP_ONE_PREFIX);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.printStacksModel',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor) {
console.log(`${accessor.get(IEditorGroupService).getStacksModel().toString()}\n\n`);
},
when: void 0,
primary: void 0
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.validateStacksModel',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor) {
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
},
when: void 0,
primary: void 0
});
}
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorCommandsContext): { position: Position, input: IEditorInput } {
// Resolve from context
const model = editorGroupService.getStacksModel();
const group = context ? model.getGroup(context.groupId) : undefined;
let position = group ? model.positionOfGroup(group) : undefined;
let input = group && types.isNumber(context.editorIndex) ? group.getEditor(context.editorIndex) : undefined;
// If position or input are not passed in take the position and input of the active editor.
const active = editorService.getActiveEditor();
if (active) {
position = typeof position === 'number' ? position : active.position;
input = input ? input : <EditorInput>active.input;
}
return { position, input };
}
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService): IEditorCommandsContext[] {
// First check for a focused list to return the selected items from
const list = listService.lastFocusedList;
if (list instanceof List && list.isDOMFocused()) {
const elementToContext = (element: IEditorIdentifier | EditorGroup) =>
element instanceof EditorGroup ? { groupId: element.id, editorIndex: undefined } : { groupId: element.group.id, editorIndex: element.group.indexOf(element.editor) };
const onlyEditorGroupAndEditor = (e: IEditorIdentifier | EditorGroup) => e instanceof EditorGroup || ('editor' in e && 'group' in e);
const focusedElements: (IEditorIdentifier | EditorGroup)[] = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
// need to take into account when editor context is { group: group }
const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : undefined;
if (focus) {
const selection: (IEditorIdentifier | EditorGroup)[] = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
// Only respect selection if it contains focused element
if (selection && selection.some(s => s instanceof EditorGroup ? s.id === focus.groupId : s.group.id === focus.groupId && s.group.indexOf(s.editor) === focus.editorIndex)) {
return selection.map(elementToContext);
}
return [focus];
}
}
// Otherwise go with passed in context
return !!editorContext ? [editorContext] : [];
}

View File

@@ -24,22 +24,21 @@ import { IEditorGroupService, IEditorTabOptions, GroupArrangement, GroupOrientat
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { TitleControl, ITitleAreaControl, handleWorkspaceExternalDrop } from 'vs/workbench/browser/parts/editor/titleControl';
import { ITitleAreaControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl';
import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup, EditorOptions, TextEditorOptions, IEditorIdentifier } from 'vs/workbench/common/editor';
import { extractResources } from 'vs/workbench/browser/editor';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup, EditorOptions, TextEditorOptions, IEditorIdentifier, EditorInput, PREFERENCES_EDITOR_ID, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { editorBackground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { Themable, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_GROUP_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme';
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IMessageService } from 'vs/platform/message/common/message';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ResourcesDropHandler, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPartService } from 'vs/workbench/services/part/common/partService';
export enum Rochade {
NONE,
@@ -93,20 +92,30 @@ export interface IEditorGroupsControl {
dispose(): void;
}
interface CenteredEditorLayoutData {
leftMarginRatio: number;
size: number;
}
/**
* Helper class to manage multiple side by side editors for the editor part.
*/
export class EditorGroupsControl extends Themable implements IEditorGroupsControl, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider {
private static readonly CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY = 'workbench.centerededitorlayout.data';
private static readonly TITLE_AREA_CONTROL_KEY = '__titleAreaControl';
private static readonly PROGRESS_BAR_CONTROL_KEY = '__progressBar';
private static readonly INSTANTIATION_SERVICE_KEY = '__instantiationService';
private static readonly GOLDEN_RATIO = 0.61;
private static readonly MIN_EDITOR_WIDTH = 170;
private static readonly MIN_EDITOR_HEIGHT = 70;
private static readonly EDITOR_TITLE_HEIGHT = 35;
private static readonly CENTERED_EDITOR_MIN_MARGIN = 10;
private static readonly SNAP_TO_MINIMIZED_THRESHOLD_WIDTH = 50;
private static readonly SNAP_TO_MINIMIZED_THRESHOLD_HEIGHT = 20;
@@ -131,6 +140,22 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
private sashTwo: Sash;
private startSiloThreeSize: number;
// if the centered editor layout is activated, the editor inside of silo ONE is centered
// the silo will then contain:
// [left margin]|[editor]|[right margin]
// - The size of the editor is defined by centeredEditorSize
// - The position is defined by the ratio centeredEditorLeftMarginRatio = left-margin/(left-margin + editor + right-margin).
// - The two sashes can be used to control the size and position of the editor inside of the silo.
// - In order to seperate the two sashes from the sashes that control the size of bordering widgets
// CENTERED_EDITOR_MIN_MARGIN is forced as a minimum size for the two margins.
private centeredEditorActive: boolean;
private centeredEditorSashLeft: Sash;
private centeredEditorSashRight: Sash;
private centeredEditorPreferedSize: number;
private centeredEditorLeftMarginRatio: number;
private centeredEditorDragStartPosition: number;
private centeredEditorDragStartSize: number;
private visibleEditors: BaseEditor[];
private lastActiveEditor: BaseEditor;
@@ -143,20 +168,20 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
private onStacksChangeScheduler: RunOnceScheduler;
private stacksChangedBuffer: IStacksModelChangeEvent[];
private transfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
constructor(
parent: Builder,
groupOrientation: GroupOrientation,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IPartService private partService: IPartService,
@IStorageService private storageServise: IStorageService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IExtensionService private extensionService: IExtensionService,
@IInstantiationService private instantiationService: IInstantiationService,
@IWindowService private windowService: IWindowService,
@IWindowsService private windowsService: IWindowsService,
@IThemeService themeService: IThemeService,
@IFileService private fileService: IFileService,
@IMessageService private messageService: IMessageService,
@IWorkspacesService private workspacesService: IWorkspacesService
@ITelemetryService private telemetryService: ITelemetryService
) {
super(themeService);
@@ -431,13 +456,9 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
this.visibleEditorFocusTrackerDisposable[position].dispose();
}
// Track focus on editor container
const focusTracker = DOM.trackFocus(editor.getContainer().getHTMLElement());
const listenerDispose = focusTracker.onDidFocus(() => {
this.visibleEditorFocusTrackerDisposable[position] = editor.onDidFocus(() => {
this.onFocusGained(editor);
});
this.visibleEditorFocusTrackerDisposable[position] = combinedDisposable([focusTracker, listenerDispose]);
}
private onFocusGained(editor: BaseEditor): void {
@@ -979,41 +1000,74 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
// For each position
POSITIONS.forEach(position => {
const silo = this.silos[position];
// Containers (they contain everything and can move between silos)
const container = $(silo).div({ 'class': 'container' });
// InstantiationServices
const instantiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, this.contextKeyService.createScoped(container.getHTMLElement())]
));
container.setProperty(EditorGroupsControl.INSTANTIATION_SERVICE_KEY, instantiationService); // associate with container
// Title containers
const titleContainer = $(container).div({ 'class': 'title' });
if (this.tabOptions.showTabs) {
titleContainer.addClass('tabs');
}
if (this.tabOptions.showIcons) {
titleContainer.addClass('show-file-icons');
}
this.hookTitleDragListener(titleContainer);
// Title Control
this.createTitleControl(this.stacks.groupAt(position), silo, titleContainer, instantiationService);
// Progress Bar
const progressBar = new ProgressBar($(container));
this.toUnbind.push(attachProgressBarStyler(progressBar, this.themeService));
progressBar.getContainer().hide();
container.setProperty(EditorGroupsControl.PROGRESS_BAR_CONTROL_KEY, progressBar); // associate with container
this.createSilo(position);
});
// Update Styles
this.updateStyles();
}
private createSilo(position: Position): void {
const silo = this.silos[position];
// Containers (they contain everything and can move between silos)
const container = $(silo).div({ 'class': 'container' });
// InstantiationServices
const instantiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, this.contextKeyService.createScoped(container.getHTMLElement())]
));
container.setProperty(EditorGroupsControl.INSTANTIATION_SERVICE_KEY, instantiationService); // associate with container
// Title containers
const titleContainer = $(container).div({ 'class': 'title' });
if (this.tabOptions.showTabs) {
titleContainer.addClass('tabs');
}
if (this.tabOptions.showIcons) {
titleContainer.addClass('show-file-icons');
}
this.hookTitleDragListener(titleContainer);
// Title Control
this.createTitleControl(this.stacks.groupAt(position), silo, titleContainer, instantiationService);
// Progress Bar
const progressBar = new ProgressBar($(container));
this.toUnbind.push(attachProgressBarStyler(progressBar, this.themeService));
progressBar.getContainer().hide();
container.setProperty(EditorGroupsControl.PROGRESS_BAR_CONTROL_KEY, progressBar); // associate with container
// Sash for first position to support centered editor layout
if (position === Position.ONE) {
// Center Layout stuff
const registerSashListeners = (sash: Sash) => {
this.toUnbind.push(sash.onDidStart(() => this.onCenterSashDragStart()));
this.toUnbind.push(sash.onDidChange((e: ISashEvent) => this.onCenterSashDrag(sash, e)));
this.toUnbind.push(sash.onDidEnd(() => this.storeCenteredLayoutData()));
this.toUnbind.push(sash.onDidReset(() => this.resetCenteredEditor()));
};
this.centeredEditorSashLeft = new Sash(container.getHTMLElement(), this, { baseSize: 5, orientation: Orientation.VERTICAL });
this.centeredEditorSashRight = new Sash(container.getHTMLElement(), this, { baseSize: 5, orientation: Orientation.VERTICAL });
registerSashListeners(this.centeredEditorSashLeft);
registerSashListeners(this.centeredEditorSashRight);
this.centeredEditorSashLeft.hide();
this.centeredEditorSashRight.hide();
this.centeredEditorActive = false;
this.centeredEditorLeftMarginRatio = 0.5;
// Restore centered layout position and size
const centeredLayoutDataString = this.storageServise.get(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL);
if (centeredLayoutDataString) {
const centeredLayout = <CenteredEditorLayoutData>JSON.parse(centeredLayoutDataString);
this.centeredEditorLeftMarginRatio = centeredLayout.leftMarginRatio;
this.centeredEditorPreferedSize = centeredLayout.size;
}
}
}
protected updateStyles(): void {
super.updateStyles();
@@ -1073,8 +1127,17 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
return options;
}
const isCopyDrag = (draggedEditor: IEditorIdentifier, e: DragEvent) => {
if (draggedEditor && draggedEditor.editor instanceof EditorInput) {
if (!draggedEditor.editor.supportsSplitEditor()) {
return false;
}
}
return (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
};
function onDrop(e: DragEvent, position: Position, splitTo?: Position): void {
$this.updateFromDropping(node, false);
$this.updateFromDragAndDrop(node, false);
cleanUp();
const editorService = $this.editorService;
@@ -1084,9 +1147,9 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
const freeGroup = (stacks.groups.length === 1) ? Position.TWO : Position.THREE;
// Check for transfer from title control
const draggedEditor = TitleControl.getDraggedEditor();
if (draggedEditor) {
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
if ($this.transfer.hasData(DraggedEditorIdentifier.prototype)) {
const draggedEditor = $this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier;
const isCopy = isCopyDrag(draggedEditor, e);
// Copy editor to new location
if (isCopy) {
@@ -1124,44 +1187,22 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
// Check for URI transfer
else {
const droppedResources = extractResources(e).filter(r => r.resource.scheme === 'file' || r.resource.scheme === 'untitled');
if (droppedResources.length) {
handleWorkspaceExternalDrop(droppedResources, $this.fileService, $this.messageService, $this.windowsService, $this.windowService, $this.workspacesService).then(handled => {
if (handled) {
return;
}
const dropHandler = $this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true /* open workspace instead of file if dropped */ });
dropHandler.handleDrop(e, () => {
if (splitEditor && splitTo !== freeGroup) {
groupService.moveGroup(freeGroup, splitTo);
}
// 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(() => editorService.openEditors(droppedResources.map(d => {
return {
input: { resource: d.resource, options: { pinned: true } },
position: splitEditor ? freeGroup : position
};
}))).then(() => {
if (splitEditor && splitTo !== freeGroup) {
groupService.moveGroup(freeGroup, splitTo);
}
groupService.focusGroup(splitEditor ? splitTo : position);
})
.done(null, errors.onUnexpectedError);
});
}
groupService.focusGroup(splitEditor ? splitTo : position);
}, splitEditor ? freeGroup : position);
}
}
function positionOverlay(e: DragEvent, groups: number, position: Position): void {
const target = <HTMLElement>e.target;
const overlayIsSplit = typeof overlay.getProperty(splitToPropertyKey) === 'number';
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
const draggedEditor = TitleControl.getDraggedEditor();
const draggedEditor = $this.transfer.hasData(DraggedEditorIdentifier.prototype) ? $this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
const isCopy = isCopyDrag(draggedEditor, e);
const overlaySize = $this.layoutVertically ? target.clientWidth : target.clientHeight;
const splitThreshold = overlayIsSplit ? overlaySize / 5 : overlaySize / 10;
@@ -1266,7 +1307,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
// 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()) {
if (!$this.transfer.hasData(DraggedEditorIdentifier.prototype)) {
e.dataTransfer.dropEffect = 'copy';
}
@@ -1300,14 +1341,14 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
DOM.EventHelper.stop(e, true);
onDrop(e, Position.ONE);
} else {
this.updateFromDropping(node, false);
this.updateFromDragAndDrop(node, false);
}
}));
// Drag enter
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
this.toUnbind.push(DOM.addDisposableListener(node, DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
if (!TitleControl.getDraggedEditor()) {
if (!$this.transfer.hasData(DraggedEditorIdentifier.prototype)) {
// we used to check for the dragged resources here (via dnd.extractResources()) but this
// seems to be not possible on Linux and Windows where during DRAG_ENTER the resources
// are always undefined up until they are dropped when dragged from the tree. The workaround
@@ -1318,7 +1359,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
}
counter++;
this.updateFromDropping(node, true);
this.updateFromDragAndDrop(node, true);
const target = <HTMLElement>e.target;
if (target) {
@@ -1328,7 +1369,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
createOverlay(target);
if (overlay) {
this.updateFromDropping(node, false); // if we show an overlay, we can remove the drop feedback from the editor background
this.updateFromDragAndDrop(node, false); // if we show an overlay, we can remove the drop feedback from the editor background
}
}
}));
@@ -1337,7 +1378,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
this.toUnbind.push(DOM.addDisposableListener(node, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
counter--;
if (counter === 0) {
this.updateFromDropping(node, false);
this.updateFromDragAndDrop(node, false);
}
}));
@@ -1345,7 +1386,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
[node, window].forEach(container => {
this.toUnbind.push(DOM.addDisposableListener(container, DOM.EventType.DRAG_END, (e: DragEvent) => {
counter = 0;
this.updateFromDropping(node, false);
this.updateFromDragAndDrop(node, false);
cleanUp();
}));
});
@@ -1552,6 +1593,14 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
// Move to valid position if any
if (moveTo !== null) {
// TODO@Ben remove me after a while
/* __GDPR__
"editorGroupMoved" : {
"source" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"to": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('editorGroupMoved', { source: position, to: moveTo });
this.editorGroupService.moveGroup(position, moveTo);
}
@@ -1595,16 +1644,18 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
}
}
private updateFromDropping(element: HTMLElement, isDropping: boolean): void {
private updateFromDragAndDrop(element: HTMLElement, isDraggedOver: boolean): void {
const groupCount = this.stacks.groups.length;
const background = this.getColor(isDropping ? EDITOR_DRAG_AND_DROP_BACKGROUND : groupCount > 0 ? EDITOR_GROUP_BACKGROUND : null);
const background = this.getColor(isDraggedOver ? EDITOR_DRAG_AND_DROP_BACKGROUND : groupCount > 0 ? EDITOR_GROUP_BACKGROUND : null);
element.style.backgroundColor = background;
const activeContrastBorderColor = this.getColor(activeContrastBorder);
element.style.outlineColor = isDropping ? activeContrastBorderColor : null;
element.style.outlineStyle = isDropping && activeContrastBorderColor ? 'dashed' : null;
element.style.outlineWidth = isDropping && activeContrastBorderColor ? '2px' : null;
element.style.outlineOffset = isDropping && activeContrastBorderColor ? '-2px' : null;
element.style.outlineColor = isDraggedOver ? activeContrastBorderColor : null;
element.style.outlineStyle = isDraggedOver && activeContrastBorderColor ? 'dashed' : null;
element.style.outlineWidth = isDraggedOver && activeContrastBorderColor ? '2px' : null;
element.style.outlineOffset = isDraggedOver && activeContrastBorderColor ? '-2px' : null;
DOM.toggleClass(element, 'dragged-over', isDraggedOver);
}
private posSilo(pos: number, leftTop: string | number, rightBottom?: string | number, borderLeftTopWidth?: string | number): void {
@@ -1878,12 +1929,68 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
this.sashTwo.layout();
}
private get centeredEditorAvailableSize(): number {
return this.silosSize[Position.ONE] - EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN * 2;
}
private get centeredEditorSize(): number {
return Math.min(this.centeredEditorAvailableSize, this.centeredEditorPreferedSize);
}
private get centeredEditorPosition(): number {
return EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN + this.centeredEditorLeftMarginRatio * (this.centeredEditorAvailableSize - this.centeredEditorSize);
}
private onCenterSashDragStart(): void {
this.centeredEditorDragStartPosition = this.centeredEditorPosition;
this.centeredEditorDragStartSize = this.centeredEditorSize;
}
private onCenterSashDrag(sash: Sash, e: ISashEvent): void {
const sashesCoupled = !e.altKey;
const delta = sash === this.centeredEditorSashLeft ? e.startX - e.currentX : e.currentX - e.startX;
const size = this.centeredEditorDragStartSize + (sashesCoupled ? 2 * delta : delta);
let position = this.centeredEditorDragStartPosition;
if (sash === this.centeredEditorSashLeft || sashesCoupled) {
position -= delta;
}
if (size > 3 * this.minSize && size < this.centeredEditorAvailableSize) {
this.centeredEditorPreferedSize = size;
position -= EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN;
position = Math.min(position, this.centeredEditorAvailableSize - this.centeredEditorSize);
position = Math.max(0, position);
this.centeredEditorLeftMarginRatio = position / (this.centeredEditorAvailableSize - this.centeredEditorSize);
this.layoutContainers();
}
}
private storeCenteredLayoutData(): void {
const data: CenteredEditorLayoutData = {
leftMarginRatio: this.centeredEditorLeftMarginRatio,
size: this.centeredEditorSize
};
this.storageServise.store(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, JSON.stringify(data), StorageScope.GLOBAL);
}
public getVerticalSashTop(sash: Sash): number {
return 0;
}
public getVerticalSashLeft(sash: Sash): number {
return sash === this.sashOne ? this.silosSize[Position.ONE] : this.silosSize[Position.TWO] + this.silosSize[Position.ONE];
switch (sash) {
case this.sashOne:
return this.silosSize[Position.ONE];
case this.sashTwo:
return this.silosSize[Position.TWO] + this.silosSize[Position.ONE];
case this.centeredEditorSashLeft:
return this.centeredEditorPosition;
case this.centeredEditorSashRight:
return this.centeredEditorPosition + this.centeredEditorSize;
default:
return 0;
}
}
public getVerticalSashHeight(sash: Sash): number {
@@ -2033,6 +2140,29 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
}
});
// Layout centered Editor (only in vertical layout when one group is opened)
const id = this.visibleEditors[Position.ONE] ? this.visibleEditors[Position.ONE].getId() : undefined;
const doCentering = this.layoutVertically && this.stacks.groups.length === 1 && this.partService.isEditorLayoutCentered() && id !== PREFERENCES_EDITOR_ID && id !== TEXT_DIFF_EDITOR_ID;
if (doCentering && !this.centeredEditorActive) {
this.centeredEditorSashLeft.show();
this.centeredEditorSashRight.show();
// no size set yet. Calculate a default value
if (!this.centeredEditorPreferedSize) {
this.resetCenteredEditor(false);
}
} else if (!doCentering && this.centeredEditorActive) {
this.centeredEditorSashLeft.hide();
this.centeredEditorSashRight.hide();
}
this.centeredEditorActive = doCentering;
this.silos[Position.ONE].setClass('centered', doCentering);
if (this.centeredEditorActive) {
this.centeredEditorSashLeft.layout();
this.centeredEditorSashRight.layout();
}
// Layout visible editors
POSITIONS.forEach(position => {
this.layoutEditor(position);
@@ -2051,10 +2181,17 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
private layoutEditor(position: Position): void {
const editorSize = this.silosSize[position];
if (editorSize && this.visibleEditors[position]) {
const editor = this.visibleEditors[position];
if (editorSize && editor) {
let editorWidth = this.layoutVertically ? editorSize : this.dimension.width;
let editorHeight = (this.layoutVertically ? this.dimension.height : this.silosSize[position]) - EditorGroupsControl.EDITOR_TITLE_HEIGHT;
let editorPosition = 0;
if (this.centeredEditorActive) {
editorWidth = this.centeredEditorSize;
editorPosition = this.centeredEditorPosition;
}
if (position !== Position.ONE) {
if (this.layoutVertically) {
editorWidth--; // accomodate for 1px left-border in containers TWO, THREE when laying out vertically
@@ -2063,10 +2200,23 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
}
}
this.visibleEditors[position].layout(new Dimension(editorWidth, editorHeight));
const editorContainer = editor.getContainer();
editorContainer.style('margin-left', this.centeredEditorActive ? `${editorPosition}px` : null);
editorContainer.style('width', this.centeredEditorActive ? `${editorWidth}px` : null);
editorContainer.style('border-color', this.centeredEditorActive ? this.getColor(EDITOR_GROUP_BORDER) || this.getColor(contrastBorder) : null);
editor.layout(new Dimension(editorWidth, editorHeight));
}
}
private resetCenteredEditor(layout: boolean = true) {
this.centeredEditorLeftMarginRatio = 0.5;
this.centeredEditorPreferedSize = Math.floor(this.dimension.width * EditorGroupsControl.GOLDEN_RATIO);
if (layout) {
this.layoutContainers();
}
this.storageServise.remove(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL);
}
public getInstantiationService(position: Position): IInstantiationService {
return this.getFromContainer(position, EditorGroupsControl.INSTANTIATION_SERVICE_KEY);
}

View File

@@ -32,7 +32,6 @@ import { Position, POSITIONS, Direction, IEditor } from 'vs/platform/editor/comm
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IMessageService, IMessageWithAction, Severity } from 'vs/platform/message/common/message';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { EditorStacksModel, EditorGroup, EditorIdentifier, EditorCloseEvent } from 'vs/workbench/common/editor/editorStacksModel';
@@ -41,12 +40,15 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { EDITOR_GROUP_BACKGROUND } from 'vs/workbench/common/theme';
import { createCSSRule, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { createCSSRule } from 'vs/base/browser/dom';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { join } from 'vs/base/common/paths';
import { IEditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { ThrottledEmitter } from 'vs/base/common/async';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { INotificationService, Severity, INotificationActions } from 'vs/platform/notification/common/notification';
import { isErrorWithActions } from 'vs/base/common/errors';
// {{SQL CARBON EDIT}}
import { convertEditorInput } from 'sql/parts/common/customInputConverter';
@@ -76,6 +78,10 @@ interface IEditorReplacement extends EditorIdentifier {
options?: EditorOptions;
}
export type ICloseEditorsFilter = { except?: EditorInput, direction?: Direction, savedOnly?: boolean };
export type ICloseEditorsByFilterArgs = { positionOne?: ICloseEditorsFilter, positionTwo?: ICloseEditorsFilter, positionThree?: ICloseEditorsFilter };
export type ICloseEditorsArgs = { positionOne?: EditorInput[], positionTwo?: EditorInput[], positionThree?: EditorInput[] };
/**
* The editor part is the container for editors in the workbench. Based on the editor input being opened, it asks the registered
* editor for the given input to show the contents. The editor part supports up to 3 side-by-side editors.
@@ -124,7 +130,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
constructor(
id: string,
restoreFromStorage: boolean,
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IStorageService private storageService: IStorageService,
@IPartService private partService: IPartService,
@@ -543,24 +549,24 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
});
}
private doHandleSetInputError(e: Error | IMessageWithAction, group: EditorGroup, editor: BaseEditor, input: EditorInput, options: EditorOptions, monitor: ProgressMonitor): void {
private doHandleSetInputError(error: Error, group: EditorGroup, editor: BaseEditor, input: EditorInput, options: EditorOptions, monitor: ProgressMonitor): void {
const position = this.stacks.positionOfGroup(group);
// Stop loading promise if any
monitor.cancel();
// Report error only if this was not us restoring previous error state
if (this.partService.isCreated() && !errors.isPromiseCanceledError(e)) {
const errorMessage = nls.localize('editorOpenError', "Unable to open '{0}': {1}.", input.getName(), toErrorMessage(e));
let error: any;
if (e && (<IMessageWithAction>e).actions && (<IMessageWithAction>e).actions.length) {
error = errors.create(errorMessage, { actions: (<IMessageWithAction>e).actions }); // Support error actions from thrower
} else {
error = errorMessage;
if (this.partService.isCreated() && !errors.isPromiseCanceledError(error)) {
const actions: INotificationActions = { primary: [] };
if (isErrorWithActions(error)) {
actions.primary = error.actions;
}
this.messageService.show(Severity.Error, types.isString(error) ? new Error(error) : error);
this.notificationService.notify({
severity: Severity.Error,
message: nls.localize('editorOpenError', "Unable to open '{0}': {1}.", input.getName(), toErrorMessage(error)),
actions
});
}
this.editorGroupsControl.updateProgress(position, ProgressState.DONE);
@@ -691,65 +697,199 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
this.textCompareEditorVisible.set(this.visibleEditors.some(e => e && e.isVisible() && e.getId() === TEXT_DIFF_EDITOR_ID));
}
public closeAllEditors(except?: Position): TPromise<void> {
public closeEditors(positions?: Position[]): TPromise<void>;
public closeEditors(position: Position, filter?: ICloseEditorsFilter): TPromise<void>;
public closeEditors(position: Position, editors: EditorInput[]): TPromise<void>;
public closeEditors(editors: ICloseEditorsByFilterArgs): TPromise<void>;
public closeEditors(editors: ICloseEditorsArgs): TPromise<void>;
public closeEditors(positionsOrEditors?: Position[] | Position | ICloseEditorsByFilterArgs | ICloseEditorsArgs, filterOrEditors?: ICloseEditorsFilter | EditorInput[]): TPromise<void> {
// First check for specific position to close
if (typeof positionsOrEditors === 'number') {
return this.doCloseEditorsAtPosition(positionsOrEditors, filterOrEditors);
}
// Then check for array of positions to close
if (Array.isArray(positionsOrEditors) || isUndefinedOrNull(positionsOrEditors)) {
return this.doCloseAllEditorsAtPositions(positionsOrEditors as Position[]);
}
// Finally, close specific editors at multiple positions
return this.doCloseEditorsAtPositions(positionsOrEditors);
}
private doCloseEditorsAtPositions(editors: ICloseEditorsByFilterArgs | ICloseEditorsArgs): TPromise<void> {
// Extract editors to close for veto
const editorsToClose: EditorIdentifier[] = [];
let groupsWithEditorsToClose = 0;
POSITIONS.forEach(position => {
const details = (position === Position.ONE) ? editors.positionOne : (position === Position.TWO) ? editors.positionTwo : editors.positionThree;
if (details && this.stacks.groupAt(position)) {
groupsWithEditorsToClose++;
editorsToClose.push(...this.extractCloseEditorDetails(position, details).editorsToClose);
}
});
// Check for dirty and veto
const ignoreDirtyIfOpenedInOtherGroup = (groupsWithEditorsToClose === 1);
return this.handleDirty(editorsToClose, ignoreDirtyIfOpenedInOtherGroup).then(veto => {
if (veto) {
return void 0;
}
// Close by positions starting from last to first to prevent issues when
// editor groups close and thus move other editors around that are still open.
[Position.THREE, Position.TWO, Position.ONE].forEach(position => {
const details = (position === Position.ONE) ? editors.positionOne : (position === Position.TWO) ? editors.positionTwo : editors.positionThree;
if (details && this.stacks.groupAt(position)) {
const { group, editorsToClose, filter } = this.extractCloseEditorDetails(position, details);
// Close with filter
if (filter) {
this.doCloseEditorsWithFilter(group, filter);
}
// Close without filter
else {
this.doCloseEditors(group, editorsToClose.map(e => e.editor));
}
return void 0;
}
});
});
}
private doCloseAllEditorsAtPositions(positions?: Position[]): TPromise<void> {
let groups = this.stacks.groups.reverse(); // start from the end to prevent layout to happen through rochade
// Remove position to exclude if we have any
if (typeof except === 'number') {
groups = groups.filter(group => this.stacks.positionOfGroup(group) !== except);
// Remove positions that are not being asked for if provided
if (Array.isArray(positions)) {
groups = groups.filter(group => positions.indexOf(this.stacks.positionOfGroup(group)) >= 0);
}
// Check for dirty and veto
return this.handleDirty(arrays.flatten(groups.map(group => group.getEditors(true /* in MRU order */).map(editor => { return { group, editor }; })))).then(veto => {
const ignoreDirtyIfOpenedInOtherGroup = (groups.length === 1);
return this.handleDirty(arrays.flatten(groups.map(group => group.getEditors(true /* in MRU order */).map(editor => ({ group, editor })))), ignoreDirtyIfOpenedInOtherGroup).then(veto => {
if (veto) {
return;
}
groups.forEach(group => this.doCloseEditors(group));
groups.forEach(group => this.doCloseAllEditorsInGroup(group));
});
}
public closeEditors(position: Position, filter: { except?: EditorInput, direction?: Direction, unmodifiedOnly?: boolean } = Object.create(null)): TPromise<void> {
private doCloseAllEditorsInGroup(group: EditorGroup): void {
// Update stacks model: remove all non active editors first to prevent opening the next editor in group
group.closeEditors(group.activeEditor);
// Now close active editor in group which will close the group
this.doCloseActiveEditor(group);
}
private doCloseEditorsAtPosition(position: Position, filterOrEditors?: ICloseEditorsFilter | EditorInput[]): TPromise<void> {
const closeEditorsDetails = this.extractCloseEditorDetails(position, filterOrEditors);
if (!closeEditorsDetails) {
return TPromise.wrap(void 0);
}
const { group, editorsToClose, filter } = closeEditorsDetails;
// Check for dirty and veto
return this.handleDirty(editorsToClose, true /* ignore if opened in other group */).then(veto => {
if (veto) {
return void 0;
}
// Close with filter
if (filter) {
this.doCloseEditorsWithFilter(group, filter);
}
// Close without filter
else {
this.doCloseEditors(group, editorsToClose.map(e => e.editor));
}
return void 0;
});
}
private extractCloseEditorDetails(position: Position, filterOrEditors?: ICloseEditorsFilter | EditorInput[]): { group: EditorGroup, editorsToClose: EditorIdentifier[], filter?: ICloseEditorsFilter } {
const group = this.stacks.groupAt(position);
if (!group) {
return TPromise.wrap<void>(null);
return void 0;
}
let editorsToClose = group.getEditors(true /* in MRU order */);
let editorsToClose: EditorInput[];
let filter: ICloseEditorsFilter;
// Filter: unmodified only
if (filter.unmodifiedOnly) {
editorsToClose = editorsToClose.filter(e => !e.isDirty());
// Close: Specific Editors
if (Array.isArray(filterOrEditors)) {
editorsToClose = filterOrEditors;
}
// Filter: direction (left / right)
if (!types.isUndefinedOrNull(filter.direction)) {
editorsToClose = (filter.direction === Direction.LEFT) ? editorsToClose.slice(0, group.indexOf(filter.except)) : editorsToClose.slice(group.indexOf(filter.except) + 1);
}
// Filter: except
// Close: By Filter or all
else {
editorsToClose = editorsToClose.filter(e => !filter.except || !e.matches(filter.except));
}
editorsToClose = group.getEditors(true /* in MRU order */);
filter = filterOrEditors || Object.create(null);
// Check for dirty and veto
return this.handleDirty(editorsToClose.map(editor => { return { group, editor }; }), true /* ignore if opened in other group */).then(veto => {
if (veto) {
return;
// Filter: saved only
if (filter.savedOnly) {
editorsToClose = editorsToClose.filter(e => !e.isDirty());
}
this.doCloseEditors(group, filter);
});
// Filter: direction (left / right)
else if (!types.isUndefinedOrNull(filter.direction)) {
editorsToClose = (filter.direction === Direction.LEFT) ? editorsToClose.slice(0, group.indexOf(filter.except)) : editorsToClose.slice(group.indexOf(filter.except) + 1);
}
// Filter: except
else if (filter.except) {
editorsToClose = editorsToClose.filter(e => !e.matches(filter.except));
}
}
return { group, editorsToClose: editorsToClose.map(editor => ({ editor, group })), filter };
}
private doCloseEditors(group: EditorGroup, filter: { except?: EditorInput, direction?: Direction, unmodifiedOnly?: boolean } = Object.create(null)): void {
private doCloseEditors(group: EditorGroup, editors: EditorInput[]): void {
// Close all editors in group
if (editors.length === group.count) {
this.doCloseAllEditorsInGroup(group);
}
// Close specific editors in group
else {
// Editors to close are not active, so we can just close them
if (!editors.some(editor => group.activeEditor.matches(editor))) {
editors.forEach(editor => this.doCloseInactiveEditor(group, editor));
}
// Active editor is also a candidate to close, thus we make the first
// non-candidate editor active and then close the other ones
else {
const firstEditorToKeep = group.getEditors(true).filter(editorInGroup => !editors.some(editor => editor.matches(editorInGroup)))[0];
this.openEditor(firstEditorToKeep, null, this.stacks.positionOfGroup(group)).done(() => {
editors.forEach(editor => this.doCloseInactiveEditor(group, editor));
}, errors.onUnexpectedError);
}
}
}
private doCloseEditorsWithFilter(group: EditorGroup, filter: { except?: EditorInput, direction?: Direction, savedOnly?: boolean }): void {
// Close all editors if there is no editor to except and
// we either are not only closing unmodified editors or
// we either are not only closing saved editors or
// there are no dirty editors.
let closeAllEditors = false;
if (!filter.except) {
if (!filter.unmodifiedOnly) {
if (!filter.savedOnly) {
closeAllEditors = true;
} else {
closeAllEditors = !group.getEditors().some(e => e.isDirty());
@@ -758,18 +898,13 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
// Close all editors in group
if (closeAllEditors) {
// Update stacks model: remove all non active editors first to prevent opening the next editor in group
group.closeEditors(group.activeEditor);
// Now close active editor in group which will close the group
this.doCloseActiveEditor(group);
this.doCloseAllEditorsInGroup(group);
}
// Close unmodified editors in group
else if (filter.unmodifiedOnly) {
// Close saved editors in group
else if (filter.savedOnly) {
// We can just close all unmodified editors around the currently active dirty one
// We can just close all saved editors around the currently active dirty one
if (group.activeEditor.isDirty()) {
group.getEditors().filter(editor => !editor.isDirty() && !editor.matches(filter.except)).forEach(editor => this.doCloseInactiveEditor(group, editor));
}
@@ -777,10 +912,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
// Active editor is also a candidate to close, thus we make the first dirty editor
// active and then close the other ones
else {
const firstDirtyEditor = group.getEditors().filter(editor => editor.isDirty())[0];
const firstDirtyEditor = group.getEditors(true).filter(editor => editor.isDirty())[0];
this.openEditor(firstDirtyEditor, null, this.stacks.positionOfGroup(group)).done(() => {
this.doCloseEditors(group, filter);
this.doCloseEditorsWithFilter(group, filter);
}, errors.onUnexpectedError);
}
}
@@ -801,7 +936,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
// being the expected one, otherwise we end up in an endless loop trying to open the
// editor
if (filter.except.matches(group.activeEditor)) {
this.doCloseEditors(group, filter);
this.doCloseEditorsWithFilter(group, filter);
}
}, errors.onUnexpectedError);
}
@@ -830,14 +965,30 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
// Switch to editor that we want to handle
return this.openEditor(identifier.editor, null, this.stacks.positionOfGroup(identifier.group)).then(() => {
return this.ensureEditorOpenedBeforePrompt().then(() => {
const res = editor.confirmSave();
return editor.confirmSave().then(res => {
// It could be that the editor saved meanwhile, so we check again
// to see if anything needs to happen before closing for good.
// This can happen for example if autoSave: onFocusChange is configured
// so that the save happens when the dialog opens.
if (!editor.isDirty()) {
return res === ConfirmResult.CANCEL ? true : false;
}
// Otherwise, handle accordingly
switch (res) {
case ConfirmResult.SAVE:
return editor.save().then(ok => !ok);
case ConfirmResult.DONT_SAVE:
return editor.revert().then(ok => !ok);
// first try a normal revert where the contents of the editor are restored
return editor.revert().then(ok => !ok, error => {
// if that fails, since we are about to close the editor, we accept that
// the editor cannot be reverted and instead do a soft revert that just
// enables us to close the editor. With this, a user can always close a
// dirty editor even when reverting fails.
return editor.revert({ soft: true }).then(ok => !ok);
});
case ConfirmResult.CANCEL:
return true; // veto
@@ -846,27 +997,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
});
}
private ensureEditorOpenedBeforePrompt(): TPromise<void> {
// Force title area update
this.editorGroupsControl.updateTitleAreas(true /* refresh active group */);
// TODO@Ben our dialogs currently use the sync API, which means they block the JS
// thread when showing. As such, any UI update will not happen unless we wait a little
// bit. We wait for 2 request animation frames before showing the confirm. The first
// frame is where the UI is updating and the second is good enough to bring up the dialog.
// See also https://github.com/Microsoft/vscode/issues/39536
return new TPromise<void>(c => {
scheduleAtNextAnimationFrame(() => {
// Here the UI is updating
scheduleAtNextAnimationFrame(() => {
// Here we can show a blocking dialog
c(void 0);
});
});
});
}
private countEditors(editor: EditorInput): number {
const editors = [editor];
if (editor instanceof SideBySideEditorInput) {
@@ -1123,7 +1253,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
return res;
}
public openEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[]): TPromise<IEditor[]> {
public openEditors(editors: { input: EditorInput, position?: Position, options?: EditorOptions }[]): TPromise<IEditor[]>;
public openEditors(editors: { input: EditorInput, options?: EditorOptions }[], sideBySide?: boolean): TPromise<IEditor[]>;
public openEditors(editors: { input: EditorInput, position?: Position, options?: EditorOptions }[], sideBySide?: boolean): TPromise<IEditor[]> {
if (!editors.length) {
return TPromise.as<IEditor[]>([]);
}
@@ -1135,7 +1267,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
const ratio = this.editorGroupsControl.getRatio();
return this.doOpenEditors(editors, activePosition, ratio);
return this.doOpenEditors(editors, activePosition, ratio, sideBySide);
}
public hasEditorsToRestore(): boolean {
@@ -1166,7 +1298,15 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
return this._onEditorsChanged.throttle(this.doOpenEditors(editors, activePosition, editorState && editorState.ratio));
}
private doOpenEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[], activePosition?: number, ratio?: number[]): TPromise<IEditor[]> {
private doOpenEditors(editors: { input: EditorInput, position?: Position, options?: EditorOptions }[], activePosition?: number, ratio?: number[], sideBySide?: boolean): TPromise<IEditor[]> {
// Find position if not provided already from calling side
editors.forEach(editor => {
if (typeof editor.position !== 'number') {
editor.position = this.findPosition(editor.input, editor.options, sideBySide);
}
});
const positionOneEditors = editors.filter(e => e.position === Position.ONE);
const positionTwoEditors = editors.filter(e => e.position === Position.TWO);
const positionThreeEditors = editors.filter(e => e.position === Position.THREE);
@@ -1422,8 +1562,14 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
return Position.ONE; // can only be ONE
}
// Ignore revealIfVisible/revealIfOpened option if we got instructed explicitly to
// * open at a specific index
// * open to the side
// * open in a specific group
const skipReveal = (options && options.index) || arg1 === true /* open to side */ || typeof arg1 === 'number' /* open specific group */;
// Respect option to reveal an editor if it is already visible
if (options && options.revealIfVisible) {
if (!skipReveal && options && options.revealIfVisible) {
const group = this.stacks.findGroup(input, true);
if (group) {
return this.stacks.positionOfGroup(group);
@@ -1431,8 +1577,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
}
// Respect option to reveal an editor if it is open (not necessarily visible)
const skipRevealIfOpen = (options && options.index) || arg1 === true /* open to side */ || typeof arg1 === 'number' /* open specific group */;
if (!skipRevealIfOpen && (this.revealIfOpen || (options && options.revealIfOpened))) {
if (!skipReveal && (this.revealIfOpen || (options && options.revealIfOpened))) {
const group = this.stacks.findGroup(input);
if (group) {
return this.stacks.positionOfGroup(group);

View File

@@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import nls = require('vs/nls');
import URI from 'vs/base/common/uri';
import errors = require('vs/base/common/errors');
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { IModeService } from 'vs/editor/common/services/modeService';
@@ -39,7 +39,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup {
this.stacks = editorGroupService.getStacksModel();
}
public getLabelOptions(): IIconLabelOptions {
public getLabelOptions(): IIconLabelValueOptions {
return {
extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()),
italic: this._group.isPreview(this.editor)

View File

@@ -23,7 +23,8 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor';
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorAction, EndOfLineSequence, IModel } from 'vs/editor/common/editorCommon';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model';
import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/linesOperations';
import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/indentation';
@@ -49,15 +50,17 @@ import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/con
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { attachStylerCallback, attachButtonStyler } from 'vs/platform/theme/common/styler';
import { widgetShadow, editorWidgetBackground } from 'vs/platform/theme/common/colorRegistry';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { deepClone } from 'vs/base/common/objects';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Button } from 'vs/base/browser/ui/button/button';
import { Schemas } from 'vs/base/common/network';
// TODO@Sandeep layer breaker
// tslint:disable-next-line:import-patterns
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { deepClone } from 'vs/base/common/objects';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
// {{SQL CARBON EDIT}}
import { QueryEditorService } from 'sql/parts/query/services/queryEditorService';
@@ -768,7 +771,7 @@ function isWritableBaseEditor(e: IBaseEditor): boolean {
export class ShowLanguageExtensionsAction extends Action {
static ID = 'workbench.action.showLanguageExtensions';
static readonly ID = 'workbench.action.showLanguageExtensions';
constructor(
private fileExtension: string,
@@ -816,7 +819,7 @@ export class ChangeModeAction extends Action {
const resource = toResource(activeEditor.input, { supportSideBySide: true });
let hasLanguageSupport = !!resource;
if (resource.scheme === 'untitled' && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
if (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1")
}
@@ -888,7 +891,7 @@ export class ChangeModeAction extends Action {
picks.unshift(autoDetectMode);
}
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode") }).then(pick => {
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
if (!pick) {
return;
}
@@ -913,7 +916,7 @@ export class ChangeModeAction extends Action {
// Change mode for active editor
activeEditor = this.editorService.getActiveEditor();
const codeOrDiffEditor = getCodeOrDiffEditor(activeEditor);
const models: IModel[] = [];
const models: ITextModel[] = [];
if (codeOrDiffEditor.codeEditor) {
const codeEditorModel = codeOrDiffEditor.codeEditor.getModel();
if (codeEditorModel) {
@@ -1285,19 +1288,24 @@ class ScreenReaderDetectedExplanation {
const question = $('p.question', {}, nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code?"));
domNode.appendChild(question);
const yesBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"));
this._toDispose.push(addDisposableListener(yesBtn, 'click', () => {
const buttonContainer = $('div.buttons');
domNode.appendChild(buttonContainer);
const yesBtn = new Button(buttonContainer);
yesBtn.label = nls.localize('screenReaderDetectedExplanation.answerYes', "Yes");
this._toDispose.push(attachButtonStyler(yesBtn, this.themeService));
this._toDispose.push(yesBtn.onDidClick(e => {
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
this.contextViewService.hideContextView();
}));
domNode.appendChild(yesBtn);
const noBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerNo', "No"));
this._toDispose.push(addDisposableListener(noBtn, 'click', () => {
const noBtn = new Button(buttonContainer);
noBtn.label = nls.localize('screenReaderDetectedExplanation.answerNo', "No");
this._toDispose.push(attachButtonStyler(noBtn, this.themeService));
this._toDispose.push(noBtn.onDidClick(e => {
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
this.contextViewService.hideContextView();
}));
domNode.appendChild(noBtn);
const clear = $('div');
clear.style.clear = 'both';
@@ -1320,7 +1328,7 @@ class ScreenReaderDetectedExplanation {
this._toDispose.push(attachStylerCallback(this.themeService, { widgetShadow, editorWidgetBackground }, colors => {
domNode.style.backgroundColor = colors.editorWidgetBackground;
if (colors.widgetShadow) {
domNode.style.boxShadow = `0 2px 8px ${colors.widgetShadow}`;
domNode.style.boxShadow = `0 5px 8px ${colors.widgetShadow}`;
}
}));

View File

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

After

Width:  |  Height:  |  Size: 552 B

View File

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

After

Width:  |  Height:  |  Size: 552 B

View File

@@ -7,6 +7,11 @@
display: none; /* hide sashes while dragging editors around */
}
.monaco-workbench > .editor .one-editor-silo.centered .editor-container {
border-style: none solid;
border-width: 0px 1px;
}
#monaco-workbench-editor-move-overlay,
#monaco-workbench-editor-drop-overlay {
position: absolute;
@@ -84,4 +89,4 @@
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
height: calc(100% - 35px); /* Editor is below editor title */
}
}

View File

@@ -35,7 +35,6 @@
position: absolute;
top: 0;
right: 0;
margin: .5em 0 0;
padding: .5em;
width: 22px;
height: 22px;
@@ -56,20 +55,18 @@
.monaco-shell .screen-reader-detected-explanation p.question {
font-size: 1.4em;
font-weight: bold;
}
.monaco-shell .screen-reader-detected-explanation .button {
color: white;
border: none;
cursor: pointer;
background-color: #007ACC;
.monaco-shell .screen-reader-detected-explanation .buttons {
display: flex;
}
.monaco-shell .screen-reader-detected-explanation .buttons a {
font-size: 13px;
padding-left: 12px;
padding-right: 12px;
border: 4px solid #007ACC;
border-radius: 4px;
float: left;
margin-right: 5px;
max-width: fit-content;
}
.monaco-shell.vs .screen-reader-detected-explanation .cancel {

View File

@@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-resource-viewer:focus {
outline: none !important;
}
.monaco-resource-viewer {
padding: 5px 0 0 10px;
box-sizing: border-box;
}
.monaco-resource-viewer.image {
padding: 0;
background-position: 0 0, 8px 8px;
background-size: 16px 16px;
display: flex;
box-sizing: border-box;
}
.vs .monaco-resource-viewer.image {
background-image:
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230)),
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230));
}
.vs-dark .monaco-resource-viewer.image {
background-image:
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20)),
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20));
}
.monaco-resource-viewer img.pixelated {
image-rendering: pixelated;
}
.monaco-resource-viewer img.scale-to-fit {
max-width: calc(100% - 20px);
max-height: calc(100% - 20px);
object-fit: contain;
}
.monaco-resource-viewer img {
margin: auto;
}
.monaco-resource-viewer.zoom-in {
cursor: zoom-in;
}
.monaco-resource-viewer.zoom-out {
cursor: zoom-out;
}
.monaco-resource-viewer .open-external,
.monaco-resource-viewer .open-external:hover {
cursor: pointer;
text-decoration: underline;
}

View File

@@ -87,11 +87,33 @@
margin-bottom: auto;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink .tab-label {
position: relative;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
content: "";
position: absolute;
right: 0;
height: 100%;
width: 5px;
opacity: 1;
padding: 0;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-description-container {
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
text-overflow: clip;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
text-overflow: ellipsis;
}
.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 */
}

View File

@@ -19,4 +19,22 @@
.vs-dark .monaco-workbench .textdiff-editor-action.previous,
.hc-black .monaco-workbench .textdiff-editor-action.previous {
background: url('previous-diff-inverse.svg') center center no-repeat;
}
}
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
opacity: 1;
background: url('Paragraph_16x_nohalo.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace,
.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
opacity: 1;
background: url('Paragraph_16x_nohalo_inversep.svg') center center no-repeat;
}
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked {
opacity: 0.5;
}
.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked,
.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked {
opacity: 0.5;
}

View File

@@ -11,6 +11,11 @@
flex: 1;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo.centered > .container > .title .title-label {
flex-direction: row;
justify-content: center;
}
.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;
@@ -104,4 +109,4 @@
.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

@@ -7,7 +7,7 @@
import 'vs/css!./media/notabstitle';
import errors = require('vs/base/common/errors');
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
import { toResource } from 'vs/workbench/common/editor';
import DOM = require('vs/base/browser/dom');
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { ResourceLabel } from 'vs/workbench/browser/labels';
@@ -19,12 +19,6 @@ export class NoTabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private editorLabel: ResourceLabel;
public setContext(group: IEditorGroup): void {
super.setContext(group);
this.editorActionsToolbar.context = { group };
}
public create(parent: HTMLElement): void {
super.create(parent);
@@ -87,14 +81,14 @@ export class NoTabsTitleControl extends TitleControl {
// Close editor on middle mouse click
if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) {
this.closeEditorAction.run({ group, editor: group.activeEditor }).done(null, errors.onUnexpectedError);
this.closeEditorAction.run({ groupId: group.id, editorIndex: group.indexOf(group.activeEditor) }).done(null, errors.onUnexpectedError);
}
// Focus editor group unless:
// - click on toolbar: should trigger actions within
// - mouse click: do not focus group if there are more than one as it otherwise makes group DND funky
// - touch: always focus
else if ((this.stacks.groups.length === 1 || !(e instanceof MouseEvent)) && !DOM.isAncestor((e.target || e.srcElement) as HTMLElement, this.editorActionsToolbar.getContainer().getHTMLElement())) {
else if ((this.stacks.groups.length === 1 || !(e instanceof MouseEvent)) && !DOM.isAncestor(((e as GestureEvent).initialTarget || e.target || e.srcElement) as HTMLElement, this.editorActionsToolbar.getContainer().getHTMLElement())) {
this.editorGroupService.focusGroup(group);
}
}

View File

@@ -6,12 +6,12 @@
import { IDisposable } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import Event, { Emitter } from 'vs/base/common/event';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IRange } from 'vs/editor/common/core/range';
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
export interface IRangeHighlightDecoration {
resource: URI;
@@ -48,7 +48,7 @@ export class RangeHighlightDecorations implements IDisposable {
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
this.removeHighlightRange();
editor.changeDecorations((changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => {
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
});
this.setEditor(editor);
@@ -93,13 +93,13 @@ export class RangeHighlightDecorations implements IDisposable {
}
private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'rangeHighlight',
isWholeLine: true
});
private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'rangeHighlight'
});

View File

@@ -0,0 +1,590 @@
/*---------------------------------------------------------------------------------------------
* 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/resourceviewer';
import nls = require('vs/nls');
import mimes = require('vs/base/common/mime');
import URI from 'vs/base/common/uri';
import paths = require('vs/base/common/paths');
import { Builder, $, Dimension } from 'vs/base/browser/builder';
import DOM = require('vs/base/browser/dom');
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { LRUCache } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { clamp } from 'vs/base/common/numbers';
import { Themable } from 'vs/workbench/common/theme';
import { IStatusbarItem, StatusbarItemDescriptor, IStatusbarRegistry, Extensions, StatusbarAlignment } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { } from 'vs/platform/workspace/common/workspace';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Registry } from 'vs/platform/registry/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { memoize } from 'vs/base/common/decorators';
import * as platform from 'vs/base/common/platform';
interface MapExtToMediaMimes {
[index: string]: string;
}
// Known media mimes that we can handle
const mapExtToMediaMimes: MapExtToMediaMimes = {
'.bmp': 'image/bmp',
'.gif': 'image/gif',
'.jpg': 'image/jpg',
'.jpeg': 'image/jpg',
'.jpe': 'image/jpg',
'.png': 'image/png',
'.tiff': 'image/tiff',
'.tif': 'image/tiff',
'.ico': 'image/x-icon',
'.tga': 'image/x-tga',
'.psd': 'image/vnd.adobe.photoshop',
'.webp': 'image/webp',
'.mid': 'audio/midi',
'.midi': 'audio/midi',
'.mp4a': 'audio/mp4',
'.mpga': 'audio/mpeg',
'.mp2': 'audio/mpeg',
'.mp2a': 'audio/mpeg',
'.mp3': 'audio/mpeg',
'.m2a': 'audio/mpeg',
'.m3a': 'audio/mpeg',
'.oga': 'audio/ogg',
'.ogg': 'audio/ogg',
'.spx': 'audio/ogg',
'.aac': 'audio/x-aac',
'.wav': 'audio/x-wav',
'.wma': 'audio/x-ms-wma',
'.mp4': 'video/mp4',
'.mp4v': 'video/mp4',
'.mpg4': 'video/mp4',
'.mpeg': 'video/mpeg',
'.mpg': 'video/mpeg',
'.mpe': 'video/mpeg',
'.m1v': 'video/mpeg',
'.m2v': 'video/mpeg',
'.ogv': 'video/ogg',
'.qt': 'video/quicktime',
'.mov': 'video/quicktime',
'.webm': 'video/webm',
'.mkv': 'video/x-matroska',
'.mk3d': 'video/x-matroska',
'.mks': 'video/x-matroska',
'.wmv': 'video/x-ms-wmv',
'.flv': 'video/x-flv',
'.avi': 'video/x-msvideo',
'.movie': 'video/x-sgi-movie'
};
export interface IResourceDescriptor {
resource: URI;
name: string;
size: number;
etag: string;
mime: string;
}
class BinarySize {
public static readonly KB = 1024;
public static readonly MB = BinarySize.KB * BinarySize.KB;
public static readonly GB = BinarySize.MB * BinarySize.KB;
public static readonly TB = BinarySize.GB * BinarySize.KB;
public static formatSize(size: number): string {
if (size < BinarySize.KB) {
return nls.localize('sizeB', "{0}B", size);
}
if (size < BinarySize.MB) {
return nls.localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2));
}
if (size < BinarySize.GB) {
return nls.localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2));
}
if (size < BinarySize.TB) {
return nls.localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2));
}
return nls.localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2));
}
}
export interface ResourceViewerContext {
layout(dimension: Dimension): void;
}
/**
* Helper to actually render the given resource into the provided container. Will adjust scrollbar (if provided) automatically based on loading
* progress of the binary resource.
*/
export class ResourceViewer {
public static show(
descriptor: IResourceDescriptor,
container: Builder,
scrollbar: DomScrollableElement,
openExternal: (uri: URI) => void,
metadataClb: (meta: string) => void
): ResourceViewerContext {
// Ensure CSS class
$(container).setClass('monaco-resource-viewer');
if (ResourceViewer.isImageResource(descriptor)) {
return ImageView.create(container, descriptor, scrollbar, openExternal, metadataClb);
}
GenericBinaryFileView.create(container, metadataClb, descriptor, scrollbar);
return null;
}
private static isImageResource(descriptor: IResourceDescriptor) {
const mime = ResourceViewer.getMime(descriptor);
return mime.indexOf('image/') >= 0;
}
private static getMime(descriptor: IResourceDescriptor): string {
let mime = descriptor.mime;
if (!mime && descriptor.resource.scheme !== Schemas.data) {
const ext = paths.extname(descriptor.resource.toString());
if (ext) {
mime = mapExtToMediaMimes[ext.toLowerCase()];
}
}
return mime || mimes.MIME_BINARY;
}
}
class ImageView {
private static readonly MAX_IMAGE_SIZE = BinarySize.MB; // showing images inline is memory intense, so we have a limit
private static readonly BASE64_MARKER = 'base64,';
public static create(
container: Builder,
descriptor: IResourceDescriptor,
scrollbar: DomScrollableElement,
openExternal: (uri: URI) => void,
metadataClb: (meta: string) => void
): ResourceViewerContext | null {
if (ImageView.shouldShowImageInline(descriptor)) {
return InlineImageView.create(container, descriptor, scrollbar, metadataClb);
}
LargeImageView.create(container, descriptor, openExternal);
return null;
}
private static shouldShowImageInline(descriptor: IResourceDescriptor): boolean {
let skipInlineImage: boolean;
// Data URI
if (descriptor.resource.scheme === Schemas.data) {
const base64MarkerIndex = descriptor.resource.path.indexOf(ImageView.BASE64_MARKER);
const hasData = base64MarkerIndex >= 0 && descriptor.resource.path.substring(base64MarkerIndex + ImageView.BASE64_MARKER.length).length > 0;
skipInlineImage = !hasData || descriptor.size > ImageView.MAX_IMAGE_SIZE || descriptor.resource.path.length > ImageView.MAX_IMAGE_SIZE;
}
// File URI
else {
skipInlineImage = typeof descriptor.size !== 'number' || descriptor.size > ImageView.MAX_IMAGE_SIZE;
}
return !skipInlineImage;
}
}
class LargeImageView {
public static create(
container: Builder,
descriptor: IResourceDescriptor,
openExternal: (uri: URI) => void
) {
const imageContainer = $(container)
.empty()
.p({
text: nls.localize('largeImageError', "The file size of the image is too large (>1MB) to display in the editor. ")
});
if (descriptor.resource.scheme !== Schemas.data) {
imageContainer.append($('a', {
role: 'button',
class: 'open-external',
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
}).on(DOM.EventType.CLICK, (e) => {
openExternal(descriptor.resource);
}));
}
}
}
class GenericBinaryFileView {
public static create(
container: Builder,
metadataClb: (meta: string) => void,
descriptor: IResourceDescriptor,
scrollbar: DomScrollableElement
) {
$(container)
.empty()
.span({
text: nls.localize('nativeBinaryError', "The file will not be displayed in the editor because it is either binary, very large or uses an unsupported text encoding.")
});
if (metadataClb) {
metadataClb(BinarySize.formatSize(descriptor.size));
}
scrollbar.scanDomNode();
}
}
type Scale = number | 'fit';
class ZoomStatusbarItem extends Themable implements IStatusbarItem {
showTimeout: number;
public static instance: ZoomStatusbarItem;
private statusBarItem: HTMLElement;
private onSelectScale?: (scale: Scale) => void;
constructor(
@IContextMenuService private contextMenuService: IContextMenuService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IThemeService themeService: IThemeService
) {
super(themeService);
ZoomStatusbarItem.instance = this;
this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
}
private onEditorsChanged(): void {
this.hide();
this.onSelectScale = undefined;
}
public show(scale: Scale, onSelectScale: (scale: number) => void) {
clearTimeout(this.showTimeout);
this.showTimeout = setTimeout(() => {
this.onSelectScale = onSelectScale;
this.statusBarItem.style.display = 'block';
this.updateLabel(scale);
}, 0);
}
public hide() {
this.statusBarItem.style.display = 'none';
}
public render(container: HTMLElement): IDisposable {
if (!this.statusBarItem && container) {
this.statusBarItem = $(container).a()
.addClass('.zoom-statusbar-item')
.on('click', () => {
this.contextMenuService.showContextMenu({
getAnchor: () => container,
getActions: () => TPromise.as(this.zoomActions)
});
})
.getHTMLElement();
this.statusBarItem.style.display = 'none';
}
return this;
}
private updateLabel(scale: Scale) {
this.statusBarItem.textContent = ZoomStatusbarItem.zoomLabel(scale);
}
@memoize
private get zoomActions(): Action[] {
const scales: Scale[] = [10, 5, 2, 1, 0.5, 0.2, 'fit'];
return scales.map(scale =>
new Action('zoom.' + scale, ZoomStatusbarItem.zoomLabel(scale), undefined, undefined, () => {
if (this.onSelectScale) {
this.onSelectScale(scale);
}
return null;
}));
}
private static zoomLabel(scale: Scale): string {
return scale === 'fit'
? nls.localize('zoom.action.fit.label', 'Whole Image')
: `${Math.round(scale * 100)}%`;
}
}
Registry.as<IStatusbarRegistry>(Extensions.Statusbar).registerStatusbarItem(
new StatusbarItemDescriptor(ZoomStatusbarItem, StatusbarAlignment.RIGHT, 101 /* to the left of editor status (100) */)
);
interface ImageState {
scale: Scale;
offsetX: number;
offsetY: number;
}
class InlineImageView {
private static readonly SCALE_PINCH_FACTOR = 0.075;
private static readonly MAX_SCALE = 20;
private static readonly MIN_SCALE = 0.1;
private static readonly zoomLevels: Scale[] = [
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
0.9,
1,
1.5,
2,
3,
5,
7,
10,
15,
20
];
/**
* Enable image-rendering: pixelated for images scaled by more than this.
*/
private static readonly PIXELATION_THRESHOLD = 3;
/**
* Chrome is caching images very aggressively and so we use the ETag information to find out if
* we need to bypass the cache or not. We could always bypass the cache everytime we show the image
* however that has very bad impact on memory consumption because each time the image gets shown,
* memory grows (see also https://github.com/electron/electron/issues/6275)
*/
private static IMAGE_RESOURCE_ETAG_CACHE = new LRUCache<string, { etag: string, src: string }>(100);
/**
* Store the scale and position of an image so it can be restored when changing editor tabs
*/
private static readonly imageStateCache = new LRUCache<string, ImageState>(100);
public static create(
container: Builder,
descriptor: IResourceDescriptor,
scrollbar: DomScrollableElement,
metadataClb: (meta: string) => void
) {
const context = {
layout(dimension: Dimension) { }
};
const cacheKey = descriptor.resource.toString();
let ctrlPressed = false;
let altPressed = false;
const initialState: ImageState = InlineImageView.imageStateCache.get(cacheKey) || { scale: 'fit', offsetX: 0, offsetY: 0 };
let scale = initialState.scale;
let img: Builder | null = null;
let imgElement: HTMLImageElement | null = null;
function updateScale(newScale: Scale) {
if (!img || !imgElement.parentElement) {
return;
}
if (newScale === 'fit') {
scale = 'fit';
img.addClass('scale-to-fit');
img.removeClass('pixelated');
img.style('min-width', 'auto');
img.style('width', 'auto');
InlineImageView.imageStateCache.set(cacheKey, null);
} else {
const oldWidth = imgElement.width;
const oldHeight = imgElement.height;
scale = clamp(newScale, InlineImageView.MIN_SCALE, InlineImageView.MAX_SCALE);
if (scale >= InlineImageView.PIXELATION_THRESHOLD) {
img.addClass('pixelated');
} else {
img.removeClass('pixelated');
}
const { scrollTop, scrollLeft } = imgElement.parentElement;
const dx = (scrollLeft + imgElement.parentElement.clientWidth / 2) / imgElement.parentElement.scrollWidth;
const dy = (scrollTop + imgElement.parentElement.clientHeight / 2) / imgElement.parentElement.scrollHeight;
img.removeClass('scale-to-fit');
img.style('min-width', `${(imgElement.naturalWidth * scale)}px`);
img.style('width', `${(imgElement.naturalWidth * scale)}px`);
const newWidth = imgElement.width;
const scaleFactor = (newWidth - oldWidth) / oldWidth;
const newScrollLeft = ((oldWidth * scaleFactor * dx) + scrollLeft);
const newScrollTop = ((oldHeight * scaleFactor * dy) + scrollTop);
scrollbar.setScrollPosition({
scrollLeft: newScrollLeft,
scrollTop: newScrollTop,
});
InlineImageView.imageStateCache.set(cacheKey, { scale: scale, offsetX: newScrollLeft, offsetY: newScrollTop });
}
ZoomStatusbarItem.instance.show(scale, updateScale);
scrollbar.scanDomNode();
}
function firstZoom() {
scale = imgElement.clientWidth / imgElement.naturalWidth;
updateScale(scale);
}
$(container)
.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent, c) => {
if (!img) {
return;
}
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
if (platform.isMacintosh ? altPressed : ctrlPressed) {
c.removeClass('zoom-in').addClass('zoom-out');
}
})
.on(DOM.EventType.KEY_UP, (e: KeyboardEvent, c) => {
if (!img) {
return;
}
ctrlPressed = e.ctrlKey;
altPressed = e.altKey;
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) {
c.removeClass('zoom-out').addClass('zoom-in');
}
})
.on(DOM.EventType.CLICK, (e: MouseEvent) => {
if (!img) {
return;
}
if (e.button !== 0) {
return;
}
// left click
if (scale === 'fit') {
firstZoom();
}
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) { // zoom in
let i = 0;
for (; i < InlineImageView.zoomLevels.length; ++i) {
if (InlineImageView.zoomLevels[i] > scale) {
break;
}
}
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MAX_SCALE);
} else {
let i = InlineImageView.zoomLevels.length - 1;
for (; i >= 0; --i) {
if (InlineImageView.zoomLevels[i] < scale) {
break;
}
}
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MIN_SCALE);
}
})
.on(DOM.EventType.WHEEL, (e: WheelEvent) => {
if (!img) {
return;
}
const isScrollWhellKeyPressed = platform.isMacintosh ? altPressed : ctrlPressed;
if (!isScrollWhellKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl
return;
}
e.preventDefault();
e.stopPropagation();
if (scale === 'fit') {
firstZoom();
}
let delta = e.deltaY < 0 ? 1 : -1;
// Pinching should increase the scale
if (e.ctrlKey && !isScrollWhellKeyPressed) {
delta *= -1;
}
updateScale(scale as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR));
})
.on(DOM.EventType.SCROLL, () => {
if (!imgElement || !imgElement.parentElement || scale === 'fit') {
return;
}
const entry = InlineImageView.imageStateCache.get(cacheKey);
if (entry) {
const { scrollTop, scrollLeft } = imgElement.parentElement;
InlineImageView.imageStateCache.set(cacheKey, { scale: entry.scale, offsetX: scrollLeft, offsetY: scrollTop });
}
});
$(container)
.empty()
.addClass('image', 'zoom-in')
.img({ src: InlineImageView.imageSrc(descriptor) })
.style('visibility', 'hidden')
.addClass('scale-to-fit')
.on(DOM.EventType.LOAD, (e, i) => {
img = i;
imgElement = img.getHTMLElement() as HTMLImageElement;
metadataClb(nls.localize('imgMeta', '{0}x{1} {2}', imgElement.naturalWidth, imgElement.naturalHeight, BinarySize.formatSize(descriptor.size)));
scrollbar.scanDomNode();
img.style('visibility', 'visible');
updateScale(scale);
if (initialState.scale !== 'fit') {
scrollbar.setScrollPosition({
scrollLeft: initialState.offsetX,
scrollTop: initialState.offsetY,
});
}
});
return context;
}
private static imageSrc(descriptor: IResourceDescriptor): string {
if (descriptor.resource.scheme === Schemas.data) {
return descriptor.resource.toString(true /* skip encoding */);
}
const src = descriptor.resource.toString();
let cached = InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.get(src);
if (!cached) {
cached = { etag: descriptor.etag, src };
InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.set(src, cached);
}
if (cached.etag !== descriptor.etag) {
cached.etag = descriptor.etag;
cached.src = `${src}?${Date.now()}`; // bypass cache with this trick
}
return cached.src;
}
}

View File

@@ -21,7 +21,7 @@ import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/br
export class SideBySideEditor extends BaseEditor {
public static ID: string = 'workbench.editor.sidebysideEditor';
public static readonly ID: string = 'workbench.editor.sidebysideEditor';
private dimension: Dimension;

View File

@@ -11,8 +11,7 @@ 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 { shorten } 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';
@@ -24,32 +23,30 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } 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 { TitleControl } 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/workbench/browser/editor';
import { getOrSet } from 'vs/base/common/map';
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';
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_BACKGROUND, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { Dimension } from 'vs/base/browser/builder';
import { scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { ResourcesDropHandler, fillResourceDataTransfers, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
import { Color } from 'vs/base/common/color';
import { INotificationService } from 'vs/platform/notification/common/notification';
// {{SQL CARBON EDIT}} -- Display the editor's tab color
import { HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { Color } from 'vs/base/common/color';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import * as QueryConstants from 'sql/parts/query/common/constants';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
@@ -77,6 +74,7 @@ export class TabsTitleControl extends TitleControl {
private blockRevealActiveTab: boolean;
private dimension: Dimension;
private layoutScheduled: IDisposable;
private transfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
constructor(
@IContextMenuService contextMenuService: IContextMenuService,
@@ -86,22 +84,17 @@ export class TabsTitleControl extends TitleControl {
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService keybindingService: IKeybindingService,
@ITelemetryService telemetryService: ITelemetryService,
@IMessageService messageService: IMessageService,
@INotificationService notificationService: INotificationService,
@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,
// {{SQL CARBON EDIT}} -- Display the editor's tab color
@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
@IConnectionManagementService private connectionService: IConnectionManagementService,
@IQueryEditorService private queryEditorService: IQueryEditorService,
@IObjectExplorerService private objectExplorerService: IObjectExplorerService,
@IWorkbenchEditorService private workbenchEditorService: IWorkbenchEditorService,
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
) {
super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService, themeService);
super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService);
this.tabDisposeables = [];
this.editorLabels = [];
@@ -135,12 +128,6 @@ export class TabsTitleControl extends TitleControl {
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);
@@ -196,7 +183,7 @@ export class TabsTitleControl extends TitleControl {
// Drag over
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_OVER, (e: DragEvent) => {
const draggedEditor = TabsTitleControl.getDraggedEditor();
const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
// 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
@@ -309,7 +296,9 @@ export class TabsTitleControl extends TitleControl {
const isGroupActive = this.stacks.isActive(group);
if (isGroupActive) {
DOM.addClass(this.titleContainer, 'active');
DOM.removeClass(this.titleContainer, 'inactive');
} else {
DOM.addClass(this.titleContainer, 'inactive');
DOM.removeClass(this.titleContainer, 'active');
}
@@ -583,7 +572,10 @@ export class TabsTitleControl extends TitleControl {
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) });
const actionRunner = new TabActionRunner(() => this.context, index);
this.tabDisposeables.push(actionRunner);
const bar = new ActionBar(tabCloseContainer, { ariaLabel: nls.localize('araLabelTabActions', "Tab actions"), actionRunner });
bar.push(this.closeEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeEditorAction) });
// Eventing
@@ -659,7 +651,7 @@ export class TabsTitleControl extends TitleControl {
private hookTabListeners(tab: HTMLElement, index: number): IDisposable {
const disposables: IDisposable[] = [];
const handleClickOrTouch = (e: MouseEvent | GestureEvent) => {
const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => {
tab.blur();
if (e instanceof MouseEvent && e.button !== 0) {
@@ -670,8 +662,8 @@ export class TabsTitleControl extends TitleControl {
return void 0; // only for left mouse click
}
const { editor, position } = this.toTabContext(index);
if (!this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
const { editor, position } = this.getGroupPositionAndEditor(index);
if (!this.isTabActionBar(((e as GestureEvent).initialTarget || 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
}
@@ -681,7 +673,7 @@ export class TabsTitleControl extends TitleControl {
const showContextMenu = (e: Event) => {
DOM.EventHelper.stop(e);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.onContextMenu({ group, editor }, e, tab);
};
@@ -703,7 +695,7 @@ export class TabsTitleControl extends TitleControl {
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);
this.closeEditorAction.run({ groupId: this.context.id, editorIndex: index }).done(null, errors.onUnexpectedError);
}
}));
@@ -725,7 +717,7 @@ export class TabsTitleControl extends TitleControl {
const event = new StandardKeyboardEvent(e);
let handled = false;
const { group, position, editor } = this.toTabContext(index);
const { group, position, editor } = this.getGroupPositionAndEditor(index);
// Run action on Enter/Space
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
@@ -768,7 +760,7 @@ export class TabsTitleControl extends TitleControl {
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DBLCLICK, (e: MouseEvent) => {
DOM.EventHelper.stop(e);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.editorGroupService.pinEditor(group, editor);
}));
@@ -776,28 +768,23 @@ export class TabsTitleControl extends TitleControl {
// Context menu
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.CONTEXT_MENU, (e: Event) => {
DOM.EventHelper.stop(e, true);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(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);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.transfer.setData([new DraggedEditorIdentifier({ editor, group })], DraggedEditorIdentifier.prototype);
this.onEditorDragStart({ editor, group });
e.dataTransfer.effectAllowed = 'copyMove';
// Insert transfer accordingly
// Apply some datatransfer types to allow for dragging the element outside of the application
const resource = toResource(editor, { supportSideBySide: true });
if (resource) {
const resourceStr = resource.toString();
e.dataTransfer.setData('URL', resourceStr); // enables cross window DND of tabs
e.dataTransfer.setData('text/plain', getPathLabel(resource)); // enables dropping tab resource path into text controls
if (resource.scheme === 'file') {
e.dataTransfer.setData('DownloadURL', [MIME_BINARY, editor.getName(), resourceStr].join(':')); // enables support to drag a tab as file to desktop
}
this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
}
// Fixes https://github.com/Microsoft/vscode/issues/18733
@@ -818,9 +805,9 @@ export class TabsTitleControl extends TitleControl {
// Find out if the currently dragged editor is this tab and in that
// case we do not want to show any drop feedback
let draggedEditorIsTab = false;
const draggedEditor = TabsTitleControl.getDraggedEditor();
const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
if (draggedEditor) {
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
if (draggedEditor.editor === editor && draggedEditor.group === group) {
draggedEditorIsTab = true;
}
@@ -848,7 +835,7 @@ export class TabsTitleControl extends TitleControl {
DOM.removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);
this.onEditorDragEnd();
this.transfer.clearData();
}));
// Drop
@@ -857,7 +844,7 @@ export class TabsTitleControl extends TitleControl {
DOM.removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);
const { group, position } = this.toTabContext(index);
const { group, position } = this.getGroupPositionAndEditor(index);
this.onDrop(e, group, position, index);
}));
@@ -869,7 +856,7 @@ export class TabsTitleControl extends TitleControl {
return !!DOM.findParentWithClass(element, 'monaco-action-bar', 'tab');
}
private toTabContext(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
private getGroupPositionAndEditor(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
const group = this.context;
const position = this.stacks.positionOfGroup(group);
const editor = group.getEditor(index);
@@ -878,13 +865,14 @@ export class TabsTitleControl extends TitleControl {
}
private onDrop(e: DragEvent, group: IEditorGroup, targetPosition: Position, targetIndex: number): void {
DOM.EventHelper.stop(e, true);
this.updateDropFeedback(this.tabsContainer, false);
DOM.removeClass(this.tabsContainer, 'scroll');
// Local DND
const draggedEditor = TabsTitleControl.getDraggedEditor();
const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
if (draggedEditor) {
DOM.EventHelper.stop(e, true);
// Move editor to target position and index
if (this.isMoveOperation(e, draggedEditor.group, group)) {
@@ -896,42 +884,13 @@ export class TabsTitleControl extends TitleControl {
this.editorService.openEditor(draggedEditor.editor, { pinned: true, index: targetIndex }, targetPosition).done(null, errors.onUnexpectedError);
}
this.onEditorDragEnd();
this.transfer.clearData();
}
// 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);
});
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false /* open workspace file as file if dropped */ });
dropHandler.handleDrop(e, () => this.editorGroupService.focusGroup(targetPosition), targetPosition, targetIndex);
}
}
@@ -970,7 +929,10 @@ export class TabsTitleControl extends TitleControl {
class TabActionRunner extends ActionRunner {
constructor(private group: () => IEditorGroup, private index: number) {
constructor(
private group: () => IEditorGroup,
private index: number
) {
super();
}
@@ -980,7 +942,7 @@ class TabActionRunner extends ActionRunner {
return TPromise.as(void 0);
}
return super.run(action, { group, editor: group.getEditor(this.index) });
return super.run(action, { groupId: group.id, editorIndex: this.index });
}
}
@@ -1009,4 +971,134 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
}
`);
}
});
// Hover Background
const tabHoverBackground = theme.getColor(TAB_HOVER_BACKGROUND);
if (tabHoverBackground) {
collector.addRule(`
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover {
background: ${tabHoverBackground} !important;
}
`);
}
const tabUnfocusedHoverBackground = theme.getColor(TAB_UNFOCUSED_HOVER_BACKGROUND);
if (tabUnfocusedHoverBackground) {
collector.addRule(`
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.inactive .tabs-container > .tab:hover {
background: ${tabUnfocusedHoverBackground} !important;
}
`);
}
// Hover Border
const tabHoverBorder = theme.getColor(TAB_HOVER_BORDER);
if (tabHoverBorder) {
collector.addRule(`
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover {
box-shadow: ${tabHoverBorder} 0 -1px inset !important;
}
`);
}
const tabUnfocusedHoverBorder = theme.getColor(TAB_UNFOCUSED_HOVER_BORDER);
if (tabUnfocusedHoverBorder) {
collector.addRule(`
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.inactive .tabs-container > .tab:hover {
box-shadow: ${tabUnfocusedHoverBorder} 0 -1px inset !important;
}
`);
}
// Fade out styles via linear gradient (when tabs are set to shrink)
if (theme.type !== 'hc') {
const workbenchBackground = WORKBENCH_BACKGROUND(theme);
const editorBackgroundColor = theme.getColor(editorBackground);
const editorGroupBackground = theme.getColor(EDITOR_GROUP_BACKGROUND);
const editorGroupHeaderTabsBackground = theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND);
const editorDragAndDropBackground = theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND);
let adjustedTabBackground: Color;
if (editorGroupHeaderTabsBackground && editorBackgroundColor && editorGroupBackground) {
adjustedTabBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorGroupBackground, editorBackgroundColor, workbenchBackground);
}
let adjustedTabDragBackground: Color;
if (editorGroupHeaderTabsBackground && editorBackgroundColor && editorDragAndDropBackground && editorBackgroundColor) {
adjustedTabDragBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorDragAndDropBackground, editorBackgroundColor, workbenchBackground);
}
// Adjust gradient for (focused) hover background
if (tabHoverBackground && adjustedTabBackground && adjustedTabDragBackground) {
const adjustedColor = tabHoverBackground.flatten(adjustedTabBackground);
const adjustedColorDrag = tabHoverBackground.flatten(adjustedTabDragBackground);
collector.addRule(`
.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
background: linear-gradient(to left, ${adjustedColor}, transparent);
}
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
}
`);
}
// Adjust gradient for unfocused hover background
if (tabUnfocusedHoverBackground && adjustedTabBackground && adjustedTabDragBackground) {
const adjustedColor = tabUnfocusedHoverBackground.flatten(adjustedTabBackground);
const adjustedColorDrag = tabUnfocusedHoverBackground.flatten(adjustedTabDragBackground);
collector.addRule(`
.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
background: linear-gradient(to left, ${adjustedColor}, transparent);
}
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
}
`);
}
// Adjust gradient for drag and drop background
if (editorDragAndDropBackground && adjustedTabDragBackground) {
const adjustedColorDrag = editorDragAndDropBackground.flatten(adjustedTabDragBackground);
collector.addRule(`
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged) > .tab-label::after,
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
}
`);
}
// Adjust gradient for active tab background
const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND);
if (tabActiveBackground && adjustedTabBackground && adjustedTabDragBackground) {
const adjustedColor = tabActiveBackground.flatten(adjustedTabBackground);
const adjustedColorDrag = tabActiveBackground.flatten(adjustedTabDragBackground);
collector.addRule(`
.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
background: linear-gradient(to left, ${adjustedColor}, transparent);
}
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
}
`);
}
// Adjust gradient for inactive tab background
const tabInactiveBackground = theme.getColor(TAB_INACTIVE_BACKGROUND);
if (tabInactiveBackground && adjustedTabBackground && adjustedTabDragBackground) {
const adjustedColor = tabInactiveBackground.flatten(adjustedTabBackground);
const adjustedColorDrag = tabInactiveBackground.flatten(adjustedTabDragBackground);
collector.addRule(`
.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink:not(.dragged) > .tab-label::after {
background: linear-gradient(to left, ${adjustedColor}, transparent);
}
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink:not(.dragged) > .tab-label::after {
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
}
`);
}
}
});

View File

@@ -16,7 +16,7 @@ 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 { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } 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';
@@ -32,8 +32,10 @@ import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/wo
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorInput } from 'vs/platform/editor/common/editor';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
/**
* The text editor that leverages the diff text editor for the editing experience.
@@ -45,18 +47,27 @@ export class TextDiffEditor extends BaseTextEditor {
private diffNavigator: DiffNavigator;
private nextDiffAction: NavigateAction;
private previousDiffAction: NavigateAction;
private toggleIgnoreTrimWhitespaceAction: ToggleIgnoreTrimWhitespaceAction;
private _configurationListener: IDisposable;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IConfigurationService private readonly _actualConfigurationService: IConfigurationService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IThemeService themeService: IThemeService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@ITextFileService textFileService: ITextFileService
) {
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
this._configurationListener = this._actualConfigurationService.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('diffEditor.ignoreTrimWhitespace')) {
this.updateIgnoreTrimWhitespaceAction();
}
});
}
public getTitle(): string {
@@ -72,6 +83,8 @@ export class TextDiffEditor extends BaseTextEditor {
// Actions
this.nextDiffAction = new NavigateAction(this, true);
this.previousDiffAction = new NavigateAction(this, false);
this.toggleIgnoreTrimWhitespaceAction = new ToggleIgnoreTrimWhitespaceAction(this._actualConfigurationService);
this.updateIgnoreTrimWhitespaceAction();
// Support navigation within the diff editor by overriding the editor service within
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
@@ -163,6 +176,7 @@ export class TextDiffEditor extends BaseTextEditor {
this.nextDiffAction.updateEnablement();
this.previousDiffAction.updateEnablement();
});
this.updateIgnoreTrimWhitespaceAction();
}, error => {
// In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff.
@@ -176,6 +190,13 @@ export class TextDiffEditor extends BaseTextEditor {
});
}
private updateIgnoreTrimWhitespaceAction(): void {
const ignoreTrimWhitespace = this.configurationService.getValue<boolean>(this.getResource(), 'diffEditor.ignoreTrimWhitespace');
if (this.toggleIgnoreTrimWhitespaceAction) {
this.toggleIgnoreTrimWhitespaceAction.updateClassName(ignoreTrimWhitespace);
}
}
private openAsBinary(input: EditorInput, options: EditorOptions): boolean {
if (input instanceof DiffEditorInput) {
const originalInput = input.originalInput;
@@ -184,12 +205,13 @@ export class TextDiffEditor extends BaseTextEditor {
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();
const fileInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).getFileInputFactory();
if (fileInputFactory.isFileInput(originalInput)) {
originalInput.setForceOpenAsBinary();
}
if (types.isFunction(((modifiedInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary)) {
((modifiedInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary();
if (fileInputFactory.isFileInput(modifiedInput)) {
modifiedInput.setForceOpenAsBinary();
}
this.editorService.openEditor(binaryDiffInput, options, this.position).done(null, onUnexpectedError);
@@ -273,25 +295,12 @@ export class TextDiffEditor extends BaseTextEditor {
public getActions(): IAction[] {
return [
this.toggleIgnoreTrimWhitespaceAction,
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;
}
@@ -303,6 +312,8 @@ export class TextDiffEditor extends BaseTextEditor {
this.diffNavigator.dispose();
}
this._configurationListener.dispose();
super.dispose();
}
}
@@ -340,33 +351,25 @@ class NavigateAction extends Action {
}
}
class ToggleEditorModeAction extends Action {
private static readonly ID = 'toggle.diff.editorMode';
private static readonly INLINE_LABEL = nls.localize('inlineDiffLabel', "Switch to Inline View");
private static readonly SIDEBYSIDE_LABEL = nls.localize('sideBySideDiffLabel', "Switch to Side by Side View");
class ToggleIgnoreTrimWhitespaceAction extends Action {
static ID = 'workbench.action.compareEditor.toggleIgnoreTrimWhitespace';
constructor(private editor: TextDiffEditor) {
super(ToggleEditorModeAction.ID);
private _isChecked: boolean;
constructor(
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
super(ToggleIgnoreTrimWhitespaceAction.ID);
this.label = nls.localize('toggleIgnoreTrimWhitespace.label', "Ignore Trim Whitespace");
}
public get label(): string {
return ToggleEditorModeAction.isInlineMode(this.editor) ? ToggleEditorModeAction.SIDEBYSIDE_LABEL : ToggleEditorModeAction.INLINE_LABEL;
public updateClassName(ignoreTrimWhitespace: boolean): void {
this._isChecked = ignoreTrimWhitespace;
this.class = `textdiff-editor-action toggleIgnoreTrimWhitespace${this._isChecked ? ' is-checked' : ''}`;
}
public run(): TPromise<boolean> {
const inlineModeActive = ToggleEditorModeAction.isInlineMode(this.editor);
const control = this.editor.getControl();
control.updateOptions(<IDiffEditorOptions>{
renderSideBySide: inlineModeActive
});
return TPromise.as(true);
public run(): TPromise<any> {
this._configurationService.updateValue(`diffEditor.ignoreTrimWhitespace`, !this._isChecked);
return null;
}
private static isInlineMode(editor: TextDiffEditor): boolean {
const control = editor.getControl();
return control && !control.renderSideBySide;
}
}
}

View File

@@ -23,7 +23,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
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/browser/services/codeEditorService';
import { getCodeEditor, getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
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';
@@ -50,11 +50,11 @@ export abstract class BaseTextEditor extends BaseEditor {
constructor(
id: string,
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IStorageService private storageService: IStorageService,
@ITextResourceConfigurationService private _configurationService: ITextResourceConfigurationService,
@ITextResourceConfigurationService private readonly _configurationService: ITextResourceConfigurationService,
@IThemeService protected themeService: IThemeService,
@ITextFileService private _textFileService: ITextFileService,
@ITextFileService private readonly _textFileService: ITextFileService,
@IEditorGroupService protected editorGroupService: IEditorGroupService
) {
super(id, telemetryService, themeService);
@@ -230,48 +230,66 @@ export abstract class BaseTextEditor extends BaseEditor {
}
/**
* Saves the text editor view state under the given key.
* Saves the text editor view state for the given resource.
*/
protected saveTextEditorViewState(key: string): void {
protected saveTextEditorViewState(resource: URI): void {
const editor = getCodeOrDiffEditor(this).codeEditor;
if (!editor) {
return; // not supported for diff editors
}
const model = editor.getModel();
if (!model) {
return; // view state always needs a model
}
const modelUri = model.uri;
if (!modelUri) {
return; // model URI is needed to make sure we save the view state correctly
}
if (modelUri.toString() !== resource.toString()) {
return; // prevent saving view state for a model that is not the expected one
}
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
let textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = 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 = textEditorViewStateMemento[key];
let lastKnownViewState = textEditorViewStateMemento[resource.toString()];
if (!lastKnownViewState) {
lastKnownViewState = Object.create(null);
textEditorViewStateMemento[key] = lastKnownViewState;
textEditorViewStateMemento[resource.toString()] = lastKnownViewState;
}
if (typeof this.position === 'number') {
lastKnownViewState[this.position] = editorViewState;
lastKnownViewState[this.position] = editor.saveViewState();
}
}
/**
* Clears the text editor view state under the given key.
* Clears the text editor view state for the given resources.
*/
protected clearTextEditorViewState(keys: string[]): void {
protected clearTextEditorViewState(resources: URI[]): void {
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
const textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
if (textEditorViewStateMemento) {
keys.forEach(key => delete textEditorViewStateMemento[key]);
resources.forEach(resource => delete textEditorViewStateMemento[resource.toString()]);
}
}
/**
* Loads the text editor view state for the given key and returns it.
* Loads the text editor view state for the given resource and returns it.
*/
protected loadTextEditorViewState(key: string): IEditorViewState {
protected loadTextEditorViewState(resource: URI): IEditorViewState {
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
const textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
if (textEditorViewStateMemento) {
const viewState = textEditorViewStateMemento[key];
const viewState = textEditorViewStateMemento[resource.toString()];
if (viewState) {
return viewState[this.position];
}

View File

@@ -28,11 +28,10 @@ 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 readonly ID = 'workbench.editors.textResourceEditor';
export class AbstractTextResourceEditor extends BaseTextEditor {
constructor(
id: string,
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@@ -41,7 +40,7 @@ export class TextResourceEditor extends BaseTextEditor {
@IEditorGroupService editorGroupService: IEditorGroupService,
@ITextFileService textFileService: ITextFileService
) {
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
}
public getTitle(): string {
@@ -108,7 +107,7 @@ export class TextResourceEditor extends BaseTextEditor {
protected restoreViewState(input: EditorInput) {
if (input instanceof UntitledEditorInput || input instanceof ResourceEditorInput) {
const viewState = this.loadTextEditorViewState(input.getResource().toString());
const viewState = this.loadTextEditorViewState(input.getResource());
if (viewState) {
this.getControl().restoreViewState(viewState);
}
@@ -178,21 +177,38 @@ export class TextResourceEditor extends BaseTextEditor {
return; // only enabled for untitled and resource inputs
}
const key = input.getResource().toString();
const resource = input.getResource();
// Clear view state if input is disposed
if (input.isDisposed()) {
super.clearTextEditorViewState([key]);
super.clearTextEditorViewState([resource]);
}
// Otherwise save it
else {
super.saveTextEditorViewState(key);
super.saveTextEditorViewState(resource);
// Make sure to clean up when the input gets disposed
once(input.onDispose)(() => {
super.clearTextEditorViewState([key]);
super.clearTextEditorViewState([resource]);
});
}
}
}
export class TextResourceEditor extends AbstractTextResourceEditor {
public static readonly ID = 'workbench.editors.textResourceEditor';
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IThemeService themeService: IThemeService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@ITextFileService textFileService: ITextFileService
) {
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService);
}
}

View File

@@ -7,8 +7,7 @@
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 { prepareActions } from 'vs/workbench/browser/actions';
import { IAction, Action, IRunEvent } from 'vs/base/common/actions';
import errors = require('vs/base/common/errors');
import DOM = require('vs/base/browser/dom');
@@ -16,13 +15,12 @@ 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 arrays = require('vs/base/common/arrays');
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource } from 'vs/workbench/common/editor';
import { IActionItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource, IEditorCommandsContext } from 'vs/workbench/common/editor';
import { IActionItem, ActionsOrientation } 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';
@@ -30,21 +28,16 @@ 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 { SplitEditorAction, CloseEditorAction } 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/workbench/browser/editor';
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';
import { isDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Dimension } from 'vs/base/browser/builder';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface IToolbarActions {
primary: IAction[];
@@ -67,21 +60,13 @@ export interface ITitleAreaControl {
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;
@@ -106,7 +91,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
@IContextKeyService protected contextKeyService: IContextKeyService,
@IKeybindingService protected keybindingService: IKeybindingService,
@ITelemetryService protected telemetryService: ITelemetryService,
@IMessageService protected messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@IMenuService protected menuService: IMenuService,
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@IThemeService protected themeService: IThemeService
@@ -128,22 +113,10 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
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)));
}
@@ -183,6 +156,8 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
public setContext(group: IEditorGroup): void {
this.context = group;
this.editorActionsToolbar.context = { groupId: group ? group.id : void 0 } as IEditorCommandsContext;
}
public hasContext(): boolean {
@@ -233,12 +208,6 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
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);
}
@@ -255,7 +224,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
// Check for Error
if (e.error && !errors.isPromiseCanceledError(e.error)) {
this.messageService.show(Severity.Error, e.error);
this.notificationService.error(e.error);
}
// Log in telemetry
@@ -287,15 +256,9 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
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);
actionItem = createActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
}
return actionItem;
@@ -335,7 +298,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(_ => this.update()));
fillInActions(titleBarMenu, { arg: this.resourceContext.get() }, { primary, secondary });
fillInActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, this.contextMenuService);
}
return { primary, secondary };
@@ -353,26 +316,21 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
// Update Editor Actions Toolbar
let primaryEditorActions: IAction[] = [];
let secondaryEditorActions: IAction[] = [];
const editorActions = this.getEditorActions({ group, editor });
// Primary actions only for the active group
if (isActive) {
const editorActions = this.getEditorActions({ group, editor });
primaryEditorActions = prepareActions(editorActions.primary);
if (isActive && editor instanceof EditorInput && editor.supportsSplitEditor()) {
if (editor instanceof EditorInput && editor.supportsSplitEditor()) {
this.updateSplitActionEnablement();
primaryEditorActions.push(this.splitEditorAction);
}
secondaryEditorActions = prepareActions(editorActions.secondary);
}
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) {
@@ -418,10 +376,15 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
anchor = { x: event.posx, y: event.posy };
}
// Fill in contributed actions
const actions: IAction[] = [];
fillInActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService);
// Show it
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.as(this.getContextMenuActions(identifier)),
getActionsContext: () => identifier,
getActions: () => TPromise.as(actions),
getActionsContext: () => ({ groupId: identifier.group.id, editorIndex: identifier.group.indexOf(identifier.editor) } as IEditorCommandsContext),
getKeyBinding: (action) => this.getKeybinding(action),
onHide: (cancel) => {
@@ -447,51 +410,13 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
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
this.closeEditorAction
].forEach((action) => {
action.dispose();
});
@@ -500,74 +425,3 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
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 => ({ uri: folder }))).then(workspace => [workspace.configPath]);
}
// Open
workspacesToOpen.then(workspaces => {
windowsService.openWindow(workspaces, { forceReuseWindow: true });
});
return true;
});
}