SQL Operations Studio Public Preview 1 (0.23) release source code
375
src/vs/workbench/browser/actions.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
/**
|
||||
* The action bar contributor allows to add actions to an actionbar in a given context.
|
||||
*/
|
||||
export class ActionBarContributor {
|
||||
|
||||
/**
|
||||
* Returns true if this contributor has actions for the given context.
|
||||
*/
|
||||
public hasActions(context: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of primary actions in the given context.
|
||||
*/
|
||||
public getActions(context: any): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this contributor has secondary actions for the given context.
|
||||
*/
|
||||
public hasSecondaryActions(context: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of secondary actions in the given context.
|
||||
*/
|
||||
public getSecondaryActions(context: any): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Can return a specific IActionItem to render the given action.
|
||||
*/
|
||||
public getActionItem(context: any, action: Action): BaseActionItem {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some predefined scopes to contribute actions to
|
||||
*/
|
||||
export const Scope = {
|
||||
|
||||
/**
|
||||
* Actions inside the global activity bar (DEPRECATED)
|
||||
*/
|
||||
GLOBAL: 'global',
|
||||
|
||||
/**
|
||||
* Actions inside viewlets.
|
||||
*/
|
||||
VIEWLET: 'viewlet',
|
||||
|
||||
/**
|
||||
* Actions inside panels.
|
||||
*/
|
||||
PANEL: 'panel',
|
||||
|
||||
/**
|
||||
* Actions inside editors.
|
||||
*/
|
||||
EDITOR: 'editor',
|
||||
|
||||
/**
|
||||
* Actions inside tree widgets.
|
||||
*/
|
||||
VIEWER: 'viewer'
|
||||
};
|
||||
|
||||
/**
|
||||
* The ContributableActionProvider leverages the actionbar contribution model to find actions.
|
||||
*/
|
||||
export class ContributableActionProvider implements IActionProvider {
|
||||
private registry: IActionBarRegistry;
|
||||
|
||||
constructor() {
|
||||
this.registry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
}
|
||||
|
||||
private toContext(tree: ITree, element: any): any {
|
||||
return {
|
||||
viewer: tree,
|
||||
element: element
|
||||
};
|
||||
}
|
||||
|
||||
public hasActions(tree: ITree, element: any): boolean {
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
if (contributor.hasActions(context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
|
||||
let actions: IAction[] = [];
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
// Collect Actions
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
if (contributor.hasActions(context)) {
|
||||
actions.push(...contributor.getActions(context));
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(prepareActions(actions));
|
||||
}
|
||||
|
||||
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
if (contributor.hasSecondaryActions(context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
|
||||
let actions: IAction[] = [];
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
// Collect Actions
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
if (contributor.hasSecondaryActions(context)) {
|
||||
actions.push(...contributor.getSecondaryActions(context));
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(prepareActions(actions));
|
||||
}
|
||||
|
||||
public getActionItem(tree: ITree, element: any, action: Action): BaseActionItem {
|
||||
let contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
let context = this.toContext(tree, element);
|
||||
|
||||
for (let i = contributors.length - 1; i >= 0; i--) {
|
||||
let contributor = contributors[i];
|
||||
|
||||
let itemProvider = contributor.getActionItem(context, action);
|
||||
if (itemProvider) {
|
||||
return itemProvider;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function used in parts to massage actions before showing in action areas
|
||||
export function prepareActions(actions: IAction[]): IAction[] {
|
||||
if (!actions.length) {
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Patch order if not provided
|
||||
for (let l = 0; l < actions.length; l++) {
|
||||
let a = <any>actions[l];
|
||||
if (types.isUndefinedOrNull(a.order)) {
|
||||
a.order = l;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by order
|
||||
actions = actions.sort((first: Action, second: Action) => {
|
||||
let firstOrder = first.order;
|
||||
let secondOrder = second.order;
|
||||
if (firstOrder < secondOrder) {
|
||||
return -1;
|
||||
} else if (firstOrder > secondOrder) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up leading separators
|
||||
let firstIndexOfAction = -1;
|
||||
for (let i = 0; i < actions.length; i++) {
|
||||
if (actions[i].id === Separator.ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
firstIndexOfAction = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (firstIndexOfAction === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
actions = actions.slice(firstIndexOfAction);
|
||||
|
||||
// Clean up trailing separators
|
||||
for (let h = actions.length - 1; h >= 0; h--) {
|
||||
let isSeparator = actions[h].id === Separator.ID;
|
||||
if (isSeparator) {
|
||||
actions.splice(h, 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up separator duplicates
|
||||
let foundAction = false;
|
||||
for (let k = actions.length - 1; k >= 0; k--) {
|
||||
let isSeparator = actions[k].id === Separator.ID;
|
||||
if (isSeparator && !foundAction) {
|
||||
actions.splice(k, 1);
|
||||
} else if (!isSeparator) {
|
||||
foundAction = true;
|
||||
} else if (isSeparator) {
|
||||
foundAction = false;
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
export const Extensions = {
|
||||
Actionbar: 'workbench.contributions.actionbar'
|
||||
};
|
||||
|
||||
export interface IActionBarRegistry {
|
||||
|
||||
/**
|
||||
* Goes through all action bar contributors and asks them for contributed actions for
|
||||
* the provided scope and context. Supports primary actions.
|
||||
*/
|
||||
getActionBarActionsForContext(scope: string, context: any): IAction[];
|
||||
|
||||
/**
|
||||
* Goes through all action bar contributors and asks them for contributed actions for
|
||||
* the provided scope and context. Supports secondary actions.
|
||||
*/
|
||||
getSecondaryActionBarActionsForContext(scope: string, context: any): IAction[];
|
||||
|
||||
/**
|
||||
* Goes through all action bar contributors and asks them for contributed action item for
|
||||
* the provided scope and context.
|
||||
*/
|
||||
getActionItemForContext(scope: string, context: any, action: Action): BaseActionItem;
|
||||
|
||||
/**
|
||||
* Registers an Actionbar contributor. It will be called to contribute actions to all the action bars
|
||||
* that are used in the Workbench in the given scope.
|
||||
*/
|
||||
registerActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void;
|
||||
|
||||
/**
|
||||
* Returns an array of registered action bar contributors known to the workbench for the given scope.
|
||||
*/
|
||||
getActionBarContributors(scope: string): ActionBarContributor[];
|
||||
|
||||
setInstantiationService(service: IInstantiationService): void;
|
||||
}
|
||||
|
||||
class ActionBarRegistry implements IActionBarRegistry {
|
||||
private actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0<ActionBarContributor>; }[] = [];
|
||||
private actionBarContributorInstances: { [scope: string]: ActionBarContributor[] } = Object.create(null);
|
||||
private instantiationService: IInstantiationService;
|
||||
|
||||
public setInstantiationService(service: IInstantiationService): void {
|
||||
this.instantiationService = service;
|
||||
|
||||
while (this.actionBarContributorConstructors.length > 0) {
|
||||
let entry = this.actionBarContributorConstructors.shift();
|
||||
this.createActionBarContributor(entry.scope, entry.ctor);
|
||||
}
|
||||
}
|
||||
|
||||
private createActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
|
||||
const instance = this.instantiationService.createInstance(ctor);
|
||||
let target = this.actionBarContributorInstances[scope];
|
||||
if (!target) {
|
||||
target = this.actionBarContributorInstances[scope] = [];
|
||||
}
|
||||
target.push(instance);
|
||||
}
|
||||
|
||||
private getContributors(scope: string): ActionBarContributor[] {
|
||||
return this.actionBarContributorInstances[scope] || [];
|
||||
}
|
||||
|
||||
public getActionBarActionsForContext(scope: string, context: any): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
|
||||
// Go through contributors for scope
|
||||
this.getContributors(scope).forEach((contributor: ActionBarContributor) => {
|
||||
|
||||
// Primary Actions
|
||||
if (contributor.hasActions(context)) {
|
||||
actions.push(...contributor.getActions(context));
|
||||
}
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public getSecondaryActionBarActionsForContext(scope: string, context: any): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
|
||||
// Go through contributors
|
||||
this.getContributors(scope).forEach((contributor: ActionBarContributor) => {
|
||||
|
||||
// Secondary Actions
|
||||
if (contributor.hasSecondaryActions(context)) {
|
||||
actions.push(...contributor.getSecondaryActions(context));
|
||||
}
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public getActionItemForContext(scope: string, context: any, action: Action): BaseActionItem {
|
||||
let contributors = this.getContributors(scope);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
let contributor = contributors[i];
|
||||
let item = contributor.getActionItem(context, action);
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public registerActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
|
||||
if (!this.instantiationService) {
|
||||
this.actionBarContributorConstructors.push({
|
||||
scope: scope,
|
||||
ctor: ctor
|
||||
});
|
||||
} else {
|
||||
this.createActionBarContributor(scope, ctor);
|
||||
}
|
||||
}
|
||||
|
||||
public getActionBarContributors(scope: string): ActionBarContributor[] {
|
||||
return this.getContributors(scope).slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Actionbar, new ActionBarRegistry());
|
||||
93
src/vs/workbench/browser/actions/configureLocale.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import * as Path from 'vs/base/common/paths';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as Labels from 'vs/base/common/labels';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
|
||||
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
|
||||
class ConfigureLocaleAction extends Action {
|
||||
public static ID = 'workbench.action.configureLocale';
|
||||
public static LABEL = nls.localize('configureLocale', "Configure Language");
|
||||
|
||||
private static DEFAULT_CONTENT: string = [
|
||||
'{',
|
||||
`\t// ${nls.localize('displayLanguage', 'Defines VSCode\'s display language.')}`,
|
||||
`\t// ${nls.localize('doc', 'See {0} for a list of supported languages.', 'https://go.microsoft.com/fwlink/?LinkId=761051')}`,
|
||||
`\t// ${nls.localize('restart', 'Changing the value requires restarting VSCode.')}`,
|
||||
`\t"locale":"${Platform.language}"`,
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
constructor(id, label,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(event?: any): TPromise<IEditor> {
|
||||
const file = URI.file(Path.join(this.environmentService.appSettingsHome, 'locale.json'));
|
||||
return this.fileService.resolveFile(file).then(null, (error) => {
|
||||
return this.fileService.createFile(file, ConfigureLocaleAction.DEFAULT_CONTENT);
|
||||
}).then((stat) => {
|
||||
if (!stat) {
|
||||
return undefined;
|
||||
}
|
||||
return this.editorService.openEditor({
|
||||
resource: stat.resource,
|
||||
options: {
|
||||
forceOpen: true
|
||||
}
|
||||
});
|
||||
}, (error) => {
|
||||
throw new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", Labels.getPathLabel(file, this.contextService), error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Language');
|
||||
|
||||
const schemaId = 'vscode://schemas/locale';
|
||||
// Keep en-US since we generated files with that content.
|
||||
const schema: IJSONSchema =
|
||||
{
|
||||
id: schemaId,
|
||||
description: 'Locale Definition file',
|
||||
type: 'object',
|
||||
default: {
|
||||
'locale': 'en'
|
||||
},
|
||||
required: ['locale'],
|
||||
properties: {
|
||||
locale: {
|
||||
type: 'string',
|
||||
enum: ['de', 'en', 'en-US', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-CN', 'zh-TW'],
|
||||
description: nls.localize('JsonSchema.locale', 'The UI Language to use.')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
|
||||
jsonRegistry.registerSchema(schemaId, schema);
|
||||
13
src/vs/workbench/browser/actions/media/actions.css
Normal file
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .monaco-workbench .toggle-editor-layout {
|
||||
background-image: url('editor-layout.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .toggle-editor-layout,
|
||||
.hc-black .monaco-workbench .toggle-editor-layout {
|
||||
background-image: url('editor-layout-inverse.svg') !important;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 16V0h11v6h5v10H0z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M4 14H2V4h7v3H4v7zm10 0H5v-4h9v4z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M10 7V1H1v14h14V7h-5zm-6 7H2V4h7v3H4v7zm10 0H5v-4h9v4z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
1
src/vs/workbench/browser/actions/media/editor-layout.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 16V0h11v6h5v10H0z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M4 14H2V4h7v3H4v7zm10 0H5v-4h9v4z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M10 7V1H1v14h14V7h-5zm-6 7H2V4h7v3H4v7zm10 0H5v-4h9v4z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class ToggleActivityBarVisibilityAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleActivityBarVisibility';
|
||||
public static LABEL = nls.localize('toggleActivityBar', "Toggle Activity Bar Visibility");
|
||||
|
||||
private static activityBarVisibleKey = 'workbench.activityBar.visible';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService,
|
||||
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const visibility = this.partService.isVisible(Parts.ACTIVITYBAR_PART);
|
||||
const newVisibilityValue = !visibility;
|
||||
|
||||
this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleActivityBarVisibilityAction.activityBarVisibleKey, value: newVisibilityValue });
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', nls.localize('view', "View"));
|
||||
84
src/vs/workbench/browser/actions/toggleEditorLayout.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IEditorGroupService, GroupOrientation } from 'vs/workbench/services/group/common/groupService';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ToggleEditorLayoutAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleEditorGroupLayout';
|
||||
public static LABEL = nls.localize('toggleEditorGroupLayout', "Toggle Editor Group Vertical/Horizontal Layout");
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.class = 'toggle-editor-layout';
|
||||
this.updateEnablement();
|
||||
this.updateLabel();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.editorGroupService.onEditorsChanged(() => this.updateEnablement()));
|
||||
this.toDispose.push(this.editorGroupService.onGroupOrientationChanged(() => this.updateLabel()));
|
||||
}
|
||||
|
||||
private updateLabel(): void {
|
||||
const editorGroupLayoutVertical = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
this.label = editorGroupLayoutVertical ? nls.localize('horizontalLayout', "Horizontal Editor Group Layout") : nls.localize('verticalLayout', "Vertical Editor Group Layout");
|
||||
}
|
||||
|
||||
private updateEnablement(): void {
|
||||
this.enabled = this.editorGroupService.getStacksModel().groups.length > 0;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const groupOrientiation = this.editorGroupService.getGroupOrientation();
|
||||
const newGroupOrientation: GroupOrientation = (groupOrientiation === 'vertical') ? 'horizontal' : 'vertical';
|
||||
|
||||
this.editorGroupService.setGroupOrientation(newGroupOrientation);
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.editor.setGroupOrientation', function (accessor: ServicesAccessor, args: [GroupOrientation]) {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const [orientation] = args;
|
||||
|
||||
editorGroupService.setGroupOrientation(orientation);
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
});
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
const group = nls.localize('view', "View");
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_1 } }), 'View: Toggle Editor Group Vertical/Horizontal Layout', group);
|
||||
45
src/vs/workbench/browser/actions/toggleSidebarPosition.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IPartService, Position } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class ToggleSidebarPositionAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleSidebarPosition';
|
||||
public static LABEL = nls.localize('toggleLocation', "Toggle Side Bar Location");
|
||||
|
||||
private static sidebarPositionConfigurationKey = 'workbench.sideBar.location';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService,
|
||||
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.partService && !!this.configurationEditingService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const position = this.partService.getSideBarPosition();
|
||||
const newPositionValue = (position === Position.LEFT) ? 'right' : 'left';
|
||||
|
||||
this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleSidebarPositionAction.sidebarPositionConfigurationKey, value: newPositionValue });
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Location', nls.localize('view', "View"));
|
||||
38
src/vs/workbench/browser/actions/toggleSidebarVisibility.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export class ToggleSidebarVisibilityAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleSidebarVisibility';
|
||||
public static LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const hideSidebar = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
return this.partService.setSideBarHidden(hideSidebar);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', nls.localize('view', "View"));
|
||||
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class ToggleStatusbarVisibilityAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleStatusbarVisibility';
|
||||
public static LABEL = nls.localize('toggleStatusbar', "Toggle Status Bar Visibility");
|
||||
|
||||
private static statusbarVisibleKey = 'workbench.statusBar.visible';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService,
|
||||
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const visibility = this.partService.isVisible(Parts.STATUSBAR_PART);
|
||||
const newVisibilityValue = !visibility;
|
||||
|
||||
this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: ToggleStatusbarVisibilityAction.statusbarVisibleKey, value: newVisibilityValue });
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', nls.localize('view', "View"));
|
||||
36
src/vs/workbench/browser/actions/toggleZenMode.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
class ToggleZenMode extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleZenMode';
|
||||
public static LABEL = nls.localize('toggleZenMode', "Toggle Zen Mode");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
this.partService.toggleZenMode();
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', nls.localize('view', "View"));
|
||||
290
src/vs/workbench/browser/actions/workspaceActions.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import nls = require('vs/nls');
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkspacesService, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { dirname } from 'vs/base/common/paths';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { isParent } from 'vs/platform/files/common/files';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export class OpenFolderAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.files.openFolder';
|
||||
static LABEL = nls.localize('openFolder', "Open Folder...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService private windowService: IWindowService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any, data?: ITelemetryData): TPromise<any> {
|
||||
return this.windowService.pickFolderAndOpen({ telemetryExtraData: data });
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenFileFolderAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.files.openFileFolder';
|
||||
static LABEL = nls.localize('openFileFolder', "Open...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService private windowService: IWindowService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any, data?: ITelemetryData): TPromise<any> {
|
||||
return this.windowService.pickFileFolderAndOpen({ telemetryExtraData: data });
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseWorkspacesAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
protected windowService: IWindowService,
|
||||
protected environmentService: IEnvironmentService,
|
||||
protected contextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
protected pickFolders(buttonLabel: string, title: string): string[] {
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
let defaultPath: string;
|
||||
if (workspace && workspace.roots.length > 0) {
|
||||
defaultPath = dirname(workspace.roots[0].fsPath); // pick the parent of the first root by default
|
||||
}
|
||||
|
||||
return this.windowService.showOpenDialog({
|
||||
buttonLabel,
|
||||
title,
|
||||
properties: ['multiSelections', 'openDirectory', 'createDirectory'],
|
||||
defaultPath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AddRootFolderAction extends BaseWorkspacesAction {
|
||||
|
||||
static ID = 'workbench.action.addRootFolder';
|
||||
static LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService windowService: IWindowService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
|
||||
@IViewletService private viewletService: IViewletService
|
||||
) {
|
||||
super(id, label, windowService, environmentService, contextService);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
if (!this.contextService.hasWorkspace()) {
|
||||
return this.instantiationService.createInstance(NewWorkspaceAction, NewWorkspaceAction.ID, NewWorkspaceAction.LABEL, []).run();
|
||||
}
|
||||
|
||||
if (this.contextService.hasFolderWorkspace()) {
|
||||
return this.instantiationService.createInstance(NewWorkspaceAction, NewWorkspaceAction.ID, NewWorkspaceAction.LABEL, this.contextService.getWorkspace().roots).run();
|
||||
}
|
||||
|
||||
const folders = super.pickFolders(mnemonicButtonLabel(nls.localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"));
|
||||
if (!folders || !folders.length) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.workspaceEditingService.addRoots(folders.map(folder => URI.file(folder))).then(() => {
|
||||
return this.viewletService.openViewlet(this.viewletService.getDefaultViewletId(), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class NewWorkspaceAction extends BaseWorkspacesAction {
|
||||
|
||||
static ID = 'workbench.action.newWorkspace';
|
||||
static LABEL = nls.localize('newWorkspace', "New Workspace...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private presetRoots: URI[],
|
||||
@IWindowService windowService: IWindowService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IWorkspacesService protected workspacesService: IWorkspacesService,
|
||||
@IWindowsService protected windowsService: IWindowsService,
|
||||
) {
|
||||
super(id, label, windowService, environmentService, contextService);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const folders = super.pickFolders(mnemonicButtonLabel(nls.localize({ key: 'select', comment: ['&& denotes a mnemonic'] }, "&&Select")), nls.localize('selectWorkspace', "Select Folders for Workspace"));
|
||||
if (folders && folders.length) {
|
||||
return this.createWorkspace([...this.presetRoots, ...folders.map(folder => URI.file(folder))]);
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private createWorkspace(folders: URI[]): TPromise<void> {
|
||||
const workspaceFolders = distinct(folders.map(folder => folder.fsPath), folder => isLinux ? folder : folder.toLowerCase());
|
||||
|
||||
return this.windowService.createAndOpenWorkspace(workspaceFolders);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveRootFolderAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.removeRootFolder';
|
||||
static LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace");
|
||||
|
||||
constructor(
|
||||
private rootUri: URI,
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
return this.workspaceEditingService.removeRoots([this.rootUri]);
|
||||
}
|
||||
}
|
||||
|
||||
export class SaveWorkspaceAsAction extends BaseWorkspacesAction {
|
||||
|
||||
static ID = 'workbench.action.saveWorkspaceAs';
|
||||
static LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService windowService: IWindowService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IWorkspacesService protected workspacesService: IWorkspacesService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IMessageService private messageService: IMessageService
|
||||
) {
|
||||
super(id, label, windowService, environmentService, contextService);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
if (!this.contextService.hasWorkspace()) {
|
||||
this.messageService.show(Severity.Info, nls.localize('saveEmptyWorkspaceNotSupported', "Please open a workspace first to save."));
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const configPath = this.getNewWorkspaceConfigPath();
|
||||
if (configPath) {
|
||||
if (this.contextService.hasFolderWorkspace()) {
|
||||
return this.saveFolderWorkspace(configPath);
|
||||
}
|
||||
|
||||
if (this.contextService.hasMultiFolderWorkspace()) {
|
||||
return this.saveWorkspace(configPath);
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private saveWorkspace(configPath: string): TPromise<void> {
|
||||
return this.windowService.saveAndOpenWorkspace(configPath);
|
||||
}
|
||||
|
||||
private saveFolderWorkspace(configPath: string): TPromise<void> {
|
||||
const workspaceFolders = this.contextService.getWorkspace().roots.map(root => root.fsPath);
|
||||
|
||||
return this.windowService.createAndOpenWorkspace(workspaceFolders, configPath);
|
||||
}
|
||||
|
||||
private getNewWorkspaceConfigPath(): string {
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
let defaultPath: string;
|
||||
if (this.contextService.hasMultiFolderWorkspace() && !this.isUntitledWorkspace(workspace.configuration.fsPath)) {
|
||||
defaultPath = workspace.configuration.fsPath;
|
||||
} else if (workspace && workspace.roots.length > 0) {
|
||||
defaultPath = dirname(workspace.roots[0].fsPath); // pick the parent of the first root by default
|
||||
}
|
||||
|
||||
return this.windowService.showSaveDialog({
|
||||
buttonLabel: mnemonicButtonLabel(nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
|
||||
title: nls.localize('saveWorkspace', "Save Workspace"),
|
||||
filters: WORKSPACE_FILTER,
|
||||
defaultPath
|
||||
});
|
||||
}
|
||||
|
||||
private isUntitledWorkspace(path: string): boolean {
|
||||
return isParent(path, this.environmentService.workspacesHome, !isLinux /* ignore case */);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenWorkspaceAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.openWorkspace';
|
||||
static LABEL = nls.localize('openWorkspaceAction', "Open Workspace...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
return this.windowService.openWorkspace();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenWorkspaceConfigFileAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.openWorkspaceConfigFile';
|
||||
public static LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = this.workspaceContextService.hasMultiFolderWorkspace();
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
return this.editorService.openEditor({ resource: this.workspaceContextService.getWorkspace().configuration });
|
||||
}
|
||||
}
|
||||
42
src/vs/workbench/browser/activity.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IActivity {
|
||||
id: string;
|
||||
name: string;
|
||||
cssClass: string;
|
||||
}
|
||||
|
||||
export interface IGlobalActivity extends IActivity {
|
||||
getActions(): IAction[];
|
||||
}
|
||||
|
||||
export const GlobalActivityExtensions = 'workbench.contributions.globalActivities';
|
||||
|
||||
export interface IGlobalActivityRegistry {
|
||||
registerActivity(descriptor: IConstructorSignature0<IGlobalActivity>): void;
|
||||
getActivities(): IConstructorSignature0<IGlobalActivity>[];
|
||||
}
|
||||
|
||||
export class GlobalActivityRegistry implements IGlobalActivityRegistry {
|
||||
|
||||
private activityDescriptors = new Set<IConstructorSignature0<IGlobalActivity>>();
|
||||
|
||||
registerActivity(descriptor: IConstructorSignature0<IGlobalActivity>): void {
|
||||
this.activityDescriptors.add(descriptor);
|
||||
}
|
||||
|
||||
getActivities(): IConstructorSignature0<IGlobalActivity>[] {
|
||||
const result: IConstructorSignature0<IGlobalActivity>[] = [];
|
||||
this.activityDescriptors.forEach(d => result.push(d));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(GlobalActivityExtensions, new GlobalActivityRegistry());
|
||||
273
src/vs/workbench/browser/composite.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
import { IEditorControl } from 'vs/platform/editor/common/editor';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
/**
|
||||
* Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite
|
||||
* can be open in the sidebar, and only one composite can be open in the panel.
|
||||
* Each composite has a minimized representation that is good enough to provide some
|
||||
* information about the state of the composite data.
|
||||
* The workbench will keep a composite alive after it has been created and show/hide it based on
|
||||
* user interaction. The lifecycle of a composite goes in the order create(), setVisible(true|false),
|
||||
* layout(), focus(), dispose(). During use of the workbench, a composite will often receive a setVisible,
|
||||
* layout and focus call, but only one create and dispose call.
|
||||
*/
|
||||
export abstract class Composite extends Component implements IComposite {
|
||||
private _telemetryData: any = {};
|
||||
private visible: boolean;
|
||||
private parent: Builder;
|
||||
private _onTitleAreaUpdate: Emitter<void>;
|
||||
|
||||
protected actionRunner: IActionRunner;
|
||||
|
||||
/**
|
||||
* Create a new composite with the given ID and context.
|
||||
*/
|
||||
constructor(
|
||||
id: string,
|
||||
private _telemetryService: ITelemetryService,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(id, themeService);
|
||||
|
||||
this.visible = false;
|
||||
this._onTitleAreaUpdate = new Emitter<void>();
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected get telemetryService(): ITelemetryService {
|
||||
return this._telemetryService;
|
||||
}
|
||||
|
||||
public get onTitleAreaUpdate(): Event<void> {
|
||||
return this._onTitleAreaUpdate.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Called to create this composite on the provided builder. This method is only
|
||||
* called once during the lifetime of the workbench.
|
||||
* Note that DOM-dependent calculations should be performed from the setVisible()
|
||||
* call. Only then the composite will be part of the DOM.
|
||||
*/
|
||||
public create(parent: Builder): TPromise<void> {
|
||||
this.parent = parent;
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
super.updateStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the container this composite is being build in.
|
||||
*/
|
||||
public getContainer(): Builder {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Called to indicate that the composite has become visible or hidden. This method
|
||||
* is called more than once during workbench lifecycle depending on the user interaction.
|
||||
* The composite will be on-DOM if visible is set to true and off-DOM otherwise.
|
||||
*
|
||||
* The returned promise is complete when the composite is visible. As such it is valid
|
||||
* to do a long running operation from this call. Typically this operation should be
|
||||
* fast though because setVisible might be called many times during a session.
|
||||
*/
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
this.visible = visible;
|
||||
|
||||
// Reset telemetry data when composite becomes visible
|
||||
if (visible) {
|
||||
this._telemetryData = {};
|
||||
this._telemetryData.startTime = new Date();
|
||||
|
||||
// Only submit telemetry data when not running from an integration test
|
||||
if (this._telemetryService && this._telemetryService.publicLog) {
|
||||
const eventName: string = 'compositeOpen';
|
||||
this._telemetryService.publicLog(eventName, { composite: this.getId() });
|
||||
}
|
||||
}
|
||||
|
||||
// Send telemetry data when composite hides
|
||||
else {
|
||||
this._telemetryData.timeSpent = (Date.now() - this._telemetryData.startTime) / 1000;
|
||||
delete this._telemetryData.startTime;
|
||||
|
||||
// Only submit telemetry data when not running from an integration test
|
||||
if (this._telemetryService && this._telemetryService.publicLog) {
|
||||
const eventName: string = 'compositeShown';
|
||||
this._telemetryData.composite = this.getId();
|
||||
this._telemetryService.publicLog(eventName, this._telemetryData);
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this composite should receive keyboard focus.
|
||||
*/
|
||||
public focus(): void {
|
||||
// Subclasses can implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout the contents of this composite using the provided dimensions.
|
||||
*/
|
||||
public abstract layout(dimension: Dimension): void;
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the action bar of the composite.
|
||||
*/
|
||||
public getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the action bar of the composite
|
||||
* in a less prominent way then action from getActions.
|
||||
*/
|
||||
public getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the context menu of the composite
|
||||
*/
|
||||
public getContextMenuActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* For any of the actions returned by this composite, provide an IActionItem in
|
||||
* cases where the implementor of the composite wants to override the presentation
|
||||
* of an action. Returns null to indicate that the action is not rendered through
|
||||
* an action item.
|
||||
*/
|
||||
public getActionItem(action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of IActionRunner to use with this composite for the
|
||||
* composite tool bar.
|
||||
*/
|
||||
public getActionRunner(): IActionRunner {
|
||||
if (!this.actionRunner) {
|
||||
this.actionRunner = new ActionRunner();
|
||||
}
|
||||
|
||||
return this.actionRunner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for composite implementors to indicate to the composite container that the title or the actions
|
||||
* of the composite have changed. Calling this method will cause the container to ask for title (getTitle())
|
||||
* and actions (getActions(), getSecondaryActions()) if the composite is visible or the next time the composite
|
||||
* gets visible.
|
||||
*/
|
||||
protected updateTitleArea(): void {
|
||||
this._onTitleAreaUpdate.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this composite is currently visible and false otherwise.
|
||||
*/
|
||||
public isVisible(): boolean {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying composite control or null if it is not accessible.
|
||||
*/
|
||||
public getControl(): IEditorControl {
|
||||
return null;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._onTitleAreaUpdate.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composite descriptor is a leightweight descriptor of a composite in the workbench.
|
||||
*/
|
||||
export abstract class CompositeDescriptor<T extends Composite> extends AsyncDescriptor<T> {
|
||||
public id: string;
|
||||
public name: string;
|
||||
public cssClass: string;
|
||||
public order: number;
|
||||
|
||||
constructor(moduleId: string, ctorName: string, id: string, name: string, cssClass?: string, order?: number) {
|
||||
super(moduleId, ctorName);
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.cssClass = cssClass;
|
||||
this.order = order;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class CompositeRegistry<T extends Composite> {
|
||||
private composites: CompositeDescriptor<T>[];
|
||||
|
||||
constructor() {
|
||||
this.composites = [];
|
||||
}
|
||||
|
||||
protected registerComposite(descriptor: CompositeDescriptor<T>): void {
|
||||
if (this.compositeById(descriptor.id) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.composites.push(descriptor);
|
||||
}
|
||||
|
||||
public getComposite(id: string): CompositeDescriptor<T> {
|
||||
return this.compositeById(id);
|
||||
}
|
||||
|
||||
protected getComposites(): CompositeDescriptor<T>[] {
|
||||
return this.composites.slice(0);
|
||||
}
|
||||
|
||||
protected setComposites(compositesToSet: CompositeDescriptor<T>[]): void {
|
||||
this.composites = compositesToSet;
|
||||
}
|
||||
|
||||
private compositeById(id: string): CompositeDescriptor<T> {
|
||||
for (let i = 0; i < this.composites.length; i++) {
|
||||
if (this.composites[i].id === id) {
|
||||
return this.composites[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
262
src/vs/workbench/browser/labels.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 uri from 'vs/base/common/uri';
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { IconLabel, IIconLabelOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { toResource } from 'vs/workbench/common/editor';
|
||||
import { getPathLabel, IRootProvider } from 'vs/base/common/labels';
|
||||
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
|
||||
export interface IEditorLabel {
|
||||
name: string;
|
||||
description?: string;
|
||||
resource?: uri;
|
||||
}
|
||||
|
||||
export interface IResourceLabelOptions extends IIconLabelOptions {
|
||||
fileKind?: FileKind;
|
||||
}
|
||||
|
||||
export class ResourceLabel extends IconLabel {
|
||||
private toDispose: IDisposable[];
|
||||
private label: IEditorLabel;
|
||||
private options: IResourceLabelOptions;
|
||||
private computedIconClasses: string[];
|
||||
private lastKnownConfiguredLangId: string;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
options: IIconLabelCreationOptions,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IEnvironmentService protected environmentService: IEnvironmentService
|
||||
) {
|
||||
super(container, options);
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.extensionService.onReady().then(() => this.render(true /* clear cache */)); // update when extensions are loaded with potentially new languages
|
||||
this.toDispose.push(this.configurationService.onDidUpdateConfiguration(() => this.render(true /* clear cache */))); // update when file.associations change
|
||||
}
|
||||
|
||||
public setLabel(label: IEditorLabel, options?: IResourceLabelOptions): void {
|
||||
const hasResourceChanged = this.hasResourceChanged(label, options);
|
||||
|
||||
this.label = label;
|
||||
this.options = options;
|
||||
|
||||
this.render(hasResourceChanged);
|
||||
}
|
||||
|
||||
private hasResourceChanged(label: IEditorLabel, options: IResourceLabelOptions): boolean {
|
||||
const newResource = label ? label.resource : void 0;
|
||||
const oldResource = this.label ? this.label.resource : void 0;
|
||||
|
||||
const newFileKind = options ? options.fileKind : void 0;
|
||||
const oldFileKind = this.options ? this.options.fileKind : void 0;
|
||||
|
||||
if (newFileKind !== oldFileKind) {
|
||||
return true; // same resource but different kind (file, folder)
|
||||
}
|
||||
|
||||
if (newResource && oldResource) {
|
||||
return newResource.toString() !== oldResource.toString();
|
||||
}
|
||||
|
||||
if (!newResource && !oldResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.label = void 0;
|
||||
this.options = void 0;
|
||||
this.lastKnownConfiguredLangId = void 0;
|
||||
this.computedIconClasses = void 0;
|
||||
|
||||
this.setValue();
|
||||
}
|
||||
|
||||
private render(clearIconCache: boolean): void {
|
||||
if (this.label) {
|
||||
const configuredLangId = getConfiguredLangId(this.modelService, this.label.resource);
|
||||
if (this.lastKnownConfiguredLangId !== configuredLangId) {
|
||||
clearIconCache = true;
|
||||
this.lastKnownConfiguredLangId = configuredLangId;
|
||||
}
|
||||
}
|
||||
|
||||
if (clearIconCache) {
|
||||
this.computedIconClasses = void 0;
|
||||
}
|
||||
|
||||
if (!this.label) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resource = this.label.resource;
|
||||
|
||||
let title = '';
|
||||
if (this.options && typeof this.options.title === 'string') {
|
||||
title = this.options.title;
|
||||
} else if (resource) {
|
||||
title = getPathLabel(resource.fsPath, void 0, this.environmentService);
|
||||
}
|
||||
|
||||
if (!this.computedIconClasses) {
|
||||
this.computedIconClasses = getIconClasses(this.modelService, this.modeService, resource, this.options && this.options.fileKind);
|
||||
}
|
||||
|
||||
let extraClasses = this.computedIconClasses.slice(0);
|
||||
if (this.options && this.options.extraClasses) {
|
||||
extraClasses.push(...this.options.extraClasses);
|
||||
}
|
||||
|
||||
const italic = this.options && this.options.italic;
|
||||
const matches = this.options && this.options.matches;
|
||||
|
||||
this.setValue(this.label.name, this.label.description, { title, extraClasses, italic, matches });
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
this.label = void 0;
|
||||
this.options = void 0;
|
||||
this.lastKnownConfiguredLangId = void 0;
|
||||
this.computedIconClasses = void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorLabel extends ResourceLabel {
|
||||
|
||||
public setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void {
|
||||
this.setLabel({
|
||||
resource: toResource(editor, { supportSideBySide: true }),
|
||||
name: editor.getName(),
|
||||
description: editor.getDescription()
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IFileLabelOptions extends IResourceLabelOptions {
|
||||
hideLabel?: boolean;
|
||||
hidePath?: boolean;
|
||||
root?: uri;
|
||||
}
|
||||
|
||||
export class FileLabel extends ResourceLabel {
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
options: IIconLabelCreationOptions,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUntitledEditorService private untitledEditorService: IUntitledEditorService
|
||||
) {
|
||||
super(container, options, extensionService, contextService, configurationService, modeService, modelService, environmentService);
|
||||
}
|
||||
|
||||
public setFile(resource: uri, options: IFileLabelOptions = Object.create(null)): void {
|
||||
const hidePath = options.hidePath || (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource));
|
||||
const rootProvider: IRootProvider = options.root ? {
|
||||
getRoot(): uri { return options.root; },
|
||||
getWorkspace(): { roots: uri[]; } { return { roots: [options.root] }; },
|
||||
} : this.contextService;
|
||||
this.setLabel({
|
||||
resource,
|
||||
name: !options.hideLabel ? paths.basename(resource.fsPath) : void 0,
|
||||
description: !hidePath ? getPathLabel(paths.dirname(resource.fsPath), rootProvider, this.environmentService) : void 0
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
export function getIconClasses(modelService: IModelService, modeService: IModeService, resource: uri, fileKind?: FileKind): string[] {
|
||||
|
||||
// we always set these base classes even if we do not have a path
|
||||
const classes = fileKind === FileKind.ROOT_FOLDER ? ['rootfolder-icon'] : fileKind === FileKind.FOLDER ? ['folder-icon'] : ['file-icon'];
|
||||
|
||||
let path: string;
|
||||
if (resource) {
|
||||
path = resource.fsPath;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
const basename = cssEscape(paths.basename(path).toLowerCase());
|
||||
|
||||
// Folders
|
||||
if (fileKind === FileKind.FOLDER) {
|
||||
classes.push(`${basename}-name-folder-icon`);
|
||||
}
|
||||
|
||||
// Files
|
||||
else {
|
||||
|
||||
// Name
|
||||
classes.push(`${basename}-name-file-icon`);
|
||||
|
||||
// Extension(s)
|
||||
const dotSegments = basename.split('.');
|
||||
for (let i = 1; i < dotSegments.length; i++) {
|
||||
classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one
|
||||
}
|
||||
|
||||
// Configured Language
|
||||
let configuredLangId = getConfiguredLangId(modelService, resource);
|
||||
configuredLangId = configuredLangId || modeService.getModeIdByFilenameOrFirstLine(path);
|
||||
if (configuredLangId) {
|
||||
classes.push(`${cssEscape(configuredLangId)}-lang-file-icon`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
function getConfiguredLangId(modelService: IModelService, resource: uri): string {
|
||||
let configuredLangId: string;
|
||||
if (resource) {
|
||||
const model = modelService.getModel(resource);
|
||||
if (model) {
|
||||
const modeId = model.getLanguageIdentifier().language;
|
||||
if (modeId && modeId !== PLAINTEXT_MODE_ID) {
|
||||
configuredLangId = modeId; // only take if the mode is specific (aka no just plain text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configuredLangId;
|
||||
}
|
||||
|
||||
function cssEscape(val: string): string {
|
||||
return val.replace(/\s/g, '\\$&'); // make sure to not introduce CSS classes from files that contain whitespace
|
||||
}
|
||||
595
src/vs/workbench/browser/layout.ts
Normal file
@@ -0,0 +1,595 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController';
|
||||
import { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IPartService, Position, ILayoutOptions, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { getZoomFactor } from 'vs/base/browser/browser';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
const MIN_SIDEBAR_PART_WIDTH = 170;
|
||||
const MIN_EDITOR_PART_HEIGHT = 70;
|
||||
const MIN_EDITOR_PART_WIDTH = 220;
|
||||
const MIN_PANEL_PART_HEIGHT = 77;
|
||||
const DEFAULT_PANEL_HEIGHT_COEFFICIENT = 0.4;
|
||||
const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50;
|
||||
const HIDE_PANEL_HEIGHT_THRESHOLD = 50;
|
||||
const TITLE_BAR_HEIGHT = 22;
|
||||
const STATUS_BAR_HEIGHT = 22;
|
||||
const ACTIVITY_BAR_WIDTH = 50;
|
||||
|
||||
interface PartLayoutInfo {
|
||||
titlebar: { height: number; };
|
||||
activitybar: { width: number; };
|
||||
sidebar: { minWidth: number; };
|
||||
panel: { minHeight: number; };
|
||||
editor: { minWidth: number; minHeight: number; };
|
||||
statusbar: { height: number; };
|
||||
}
|
||||
|
||||
/**
|
||||
* The workbench layout is responsible to lay out all parts that make the Workbench.
|
||||
*/
|
||||
export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider {
|
||||
|
||||
private static sashXWidthSettingsKey = 'workbench.sidebar.width';
|
||||
private static sashYHeightSettingsKey = 'workbench.panel.height';
|
||||
|
||||
private parent: Builder;
|
||||
private workbenchContainer: Builder;
|
||||
private titlebar: Part;
|
||||
private activitybar: Part;
|
||||
private editor: Part;
|
||||
private sidebar: Part;
|
||||
private panel: Part;
|
||||
private statusbar: Part;
|
||||
private quickopen: QuickOpenController;
|
||||
private toUnbind: IDisposable[];
|
||||
private partLayoutInfo: PartLayoutInfo;
|
||||
private workbenchSize: Dimension;
|
||||
private sashX: Sash;
|
||||
private sashY: Sash;
|
||||
private startSidebarWidth: number;
|
||||
private sidebarWidth: number;
|
||||
private sidebarHeight: number;
|
||||
private titlebarHeight: number;
|
||||
private activitybarWidth: number;
|
||||
private statusbarHeight: number;
|
||||
private startPanelHeight: number;
|
||||
private panelHeight: number;
|
||||
private panelHeightBeforeMaximized: number;
|
||||
private panelMaximized: boolean;
|
||||
private panelWidth: number;
|
||||
private layoutEditorGroupsVertically: boolean;
|
||||
|
||||
// Take parts as an object bag since instatation service does not have typings for constructors with 9+ arguments
|
||||
constructor(
|
||||
parent: Builder,
|
||||
workbenchContainer: Builder,
|
||||
parts: {
|
||||
titlebar: Part,
|
||||
activitybar: Part,
|
||||
editor: Part,
|
||||
sidebar: Part,
|
||||
panel: Part,
|
||||
statusbar: Part
|
||||
},
|
||||
quickopen: QuickOpenController,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IContextViewService private contextViewService: IContextViewService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
this.parent = parent;
|
||||
this.workbenchContainer = workbenchContainer;
|
||||
this.titlebar = parts.titlebar;
|
||||
this.activitybar = parts.activitybar;
|
||||
this.editor = parts.editor;
|
||||
this.sidebar = parts.sidebar;
|
||||
this.panel = parts.panel;
|
||||
this.statusbar = parts.statusbar;
|
||||
this.quickopen = quickopen;
|
||||
this.toUnbind = [];
|
||||
this.partLayoutInfo = this.getPartLayoutInfo();
|
||||
this.panelHeightBeforeMaximized = 0;
|
||||
this.panelMaximized = false;
|
||||
|
||||
this.sashX = new Sash(this.workbenchContainer.getHTMLElement(), this, {
|
||||
baseSize: 5
|
||||
});
|
||||
|
||||
this.sashY = new Sash(this.workbenchContainer.getHTMLElement(), this, {
|
||||
baseSize: 4,
|
||||
orientation: Orientation.HORIZONTAL
|
||||
});
|
||||
|
||||
this.sidebarWidth = this.storageService.getInteger(WorkbenchLayout.sashXWidthSettingsKey, StorageScope.GLOBAL, -1);
|
||||
this.panelHeight = this.storageService.getInteger(WorkbenchLayout.sashYHeightSettingsKey, StorageScope.GLOBAL, 0);
|
||||
|
||||
this.layoutEditorGroupsVertically = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
|
||||
this.toUnbind.push(themeService.onThemeChange(_ => this.layout()));
|
||||
this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
|
||||
this.toUnbind.push(editorGroupService.onGroupOrientationChanged(e => this.onGroupOrientationChanged()));
|
||||
|
||||
this.registerSashListeners();
|
||||
}
|
||||
|
||||
private getPartLayoutInfo(): PartLayoutInfo {
|
||||
return {
|
||||
titlebar: {
|
||||
height: TITLE_BAR_HEIGHT
|
||||
},
|
||||
activitybar: {
|
||||
width: ACTIVITY_BAR_WIDTH
|
||||
},
|
||||
sidebar: {
|
||||
minWidth: MIN_SIDEBAR_PART_WIDTH
|
||||
},
|
||||
panel: {
|
||||
minHeight: MIN_PANEL_PART_HEIGHT
|
||||
},
|
||||
editor: {
|
||||
minWidth: MIN_EDITOR_PART_WIDTH,
|
||||
minHeight: MIN_EDITOR_PART_HEIGHT
|
||||
},
|
||||
statusbar: {
|
||||
height: STATUS_BAR_HEIGHT
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private registerSashListeners(): void {
|
||||
let startX: number = 0;
|
||||
let startY: number = 0;
|
||||
|
||||
this.sashX.addListener('start', (e: ISashEvent) => {
|
||||
this.startSidebarWidth = this.sidebarWidth;
|
||||
startX = e.startX;
|
||||
});
|
||||
|
||||
this.sashY.addListener('start', (e: ISashEvent) => {
|
||||
this.startPanelHeight = this.panelHeight;
|
||||
startY = e.startY;
|
||||
});
|
||||
|
||||
this.sashX.addListener('change', (e: ISashEvent) => {
|
||||
let doLayout = false;
|
||||
let sidebarPosition = this.partService.getSideBarPosition();
|
||||
let isSidebarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
let newSashWidth = (sidebarPosition === Position.LEFT) ? this.startSidebarWidth + e.currentX - startX : this.startSidebarWidth - e.currentX + startX;
|
||||
let promise = TPromise.as<void>(null);
|
||||
|
||||
// Sidebar visible
|
||||
if (isSidebarVisible) {
|
||||
|
||||
// Automatically hide side bar when a certain threshold is met
|
||||
if (newSashWidth + HIDE_SIDEBAR_WIDTH_THRESHOLD < this.partLayoutInfo.sidebar.minWidth) {
|
||||
let dragCompensation = MIN_SIDEBAR_PART_WIDTH - HIDE_SIDEBAR_WIDTH_THRESHOLD;
|
||||
promise = this.partService.setSideBarHidden(true);
|
||||
startX = (sidebarPosition === Position.LEFT) ? Math.max(this.activitybarWidth, e.currentX - dragCompensation) : Math.min(e.currentX + dragCompensation, this.workbenchSize.width - this.activitybarWidth);
|
||||
this.sidebarWidth = this.startSidebarWidth; // when restoring sidebar, restore to the sidebar width we started from
|
||||
}
|
||||
|
||||
// Otherwise size the sidebar accordingly
|
||||
else {
|
||||
this.sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, newSashWidth); // Sidebar can not become smaller than MIN_PART_WIDTH
|
||||
doLayout = newSashWidth >= this.partLayoutInfo.sidebar.minWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar hidden
|
||||
else {
|
||||
if ((sidebarPosition === Position.LEFT && e.currentX - startX >= this.partLayoutInfo.sidebar.minWidth) ||
|
||||
(sidebarPosition === Position.RIGHT && startX - e.currentX >= this.partLayoutInfo.sidebar.minWidth)) {
|
||||
this.startSidebarWidth = this.partLayoutInfo.sidebar.minWidth - (sidebarPosition === Position.LEFT ? e.currentX - startX : startX - e.currentX);
|
||||
this.sidebarWidth = this.partLayoutInfo.sidebar.minWidth;
|
||||
promise = this.partService.setSideBarHidden(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (doLayout) {
|
||||
promise.done(() => this.layout(), errors.onUnexpectedError);
|
||||
}
|
||||
});
|
||||
|
||||
this.sashY.addListener('change', (e: ISashEvent) => {
|
||||
let doLayout = false;
|
||||
let isPanelVisible = this.partService.isVisible(Parts.PANEL_PART);
|
||||
let newSashHeight = this.startPanelHeight - (e.currentY - startY);
|
||||
let promise = TPromise.as<void>(null);
|
||||
|
||||
// Panel visible
|
||||
if (isPanelVisible) {
|
||||
|
||||
// Automatically hide panel when a certain threshold is met
|
||||
if (newSashHeight + HIDE_PANEL_HEIGHT_THRESHOLD < this.partLayoutInfo.panel.minHeight) {
|
||||
let dragCompensation = MIN_PANEL_PART_HEIGHT - HIDE_PANEL_HEIGHT_THRESHOLD;
|
||||
promise = this.partService.setPanelHidden(true);
|
||||
startY = Math.min(this.sidebarHeight - this.statusbarHeight - this.titlebarHeight, e.currentY + dragCompensation);
|
||||
this.panelHeight = this.startPanelHeight; // when restoring panel, restore to the panel height we started from
|
||||
}
|
||||
|
||||
// Otherwise size the panel accordingly
|
||||
else {
|
||||
this.panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, newSashHeight); // Panel can not become smaller than MIN_PART_HEIGHT
|
||||
doLayout = newSashHeight >= this.partLayoutInfo.panel.minHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Panel hidden
|
||||
else {
|
||||
if (startY - e.currentY >= this.partLayoutInfo.panel.minHeight) {
|
||||
this.startPanelHeight = 0;
|
||||
this.panelHeight = this.partLayoutInfo.panel.minHeight;
|
||||
promise = this.partService.setPanelHidden(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (doLayout) {
|
||||
promise.done(() => this.layout(), errors.onUnexpectedError);
|
||||
}
|
||||
});
|
||||
|
||||
this.sashX.addListener('end', () => {
|
||||
this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
this.sashY.addListener('end', () => {
|
||||
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
this.sashY.addListener('reset', () => {
|
||||
this.panelHeight = this.sidebarHeight * DEFAULT_PANEL_HEIGHT_COEFFICIENT;
|
||||
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
|
||||
this.partService.setPanelHidden(false).done(() => this.layout(), errors.onUnexpectedError);
|
||||
});
|
||||
|
||||
this.sashX.addListener('reset', () => {
|
||||
let activeViewlet = this.viewletService.getActiveViewlet();
|
||||
let optimalWidth = activeViewlet && activeViewlet.getOptimalWidth();
|
||||
this.sidebarWidth = Math.max(MIN_SIDEBAR_PART_WIDTH, optimalWidth || 0);
|
||||
this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
|
||||
this.partService.setSideBarHidden(false).done(() => this.layout(), errors.onUnexpectedError);
|
||||
});
|
||||
}
|
||||
|
||||
private onEditorsChanged(): void {
|
||||
|
||||
// Make sure that we layout properly in case we detect that the sidebar or panel is large enought to cause
|
||||
// multiple opened editors to go below minimal size. The fix is to trigger a layout for any editor
|
||||
// input change that falls into this category.
|
||||
if (this.workbenchSize && (this.sidebarWidth || this.panelHeight)) {
|
||||
let visibleEditors = this.editorService.getVisibleEditors().length;
|
||||
if (visibleEditors > 1) {
|
||||
const sidebarOverflow = this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.sidebarWidth < visibleEditors * MIN_EDITOR_PART_WIDTH);
|
||||
const panelOverflow = !this.layoutEditorGroupsVertically && (this.workbenchSize.height - this.panelHeight < visibleEditors * MIN_EDITOR_PART_HEIGHT);
|
||||
|
||||
if (sidebarOverflow || panelOverflow) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onGroupOrientationChanged(): void {
|
||||
const newLayoutEditorGroupsVertically = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
|
||||
const doLayout = this.layoutEditorGroupsVertically !== newLayoutEditorGroupsVertically;
|
||||
this.layoutEditorGroupsVertically = newLayoutEditorGroupsVertically;
|
||||
|
||||
if (doLayout) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public layout(options?: ILayoutOptions): void {
|
||||
this.workbenchSize = this.parent.getClientArea();
|
||||
|
||||
const isActivityBarHidden = !this.partService.isVisible(Parts.ACTIVITYBAR_PART);
|
||||
const isTitlebarHidden = !this.partService.isVisible(Parts.TITLEBAR_PART);
|
||||
const isPanelHidden = !this.partService.isVisible(Parts.PANEL_PART);
|
||||
const isStatusbarHidden = !this.partService.isVisible(Parts.STATUSBAR_PART);
|
||||
const isSidebarHidden = !this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const sidebarPosition = this.partService.getSideBarPosition();
|
||||
|
||||
// Sidebar
|
||||
let sidebarWidth: number;
|
||||
if (isSidebarHidden) {
|
||||
sidebarWidth = 0;
|
||||
} else if (this.sidebarWidth !== -1) {
|
||||
sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, this.sidebarWidth);
|
||||
} else {
|
||||
sidebarWidth = this.workbenchSize.width / 5;
|
||||
this.sidebarWidth = sidebarWidth;
|
||||
}
|
||||
|
||||
this.statusbarHeight = isStatusbarHidden ? 0 : this.partLayoutInfo.statusbar.height;
|
||||
this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / getZoomFactor(); // adjust for zoom prevention
|
||||
|
||||
const previousMaxPanelHeight = this.sidebarHeight - MIN_EDITOR_PART_HEIGHT;
|
||||
this.sidebarHeight = this.workbenchSize.height - this.statusbarHeight - this.titlebarHeight;
|
||||
let sidebarSize = new Dimension(sidebarWidth, this.sidebarHeight);
|
||||
|
||||
// Activity Bar
|
||||
this.activitybarWidth = isActivityBarHidden ? 0 : this.partLayoutInfo.activitybar.width;
|
||||
let activityBarSize = new Dimension(this.activitybarWidth, sidebarSize.height);
|
||||
|
||||
// Panel part
|
||||
let panelHeight: number;
|
||||
const editorCountForHeight = this.editorGroupService.getGroupOrientation() === 'horizontal' ? this.editorGroupService.getStacksModel().groups.length : 1;
|
||||
const maxPanelHeight = sidebarSize.height - editorCountForHeight * MIN_EDITOR_PART_HEIGHT;
|
||||
if (isPanelHidden) {
|
||||
panelHeight = 0;
|
||||
} else if (this.panelHeight === previousMaxPanelHeight) {
|
||||
panelHeight = maxPanelHeight;
|
||||
} else if (this.panelHeight > 0) {
|
||||
panelHeight = Math.min(maxPanelHeight, Math.max(this.partLayoutInfo.panel.minHeight, this.panelHeight));
|
||||
} else {
|
||||
panelHeight = sidebarSize.height * DEFAULT_PANEL_HEIGHT_COEFFICIENT;
|
||||
}
|
||||
if (options && options.toggleMaximizedPanel) {
|
||||
panelHeight = this.panelMaximized ? Math.max(this.partLayoutInfo.panel.minHeight, Math.min(this.panelHeightBeforeMaximized, maxPanelHeight)) : maxPanelHeight;
|
||||
}
|
||||
this.panelMaximized = panelHeight === maxPanelHeight;
|
||||
if (panelHeight / maxPanelHeight < 0.7) {
|
||||
// Remember the previous height only if the panel size is not too large.
|
||||
// To get a nice minimize effect even if a user dragged the panel sash to maximum.
|
||||
this.panelHeightBeforeMaximized = panelHeight;
|
||||
}
|
||||
const panelDimension = new Dimension(this.workbenchSize.width - sidebarSize.width - activityBarSize.width, panelHeight);
|
||||
this.panelWidth = panelDimension.width;
|
||||
|
||||
// Editor
|
||||
let editorSize = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
remainderLeft: 0,
|
||||
remainderRight: 0
|
||||
};
|
||||
|
||||
editorSize.width = panelDimension.width;
|
||||
editorSize.height = sidebarSize.height - panelDimension.height;
|
||||
|
||||
// Sidebar hidden
|
||||
if (isSidebarHidden) {
|
||||
editorSize.width = this.workbenchSize.width - activityBarSize.width;
|
||||
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
editorSize.remainderLeft = Math.round((this.workbenchSize.width - editorSize.width + activityBarSize.width) / 2);
|
||||
editorSize.remainderRight = this.workbenchSize.width - editorSize.width - editorSize.remainderLeft;
|
||||
} else {
|
||||
editorSize.remainderRight = Math.round((this.workbenchSize.width - editorSize.width + activityBarSize.width) / 2);
|
||||
editorSize.remainderLeft = this.workbenchSize.width - editorSize.width - editorSize.remainderRight;
|
||||
}
|
||||
}
|
||||
|
||||
// Assert Sidebar and Editor Size to not overflow
|
||||
let editorMinWidth = this.partLayoutInfo.editor.minWidth;
|
||||
let editorMinHeight = this.partLayoutInfo.editor.minHeight;
|
||||
let visibleEditorCount = this.editorService.getVisibleEditors().length;
|
||||
if (visibleEditorCount > 1) {
|
||||
if (this.layoutEditorGroupsVertically) {
|
||||
editorMinWidth *= visibleEditorCount; // when editors layout vertically, multiply the min editor width by number of visible editors
|
||||
} else {
|
||||
editorMinHeight *= visibleEditorCount; // when editors layout horizontally, multiply the min editor height by number of visible editors
|
||||
}
|
||||
}
|
||||
|
||||
if (editorSize.width < editorMinWidth) {
|
||||
let diff = editorMinWidth - editorSize.width;
|
||||
editorSize.width = editorMinWidth;
|
||||
panelDimension.width = editorMinWidth;
|
||||
sidebarSize.width -= diff;
|
||||
sidebarSize.width = Math.max(MIN_SIDEBAR_PART_WIDTH, sidebarSize.width);
|
||||
}
|
||||
|
||||
if (editorSize.height < editorMinHeight) {
|
||||
let diff = editorMinHeight - editorSize.height;
|
||||
editorSize.height = editorMinHeight;
|
||||
panelDimension.height -= diff;
|
||||
panelDimension.height = Math.max(MIN_PANEL_PART_HEIGHT, panelDimension.height);
|
||||
}
|
||||
|
||||
if (!isSidebarHidden) {
|
||||
this.sidebarWidth = sidebarSize.width;
|
||||
this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
if (!isPanelHidden) {
|
||||
this.panelHeight = panelDimension.height;
|
||||
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
// Workbench
|
||||
this.workbenchContainer
|
||||
.position(0, 0, 0, 0, 'relative')
|
||||
.size(this.workbenchSize.width, this.workbenchSize.height);
|
||||
|
||||
// Bug on Chrome: Sometimes Chrome wants to scroll the workbench container on layout changes. The fix is to reset scrolling in this case.
|
||||
const workbenchContainer = this.workbenchContainer.getHTMLElement();
|
||||
if (workbenchContainer.scrollTop > 0) {
|
||||
workbenchContainer.scrollTop = 0;
|
||||
}
|
||||
if (workbenchContainer.scrollLeft > 0) {
|
||||
workbenchContainer.scrollLeft = 0;
|
||||
}
|
||||
|
||||
// Title Part
|
||||
if (isTitlebarHidden) {
|
||||
this.titlebar.getContainer().hide();
|
||||
} else {
|
||||
this.titlebar.getContainer().show();
|
||||
}
|
||||
|
||||
// Editor Part and Panel part
|
||||
this.editor.getContainer().size(editorSize.width, editorSize.height);
|
||||
this.panel.getContainer().size(panelDimension.width, panelDimension.height);
|
||||
|
||||
const editorBottom = this.statusbarHeight + panelDimension.height;
|
||||
if (isSidebarHidden) {
|
||||
this.editor.getContainer().position(this.titlebarHeight, editorSize.remainderRight, editorBottom, editorSize.remainderLeft);
|
||||
this.panel.getContainer().position(editorSize.height + this.titlebarHeight, editorSize.remainderRight, this.statusbarHeight, editorSize.remainderLeft);
|
||||
} else if (sidebarPosition === Position.LEFT) {
|
||||
this.editor.getContainer().position(this.titlebarHeight, 0, editorBottom, sidebarSize.width + activityBarSize.width);
|
||||
this.panel.getContainer().position(editorSize.height + this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width);
|
||||
} else {
|
||||
this.editor.getContainer().position(this.titlebarHeight, sidebarSize.width, editorBottom, 0);
|
||||
this.panel.getContainer().position(editorSize.height + this.titlebarHeight, sidebarSize.width, this.statusbarHeight, 0);
|
||||
}
|
||||
|
||||
// Activity Bar Part
|
||||
this.activitybar.getContainer().size(null, activityBarSize.height);
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
this.activitybar.getContainer().getHTMLElement().style.right = '';
|
||||
this.activitybar.getContainer().position(this.titlebarHeight, null, 0, 0);
|
||||
} else {
|
||||
this.activitybar.getContainer().getHTMLElement().style.left = '';
|
||||
this.activitybar.getContainer().position(this.titlebarHeight, 0, 0, null);
|
||||
}
|
||||
if (isActivityBarHidden) {
|
||||
this.activitybar.getContainer().hide();
|
||||
} else {
|
||||
this.activitybar.getContainer().show();
|
||||
}
|
||||
|
||||
// Sidebar Part
|
||||
this.sidebar.getContainer().size(sidebarSize.width, sidebarSize.height);
|
||||
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
this.sidebar.getContainer().position(this.titlebarHeight, editorSize.width, 0, activityBarSize.width);
|
||||
} else {
|
||||
this.sidebar.getContainer().position(this.titlebarHeight, null, 0, editorSize.width);
|
||||
}
|
||||
|
||||
// Statusbar Part
|
||||
this.statusbar.getContainer().position(this.workbenchSize.height - this.statusbarHeight);
|
||||
if (isStatusbarHidden) {
|
||||
this.statusbar.getContainer().hide();
|
||||
} else {
|
||||
this.statusbar.getContainer().show();
|
||||
}
|
||||
|
||||
// Quick open
|
||||
this.quickopen.layout(this.workbenchSize);
|
||||
|
||||
// Sashes
|
||||
this.sashX.layout();
|
||||
this.sashY.layout();
|
||||
|
||||
// Propagate to Part Layouts
|
||||
this.titlebar.layout(new Dimension(this.workbenchSize.width, this.titlebarHeight));
|
||||
this.editor.layout(new Dimension(editorSize.width, editorSize.height));
|
||||
this.sidebar.layout(sidebarSize);
|
||||
this.panel.layout(panelDimension);
|
||||
this.activitybar.layout(activityBarSize);
|
||||
|
||||
// Propagate to Context View
|
||||
this.contextViewService.layout();
|
||||
}
|
||||
|
||||
public getVerticalSashTop(sash: Sash): number {
|
||||
return this.titlebarHeight;
|
||||
}
|
||||
|
||||
public getVerticalSashLeft(sash: Sash): number {
|
||||
let isSidebarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
let sidebarPosition = this.partService.getSideBarPosition();
|
||||
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
return isSidebarVisible ? this.sidebarWidth + this.activitybarWidth : this.activitybarWidth;
|
||||
}
|
||||
|
||||
return isSidebarVisible ? this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth : this.workbenchSize.width - this.activitybarWidth;
|
||||
}
|
||||
|
||||
public getVerticalSashHeight(sash: Sash): number {
|
||||
return this.sidebarHeight;
|
||||
}
|
||||
|
||||
public getHorizontalSashTop(sash: Sash): number {
|
||||
// Horizontal sash should be a bit lower than the editor area, thus add 2px #5524
|
||||
return 2 + (this.partService.isVisible(Parts.PANEL_PART) ? this.sidebarHeight - this.panelHeight + this.titlebarHeight : this.sidebarHeight + this.titlebarHeight);
|
||||
}
|
||||
|
||||
public getHorizontalSashLeft(sash: Sash): number {
|
||||
return this.partService.getSideBarPosition() === Position.LEFT ? this.getVerticalSashLeft(sash) : 0;
|
||||
}
|
||||
|
||||
public getHorizontalSashWidth(sash: Sash): number {
|
||||
return this.panelWidth;
|
||||
}
|
||||
|
||||
public isPanelMaximized(): boolean {
|
||||
return this.panelMaximized;
|
||||
}
|
||||
|
||||
// change part size along the main axis
|
||||
public resizePart(part: Parts, sizeChange: number): void {
|
||||
const visibleEditors = this.editorService.getVisibleEditors().length;
|
||||
const sizeChangePxWidth = this.workbenchSize.width * (sizeChange / 100);
|
||||
const sizeChangePxHeight = this.workbenchSize.height * (sizeChange / 100);
|
||||
|
||||
let doLayout = false;
|
||||
let newSashSize: number = 0;
|
||||
|
||||
switch (part) {
|
||||
case Parts.SIDEBAR_PART:
|
||||
newSashSize = this.sidebarWidth + sizeChangePxWidth;
|
||||
this.sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, newSashSize); // Sidebar can not become smaller than MIN_PART_WIDTH
|
||||
|
||||
if (this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.sidebarWidth < visibleEditors * MIN_EDITOR_PART_WIDTH)) {
|
||||
this.sidebarWidth = (this.workbenchSize.width - visibleEditors * MIN_EDITOR_PART_WIDTH);
|
||||
}
|
||||
|
||||
doLayout = true;
|
||||
break;
|
||||
case Parts.PANEL_PART:
|
||||
newSashSize = this.panelHeight + sizeChangePxHeight;
|
||||
this.panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, newSashSize);
|
||||
doLayout = true;
|
||||
break;
|
||||
case Parts.EDITOR_PART:
|
||||
// If we have one editor we can cheat and resize sidebar with the negative delta
|
||||
const visibleEditorCount = this.editorService.getVisibleEditors().length;
|
||||
|
||||
if (visibleEditorCount === 1) {
|
||||
this.sidebarWidth = this.sidebarWidth - sizeChangePxWidth;
|
||||
doLayout = true;
|
||||
} else {
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const activeGroup = stacks.positionOfGroup(stacks.activeGroup);
|
||||
|
||||
this.editorGroupService.resizeGroup(activeGroup, sizeChangePxWidth);
|
||||
doLayout = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (doLayout) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.toUnbind) {
|
||||
dispose(this.toUnbind);
|
||||
this.toUnbind = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/vs/workbench/browser/media/part.css
Normal file
@@ -0,0 +1,81 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .part > .title {
|
||||
display: none; /* Parts have to opt in to show title area */
|
||||
}
|
||||
|
||||
/* title styles are defined for two classes because the editor puts the title into the content */
|
||||
|
||||
.monaco-workbench > .part > .title,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title {
|
||||
height: 35px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label {
|
||||
line-height: 35px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label span {
|
||||
font-size: 11px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label a {
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-actions {
|
||||
height: 35px;
|
||||
flex: 1;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-actions .action-label {
|
||||
display: block;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
min-width: 28px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-actions .action-label .label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .content {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .content .progress-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 33px; /* at the bottom of the 35px height title container */
|
||||
z-index: 5; /* on top of things */
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .content .progress-container .progress-bit {
|
||||
height: 2px;
|
||||
}
|
||||
116
src/vs/workbench/browser/panel.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export abstract class Panel extends Composite implements IPanel { }
|
||||
|
||||
/**
|
||||
* A panel descriptor is a leightweight descriptor of a panel in the workbench.
|
||||
*/
|
||||
export class PanelDescriptor extends CompositeDescriptor<Panel> {
|
||||
|
||||
constructor(moduleId: string, ctorName: string, id: string, name: string, cssClass?: string, order?: number, private _commandId?: string) {
|
||||
super(moduleId, ctorName, id, name, cssClass, order);
|
||||
}
|
||||
|
||||
public get commandId(): string {
|
||||
return this._commandId;
|
||||
}
|
||||
}
|
||||
|
||||
export class PanelRegistry extends CompositeRegistry<Panel> {
|
||||
private defaultPanelId: string;
|
||||
|
||||
/**
|
||||
* Registers a panel to the platform.
|
||||
*/
|
||||
public registerPanel(descriptor: PanelDescriptor): void {
|
||||
super.registerComposite(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the panel descriptor for the given id or null if none.
|
||||
*/
|
||||
public getPanel(id: string): PanelDescriptor {
|
||||
return this.getComposite(id) as PanelDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of registered panels known to the platform.
|
||||
*/
|
||||
public getPanels(): PanelDescriptor[] {
|
||||
return this.getComposites() as PanelDescriptor[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id of the panel that should open on startup by default.
|
||||
*/
|
||||
public setDefaultPanelId(id: string): void {
|
||||
this.defaultPanelId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the panel that should open on startup by default.
|
||||
*/
|
||||
public getDefaultPanelId(): string {
|
||||
return this.defaultPanelId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reusable action to toggle a panel with a specific id.
|
||||
*/
|
||||
export abstract class TogglePanelAction extends Action {
|
||||
|
||||
private panelId: string;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
panelId: string,
|
||||
protected panelService: IPanelService,
|
||||
private partService: IPartService,
|
||||
cssClass?: string
|
||||
) {
|
||||
super(id, label, cssClass);
|
||||
this.panelId = panelId;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
|
||||
if (this.isPanelShowing()) {
|
||||
return this.partService.setPanelHidden(true);
|
||||
}
|
||||
|
||||
return this.panelService.openPanel(this.panelId, true);
|
||||
}
|
||||
|
||||
private isPanelShowing(): boolean {
|
||||
const panel = this.panelService.getActivePanel();
|
||||
|
||||
return panel && panel.getId() === this.panelId;
|
||||
}
|
||||
|
||||
protected isPanelFocused(): boolean {
|
||||
const activePanel = this.panelService.getActivePanel();
|
||||
const activeElement = document.activeElement;
|
||||
|
||||
return activePanel && activeElement && DOM.isAncestor(activeElement, (<Panel>activePanel).getContainer().getHTMLElement());
|
||||
}
|
||||
}
|
||||
|
||||
export const Extensions = {
|
||||
Panels: 'workbench.contributions.panels'
|
||||
};
|
||||
|
||||
Registry.add(Extensions.Panels, new PanelRegistry());
|
||||
148
src/vs/workbench/browser/part.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/part';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export interface IPartOptions {
|
||||
hasTitle?: boolean;
|
||||
borderWidth?: () => number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parts are layed out in the workbench and have their own layout that arranges an optional title
|
||||
* and mandatory content area to show content.
|
||||
*/
|
||||
export abstract class Part extends Component {
|
||||
private parent: Builder;
|
||||
private titleArea: Builder;
|
||||
private contentArea: Builder;
|
||||
private partLayout: PartLayout;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
private options: IPartOptions,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(id, themeService);
|
||||
}
|
||||
|
||||
protected onThemeChange(theme: ITheme): void {
|
||||
|
||||
// only call if our create() method has been called
|
||||
if (this.parent) {
|
||||
super.onThemeChange(theme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Called to create title and content area of the part.
|
||||
*/
|
||||
public create(parent: Builder): void {
|
||||
this.parent = parent;
|
||||
this.titleArea = this.createTitleArea(parent);
|
||||
this.contentArea = this.createContentArea(parent);
|
||||
|
||||
this.partLayout = new PartLayout(this.parent, this.options, this.titleArea, this.contentArea);
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the overall part container.
|
||||
*/
|
||||
public getContainer(): Builder {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses override to provide a title area implementation.
|
||||
*/
|
||||
protected createTitleArea(parent: Builder): Builder {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title area container.
|
||||
*/
|
||||
protected getTitleArea(): Builder {
|
||||
return this.titleArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses override to provide a content area implementation.
|
||||
*/
|
||||
protected createContentArea(parent: Builder): Builder {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content area container.
|
||||
*/
|
||||
protected getContentArea(): Builder {
|
||||
return this.contentArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout title and content area in the given dimension.
|
||||
*/
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
return this.partLayout.layout(dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part layout implementation.
|
||||
*/
|
||||
public getLayout(): PartLayout {
|
||||
return this.partLayout;
|
||||
}
|
||||
}
|
||||
|
||||
const TITLE_HEIGHT = 35;
|
||||
|
||||
export class PartLayout {
|
||||
|
||||
constructor(private container: Builder, private options: IPartOptions, private titleArea: Builder, private contentArea: Builder) {
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
const { width, height } = dimension;
|
||||
|
||||
// Return the applied sizes to title and content
|
||||
const sizes: Dimension[] = [];
|
||||
|
||||
// Title Size: Width (Fill), Height (Variable)
|
||||
let titleSize: Dimension;
|
||||
if (this.options && this.options.hasTitle) {
|
||||
titleSize = new Dimension(width, Math.min(height, TITLE_HEIGHT));
|
||||
} else {
|
||||
titleSize = new Dimension(0, 0);
|
||||
}
|
||||
|
||||
// Content Size: Width (Fill), Height (Variable)
|
||||
const contentSize = new Dimension(width, height - titleSize.height);
|
||||
|
||||
if (this.options && typeof this.options.borderWidth === 'function') {
|
||||
contentSize.width -= this.options.borderWidth(); // adjust for border size
|
||||
}
|
||||
|
||||
sizes.push(titleSize);
|
||||
sizes.push(contentSize);
|
||||
|
||||
// Content
|
||||
if (this.contentArea) {
|
||||
this.contentArea.size(contentSize.width, contentSize.height);
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
}
|
||||
757
src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
Normal file
@@ -0,0 +1,757 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/activityaction';
|
||||
import nls = require('vs/nls');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, Separator, IBaseActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IActivityBarService, ProgressBadge, TextBadge, NumberBadge, IconBadge, IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { IActivity, IGlobalActivity } from 'vs/workbench/browser/activity';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { IViewletService, } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder, activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export interface IViewletActivity {
|
||||
badge: IBadge;
|
||||
clazz: string;
|
||||
}
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
private badge: IBadge;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
super(_activity.id, _activity.name, _activity.cssClass);
|
||||
|
||||
this.badge = null;
|
||||
}
|
||||
|
||||
public get activity(): IActivity {
|
||||
return this._activity;
|
||||
}
|
||||
|
||||
public get onDidChangeBadge(): Event<this> {
|
||||
return this._onDidChangeBadge.event;
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public getBadge(): IBadge {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.badge = badge;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletActivityAction extends ActivityAction {
|
||||
|
||||
private static preventDoubleClickDelay = 300;
|
||||
|
||||
private lastRun: number = 0;
|
||||
|
||||
constructor(
|
||||
private viewlet: ViewletDescriptor,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(viewlet);
|
||||
}
|
||||
|
||||
public run(event): TPromise<any> {
|
||||
if (event instanceof MouseEvent && event.button === 2) {
|
||||
return TPromise.as(false); // do not run on right click
|
||||
}
|
||||
|
||||
// prevent accident trigger on a doubleclick (to help nervous people)
|
||||
const now = Date.now();
|
||||
if (now > this.lastRun /* https://github.com/Microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewletActivityAction.preventDoubleClickDelay) {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
this.lastRun = now;
|
||||
|
||||
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this.viewlet.id, true)
|
||||
.then(() => this.activate());
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivityActionItem extends BaseActionItem {
|
||||
protected $container: Builder;
|
||||
protected $label: Builder;
|
||||
protected $badge: Builder;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
options: IBaseActionItemOptions,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
) {
|
||||
super(null, action, options);
|
||||
|
||||
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
return (this._action as ActivityAction).activity;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
|
||||
// Label
|
||||
if (this.$label) {
|
||||
const background = theme.getColor(ACTIVITY_BAR_FOREGROUND);
|
||||
|
||||
this.$label.style('background-color', background ? background.toString() : null);
|
||||
}
|
||||
|
||||
// Badge
|
||||
if (this.$badgeContent) {
|
||||
const badgeForeground = theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND);
|
||||
const badgeBackground = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND);
|
||||
const contrastBorderColor = theme.getColor(contrastBorder);
|
||||
|
||||
this.$badgeContent.style('color', badgeForeground ? badgeForeground.toString() : null);
|
||||
this.$badgeContent.style('background-color', badgeBackground ? badgeBackground.toString() : null);
|
||||
|
||||
this.$badgeContent.style('border-style', contrastBorderColor ? 'solid' : null);
|
||||
this.$badgeContent.style('border-width', contrastBorderColor ? '1px' : null);
|
||||
this.$badgeContent.style('border-color', contrastBorderColor ? contrastBorderColor.toString() : null);
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.$container = $(container).attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
title: this.activity.name
|
||||
});
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
this.$container.on(DOM.EventType.MOUSE_DOWN, () => {
|
||||
this.$container.addClass('clicked');
|
||||
});
|
||||
|
||||
this.$container.on(DOM.EventType.MOUSE_UP, () => {
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.mouseUpTimeout = setTimeout(() => {
|
||||
this.$container.removeClass('clicked');
|
||||
}, 800); // delayed to prevent focus feedback from showing on mouse up
|
||||
});
|
||||
|
||||
// Label
|
||||
this.$label = $('a.action-label').appendTo(this.builder);
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
}
|
||||
|
||||
this.$badge = this.builder.clone().div({ 'class': 'badge' }, (badge: Builder) => {
|
||||
this.$badgeContent = badge.div({ 'class': 'badge-content' });
|
||||
});
|
||||
|
||||
this.$badge.hide();
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onThemeChange(theme: ITheme): void {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.updateBadge(badge);
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge): void {
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
|
||||
if (badge) {
|
||||
|
||||
// Number
|
||||
if (badge instanceof NumberBadge) {
|
||||
if (badge.number) {
|
||||
this.$badgeContent.text(badge.number > 99 ? '99+' : badge.number.toString());
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof TextBadge) {
|
||||
this.$badgeContent.text(badge.text);
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof IconBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Progress
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
const description = badge.getDescription();
|
||||
this.$label.attr('aria-label', `${this.activity.name} - ${description}`);
|
||||
this.$label.title(description);
|
||||
}
|
||||
}
|
||||
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge());
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.$badge.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletActionItem extends ActivityActionItem {
|
||||
|
||||
private static manageExtensionAction: ManageExtensionAction;
|
||||
private static toggleViewletPinnedAction: ToggleViewletPinnedAction;
|
||||
private static draggedViewlet: ViewletDescriptor;
|
||||
|
||||
private _keybinding: string;
|
||||
private cssClass: string;
|
||||
|
||||
constructor(
|
||||
private action: ViewletActivityAction,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IActivityBarService private activityBarService: IActivityBarService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, { draggable: true }, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this._keybinding = this.getKeybindingLabel(this.viewlet.id);
|
||||
|
||||
if (!ViewletActionItem.manageExtensionAction) {
|
||||
ViewletActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
|
||||
if (!ViewletActionItem.toggleViewletPinnedAction) {
|
||||
ViewletActionItem.toggleViewletPinnedAction = instantiationService.createInstance(ToggleViewletPinnedAction, void 0);
|
||||
}
|
||||
}
|
||||
|
||||
private get viewlet(): ViewletDescriptor {
|
||||
return this.action.activity as ViewletDescriptor;
|
||||
}
|
||||
|
||||
private getKeybindingLabel(id: string): string {
|
||||
const kb = this.keybindingService.lookupKeybinding(id);
|
||||
if (kb) {
|
||||
return kb.getLabel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this.$container.on('contextmenu', e => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(container);
|
||||
});
|
||||
|
||||
// Allow to drag
|
||||
this.$container.on(DOM.EventType.DRAG_START, (e: DragEvent) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
this.setDraggedViewlet(this.viewlet);
|
||||
|
||||
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
||||
if (!this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
});
|
||||
|
||||
// Drag enter
|
||||
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
|
||||
this.$container.on(DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) {
|
||||
counter++;
|
||||
this.updateFromDragging(container, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag leave
|
||||
this.$container.on(DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
this.updateFromDragging(container, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Drag end
|
||||
this.$container.on(DOM.EventType.DRAG_END, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
counter = 0;
|
||||
this.updateFromDragging(container, false);
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
}
|
||||
});
|
||||
|
||||
// Drop
|
||||
this.$container.on(DOM.EventType.DROP, (e: DragEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) {
|
||||
this.updateFromDragging(container, false);
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
this.activityBarService.move(draggedViewlet.id, this.viewlet.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Keybinding
|
||||
this.keybinding = this._keybinding; // force update
|
||||
|
||||
// Activate on drag over to reveal targets
|
||||
[this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => {
|
||||
if (!ViewletActionItem.getDraggedViewlet() && !this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const dragBackground = theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND);
|
||||
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
|
||||
}
|
||||
|
||||
public static getDraggedViewlet(): ViewletDescriptor {
|
||||
return ViewletActionItem.draggedViewlet;
|
||||
}
|
||||
|
||||
private setDraggedViewlet(viewlet: ViewletDescriptor): void {
|
||||
ViewletActionItem.draggedViewlet = viewlet;
|
||||
}
|
||||
|
||||
public static clearDraggedViewlet(): void {
|
||||
ViewletActionItem.draggedViewlet = void 0;
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [ViewletActionItem.toggleViewletPinnedAction];
|
||||
if (this.viewlet.extensionId) {
|
||||
actions.push(new Separator());
|
||||
actions.push(ViewletActionItem.manageExtensionAction);
|
||||
}
|
||||
|
||||
const isPinned = this.activityBarService.isPinned(this.viewlet.id);
|
||||
if (isPinned) {
|
||||
ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('removeFromActivityBar', "Remove from Activity Bar");
|
||||
} else {
|
||||
ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar");
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getActionsContext: () => this.viewlet,
|
||||
getActions: () => TPromise.as(actions)
|
||||
});
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.$container.domFocus();
|
||||
}
|
||||
|
||||
public set keybinding(keybinding: string) {
|
||||
this._keybinding = keybinding;
|
||||
|
||||
if (!this.$label) {
|
||||
return;
|
||||
}
|
||||
|
||||
let title: string;
|
||||
if (keybinding) {
|
||||
title = nls.localize('titleKeybinding', "{0} ({1})", this.activity.name, keybinding);
|
||||
} else {
|
||||
title = this.activity.name;
|
||||
}
|
||||
|
||||
this.$label.title(title);
|
||||
this.$badge.title(title);
|
||||
this.$container.title(title);
|
||||
}
|
||||
|
||||
protected _updateClass(): void {
|
||||
if (this.cssClass) {
|
||||
this.$badge.removeClass(this.cssClass);
|
||||
}
|
||||
|
||||
this.cssClass = this.getAction().class;
|
||||
this.$badge.addClass(this.cssClass);
|
||||
}
|
||||
|
||||
protected _updateChecked(): void {
|
||||
if (this.getAction().checked) {
|
||||
this.$container.addClass('checked');
|
||||
} else {
|
||||
this.$container.removeClass('checked');
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateEnabled(): void {
|
||||
if (this.getAction().enabled) {
|
||||
this.builder.removeClass('disabled');
|
||||
} else {
|
||||
this.builder.addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
this.$label.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletOverflowActivityAction extends ActivityAction {
|
||||
|
||||
constructor(
|
||||
private showMenu: () => void
|
||||
) {
|
||||
super({
|
||||
id: 'activitybar.additionalViewlets.action',
|
||||
name: nls.localize('additionalViews', "Additional Views"),
|
||||
cssClass: 'toggle-more'
|
||||
});
|
||||
}
|
||||
|
||||
public run(event): TPromise<any> {
|
||||
this.showMenu();
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletOverflowActivityActionItem extends ActivityActionItem {
|
||||
private name: string;
|
||||
private cssClass: string;
|
||||
private actions: OpenViewletAction[];
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
private getOverflowingViewlets: () => ViewletDescriptor[],
|
||||
private getBadge: (viewlet: ViewletDescriptor) => IBadge,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, null, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this.name = action.label;
|
||||
}
|
||||
|
||||
public showMenu(): void {
|
||||
if (this.actions) {
|
||||
dispose(this.actions);
|
||||
}
|
||||
|
||||
this.actions = this.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.builder.getHTMLElement(),
|
||||
getActions: () => TPromise.as(this.actions),
|
||||
onHide: () => dispose(this.actions)
|
||||
});
|
||||
}
|
||||
|
||||
private getActions(): OpenViewletAction[] {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
return this.getOverflowingViewlets().map(viewlet => {
|
||||
const action = this.instantiationService.createInstance(OpenViewletAction, viewlet);
|
||||
action.radio = activeViewlet && activeViewlet.getId() === action.id;
|
||||
|
||||
const badge = this.getBadge(action.viewlet);
|
||||
let suffix: string | number;
|
||||
if (badge instanceof NumberBadge) {
|
||||
suffix = badge.number;
|
||||
} else if (badge instanceof TextBadge) {
|
||||
suffix = badge.text;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
action.label = nls.localize('numberBadge', "{0} ({1})", action.viewlet.name, suffix);
|
||||
} else {
|
||||
action.label = action.viewlet.name;
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.actions = dispose(this.actions);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
public run(viewlet: ViewletDescriptor): TPromise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', viewlet.extensionId);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenViewletAction extends Action {
|
||||
|
||||
constructor(
|
||||
private _viewlet: ViewletDescriptor,
|
||||
@IPartService private partService: IPartService,
|
||||
@IViewletService private viewletService: IViewletService
|
||||
) {
|
||||
super(_viewlet.id, _viewlet.name);
|
||||
}
|
||||
|
||||
public get viewlet(): ViewletDescriptor {
|
||||
return this._viewlet;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this.viewlet.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleViewletPinnedAction extends Action {
|
||||
|
||||
constructor(
|
||||
private viewlet: ViewletDescriptor,
|
||||
@IActivityBarService private activityBarService: IActivityBarService
|
||||
) {
|
||||
super('activitybar.show.toggleViewletPinned', viewlet ? viewlet.name : nls.localize('toggle', "Toggle View Pinned"));
|
||||
|
||||
this.checked = this.viewlet && this.activityBarService.isPinned(this.viewlet.id);
|
||||
}
|
||||
|
||||
public run(context?: ViewletDescriptor): TPromise<any> {
|
||||
const viewlet = this.viewlet || context;
|
||||
|
||||
if (this.activityBarService.isPinned(viewlet.id)) {
|
||||
this.activityBarService.unpin(viewlet.id);
|
||||
} else {
|
||||
this.activityBarService.pin(viewlet.id);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalActivityAction extends ActivityAction {
|
||||
|
||||
constructor(activity: IGlobalActivity) {
|
||||
super(activity);
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
|
||||
constructor(
|
||||
action: GlobalActivityAction,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(action, { draggable: false }, themeService);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Context menus are triggered on mouse down so that an item can be picked
|
||||
// and executed with releasing the mouse over it
|
||||
this.$container.on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
const event = new StandardMouseEvent(e);
|
||||
this.showContextMenu({ x: event.posx, y: event.posy });
|
||||
});
|
||||
|
||||
this.$container.on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(this.$container.getHTMLElement());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showContextMenu(location: HTMLElement | { x: number, y: number }): void {
|
||||
const globalAction = this._action as GlobalActivityAction;
|
||||
const activity = globalAction.activity as IGlobalActivity;
|
||||
const actions = activity.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => location,
|
||||
getActions: () => TPromise.as(actions),
|
||||
onHide: () => dispose(actions)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
if (outline) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 9px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:hover:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:hover:before {
|
||||
outline: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover:before {
|
||||
outline: 1px dashed;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover:before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
border-left-color: ${outline};
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:hover:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:hover:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover:before {
|
||||
outline-color: ${outline};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Styling without outline color
|
||||
else {
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover .action-label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item .action-label {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
border-left-color: ${focusBorderColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
});
|
||||
562
src/vs/workbench/browser/parts/activitybar/activitybarPart.ts
Normal file
@@ -0,0 +1,562 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/activitybarpart';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { Builder, $, Dimension } from 'vs/base/browser/builder';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ActionsOrientation, ActionBar, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/browser/activity';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { ToggleViewletPinnedAction, ViewletActivityAction, ActivityAction, GlobalActivityActionItem, ViewletActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem, GlobalActivityAction, IViewletActivity } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IActivityBarService, IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import { IPartService, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Scope as MementoScope } from 'vs/workbench/common/memento';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private static readonly ACTIVITY_ACTION_HEIGHT = 50;
|
||||
private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets';
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private dimension: Dimension;
|
||||
|
||||
private globalActionBar: ActionBar;
|
||||
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; };
|
||||
|
||||
private viewletSwitcherBar: ActionBar;
|
||||
private viewletOverflowAction: ViewletOverflowActivityAction;
|
||||
private viewletOverflowActionItem: ViewletOverflowActivityActionItem;
|
||||
|
||||
private viewletIdToActions: { [viewletId: string]: ActivityAction; };
|
||||
private viewletIdToActionItems: { [viewletId: string]: IActionItem; };
|
||||
private viewletIdToActivityStack: { [viewletId: string]: IViewletActivity[]; };
|
||||
|
||||
private memento: object;
|
||||
private pinnedViewlets: string[];
|
||||
private activeUnpinnedViewlet: ViewletDescriptor;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this.globalActivityIdToActions = Object.create(null);
|
||||
|
||||
this.viewletIdToActionItems = Object.create(null);
|
||||
this.viewletIdToActions = Object.create(null);
|
||||
this.viewletIdToActivityStack = Object.create(null);
|
||||
|
||||
this.memento = this.getMemento(this.storageService, MementoScope.GLOBAL);
|
||||
|
||||
const pinnedViewlets = this.memento[ActivitybarPart.PINNED_VIEWLETS] as string[];
|
||||
|
||||
if (pinnedViewlets) {
|
||||
this.pinnedViewlets = pinnedViewlets
|
||||
// TODO@Ben: Migrate git => scm viewlet
|
||||
.map(id => id === 'workbench.view.git' ? 'workbench.view.scm' : id)
|
||||
.filter(arrays.uniqueFilter<string>(str => str));
|
||||
} else {
|
||||
this.pinnedViewlets = this.viewletService.getViewlets().map(v => v.id);
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Activate viewlet action on opening of a viewlet
|
||||
this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.onDidViewletOpen(viewlet)));
|
||||
|
||||
// Deactivate viewlet action on close
|
||||
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.onDidViewletClose(viewlet)));
|
||||
}
|
||||
|
||||
private onDidViewletOpen(viewlet: IViewlet): void {
|
||||
const id = viewlet.getId();
|
||||
|
||||
if (this.viewletIdToActions[id]) {
|
||||
this.viewletIdToActions[id].activate();
|
||||
}
|
||||
|
||||
const activeUnpinnedViewletShouldClose = this.activeUnpinnedViewlet && this.activeUnpinnedViewlet.id !== viewlet.getId();
|
||||
const activeUnpinnedViewletShouldShow = !this.getPinnedViewlets().some(v => v.id === viewlet.getId());
|
||||
if (activeUnpinnedViewletShouldShow || activeUnpinnedViewletShouldClose) {
|
||||
this.updateViewletSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidViewletClose(viewlet: IViewlet): void {
|
||||
const id = viewlet.getId();
|
||||
|
||||
if (this.viewletIdToActions[id]) {
|
||||
this.viewletIdToActions[id].deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
public showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const action = this.globalActivityIdToActions[globalActivityId];
|
||||
if (!action) {
|
||||
throw illegalArgument('globalActivityId');
|
||||
}
|
||||
|
||||
action.setBadge(badge);
|
||||
|
||||
return toDisposable(() => action.setBadge(undefined));
|
||||
}
|
||||
|
||||
public showActivity(viewletId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const activity = <IViewletActivity>{ badge, clazz };
|
||||
const stack = this.viewletIdToActivityStack[viewletId] || (this.viewletIdToActivityStack[viewletId] = []);
|
||||
stack.unshift(activity);
|
||||
|
||||
this.updateActivity(viewletId);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
const stack = this.viewletIdToActivityStack[viewletId];
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
const idx = stack.indexOf(activity);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
stack.splice(idx, 1);
|
||||
if (stack.length === 0) {
|
||||
delete this.viewletIdToActivityStack[viewletId];
|
||||
}
|
||||
this.updateActivity(viewletId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private updateActivity(viewletId: string) {
|
||||
const action = this.viewletIdToActions[viewletId];
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
const stack = this.viewletIdToActivityStack[viewletId];
|
||||
if (!stack || !stack.length) {
|
||||
// reset
|
||||
action.setBadge(undefined);
|
||||
|
||||
} else {
|
||||
// update
|
||||
const [{ badge, clazz }] = stack;
|
||||
action.setBadge(badge);
|
||||
if (clazz) {
|
||||
action.class = clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
const $el = $(parent);
|
||||
const $result = $('.content').appendTo($el);
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createViewletSwitcher($result.clone());
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createGlobalActivityActionBar($result.getHTMLElement());
|
||||
|
||||
// Contextmenu for viewlets
|
||||
$(parent).on('contextmenu', (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(e);
|
||||
}, this.toUnbind);
|
||||
|
||||
// Allow to drop at the end to move viewlet to the end
|
||||
$(parent).on(DOM.EventType.DROP, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
const targetId = this.pinnedViewlets[this.pinnedViewlets.length - 1];
|
||||
if (targetId !== draggedViewlet.id) {
|
||||
this.move(draggedViewlet.id, this.pinnedViewlets[this.pinnedViewlets.length - 1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
// Part container
|
||||
const container = this.getContainer();
|
||||
const background = this.getColor(ACTIVITY_BAR_BACKGROUND);
|
||||
container.style('background-color', background);
|
||||
|
||||
const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder);
|
||||
const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT;
|
||||
container.style('box-sizing', borderColor && isPositionLeft ? 'border-box' : null);
|
||||
container.style('border-right-width', borderColor && isPositionLeft ? '1px' : null);
|
||||
container.style('border-right-style', borderColor && isPositionLeft ? 'solid' : null);
|
||||
container.style('border-right-color', isPositionLeft ? borderColor : null);
|
||||
container.style('border-left-width', borderColor && !isPositionLeft ? '1px' : null);
|
||||
container.style('border-left-style', borderColor && !isPositionLeft ? 'solid' : null);
|
||||
container.style('border-left-color', !isPositionLeft ? borderColor : null);
|
||||
}
|
||||
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
const event = new StandardMouseEvent(e);
|
||||
|
||||
const actions: Action[] = this.viewletService.getViewlets().map(viewlet => this.instantiationService.createInstance(ToggleViewletPinnedAction, viewlet));
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar")));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx + 1, y: event.posy }; },
|
||||
getActions: () => TPromise.as(actions),
|
||||
onHide: () => dispose(actions)
|
||||
});
|
||||
}
|
||||
|
||||
private createViewletSwitcher(div: Builder): void {
|
||||
this.viewletSwitcherBar = new ActionBar(div, {
|
||||
actionItemProvider: (action: Action) => action instanceof ViewletOverflowActivityAction ? this.viewletOverflowActionItem : this.viewletIdToActionItems[action.id],
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false
|
||||
});
|
||||
|
||||
this.updateViewletSwitcher();
|
||||
|
||||
// Update viewlet switcher when external viewlets become ready
|
||||
this.extensionService.onReady().then(() => this.updateViewletSwitcher());
|
||||
}
|
||||
|
||||
private createGlobalActivityActionBar(container: HTMLElement): void {
|
||||
const activityRegistry = Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions);
|
||||
const descriptors = activityRegistry.getActivities();
|
||||
const actions = descriptors
|
||||
.map(d => this.instantiationService.createInstance(d))
|
||||
.map(a => new GlobalActivityAction(a));
|
||||
|
||||
this.globalActionBar = new ActionBar(container, {
|
||||
actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a),
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('globalActions', "Global Actions"),
|
||||
animated: false
|
||||
});
|
||||
|
||||
actions.forEach(a => {
|
||||
this.globalActivityIdToActions[a.id] = a;
|
||||
this.globalActionBar.push(a);
|
||||
});
|
||||
}
|
||||
|
||||
private updateViewletSwitcher() {
|
||||
if (!this.viewletSwitcherBar) {
|
||||
return; // We have not been rendered yet so there is nothing to update.
|
||||
}
|
||||
|
||||
let viewletsToShow = this.getPinnedViewlets();
|
||||
|
||||
// Always show the active viewlet even if it is marked to be hidden
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet && !viewletsToShow.some(viewlet => viewlet.id === activeViewlet.getId())) {
|
||||
this.activeUnpinnedViewlet = this.viewletService.getViewlet(activeViewlet.getId());
|
||||
viewletsToShow.push(this.activeUnpinnedViewlet);
|
||||
} else {
|
||||
this.activeUnpinnedViewlet = void 0;
|
||||
}
|
||||
|
||||
// Ensure we are not showing more viewlets than we have height for
|
||||
let overflows = false;
|
||||
if (this.dimension) {
|
||||
let availableHeight = this.dimension.height;
|
||||
if (this.globalActionBar) {
|
||||
availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTIVITY_ACTION_HEIGHT); // adjust for global actions showing
|
||||
}
|
||||
|
||||
const maxVisible = Math.floor(availableHeight / ActivitybarPart.ACTIVITY_ACTION_HEIGHT);
|
||||
overflows = viewletsToShow.length > maxVisible;
|
||||
|
||||
if (overflows) {
|
||||
viewletsToShow = viewletsToShow.slice(0, maxVisible - 1 /* make room for overflow action */);
|
||||
}
|
||||
}
|
||||
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
const visibleViewletsChange = !arrays.equals(viewletsToShow.map(viewlet => viewlet.id), visibleViewlets);
|
||||
|
||||
// Pull out overflow action if there is a viewlet change so that we can add it to the end later
|
||||
if (this.viewletOverflowAction && visibleViewletsChange) {
|
||||
this.viewletSwitcherBar.pull(this.viewletSwitcherBar.length() - 1);
|
||||
|
||||
this.viewletOverflowAction.dispose();
|
||||
this.viewletOverflowAction = null;
|
||||
|
||||
this.viewletOverflowActionItem.dispose();
|
||||
this.viewletOverflowActionItem = null;
|
||||
}
|
||||
|
||||
// Pull out viewlets that overflow or got hidden
|
||||
const viewletIdsToShow = viewletsToShow.map(v => v.id);
|
||||
visibleViewlets.forEach(viewletId => {
|
||||
if (viewletIdsToShow.indexOf(viewletId) === -1) {
|
||||
this.pullViewlet(viewletId);
|
||||
}
|
||||
});
|
||||
|
||||
// Built actions for viewlets to show
|
||||
const newViewletsToShow = viewletsToShow
|
||||
.filter(viewlet => !this.viewletIdToActions[viewlet.id])
|
||||
.map(viewlet => this.toAction(viewlet));
|
||||
|
||||
// Update when we have new viewlets to show
|
||||
if (newViewletsToShow.length) {
|
||||
|
||||
// Add to viewlet switcher
|
||||
this.viewletSwitcherBar.push(newViewletsToShow, { label: true, icon: true });
|
||||
|
||||
// Make sure to activate the active one
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet) {
|
||||
const activeViewletEntry = this.viewletIdToActions[activeViewlet.getId()];
|
||||
if (activeViewletEntry) {
|
||||
activeViewletEntry.activate();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to restore activity
|
||||
Object.keys(this.viewletIdToActions).forEach(viewletId => {
|
||||
this.updateActivity(viewletId);
|
||||
});
|
||||
}
|
||||
|
||||
// Add overflow action as needed
|
||||
if (visibleViewletsChange && overflows) {
|
||||
this.viewletOverflowAction = this.instantiationService.createInstance(ViewletOverflowActivityAction, () => this.viewletOverflowActionItem.showMenu());
|
||||
this.viewletOverflowActionItem = this.instantiationService.createInstance(ViewletOverflowActivityActionItem, this.viewletOverflowAction, () => this.getOverflowingViewlets(), (viewlet: ViewletDescriptor) => this.viewletIdToActivityStack[viewlet.id] && this.viewletIdToActivityStack[viewlet.id][0].badge);
|
||||
|
||||
this.viewletSwitcherBar.push(this.viewletOverflowAction, { label: true, icon: true });
|
||||
}
|
||||
}
|
||||
|
||||
private getOverflowingViewlets(): ViewletDescriptor[] {
|
||||
const viewlets = this.getPinnedViewlets();
|
||||
if (this.activeUnpinnedViewlet) {
|
||||
viewlets.push(this.activeUnpinnedViewlet);
|
||||
}
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
|
||||
return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) === -1);
|
||||
}
|
||||
|
||||
private getVisibleViewlets(): ViewletDescriptor[] {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
|
||||
return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) >= 0);
|
||||
}
|
||||
|
||||
private getPinnedViewlets(): ViewletDescriptor[] {
|
||||
return this.pinnedViewlets.map(viewletId => this.viewletService.getViewlet(viewletId)).filter(v => !!v); // ensure to remove those that might no longer exist
|
||||
}
|
||||
|
||||
private pullViewlet(viewletId: string): void {
|
||||
const index = Object.keys(this.viewletIdToActions).indexOf(viewletId);
|
||||
if (index >= 0) {
|
||||
this.viewletSwitcherBar.pull(index);
|
||||
|
||||
const action = this.viewletIdToActions[viewletId];
|
||||
action.dispose();
|
||||
delete this.viewletIdToActions[viewletId];
|
||||
|
||||
const actionItem = this.viewletIdToActionItems[action.id];
|
||||
actionItem.dispose();
|
||||
delete this.viewletIdToActionItems[action.id];
|
||||
}
|
||||
}
|
||||
|
||||
private toAction(viewlet: ViewletDescriptor): ActivityAction {
|
||||
const action = this.instantiationService.createInstance(ViewletActivityAction, viewlet);
|
||||
|
||||
this.viewletIdToActionItems[action.id] = this.instantiationService.createInstance(ViewletActionItem, action);
|
||||
this.viewletIdToActions[viewlet.id] = action;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
public getPinned(): string[] {
|
||||
return this.pinnedViewlets;
|
||||
}
|
||||
|
||||
public unpin(viewletId: string): void {
|
||||
if (!this.isPinned(viewletId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
const defaultViewletId = this.viewletService.getDefaultViewletId();
|
||||
const visibleViewlets = this.getVisibleViewlets();
|
||||
|
||||
let unpinPromise: TPromise<any>;
|
||||
|
||||
// Case: viewlet is not the active one or the active one is a different one
|
||||
// Solv: we do nothing
|
||||
if (!activeViewlet || activeViewlet.getId() !== viewletId) {
|
||||
unpinPromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
// Case: viewlet is not the default viewlet and default viewlet is still showing
|
||||
// Solv: we open the default viewlet
|
||||
else if (defaultViewletId !== viewletId && this.isPinned(defaultViewletId)) {
|
||||
unpinPromise = this.viewletService.openViewlet(defaultViewletId, true);
|
||||
}
|
||||
|
||||
// Case: we closed the last visible viewlet
|
||||
// Solv: we hide the sidebar
|
||||
else if (visibleViewlets.length === 1) {
|
||||
unpinPromise = this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
// Case: we closed the default viewlet
|
||||
// Solv: we open the next visible viewlet from top
|
||||
else {
|
||||
unpinPromise = this.viewletService.openViewlet(visibleViewlets.filter(viewlet => viewlet.id !== viewletId)[0].id, true);
|
||||
}
|
||||
|
||||
unpinPromise.then(() => {
|
||||
|
||||
// then remove from pinned and update switcher
|
||||
const index = this.pinnedViewlets.indexOf(viewletId);
|
||||
this.pinnedViewlets.splice(index, 1);
|
||||
|
||||
this.updateViewletSwitcher();
|
||||
});
|
||||
}
|
||||
|
||||
public isPinned(viewletId: string): boolean {
|
||||
return this.pinnedViewlets.indexOf(viewletId) >= 0;
|
||||
}
|
||||
|
||||
public pin(viewletId: string, update = true): void {
|
||||
if (this.isPinned(viewletId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// first open that viewlet
|
||||
this.viewletService.openViewlet(viewletId, true).then(() => {
|
||||
|
||||
// then update
|
||||
this.pinnedViewlets.push(viewletId);
|
||||
this.pinnedViewlets = arrays.distinct(this.pinnedViewlets);
|
||||
|
||||
if (update) {
|
||||
this.updateViewletSwitcher();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public move(viewletId: string, toViewletId: string): void {
|
||||
|
||||
// Make sure a moved viewlet gets pinned
|
||||
if (!this.isPinned(viewletId)) {
|
||||
this.pin(viewletId, false /* defer update, we take care of it */);
|
||||
}
|
||||
|
||||
const fromIndex = this.pinnedViewlets.indexOf(viewletId);
|
||||
const toIndex = this.pinnedViewlets.indexOf(toViewletId);
|
||||
|
||||
this.pinnedViewlets.splice(fromIndex, 1);
|
||||
this.pinnedViewlets.splice(toIndex, 0, viewletId);
|
||||
|
||||
// Clear viewlets that are impacted by the move
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
for (let i = Math.min(fromIndex, toIndex); i < visibleViewlets.length; i++) {
|
||||
this.pullViewlet(visibleViewlets[i]);
|
||||
}
|
||||
|
||||
// timeout helps to prevent artifacts from showing up
|
||||
setTimeout(() => {
|
||||
this.updateViewletSwitcher();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout title, content and status area in the given dimension.
|
||||
*/
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// Pass to super
|
||||
const sizes = super.layout(dimension);
|
||||
|
||||
this.dimension = sizes[1];
|
||||
|
||||
// Update switcher to handle overflow issues
|
||||
this.updateViewletSwitcher();
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.viewletSwitcherBar) {
|
||||
this.viewletSwitcherBar.dispose();
|
||||
this.viewletSwitcherBar = null;
|
||||
}
|
||||
|
||||
if (this.globalActionBar) {
|
||||
this.globalActionBar.dispose();
|
||||
this.globalActionBar = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
|
||||
// Persist Hidden State
|
||||
this.memento[ActivitybarPart.PINNED_VIEWLETS] = this.pinnedViewlets;
|
||||
|
||||
// Pass to super
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-label {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin-right: 0;
|
||||
padding: 0 0 0 50px;
|
||||
box-sizing: border-box;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
height: 32px;
|
||||
width: 0;
|
||||
border-left: 2px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.clicked:focus:before {
|
||||
border-left: none !important; /* no focus feedback when using mouse */
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar.left > .content .monaco-action-bar .action-item:focus:before {
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar.right > .content .monaco-action-bar .action-item:focus:before {
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-label.toggle-more {
|
||||
-webkit-mask: url('ellipsis-global.svg') no-repeat 50% 50%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .badge {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
width: 50px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .badge .badge-content {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 8px;
|
||||
font-size: 11px;
|
||||
min-width: 8px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
padding: 0 5px;
|
||||
border-radius: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Right aligned */
|
||||
|
||||
.monaco-workbench > .activitybar.right > .content .monaco-action-bar .action-label {
|
||||
margin-left: 0;
|
||||
padding: 0 50px 0 0;
|
||||
background-position: calc(100% - 9px) center;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar.right > .content .monaco-action-bar .badge {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .part.activitybar {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar {
|
||||
text-align: left;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar [tabindex="0"]:focus {
|
||||
outline: 0 !important; /* activity bar indicates focus custom */
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0;}.cls-1{fill:#fff;}</style></defs><title>Ellipsis_32x</title><g id="canvas"><path class="icon-canvas-transparent" d="M32,32H0V0H32Z" transform="translate(0 0)"/></g><g id="iconBg"><path class="cls-1" d="M9,16a3,3,0,1,1-3-3A3,3,0,0,1,9,16Zm7-3a3,3,0,1,0,3,3A3,3,0,0,0,16,13Zm10,0a3,3,0,1,0,3,3A3,3,0,0,0,26,13Z" transform="translate(0 0)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 493 B |
581
src/vs/workbench/browser/parts/compositePart.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/compositepart';
|
||||
import nls = require('vs/nls');
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
|
||||
import events = require('vs/base/common/events');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import types = require('vs/base/common/types');
|
||||
import errors = require('vs/base/common/errors');
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { CONTEXT as ToolBarContext, ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { Part, IPartOptions } from 'vs/workbench/browser/part';
|
||||
import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
import { WorkbenchProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
|
||||
export interface ICompositeTitleLabel {
|
||||
|
||||
/**
|
||||
* Asks to update the title for the composite with the given ID.
|
||||
*/
|
||||
updateTitle(id: string, title: string, keybinding?: string): void;
|
||||
|
||||
/**
|
||||
* Called when theming information changes.
|
||||
*/
|
||||
updateStyles(): void;
|
||||
}
|
||||
|
||||
export abstract class CompositePart<T extends Composite> extends Part {
|
||||
private instantiatedCompositeListeners: IDisposable[];
|
||||
private mapCompositeToCompositeContainer: { [compositeId: string]: Builder; };
|
||||
private mapActionsBindingToComposite: { [compositeId: string]: () => void; };
|
||||
private mapProgressServiceToComposite: { [compositeId: string]: IProgressService; };
|
||||
private activeComposite: Composite;
|
||||
private lastActiveCompositeId: string;
|
||||
private instantiatedComposites: Composite[];
|
||||
private titleLabel: ICompositeTitleLabel;
|
||||
private toolBar: ToolBar;
|
||||
private compositeLoaderPromises: { [compositeId: string]: TPromise<Composite>; };
|
||||
private progressBar: ProgressBar;
|
||||
private contentAreaSize: Dimension;
|
||||
private telemetryActionsListener: IDisposable;
|
||||
private currentCompositeOpenToken: string;
|
||||
protected _onDidCompositeOpen = new Emitter<IComposite>();
|
||||
protected _onDidCompositeClose = new Emitter<IComposite>();
|
||||
|
||||
constructor(
|
||||
private messageService: IMessageService,
|
||||
private storageService: IStorageService,
|
||||
private telemetryService: ITelemetryService,
|
||||
private contextMenuService: IContextMenuService,
|
||||
protected partService: IPartService,
|
||||
private keybindingService: IKeybindingService,
|
||||
protected instantiationService: IInstantiationService,
|
||||
themeService: IThemeService,
|
||||
private registry: CompositeRegistry<T>,
|
||||
private activeCompositeSettingsKey: string,
|
||||
private defaultCompositeId: string,
|
||||
private nameForTelemetry: string,
|
||||
private compositeCSSClass: string,
|
||||
private actionContributionScope: string,
|
||||
private titleForegroundColor: string,
|
||||
id: string,
|
||||
options: IPartOptions
|
||||
) {
|
||||
super(id, options, themeService);
|
||||
|
||||
this.instantiatedCompositeListeners = [];
|
||||
this.mapCompositeToCompositeContainer = {};
|
||||
this.mapActionsBindingToComposite = {};
|
||||
this.mapProgressServiceToComposite = {};
|
||||
this.activeComposite = null;
|
||||
this.instantiatedComposites = [];
|
||||
this.compositeLoaderPromises = {};
|
||||
this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId);
|
||||
}
|
||||
|
||||
protected openComposite(id: string, focus?: boolean): TPromise<Composite> {
|
||||
// Check if composite already visible and just focus in that case
|
||||
if (this.activeComposite && this.activeComposite.getId() === id) {
|
||||
if (focus) {
|
||||
this.activeComposite.focus();
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return TPromise.as(this.activeComposite);
|
||||
}
|
||||
|
||||
// Open
|
||||
return this.doOpenComposite(id, focus);
|
||||
}
|
||||
|
||||
private doOpenComposite(id: string, focus?: boolean): TPromise<Composite> {
|
||||
|
||||
// Use a generated token to avoid race conditions from long running promises
|
||||
let currentCompositeOpenToken = defaultGenerator.nextId();
|
||||
this.currentCompositeOpenToken = currentCompositeOpenToken;
|
||||
|
||||
// Hide current
|
||||
let hidePromise: TPromise<Composite>;
|
||||
if (this.activeComposite) {
|
||||
hidePromise = this.hideActiveComposite();
|
||||
} else {
|
||||
hidePromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
return hidePromise.then(() => {
|
||||
|
||||
// Update Title
|
||||
this.updateTitle(id);
|
||||
|
||||
// Create composite
|
||||
return this.createComposite(id, true).then(composite => {
|
||||
|
||||
// Check if another composite opened meanwhile and return in that case
|
||||
if ((this.currentCompositeOpenToken !== currentCompositeOpenToken) || (this.activeComposite && this.activeComposite.getId() !== composite.getId())) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// Check if composite already visible and just focus in that case
|
||||
if (this.activeComposite && this.activeComposite.getId() === composite.getId()) {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return TPromise.as(composite);
|
||||
}
|
||||
|
||||
// Show Composite and Focus
|
||||
return this.showComposite(composite).then(() => {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return composite;
|
||||
});
|
||||
});
|
||||
}).then(composite => {
|
||||
if (composite) {
|
||||
this._onDidCompositeOpen.fire(composite);
|
||||
}
|
||||
|
||||
return composite;
|
||||
});
|
||||
}
|
||||
|
||||
protected createComposite(id: string, isActive?: boolean): TPromise<Composite> {
|
||||
|
||||
// Check if composite is already created
|
||||
for (let i = 0; i < this.instantiatedComposites.length; i++) {
|
||||
if (this.instantiatedComposites[i].getId() === id) {
|
||||
return TPromise.as(this.instantiatedComposites[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate composite from registry otherwise
|
||||
let compositeDescriptor = this.registry.getComposite(id);
|
||||
if (compositeDescriptor) {
|
||||
let loaderPromise = this.compositeLoaderPromises[id];
|
||||
if (!loaderPromise) {
|
||||
let progressService = this.instantiationService.createInstance(WorkbenchProgressService, this.progressBar, compositeDescriptor.id, isActive);
|
||||
let compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService]));
|
||||
|
||||
loaderPromise = compositeInstantiationService.createInstance(compositeDescriptor).then((composite: Composite) => {
|
||||
this.mapProgressServiceToComposite[composite.getId()] = progressService;
|
||||
|
||||
// Remember as Instantiated
|
||||
this.instantiatedComposites.push(composite);
|
||||
|
||||
// Register to title area update events from the composite
|
||||
this.instantiatedCompositeListeners.push(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId())));
|
||||
|
||||
// Remove from Promises Cache since Loaded
|
||||
delete this.compositeLoaderPromises[id];
|
||||
|
||||
return composite;
|
||||
});
|
||||
|
||||
// Report progress for slow loading composites
|
||||
progressService.showWhile(loaderPromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */);
|
||||
|
||||
// Add to Promise Cache until Loaded
|
||||
this.compositeLoaderPromises[id] = loaderPromise;
|
||||
}
|
||||
|
||||
return loaderPromise;
|
||||
}
|
||||
|
||||
throw new Error(strings.format('Unable to find composite with id {0}', id));
|
||||
}
|
||||
|
||||
protected showComposite(composite: Composite): TPromise<void> {
|
||||
|
||||
// Remember Composite
|
||||
this.activeComposite = composite;
|
||||
|
||||
// Store in preferences
|
||||
const id = this.activeComposite.getId();
|
||||
if (id !== this.defaultCompositeId) {
|
||||
this.storageService.store(this.activeCompositeSettingsKey, id, StorageScope.WORKSPACE);
|
||||
} else {
|
||||
this.storageService.remove(this.activeCompositeSettingsKey, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
// Remember
|
||||
this.lastActiveCompositeId = this.activeComposite.getId();
|
||||
|
||||
let createCompositePromise: TPromise<void>;
|
||||
|
||||
// Composites created for the first time
|
||||
let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
if (!compositeContainer) {
|
||||
|
||||
// Build Container off-DOM
|
||||
compositeContainer = $().div({
|
||||
'class': ['composite', this.compositeCSSClass],
|
||||
id: composite.getId()
|
||||
}, (div: Builder) => {
|
||||
createCompositePromise = composite.create(div).then(() => {
|
||||
composite.updateStyles();
|
||||
});
|
||||
});
|
||||
|
||||
// Remember composite container
|
||||
this.mapCompositeToCompositeContainer[composite.getId()] = compositeContainer;
|
||||
}
|
||||
|
||||
// Composite already exists but is hidden
|
||||
else {
|
||||
createCompositePromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
// Report progress for slow loading composites (but only if we did not create the composites before already)
|
||||
let progressService = this.mapProgressServiceToComposite[composite.getId()];
|
||||
if (progressService && !compositeContainer) {
|
||||
this.mapProgressServiceToComposite[composite.getId()].showWhile(createCompositePromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */);
|
||||
}
|
||||
|
||||
// Fill Content and Actions
|
||||
return createCompositePromise.then(() => {
|
||||
|
||||
// Make sure that the user meanwhile did not open another composite or closed the part containing the composite
|
||||
if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Take Composite on-DOM and show
|
||||
compositeContainer.build(this.getContentArea());
|
||||
compositeContainer.show();
|
||||
|
||||
// Setup action runner
|
||||
this.toolBar.actionRunner = composite.getActionRunner();
|
||||
|
||||
// Update title with composite title if it differs from descriptor
|
||||
let descriptor = this.registry.getComposite(composite.getId());
|
||||
if (descriptor && descriptor.name !== composite.getTitle()) {
|
||||
this.updateTitle(composite.getId(), composite.getTitle());
|
||||
}
|
||||
|
||||
// Handle Composite Actions
|
||||
let actionsBinding = this.mapActionsBindingToComposite[composite.getId()];
|
||||
if (!actionsBinding) {
|
||||
actionsBinding = this.collectCompositeActions(composite);
|
||||
this.mapActionsBindingToComposite[composite.getId()] = actionsBinding;
|
||||
}
|
||||
actionsBinding();
|
||||
|
||||
if (this.telemetryActionsListener) {
|
||||
this.telemetryActionsListener.dispose();
|
||||
this.telemetryActionsListener = null;
|
||||
}
|
||||
|
||||
// Action Run Handling
|
||||
this.telemetryActionsListener = this.toolBar.actionRunner.addListener(events.EventType.RUN, (e: any) => {
|
||||
|
||||
// Check for Error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.messageService.show(Severity.Error, e.error);
|
||||
}
|
||||
|
||||
// Log in telemetry
|
||||
if (this.telemetryService) {
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry });
|
||||
}
|
||||
});
|
||||
|
||||
// Indicate to composite that it is now visible
|
||||
return composite.setVisible(true).then(() => {
|
||||
|
||||
// Make sure that the user meanwhile did not open another composite or closed the part containing the composite
|
||||
if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the composite is layed out
|
||||
if (this.contentAreaSize) {
|
||||
composite.layout(this.contentAreaSize);
|
||||
}
|
||||
});
|
||||
}, (error: any) => this.onError(error));
|
||||
}
|
||||
|
||||
protected onTitleAreaUpdate(compositeId: string): void {
|
||||
|
||||
// Active Composite
|
||||
if (this.activeComposite && this.activeComposite.getId() === compositeId) {
|
||||
|
||||
// Title
|
||||
this.updateTitle(this.activeComposite.getId(), this.activeComposite.getTitle());
|
||||
|
||||
// Actions
|
||||
let actionsBinding = this.collectCompositeActions(this.activeComposite);
|
||||
this.mapActionsBindingToComposite[this.activeComposite.getId()] = actionsBinding;
|
||||
actionsBinding();
|
||||
}
|
||||
|
||||
// Otherwise invalidate actions binding for next time when the composite becomes visible
|
||||
else {
|
||||
delete this.mapActionsBindingToComposite[compositeId];
|
||||
}
|
||||
}
|
||||
|
||||
private updateTitle(compositeId: string, compositeTitle?: string): void {
|
||||
let compositeDescriptor = this.registry.getComposite(compositeId);
|
||||
if (!compositeDescriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!compositeTitle) {
|
||||
compositeTitle = compositeDescriptor.name;
|
||||
}
|
||||
|
||||
const keybinding = this.keybindingService.lookupKeybinding(compositeId);
|
||||
|
||||
this.titleLabel.updateTitle(compositeId, compositeTitle, keybinding ? keybinding.getLabel() : undefined);
|
||||
|
||||
this.toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle));
|
||||
}
|
||||
|
||||
private collectCompositeActions(composite: Composite): () => void {
|
||||
|
||||
// From Composite
|
||||
let primaryActions: IAction[] = composite.getActions().slice(0);
|
||||
let secondaryActions: IAction[] = composite.getSecondaryActions().slice(0);
|
||||
|
||||
// From Part
|
||||
primaryActions.push(...this.getActions());
|
||||
secondaryActions.push(...this.getSecondaryActions());
|
||||
|
||||
// From Contributions
|
||||
let actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
primaryActions.push(...actionBarRegistry.getActionBarActionsForContext(this.actionContributionScope, composite));
|
||||
secondaryActions.push(...actionBarRegistry.getSecondaryActionBarActionsForContext(this.actionContributionScope, composite));
|
||||
|
||||
// Return fn to set into toolbar
|
||||
return this.toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions));
|
||||
}
|
||||
|
||||
protected getActiveComposite(): IComposite {
|
||||
return this.activeComposite;
|
||||
}
|
||||
|
||||
protected getLastActiveCompositetId(): string {
|
||||
return this.lastActiveCompositeId;
|
||||
}
|
||||
|
||||
protected hideActiveComposite(): TPromise<Composite> {
|
||||
if (!this.activeComposite) {
|
||||
return TPromise.as(null); // Nothing to do
|
||||
}
|
||||
|
||||
let composite = this.activeComposite;
|
||||
this.activeComposite = null;
|
||||
|
||||
let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
|
||||
// Indicate to Composite
|
||||
return composite.setVisible(false).then(() => {
|
||||
|
||||
// Take Container Off-DOM and hide
|
||||
compositeContainer.offDOM();
|
||||
compositeContainer.hide();
|
||||
|
||||
// Clear any running Progress
|
||||
this.progressBar.stop().getContainer().hide();
|
||||
|
||||
// Empty Actions
|
||||
this.toolBar.setActions([])();
|
||||
this._onDidCompositeClose.fire(composite);
|
||||
|
||||
return composite;
|
||||
});
|
||||
}
|
||||
|
||||
public createTitleArea(parent: Builder): Builder {
|
||||
|
||||
// Title Area Container
|
||||
let titleArea = $(parent).div({
|
||||
'class': ['composite', 'title']
|
||||
});
|
||||
|
||||
$(titleArea).on(DOM.EventType.CONTEXT_MENU, (e: MouseEvent) => this.onTitleAreaContextMenu(new StandardMouseEvent(e)));
|
||||
|
||||
// Left Title Label
|
||||
this.titleLabel = this.createTitleLabel(titleArea);
|
||||
|
||||
// Right Actions Container
|
||||
$(titleArea).div({
|
||||
'class': 'title-actions'
|
||||
}, (div) => {
|
||||
|
||||
// Toolbar
|
||||
this.toolBar = new ToolBar(div.getHTMLElement(), this.contextMenuService, {
|
||||
actionItemProvider: (action: Action) => this.actionItemProvider(action),
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id)
|
||||
});
|
||||
});
|
||||
|
||||
return titleArea;
|
||||
}
|
||||
|
||||
protected createTitleLabel(parent: Builder): ICompositeTitleLabel {
|
||||
let titleLabel: Builder;
|
||||
$(parent).div({
|
||||
'class': 'title-label'
|
||||
}, (div) => {
|
||||
titleLabel = div.span();
|
||||
});
|
||||
|
||||
const $this = this;
|
||||
return {
|
||||
updateTitle: (id, title, keybinding) => {
|
||||
titleLabel.safeInnerHtml(title);
|
||||
titleLabel.title(keybinding ? nls.localize('titleTooltip', "{0} ({1})", title, keybinding) : title);
|
||||
},
|
||||
updateStyles: () => {
|
||||
titleLabel.style('color', $this.getColor($this.titleForegroundColor));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
// Forward to title label
|
||||
this.titleLabel.updateStyles();
|
||||
}
|
||||
|
||||
private onTitleAreaContextMenu(event: StandardMouseEvent): void {
|
||||
if (this.activeComposite) {
|
||||
const contextMenuActions = this.getTitleAreaContextMenuActions();
|
||||
if (contextMenuActions.length) {
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(contextMenuActions),
|
||||
getActionItem: (action: Action) => this.actionItemProvider(action),
|
||||
actionRunner: this.activeComposite.getActionRunner(),
|
||||
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getTitleAreaContextMenuActions(): IAction[] {
|
||||
return this.activeComposite ? this.activeComposite.getContextMenuActions() : [];
|
||||
}
|
||||
|
||||
private actionItemProvider(action: Action): IActionItem {
|
||||
let actionItem: IActionItem;
|
||||
|
||||
// Check Active Composite
|
||||
if (this.activeComposite) {
|
||||
actionItem = this.activeComposite.getActionItem(action);
|
||||
}
|
||||
|
||||
// Check Registry
|
||||
if (!actionItem) {
|
||||
let actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
actionItem = actionBarRegistry.getActionItemForContext(this.actionContributionScope, ToolBarContext, action);
|
||||
}
|
||||
|
||||
return actionItem;
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
return $(parent).div({
|
||||
'class': 'content'
|
||||
}, (div: Builder) => {
|
||||
this.progressBar = new ProgressBar(div);
|
||||
this.toUnbind.push(attachProgressBarStyler(this.progressBar, this.themeService));
|
||||
this.progressBar.getContainer().hide();
|
||||
});
|
||||
}
|
||||
|
||||
private onError(error: any): void {
|
||||
this.messageService.show(Severity.Error, types.isString(error) ? new Error(error) : error);
|
||||
}
|
||||
|
||||
public getProgressIndicator(id: string): IProgressService {
|
||||
return this.mapProgressServiceToComposite[id];
|
||||
}
|
||||
|
||||
protected getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// Pass to super
|
||||
let sizes = super.layout(dimension);
|
||||
|
||||
// Pass Contentsize to composite
|
||||
this.contentAreaSize = sizes[1];
|
||||
if (this.activeComposite) {
|
||||
this.activeComposite.layout(this.contentAreaSize);
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.instantiatedComposites.forEach(i => i.shutdown());
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.mapCompositeToCompositeContainer = null;
|
||||
this.mapProgressServiceToComposite = null;
|
||||
this.mapActionsBindingToComposite = null;
|
||||
|
||||
for (let i = 0; i < this.instantiatedComposites.length; i++) {
|
||||
this.instantiatedComposites[i].dispose();
|
||||
}
|
||||
|
||||
this.instantiatedComposites = [];
|
||||
|
||||
this.instantiatedCompositeListeners = dispose(this.instantiatedCompositeListeners);
|
||||
|
||||
this.progressBar.dispose();
|
||||
this.toolBar.dispose();
|
||||
|
||||
// Super Dispose
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
305
src/vs/workbench/browser/parts/editor/baseEditor.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { EditorInput, EditorOptions, IEditorDescriptor, IEditorInputFactory, IEditorRegistry, Extensions, IFileInputFactory } from 'vs/workbench/common/editor';
|
||||
import { IEditor, Position } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { SyncDescriptor, AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
/**
|
||||
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
|
||||
* Editors are layed out in the editor part of the workbench. Only one editor can be open at a time.
|
||||
* Each editor has a minimized representation that is good enough to provide some information about the
|
||||
* state of the editor data.
|
||||
* The workbench will keep an editor alive after it has been created and show/hide it based on
|
||||
* user interaction. The lifecycle of a editor goes in the order create(), setVisible(true|false),
|
||||
* layout(), setInput(), focus(), dispose(). During use of the workbench, a editor will often receive a
|
||||
* clearInput, setVisible, layout and focus call, but only one create and dispose call.
|
||||
*
|
||||
* This class is only intended to be subclassed and not instantiated.
|
||||
*/
|
||||
export abstract class BaseEditor extends Panel implements IEditor {
|
||||
protected _input: EditorInput;
|
||||
private _options: EditorOptions;
|
||||
private _position: Position;
|
||||
|
||||
constructor(id: string, telemetryService: ITelemetryService, themeService: IThemeService) {
|
||||
super(id, telemetryService, themeService);
|
||||
}
|
||||
|
||||
public get input(): EditorInput {
|
||||
return this._input;
|
||||
}
|
||||
|
||||
public get options(): EditorOptions {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Sets the given input with the options to the part. An editor has to deal with the
|
||||
* situation that the same input is being set with different options.
|
||||
*/
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
this._input = input;
|
||||
this._options = options;
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to indicate to the editor that the input should be cleared and resources associated with the
|
||||
* input should be freed.
|
||||
*/
|
||||
public clearInput(): void {
|
||||
this._input = null;
|
||||
this._options = null;
|
||||
}
|
||||
|
||||
public create(parent: Builder): void; // create is sync for editors
|
||||
public create(parent: Builder): TPromise<void>;
|
||||
public create(parent: Builder): TPromise<void> {
|
||||
const res = super.create(parent);
|
||||
|
||||
// Create Editor
|
||||
this.createEditor(parent);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent builder.
|
||||
*/
|
||||
protected abstract createEditor(parent: Builder): void;
|
||||
|
||||
/**
|
||||
* Overload this function to allow for passing in a position argument.
|
||||
*/
|
||||
public setVisible(visible: boolean, position?: Position): void; // setVisible is sync for editors
|
||||
public setVisible(visible: boolean, position?: Position): TPromise<void>;
|
||||
public setVisible(visible: boolean, position: Position = null): TPromise<void> {
|
||||
const promise = super.setVisible(visible);
|
||||
|
||||
// Propagate to Editor
|
||||
this.setEditorVisible(visible, position);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, position: Position = null): void {
|
||||
this._position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the position of the editor changes while it is visible.
|
||||
*/
|
||||
public changePosition(position: Position): void {
|
||||
this._position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* The position this editor is showing in or null if none.
|
||||
*/
|
||||
public get position(): Position {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._input = null;
|
||||
this._options = null;
|
||||
|
||||
// Super Dispose
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lightweight descriptor of an editor. The descriptor is deferred so that heavy editors
|
||||
* can load lazily in the workbench.
|
||||
*/
|
||||
export class EditorDescriptor extends AsyncDescriptor<BaseEditor> implements IEditorDescriptor {
|
||||
private id: string;
|
||||
private name: string;
|
||||
|
||||
constructor(id: string, name: string, moduleId: string, ctorName: string) {
|
||||
super(moduleId, ctorName);
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public describes(obj: any): boolean {
|
||||
return obj instanceof BaseEditor && (<BaseEditor>obj).getId() === this.id;
|
||||
}
|
||||
}
|
||||
|
||||
const INPUT_DESCRIPTORS_PROPERTY = '__$inputDescriptors';
|
||||
|
||||
class EditorRegistry implements IEditorRegistry {
|
||||
private editors: EditorDescriptor[];
|
||||
private instantiationService: IInstantiationService;
|
||||
private fileInputFactory: IFileInputFactory;
|
||||
private editorInputFactoryConstructors: { [editorInputId: string]: IConstructorSignature0<IEditorInputFactory> } = Object.create(null);
|
||||
private editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null);
|
||||
|
||||
constructor() {
|
||||
this.editors = [];
|
||||
}
|
||||
|
||||
public setInstantiationService(service: IInstantiationService): void {
|
||||
this.instantiationService = service;
|
||||
|
||||
for (let key in this.editorInputFactoryConstructors) {
|
||||
const element = this.editorInputFactoryConstructors[key];
|
||||
this.createEditorInputFactory(key, element);
|
||||
}
|
||||
|
||||
this.editorInputFactoryConstructors = {};
|
||||
}
|
||||
|
||||
private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
|
||||
const instance = this.instantiationService.createInstance(ctor);
|
||||
this.editorInputFactoryInstances[editorInputId] = instance;
|
||||
}
|
||||
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>): void;
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>[]): void;
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: any): void {
|
||||
|
||||
// Support both non-array and array parameter
|
||||
let inputDescriptors: SyncDescriptor<EditorInput>[] = [];
|
||||
if (!types.isArray(editorInputDescriptor)) {
|
||||
inputDescriptors.push(editorInputDescriptor);
|
||||
} else {
|
||||
inputDescriptors = editorInputDescriptor;
|
||||
}
|
||||
|
||||
// Register (Support multiple Editors per Input)
|
||||
descriptor[INPUT_DESCRIPTORS_PROPERTY] = inputDescriptors;
|
||||
this.editors.push(descriptor);
|
||||
}
|
||||
|
||||
public getEditor(input: EditorInput): EditorDescriptor {
|
||||
const findEditorDescriptors = (input: EditorInput, byInstanceOf?: boolean): EditorDescriptor[] => {
|
||||
const matchingDescriptors: EditorDescriptor[] = [];
|
||||
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
const inputDescriptors = <SyncDescriptor<EditorInput>[]>editor[INPUT_DESCRIPTORS_PROPERTY];
|
||||
for (let j = 0; j < inputDescriptors.length; j++) {
|
||||
const inputClass = inputDescriptors[j].ctor;
|
||||
|
||||
// Direct check on constructor type (ignores prototype chain)
|
||||
if (!byInstanceOf && input.constructor === inputClass) {
|
||||
matchingDescriptors.push(editor);
|
||||
break;
|
||||
}
|
||||
|
||||
// Normal instanceof check
|
||||
else if (byInstanceOf && input instanceof inputClass) {
|
||||
matchingDescriptors.push(editor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no descriptors found, continue search using instanceof and prototype chain
|
||||
if (!byInstanceOf && matchingDescriptors.length === 0) {
|
||||
return findEditorDescriptors(input, true);
|
||||
}
|
||||
|
||||
if (byInstanceOf) {
|
||||
return matchingDescriptors;
|
||||
}
|
||||
|
||||
return matchingDescriptors;
|
||||
};
|
||||
|
||||
const descriptors = findEditorDescriptors(input);
|
||||
if (descriptors && descriptors.length > 0) {
|
||||
|
||||
// Ask the input for its preferred Editor
|
||||
const preferredEditorId = input.getPreferredEditorId(descriptors.map(d => d.getId()));
|
||||
if (preferredEditorId) {
|
||||
return this.getEditorById(preferredEditorId);
|
||||
}
|
||||
|
||||
// Otherwise, first come first serve
|
||||
return descriptors[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditorById(editorId: string): EditorDescriptor {
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
if (editor.getId() === editorId) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditors(): EditorDescriptor[] {
|
||||
return this.editors.slice(0);
|
||||
}
|
||||
|
||||
public setEditors(editorsToSet: EditorDescriptor[]): void {
|
||||
this.editors = editorsToSet;
|
||||
}
|
||||
|
||||
public getEditorInputs(): any[] {
|
||||
const inputClasses: any[] = [];
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
const editorInputDescriptors = <SyncDescriptor<EditorInput>[]>editor[INPUT_DESCRIPTORS_PROPERTY];
|
||||
inputClasses.push(...editorInputDescriptors.map(descriptor => descriptor.ctor));
|
||||
}
|
||||
|
||||
return inputClasses;
|
||||
}
|
||||
|
||||
public registerFileInputFactory(factory: IFileInputFactory): void {
|
||||
this.fileInputFactory = factory;
|
||||
}
|
||||
|
||||
public getFileInputFactory(): IFileInputFactory {
|
||||
return this.fileInputFactory;
|
||||
}
|
||||
|
||||
public registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
|
||||
if (!this.instantiationService) {
|
||||
this.editorInputFactoryConstructors[editorInputId] = ctor;
|
||||
} else {
|
||||
this.createEditorInputFactory(editorInputId, ctor);
|
||||
}
|
||||
}
|
||||
|
||||
public getEditorInputFactory(editorInputId: string): IEditorInputFactory {
|
||||
return this.editorInputFactoryInstances[editorInputId];
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Editors, new EditorRegistry());
|
||||
41
src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { BINARY_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
|
||||
|
||||
/**
|
||||
* An implementation of editor for diffing binary files like images or videos.
|
||||
*/
|
||||
export class BinaryResourceDiffEditor extends SideBySideEditor {
|
||||
|
||||
public static ID = BINARY_DIFF_EDITOR_ID;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(telemetryService, instantiationService, themeService);
|
||||
}
|
||||
|
||||
public getMetadata(): string {
|
||||
const master = this.masterEditor;
|
||||
const details = this.detailsEditor;
|
||||
|
||||
if (master instanceof BaseBinaryResourceEditor && details instanceof BaseBinaryResourceEditor) {
|
||||
return nls.localize('metadataDiff', "{0} ↔ {1}", details.getMetadata(), master.getMetadata());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
151
src/vs/workbench/browser/parts/editor/binaryEditor.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
|
||||
import { ResourceViewer } from 'vs/base/browser/ui/resourceviewer/resourceViewer';
|
||||
import { EditorModel, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
/*
|
||||
* This class is only intended to be subclassed and not instantiated.
|
||||
*/
|
||||
export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
private _onMetadataChanged: Emitter<void>;
|
||||
private metadata: string;
|
||||
|
||||
private binaryContainer: Builder;
|
||||
private scrollbar: DomScrollableElement;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService,
|
||||
private windowsService: IWindowsService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
|
||||
this._onMetadataChanged = new Emitter<void>();
|
||||
}
|
||||
|
||||
public get onMetadataChanged(): Event<void> {
|
||||
return this._onMetadataChanged.event;
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer");
|
||||
}
|
||||
|
||||
protected createEditor(parent: Builder): void {
|
||||
|
||||
// Container for Binary
|
||||
const binaryContainerElement = document.createElement('div');
|
||||
binaryContainerElement.className = 'binary-container';
|
||||
this.binaryContainer = $(binaryContainerElement);
|
||||
this.binaryContainer.style('outline', 'none');
|
||||
this.binaryContainer.tabindex(0); // enable focus support from the editor part (do not remove)
|
||||
|
||||
// Custom Scrollbars
|
||||
this.scrollbar = new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto });
|
||||
parent.getHTMLElement().appendChild(this.scrollbar.getDomNode());
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = this.input;
|
||||
super.setInput(input, options);
|
||||
|
||||
// Detect options
|
||||
const forceOpen = options && options.forceOpen;
|
||||
|
||||
// Same Input
|
||||
if (!forceOpen && input.matches(oldInput)) {
|
||||
return TPromise.as<void>(null);
|
||||
}
|
||||
|
||||
// Different Input (Reload)
|
||||
return input.resolve(true).then((resolvedModel: EditorModel) => {
|
||||
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BinaryEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as binary'));
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Render Input
|
||||
const model = <BinaryEditorModel>resolvedModel;
|
||||
ResourceViewer.show(
|
||||
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag() },
|
||||
this.binaryContainer,
|
||||
this.scrollbar,
|
||||
(resource: URI) => {
|
||||
this.windowsService.openExternal(resource.toString()).then(didOpen => {
|
||||
if (!didOpen) {
|
||||
return this.windowsService.showItemInFolder(resource.fsPath);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
},
|
||||
(meta) => this.handleMetadataChanged(meta));
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
});
|
||||
}
|
||||
|
||||
private handleMetadataChanged(meta: string): void {
|
||||
this.metadata = meta;
|
||||
this._onMetadataChanged.fire();
|
||||
}
|
||||
|
||||
public getMetadata(): string {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
|
||||
// Clear Meta
|
||||
this.handleMetadataChanged(null);
|
||||
|
||||
// Empty HTML Container
|
||||
$(this.binaryContainer).empty();
|
||||
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
|
||||
// Pass on to Binary Container
|
||||
this.binaryContainer.size(dimension.width, dimension.height);
|
||||
this.scrollbar.scanDomNode();
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.binaryContainer.domFocus();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
// Destroy Container
|
||||
this.binaryContainer.destroy();
|
||||
this.scrollbar.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
408
src/vs/workbench/browser/parts/editor/editor.contribution.ts
Normal file
@@ -0,0 +1,408 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import nls = require('vs/nls');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
|
||||
import { StatusbarItemDescriptor, StatusbarAlignment, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorInput, IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
|
||||
import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import {
|
||||
CloseEditorsInGroupAction, CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, KeepEditorAction, CloseOtherEditorsInGroupAction, OpenToSideAction, RevertAndCloseEditorAction,
|
||||
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction,
|
||||
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
|
||||
NAVIGATE_IN_GROUP_TWO_PREFIX, ShowEditorsInGroupThreeAction, NAVIGATE_IN_GROUP_THREE_PREFIX, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
// Register String Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
TextResourceEditor.ID,
|
||||
nls.localize('textEditor', "Text Editor"),
|
||||
'vs/workbench/browser/parts/editor/textResourceEditor',
|
||||
'TextResourceEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(UntitledEditorInput),
|
||||
new SyncDescriptor(ResourceEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
// Register Text Diff Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
TextDiffEditor.ID,
|
||||
nls.localize('textDiffEditor', "Text Diff Editor"),
|
||||
'vs/workbench/browser/parts/editor/textDiffEditor',
|
||||
'TextDiffEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(DiffEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
// Register Binary Resource Diff Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
BinaryResourceDiffEditor.ID,
|
||||
nls.localize('binaryDiffEditor', "Binary Diff Editor"),
|
||||
'vs/workbench/browser/parts/editor/binaryDiffEditor',
|
||||
'BinaryResourceDiffEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(DiffEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
SideBySideEditor.ID,
|
||||
nls.localize('sideBySideEditor', "Side by Side Editor"),
|
||||
'vs/workbench/browser/parts/editor/sideBySideEditor',
|
||||
'SideBySideEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(SideBySideEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
interface ISerializedUntitledEditorInput {
|
||||
resource: string;
|
||||
resourceJSON: object;
|
||||
modeId: string;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
// Register Editor Input Factory
|
||||
class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
constructor(
|
||||
@ITextFileService private textFileService: ITextFileService
|
||||
) {
|
||||
}
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
if (!this.textFileService.isHotExitEnabled) {
|
||||
return null; // never restore untitled unless hot exit is enabled
|
||||
}
|
||||
|
||||
const untitledEditorInput = <UntitledEditorInput>editorInput;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (!untitledEditorInput.getResource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let resource = untitledEditorInput.getResource();
|
||||
if (untitledEditorInput.hasAssociatedFilePath) {
|
||||
resource = URI.file(resource.fsPath); // untitled with associated file path use the file schema
|
||||
}
|
||||
|
||||
const serialized: ISerializedUntitledEditorInput = {
|
||||
resource: resource.toString(), // Keep for backwards compatibility
|
||||
resourceJSON: resource.toJSON(),
|
||||
modeId: untitledEditorInput.getModeId(),
|
||||
encoding: untitledEditorInput.getEncoding()
|
||||
};
|
||||
|
||||
return JSON.stringify(serialized);
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput {
|
||||
return instantiationService.invokeFunction<UntitledEditorInput>(accessor => {
|
||||
const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput);
|
||||
const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource);
|
||||
const filePath = resource.scheme === 'file' ? resource.fsPath : void 0;
|
||||
const language = deserialized.modeId;
|
||||
const encoding = deserialized.encoding;
|
||||
|
||||
return accessor.get(IWorkbenchEditorService).createInput({ resource, filePath, language, encoding }) as UntitledEditorInput;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(UntitledEditorInput.ID, UntitledEditorInputFactory);
|
||||
|
||||
interface ISerializedSideBySideEditorInput {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
detailsSerialized: string;
|
||||
masterSerialized: string;
|
||||
|
||||
detailsTypeId: string;
|
||||
masterTypeId: string;
|
||||
}
|
||||
|
||||
// Register Side by Side Editor Input Factory
|
||||
class SideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
const input = <SideBySideEditorInput>editorInput;
|
||||
|
||||
if (input.details && input.master) {
|
||||
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
|
||||
const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId());
|
||||
const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId());
|
||||
|
||||
if (detailsInputFactory && masterInputFactory) {
|
||||
const detailsSerialized = detailsInputFactory.serialize(input.details);
|
||||
const masterSerialized = masterInputFactory.serialize(input.master);
|
||||
|
||||
if (detailsSerialized && masterSerialized) {
|
||||
return JSON.stringify(<ISerializedSideBySideEditorInput>{
|
||||
name: input.getName(),
|
||||
description: input.getDescription(),
|
||||
detailsSerialized,
|
||||
masterSerialized,
|
||||
detailsTypeId: input.details.getTypeId(),
|
||||
masterTypeId: input.master.getTypeId()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
|
||||
const detailsInputFactory = registry.getEditorInputFactory(deserialized.detailsTypeId);
|
||||
const masterInputFactory = registry.getEditorInputFactory(deserialized.masterTypeId);
|
||||
|
||||
if (detailsInputFactory && masterInputFactory) {
|
||||
const detailsInput = detailsInputFactory.deserialize(instantiationService, deserialized.detailsSerialized);
|
||||
const masterInput = masterInputFactory.deserialize(instantiationService, deserialized.masterSerialized);
|
||||
|
||||
if (detailsInput && masterInput) {
|
||||
return new SideBySideEditorInput(deserialized.name, deserialized.description, detailsInput, masterInput);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
|
||||
|
||||
// Register Editor Status
|
||||
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* High Priority */));
|
||||
|
||||
// Register Status Actions
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding');
|
||||
|
||||
export class QuickOpenActionContributor extends ActionBarContributor {
|
||||
private openToSideActionInstance: OpenToSideAction;
|
||||
|
||||
constructor( @IInstantiationService private instantiationService: IInstantiationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public hasActions(context: any): boolean {
|
||||
const entry = this.getEntry(context);
|
||||
|
||||
return !!entry;
|
||||
}
|
||||
|
||||
public getActions(context: any): IAction[] {
|
||||
const actions: Action[] = [];
|
||||
|
||||
const entry = this.getEntry(context);
|
||||
if (entry) {
|
||||
if (!this.openToSideActionInstance) {
|
||||
this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideAction);
|
||||
} else {
|
||||
this.openToSideActionInstance.updateClass();
|
||||
}
|
||||
|
||||
actions.push(this.openToSideActionInstance);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private getEntry(context: any): IEditorQuickOpenEntry {
|
||||
if (!context || !context.element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toEditorQuickOpenEntry(context.element);
|
||||
}
|
||||
}
|
||||
|
||||
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
|
||||
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor);
|
||||
|
||||
const editorPickerContextKey = 'inEditorsPicker';
|
||||
const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(editorPickerContextKey));
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupOnePicker',
|
||||
NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupOnePicker', "Show Editors in First Group")
|
||||
},
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupTwoPicker', "Show Editors in Second Group")
|
||||
},
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupThreePicker', "Show Editors in Third Group")
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupTwoPicker',
|
||||
NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupThreePicker',
|
||||
NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'AllEditorsPicker',
|
||||
NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
{
|
||||
prefix: NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('allEditorsPicker', "Show All Opened Editors")
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
// Register Editor Actions
|
||||
const category = nls.localize('view', "View");
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupOneAction, ShowEditorsInGroupOneAction.ID, ShowEditorsInGroupOneAction.LABEL), 'View: Show Editors in First Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupTwoAction, ShowEditorsInGroupTwoAction.ID, ShowEditorsInGroupTwoAction.LABEL), 'View: Show Editors in Second Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupThreeAction, ShowEditorsInGroupThreeAction.ID, ShowEditorsInGroupThreeAction.LABEL), 'View: Show Editors in Third Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'View: Clear Recently Opened', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(KeepEditorAction, KeepEditorAction.ID, KeepEditorAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter) }), 'View: Keep Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, CloseRightEditorsInGroupAction.LABEL), 'View: Close Editors to the Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, CloseUnmodifiedEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U) }), 'View: Close Unmodified Editors in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, CloseEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W) }), 'View: Close All Editors in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, CloseOtherEditorsInGroupAction.LABEL, { primary: null, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T } }), 'View: Close Other Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editors of Two Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSecondGroupAction, FocusSecondGroupAction.ID, FocusSecondGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_2 }), 'View: Focus Second Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusThirdGroupAction, FocusThirdGroupAction.ID, FocusThirdGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_3 }), 'View: Focus Third Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLastEditorInStackAction, FocusLastEditorInStackAction.ID, FocusLastEditorInStackAction.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0 } }), 'View: Open Last Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EvenGroupWidthsAction, EvenGroupWidthsAction.ID, EvenGroupWidthsAction.LABEL), 'View: Even Editor Group Widths', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Sidebar', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Minimize Other Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, MoveGroupLeftAction.ID, MoveGroupLeftAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.LeftArrow) }), 'View: Move Editor Group Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Previous Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category);
|
||||
|
||||
// Register Editor Picker Actions including quick navigate support
|
||||
const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } };
|
||||
const openPreviousEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } };
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'Open Next Recently Used Editor in Group');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'Open Previous Recently Used Editor in Group');
|
||||
|
||||
const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigateNextInEditorPickerId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true),
|
||||
when: editorPickerContext,
|
||||
primary: openNextEditorKeybinding.primary,
|
||||
mac: openNextEditorKeybinding.mac
|
||||
});
|
||||
|
||||
const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigatePreviousInEditorPickerId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false),
|
||||
when: editorPickerContext,
|
||||
primary: openPreviousEditorKeybinding.primary,
|
||||
mac: openPreviousEditorKeybinding.mac
|
||||
});
|
||||
|
||||
|
||||
// Editor Commands
|
||||
editorCommands.setup();
|
||||
1520
src/vs/workbench/browser/parts/editor/editorActions.ts
Normal file
275
src/vs/workbench/browser/parts/editor/editorCommands.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditor, Position, POSITIONS } from 'vs/platform/editor/common/editor';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { EditorStacksModel } from 'vs/workbench/common/editor/editorStacksModel';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IMessageService, Severity, CloseAction } from 'vs/platform/message/common/message';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export function setup(): void {
|
||||
registerActiveEditorMoveCommand();
|
||||
registerDiffEditorCommands();
|
||||
registerOpenEditorAtIndexCommands();
|
||||
handleCommandDeprecations();
|
||||
}
|
||||
|
||||
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
|
||||
if (!types.isObject(arg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const activeEditorMoveArg: ActiveEditorMoveArguments = arg;
|
||||
|
||||
if (!types.isString(activeEditorMoveArg.to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!types.isUndefined(activeEditorMoveArg.by) && !types.isString(activeEditorMoveArg.by)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!types.isUndefined(activeEditorMoveArg.value) && !types.isNumber(activeEditorMoveArg.value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
function registerActiveEditorMoveCommand(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: EditorCommands.MoveActiveEditor,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: EditorContextKeys.textFocus,
|
||||
primary: null,
|
||||
handler: (accessor, args: any) => moveActiveEditor(args, accessor),
|
||||
description: {
|
||||
description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
|
||||
args: [
|
||||
{
|
||||
name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
|
||||
description: nls.localize('editorCommand.activeEditorMove.arg.description', `Argument Properties:
|
||||
* 'to': String value providing where to move.
|
||||
* 'by': String value providing the unit for move. By tab or by group.
|
||||
* 'value': Number value providing how many positions or an absolute position to move.
|
||||
`),
|
||||
constraint: isActiveEditorMoveArg
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function moveActiveEditor(args: ActiveEditorMoveArguments = {}, accessor: ServicesAccessor): void {
|
||||
const showTabs = accessor.get(IEditorGroupService).getTabOptions().showTabs;
|
||||
args.to = args.to || ActiveEditorMovePositioning.RIGHT;
|
||||
args.by = showTabs ? args.by || ActiveEditorMovePositioningBy.TAB : ActiveEditorMovePositioningBy.GROUP;
|
||||
args.value = types.isUndefined(args.value) ? 1 : args.value;
|
||||
|
||||
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
|
||||
if (activeEditor) {
|
||||
switch (args.by) {
|
||||
case ActiveEditorMovePositioningBy.TAB:
|
||||
return moveActiveTab(args, activeEditor, accessor);
|
||||
case ActiveEditorMovePositioningBy.GROUP:
|
||||
return moveActiveEditorToGroup(args, activeEditor, accessor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveActiveTab(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
|
||||
const editorGroupsService: IEditorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorGroup = editorGroupsService.getStacksModel().groupAt(activeEditor.position);
|
||||
let index = editorGroup.indexOf(activeEditor.input);
|
||||
switch (args.to) {
|
||||
case ActiveEditorMovePositioning.FIRST:
|
||||
index = 0;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LAST:
|
||||
index = editorGroup.count - 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LEFT:
|
||||
index = index - args.value;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.RIGHT:
|
||||
index = index + args.value;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.CENTER:
|
||||
index = Math.round(editorGroup.count / 2) - 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.POSITION:
|
||||
index = args.value - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
index = index < 0 ? 0 : index >= editorGroup.count ? editorGroup.count - 1 : index;
|
||||
editorGroupsService.moveEditor(activeEditor.input, editorGroup, editorGroup, { index });
|
||||
}
|
||||
|
||||
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
|
||||
let newPosition = activeEditor.position;
|
||||
switch (args.to) {
|
||||
case ActiveEditorMovePositioning.LEFT:
|
||||
newPosition = newPosition - 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.RIGHT:
|
||||
newPosition = newPosition + 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.FIRST:
|
||||
newPosition = Position.ONE;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LAST:
|
||||
newPosition = Position.THREE;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.CENTER:
|
||||
newPosition = Position.TWO;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.POSITION:
|
||||
newPosition = args.value - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
newPosition = POSITIONS.indexOf(newPosition) !== -1 ? newPosition : activeEditor.position;
|
||||
accessor.get(IEditorGroupService).moveEditor(activeEditor.input, activeEditor.position, newPosition);
|
||||
}
|
||||
|
||||
function registerDiffEditorCommands(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.compareEditor.nextChange',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: TextCompareEditorVisible,
|
||||
primary: null,
|
||||
handler: accessor => navigateInDiffEditor(accessor, true)
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.compareEditor.previousChange',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: TextCompareEditorVisible,
|
||||
primary: null,
|
||||
handler: accessor => navigateInDiffEditor(accessor, false)
|
||||
});
|
||||
|
||||
function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
|
||||
let editorService = accessor.get(IWorkbenchEditorService);
|
||||
const candidates = [editorService.getActiveEditor(), ...editorService.getVisibleEditors()].filter(e => e instanceof TextDiffEditor);
|
||||
|
||||
if (candidates.length > 0) {
|
||||
next ? (<TextDiffEditor>candidates[0]).getDiffNavigator().next() : (<TextDiffEditor>candidates[0]).getDiffNavigator().previous();
|
||||
}
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.printStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
console.log(`${accessor.get(IEditorGroupService).getStacksModel().toString()}\n\n`);
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.validateStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
}
|
||||
|
||||
function handleCommandDeprecations(): void {
|
||||
const mapDeprecatedCommands = {
|
||||
'workbench.action.files.newFile': 'explorer.newFile',
|
||||
'workbench.action.files.newFolder': 'explorer.newFolder'
|
||||
};
|
||||
|
||||
Object.keys(mapDeprecatedCommands).forEach(deprecatedCommandId => {
|
||||
const newCommandId = mapDeprecatedCommands[deprecatedCommandId];
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: deprecatedCommandId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
const messageService = accessor.get(IMessageService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
messageService.show(Severity.Warning, {
|
||||
message: nls.localize('commandDeprecated', "Command **{0}** has been removed. You can use **{1}** instead", deprecatedCommandId, newCommandId),
|
||||
actions: [
|
||||
new Action('openKeybindings', nls.localize('openKeybindings', "Configure Keyboard Shortcuts"), null, true, () => {
|
||||
return commandService.executeCommand('workbench.action.openGlobalKeybindings');
|
||||
}),
|
||||
CloseAction
|
||||
]
|
||||
});
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerOpenEditorAtIndexCommands(): void {
|
||||
|
||||
// Keybindings to focus a specific index in the tab folder if tabs are enabled
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const editorIndex = i;
|
||||
const visibleIndex = i + 1;
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.openEditorAtIndex' + visibleIndex,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: KeyMod.Alt | toKeyCode(visibleIndex),
|
||||
mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
|
||||
handler: accessor => {
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
|
||||
const active = editorService.getActiveEditor();
|
||||
if (active) {
|
||||
const group = editorGroupService.getStacksModel().groupAt(active.position);
|
||||
const editor = group.getEditor(editorIndex);
|
||||
|
||||
if (editor) {
|
||||
return editorService.openEditor(editor);
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toKeyCode(index: number): KeyCode {
|
||||
switch (index) {
|
||||
case 0: return KeyCode.KEY_0;
|
||||
case 1: return KeyCode.KEY_1;
|
||||
case 2: return KeyCode.KEY_2;
|
||||
case 3: return KeyCode.KEY_3;
|
||||
case 4: return KeyCode.KEY_4;
|
||||
case 5: return KeyCode.KEY_5;
|
||||
case 6: return KeyCode.KEY_6;
|
||||
case 7: return KeyCode.KEY_7;
|
||||
case 8: return KeyCode.KEY_8;
|
||||
case 9: return KeyCode.KEY_9;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
2126
src/vs/workbench/browser/parts/editor/editorGroupsControl.ts
Normal file
1571
src/vs/workbench/browser/parts/editor/editorPart.ts
Normal file
274
src/vs/workbench/browser/parts/editor/editorPicker.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/editorpicker';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import labels = require('vs/base/common/labels');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import scorer = require('vs/base/common/scorer');
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { getIconClasses } from 'vs/workbench/browser/labels';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { EditorInput, toResource, IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor';
|
||||
|
||||
export class EditorPickerEntry extends QuickOpenEntryGroup {
|
||||
private stacks: IEditorStacksModel;
|
||||
|
||||
constructor(
|
||||
private editor: EditorInput,
|
||||
private _group: IEditorGroup,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.stacks = editorGroupService.getStacksModel();
|
||||
}
|
||||
|
||||
public getLabelOptions(): IIconLabelOptions {
|
||||
return {
|
||||
extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()),
|
||||
italic: this._group.isPreview(this.editor)
|
||||
};
|
||||
}
|
||||
|
||||
public getLabel(): string {
|
||||
return this.editor.getName();
|
||||
}
|
||||
|
||||
public getIcon(): string {
|
||||
return this.editor.isDirty() ? 'dirty' : '';
|
||||
}
|
||||
|
||||
public get group(): IEditorGroup {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
public getResource(): URI {
|
||||
return toResource(this.editor, { supportSideBySide: true });
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, editor group picker", this.getLabel());
|
||||
}
|
||||
|
||||
public getDescription(): string {
|
||||
return this.editor.getDescription();
|
||||
}
|
||||
|
||||
public run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
return this.runOpen(context);
|
||||
}
|
||||
|
||||
return super.run(mode, context);
|
||||
}
|
||||
|
||||
private runOpen(context: IEntryRunContext): boolean {
|
||||
this.editorService.openEditor(this.editor, null, this.stacks.positionOfGroup(this.group)).done(null, errors.onUnexpectedError);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
private scorerCache: { [key: string]: number };
|
||||
|
||||
constructor(
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService protected editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.scorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
public getResults(searchValue: string): TPromise<QuickOpenModel> {
|
||||
searchValue = searchValue.trim();
|
||||
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
|
||||
|
||||
const editorEntries = this.getEditorEntries();
|
||||
if (!editorEntries.length) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
|
||||
const entries = editorEntries.filter(e => {
|
||||
if (!searchValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const resource = e.getResource();
|
||||
const targetToMatch = resource ? labels.getPathLabel(e.getResource(), this.contextService) : e.getLabel();
|
||||
if (!scorer.matches(targetToMatch, normalizedSearchValueLowercase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { labelHighlights, descriptionHighlights } = QuickOpenEntry.highlight(e, searchValue, true /* fuzzy highlight */);
|
||||
e.setHighlights(labelHighlights, descriptionHighlights);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Sorting
|
||||
if (searchValue) {
|
||||
entries.sort((e1, e2) => {
|
||||
if (e1.group !== e2.group) {
|
||||
return stacks.positionOfGroup(e1.group) - stacks.positionOfGroup(e2.group);
|
||||
}
|
||||
|
||||
return QuickOpenEntry.compareByScore(e1, e2, searchValue, normalizedSearchValueLowercase, this.scorerCache);
|
||||
});
|
||||
}
|
||||
|
||||
// Grouping (for more than one group)
|
||||
if (stacks.groups.length > 1) {
|
||||
let lastGroup: IEditorGroup;
|
||||
entries.forEach(e => {
|
||||
if (!lastGroup || lastGroup !== e.group) {
|
||||
e.setGroupLabel(nls.localize('groupLabel', "Group: {0}", e.group.label));
|
||||
e.setShowBorder(!!lastGroup);
|
||||
lastGroup = e.group;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.as(new QuickOpenModel(entries));
|
||||
}
|
||||
|
||||
public onClose(canceled: boolean): void {
|
||||
this.scorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
protected abstract getEditorEntries(): EditorPickerEntry[];
|
||||
}
|
||||
|
||||
export abstract class EditorGroupPicker extends BaseEditorPicker {
|
||||
|
||||
protected getEditorEntries(): EditorPickerEntry[] {
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const group = stacks.groupAt(this.getPosition());
|
||||
if (!group) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return group.getEditors(true).map((editor, index) => this.instantiationService.createInstance(EditorPickerEntry, editor, group));
|
||||
}
|
||||
|
||||
protected abstract getPosition(): Position;
|
||||
|
||||
public getEmptyLabel(searchString: string): string {
|
||||
if (searchString) {
|
||||
return nls.localize('noResultsFoundInGroup', "No matching opened editor found in group");
|
||||
}
|
||||
|
||||
return nls.localize('noOpenedEditors', "List of opened editors is currently empty in group");
|
||||
}
|
||||
|
||||
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
if (searchValue || !context.quickNavigateConfiguration) {
|
||||
return {
|
||||
autoFocusFirstEntry: true
|
||||
};
|
||||
}
|
||||
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const group = stacks.groupAt(this.getPosition());
|
||||
if (!group) {
|
||||
return super.getAutoFocus(searchValue, context);
|
||||
}
|
||||
|
||||
const isShiftNavigate = (context.quickNavigateConfiguration && context.quickNavigateConfiguration.keybindings.some(k => {
|
||||
const [firstPart, chordPart] = k.getParts();
|
||||
if (chordPart) {
|
||||
return false;
|
||||
}
|
||||
return firstPart.shiftKey;
|
||||
}));
|
||||
if (isShiftNavigate) {
|
||||
return {
|
||||
autoFocusLastEntry: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
autoFocusFirstEntry: group.count === 1,
|
||||
autoFocusSecondEntry: group.count > 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupOnePicker extends EditorGroupPicker {
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.ONE;
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupTwoPicker extends EditorGroupPicker {
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.TWO;
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupThreePicker extends EditorGroupPicker {
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.THREE;
|
||||
}
|
||||
}
|
||||
|
||||
export class AllEditorsPicker extends BaseEditorPicker {
|
||||
|
||||
protected getEditorEntries(): EditorPickerEntry[] {
|
||||
const entries: EditorPickerEntry[] = [];
|
||||
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
stacks.groups.forEach((group, position) => {
|
||||
group.getEditors().forEach((editor, index) => {
|
||||
entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group));
|
||||
});
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public getEmptyLabel(searchString: string): string {
|
||||
if (searchString) {
|
||||
return nls.localize('noResultsFound', "No matching opened editor found");
|
||||
}
|
||||
|
||||
return nls.localize('noOpenedEditorsAllGroups', "List of opened editors is currently empty");
|
||||
}
|
||||
|
||||
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
if (searchValue) {
|
||||
return {
|
||||
autoFocusFirstEntry: true
|
||||
};
|
||||
}
|
||||
|
||||
return super.getAutoFocus(searchValue, context);
|
||||
}
|
||||
}
|
||||
1180
src/vs/workbench/browser/parts/editor/editorStatus.ts
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#C5C5C5" cx="8" cy="8" r="4"/></svg>
|
||||
|
After Width: | Height: | Size: 167 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#424242" cx="8" cy="8" r="4"/></svg>
|
||||
|
After Width: | Height: | Size: 167 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
1
src/vs/workbench/browser/parts/editor/media/close.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1,87 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .editor > .content.dragging > .monaco-sash {
|
||||
display: none; /* hide sashes while dragging editors around */
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-move-overlay,
|
||||
#monaco-workbench-editor-drop-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-drop-overlay {
|
||||
opacity: 0; /* initially not visible until moving around */
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo {
|
||||
position: absolute;
|
||||
box-sizing: border-box; /* use border box to be able to draw a border as separator between editors */
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo.editor-one {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo.dragging {
|
||||
z-index: 70;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
|
||||
border-left: 1px solid;
|
||||
border-right: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
|
||||
border-top: 1px solid;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
|
||||
border-left: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
|
||||
border-top: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.draggedunder {
|
||||
transition: left 200ms ease-out;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .editor-three.draggedunder {
|
||||
transition-property: right;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.draggedunder {
|
||||
transition: top 200ms ease-out;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .editor-three.draggedunder {
|
||||
transition-property: bottom;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo > .container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
|
||||
height: calc(100% - 35px); /* Editor is below editor title */
|
||||
}
|
||||
11
src/vs/workbench/browser/parts/editor/media/editorpart.css
Normal file
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/** Letter press styling for empty editor */
|
||||
.monaco-workbench > .part.editor.empty {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: 260px 260px;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry.editor-preview {
|
||||
font-style: italic;
|
||||
}
|
||||
24
src/vs/workbench/browser/parts/editor/media/editorstatus.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > a:not(:first-child) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-mode,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-encoding,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-eol,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-selection,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-indentation,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-metadata,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-tabfocusmode,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-screenreadermode {
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-metadata,
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item > .editor-statusbar-item > a.editor-status-screenreadermode {
|
||||
cursor: default !important;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><path fill="#C5C5C5" d="M1 4h7l-3-3h3l4 4-4 4h-3l3-3h-7v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 189 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><path fill="#656565" d="M1 4h7l-3-3h3l4 4-4 4h-3l3-3h-7v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 189 B |
47
src/vs/workbench/browser/parts/editor/media/notabstitle.css
Normal file
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Title Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label {
|
||||
line-height: 35px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before {
|
||||
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions {
|
||||
display: flex;
|
||||
flex: initial;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
|
||||
background: url('close-dirty.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
|
||||
background: url('close-dirty-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><polygon fill="#C5C5C5" points="13,4 6,4 9,1 6,1 2,5 6,9 9,9 6,6 13,6"/></svg>
|
||||
|
After Width: | Height: | Size: 199 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><polygon fill="#656565" points="13,4 6,4 9,1 6,1 2,5 6,9 9,9 6,6 13,6"/></svg>
|
||||
|
After Width: | Height: | Size: 199 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 578 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 578 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#C5C5C5" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>
|
||||
|
After Width: | Height: | Size: 218 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#656565" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>
|
||||
|
After Width: | Height: | Size: 218 B |
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#C5C5C5;}
|
||||
</style>
|
||||
<g id="outline">
|
||||
</g>
|
||||
<g id="icon_x5F_bg">
|
||||
<path class="st0" d="M7.6,5H3c0,0-1,0-1,1c0,0.8,0,5.4,0,8c0,1,1,1,1,1s1.5,0,3,0s3,0,3,0s1,0,1-1c0-2.6,0-6.9,0-6.9L7.6,5z M9,14
|
||||
H3V6h4v2h2V14z"/>
|
||||
<path class="st0" d="M9.6,3H5c0,0-1,0-1,1h5v0.9L10.2,6H11v7c1,0,1-1,1-1V5.1L9.6,3z"/>
|
||||
<path class="st0" d="M11.6,1H7c0,0-1,0-1,1h5v0.9L12.2,4H13v7c1,0,1-1,1-1V3.1L11.6,1z"/>
|
||||
</g>
|
||||
<g id="color_x5F_action">
|
||||
</g>
|
||||
<g id="icon_x5F_fg">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 822 B |
20
src/vs/workbench/browser/parts/editor/media/stackview.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#656565;}
|
||||
</style>
|
||||
<g id="outline">
|
||||
</g>
|
||||
<g id="icon_x5F_bg">
|
||||
<path class="st0" d="M7.6,5H3c0,0-1,0-1,1c0,0.8,0,5.4,0,8c0,1,1,1,1,1s1.5,0,3,0s3,0,3,0s1,0,1-1c0-2.6,0-6.9,0-6.9L7.6,5z M9,14
|
||||
H3V6h4v2h2V14z"/>
|
||||
<path class="st0" d="M9.6,3H5c0,0-1,0-1,1h5v0.9L10.2,6H11v7c1,0,1-1,1-1V5.1L9.6,3z"/>
|
||||
<path class="st0" d="M11.6,1H7c0,0-1,0-1,1h5v0.9L12.2,4H13v7c1,0,1-1,1-1V3.1L11.6,1z"/>
|
||||
</g>
|
||||
<g id="color_x5F_action">
|
||||
</g>
|
||||
<g id="icon_x5F_fg">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 822 B |
156
src/vs/workbench/browser/parts/editor/media/tabstitle.css
Normal file
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Title Container */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.empty {
|
||||
background: inherit !important; /* prevents some ugly flickering when opening first tab */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element .scrollbar {
|
||||
z-index: 3; /* on top of tabs */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Tabs Container */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container {
|
||||
display: flex;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.scroll {
|
||||
overflow: scroll !important;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tab */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
|
||||
display: flex;
|
||||
width: 120px;
|
||||
min-width: fit-content;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
height: 35px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-left {
|
||||
flex-direction: row-reverse;
|
||||
padding-left: 0;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* Tab Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label {
|
||||
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before {
|
||||
height: 16px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
/* Tab Close */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab > .tab-close {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off > .tab-close {
|
||||
display: none; /* hide the close action bar when we are configured to hide it */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab > .tab-close .action-label {
|
||||
opacity: 0;
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
|
||||
background: url('close-dirty.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
|
||||
background: url('close-dirty-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/* No Tab Close Button */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off {
|
||||
padding-right: 12px;
|
||||
transition: padding-right ease-in-out 100ms;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: center;
|
||||
background-position-x: calc(100% - 6px); /* to the right of the tab label */
|
||||
padding-right: 28px; /* make room for dirty indication when we are running without close button */
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-image: url('close-dirty.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-image: url('close-dirty-inverse.svg');
|
||||
}
|
||||
|
||||
/* Editor Actions */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions {
|
||||
cursor: default;
|
||||
flex: initial;
|
||||
padding-left: 4px;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.next {
|
||||
background: url('next-diff.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.previous {
|
||||
background: url('previous-diff.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.next,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.next {
|
||||
background: url('next-diff-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.previous,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.previous {
|
||||
background: url('previous-diff-inverse.svg') center center no-repeat;
|
||||
}
|
||||
107
src/vs/workbench/browser/parts/editor/media/titlecontrol.css
Normal file
@@ -0,0 +1,107 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Editor Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label span,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
|
||||
display: block;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
min-width: 28px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label .label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label .label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Drag Cursor */
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title.tabs .scrollbar .slider,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .title-label span {
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-move-overlay,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title.tabs .scrollbar .slider,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .title-label span {
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
|
||||
.monaco-workbench .close-editor-action {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .close-editor-action,
|
||||
.hc-black .monaco-workbench .close-editor-action {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
|
||||
background: url('split-editor-vertical.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
|
||||
background: url('split-editor-vertical-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
|
||||
background: url('split-editor-horizontal.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
|
||||
background: url('split-editor-horizontal-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .show-group-editors-action {
|
||||
background: url('stackview.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .show-group-editors-action,
|
||||
.hc-black .monaco-workbench .show-group-editors-action {
|
||||
background: url('stackview-inverse.svg') center center no-repeat;
|
||||
}
|
||||
138
src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/notabstitle';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { EditorLabel } from 'vs/workbench/browser/labels';
|
||||
import { Verbosity } from 'vs/platform/editor/common/editor';
|
||||
import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
|
||||
export class NoTabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
private editorLabel: EditorLabel;
|
||||
|
||||
public setContext(group: IEditorGroup): void {
|
||||
super.setContext(group);
|
||||
|
||||
this.editorActionsToolbar.context = { group };
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
|
||||
this.titleContainer = parent;
|
||||
|
||||
// Pin on double click
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
|
||||
// Detect mouse click
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleClick(e)));
|
||||
|
||||
// Editor Label
|
||||
this.editorLabel = this.instantiationService.createInstance(EditorLabel, this.titleContainer, void 0);
|
||||
this.toUnbind.push(this.editorLabel);
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.editorLabel.labelElement, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleLabelClick(e)));
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.editorLabel.descriptionElement, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleLabelClick(e)));
|
||||
|
||||
// Right Actions Container
|
||||
const actionsContainer = document.createElement('div');
|
||||
DOM.addClass(actionsContainer, 'title-actions');
|
||||
this.titleContainer.appendChild(actionsContainer);
|
||||
|
||||
// Editor actions toolbar
|
||||
this.createEditorActionsToolBar(actionsContainer);
|
||||
|
||||
// Context Menu
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.CONTEXT_MENU, (e: Event) => this.onContextMenu({ group: this.context, editor: this.context.activeEditor }, e, this.titleContainer)));
|
||||
}
|
||||
|
||||
private onTitleLabelClick(e: MouseEvent): void {
|
||||
DOM.EventHelper.stop(e, false);
|
||||
if (!this.dragged) {
|
||||
setTimeout(() => this.quickOpenService.show()); // delayed to let the onTitleClick() come first which can cause a focus change which can close quick open
|
||||
}
|
||||
}
|
||||
|
||||
private onTitleDoubleClick(e: MouseEvent): void {
|
||||
DOM.EventHelper.stop(e);
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = this.context;
|
||||
|
||||
this.editorGroupService.pinEditor(group, group.activeEditor);
|
||||
}
|
||||
|
||||
private onTitleClick(e: MouseEvent): void {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = this.context;
|
||||
|
||||
// Close editor on middle mouse click
|
||||
if (e.button === 1 /* Middle Button */) {
|
||||
this.closeEditorAction.run({ group, editor: group.activeEditor }).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Focus editor group unless click on toolbar
|
||||
else if (this.stacks.groups.length === 1 && !DOM.isAncestor((e.target || e.srcElement) as HTMLElement, this.editorActionsToolbar.getContainer().getHTMLElement())) {
|
||||
this.editorGroupService.focusGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
protected doRefresh(): void {
|
||||
const group = this.context;
|
||||
const editor = group && group.activeEditor;
|
||||
if (!editor) {
|
||||
this.editorLabel.clear();
|
||||
this.clearEditorActionsToolbar();
|
||||
|
||||
return; // return early if we are being closed
|
||||
}
|
||||
|
||||
const isPinned = group.isPinned(group.activeEditor);
|
||||
const isActive = this.stacks.isActive(group);
|
||||
|
||||
// Activity state
|
||||
if (isActive) {
|
||||
DOM.addClass(this.titleContainer, 'active');
|
||||
} else {
|
||||
DOM.removeClass(this.titleContainer, 'active');
|
||||
}
|
||||
|
||||
// Dirty state
|
||||
if (editor.isDirty()) {
|
||||
DOM.addClass(this.titleContainer, 'dirty');
|
||||
} else {
|
||||
DOM.removeClass(this.titleContainer, 'dirty');
|
||||
}
|
||||
|
||||
// Editor Label
|
||||
const resource = toResource(editor, { supportSideBySide: true });
|
||||
const name = editor.getName() || '';
|
||||
const description = isActive ? (editor.getDescription() || '') : '';
|
||||
let title = editor.getTitle(Verbosity.LONG);
|
||||
if (description === title) {
|
||||
title = ''; // dont repeat what is already shown
|
||||
}
|
||||
|
||||
this.editorLabel.setLabel({ name, description, resource }, { title, italic: !isPinned, extraClasses: ['title-label'] });
|
||||
if (isActive) {
|
||||
this.editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND);
|
||||
} else {
|
||||
this.editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND);
|
||||
}
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
this.updateEditorActionsToolbar();
|
||||
}
|
||||
}
|
||||
222
src/vs/workbench/browser/parts/editor/sideBySideEditor.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions, EditorInput, EditorOptions, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorControl, Position, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { VSash } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.sidebysideEditor';
|
||||
|
||||
private dimension: Dimension;
|
||||
|
||||
protected masterEditor: BaseEditor;
|
||||
private masterEditorContainer: HTMLElement;
|
||||
|
||||
protected detailsEditor: BaseEditor;
|
||||
private detailsEditorContainer: HTMLElement;
|
||||
|
||||
private sash: VSash;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(SideBySideEditor.ID, telemetryService, themeService);
|
||||
}
|
||||
|
||||
protected createEditor(parent: Builder): void {
|
||||
const parentElement = parent.getHTMLElement();
|
||||
DOM.addClass(parentElement, 'side-by-side-editor');
|
||||
this.createSash(parentElement);
|
||||
}
|
||||
|
||||
public setInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = <SideBySideEditorInput>this.input;
|
||||
return super.setInput(newInput, options)
|
||||
.then(() => this.updateInput(oldInput, newInput, options));
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, position: Position): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.setVisible(visible, position);
|
||||
}
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.setVisible(visible, position);
|
||||
}
|
||||
super.setEditorVisible(visible, position);
|
||||
}
|
||||
|
||||
public changePosition(position: Position): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.changePosition(position);
|
||||
}
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.changePosition(position);
|
||||
}
|
||||
super.changePosition(position);
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.clearInput();
|
||||
}
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.clearInput();
|
||||
}
|
||||
this.disposeEditors();
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
this.sash.setDimenesion(this.dimension);
|
||||
}
|
||||
|
||||
public getControl(): IEditorControl {
|
||||
if (this.masterEditor) {
|
||||
return this.masterEditor.getControl();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getMasterEditor(): IEditor {
|
||||
return this.masterEditor;
|
||||
}
|
||||
|
||||
public getDetailsEditor(): IEditor {
|
||||
return this.detailsEditor;
|
||||
}
|
||||
|
||||
private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
|
||||
if (!newInput.matches(oldInput)) {
|
||||
if (oldInput) {
|
||||
this.disposeEditors();
|
||||
}
|
||||
this.createEditorContainers();
|
||||
|
||||
return this.setNewInput(newInput, options);
|
||||
} else {
|
||||
this.detailsEditor.setInput(newInput.details);
|
||||
this.masterEditor.setInput(newInput.master, options);
|
||||
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
private setNewInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
|
||||
return TPromise.join([
|
||||
this._createEditor(<EditorInput>newInput.details, this.detailsEditorContainer),
|
||||
this._createEditor(<EditorInput>newInput.master, this.masterEditorContainer)
|
||||
]).then(result => this.onEditorsCreated(result[0], result[1], newInput.details, newInput.master, options));
|
||||
}
|
||||
|
||||
private _createEditor(editorInput: EditorInput, container: HTMLElement): TPromise<BaseEditor> {
|
||||
const descriptor = Registry.as<IEditorRegistry>(EditorExtensions.Editors).getEditor(editorInput);
|
||||
if (!descriptor) {
|
||||
return TPromise.wrapError<BaseEditor>(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput)));
|
||||
}
|
||||
return this.instantiationService.createInstance(<EditorDescriptor>descriptor)
|
||||
.then((editor: BaseEditor) => {
|
||||
editor.create(new Builder(container));
|
||||
editor.setVisible(this.isVisible(), this.position);
|
||||
return editor;
|
||||
});
|
||||
}
|
||||
|
||||
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions): TPromise<void> {
|
||||
this.detailsEditor = details;
|
||||
this.masterEditor = master;
|
||||
this.dolayout(this.sash.getVerticalSashLeft());
|
||||
return TPromise.join([this.detailsEditor.setInput(detailsInput), this.masterEditor.setInput(masterInput, options)]).then(() => this.focus());
|
||||
}
|
||||
|
||||
private createEditorContainers(): void {
|
||||
const parentElement = this.getContainer().getHTMLElement();
|
||||
this.detailsEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container'));
|
||||
this.detailsEditorContainer.style.position = 'absolute';
|
||||
this.masterEditorContainer = DOM.append(parentElement, DOM.$('.master-editor-container'));
|
||||
this.masterEditorContainer.style.position = 'absolute';
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
if (this.masterEditorContainer) {
|
||||
this.masterEditorContainer.style.boxShadow = `-6px 0 5px -5px ${this.getColor(scrollbarShadow)}`;
|
||||
}
|
||||
}
|
||||
|
||||
private createSash(parentElement: HTMLElement): void {
|
||||
this.sash = this._register(new VSash(parentElement, 220));
|
||||
this._register(this.sash.onPositionChange(position => this.dolayout(position)));
|
||||
}
|
||||
|
||||
private dolayout(splitPoint: number): void {
|
||||
if (!this.detailsEditor || !this.masterEditor || !this.dimension) {
|
||||
return;
|
||||
}
|
||||
const masterEditorWidth = this.dimension.width - splitPoint;
|
||||
const detailsEditorWidth = this.dimension.width - masterEditorWidth;
|
||||
|
||||
this.detailsEditorContainer.style.width = `${detailsEditorWidth}px`;
|
||||
this.detailsEditorContainer.style.height = `${this.dimension.height}px`;
|
||||
this.detailsEditorContainer.style.left = '0px';
|
||||
|
||||
this.masterEditorContainer.style.width = `${masterEditorWidth}px`;
|
||||
this.masterEditorContainer.style.height = `${this.dimension.height}px`;
|
||||
this.masterEditorContainer.style.left = `${splitPoint}px`;
|
||||
|
||||
this.detailsEditor.layout(new Dimension(detailsEditorWidth, this.dimension.height));
|
||||
this.masterEditor.layout(new Dimension(masterEditorWidth, this.dimension.height));
|
||||
}
|
||||
|
||||
private disposeEditors(): void {
|
||||
const parentContainer = this.getContainer().getHTMLElement();
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.dispose();
|
||||
this.detailsEditor = null;
|
||||
}
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.dispose();
|
||||
this.masterEditor = null;
|
||||
}
|
||||
if (this.detailsEditorContainer) {
|
||||
parentContainer.removeChild(this.detailsEditorContainer);
|
||||
this.detailsEditorContainer = null;
|
||||
}
|
||||
if (this.masterEditorContainer) {
|
||||
parentContainer.removeChild(this.masterEditorContainer);
|
||||
this.masterEditorContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.disposeEditors();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
788
src/vs/workbench/browser/parts/editor/tabsTitleControl.ts
Normal file
@@ -0,0 +1,788 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/tabstitle';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { MIME_BINARY } from 'vs/base/common/mime';
|
||||
import { shorten, getPathLabel } from 'vs/base/common/labels';
|
||||
import { ActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { Position, IEditorInput, Verbosity, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EditorLabel } from 'vs/workbench/browser/labels';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { TitleControl, handleWorkspaceExternalDrop } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { extractResources } from 'vs/base/browser/dnd';
|
||||
import { getOrSet } from 'vs/base/common/map';
|
||||
import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
interface IEditorInputLabel {
|
||||
name: string;
|
||||
hasAmbiguousName?: boolean;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export class TabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
private tabsContainer: HTMLElement;
|
||||
private activeTab: HTMLElement;
|
||||
private editorLabels: EditorLabel[];
|
||||
private scrollbar: ScrollableElement;
|
||||
private tabDisposeables: IDisposable[];
|
||||
private blockRevealActiveTab: boolean;
|
||||
|
||||
constructor(
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IMessageService messageService: IMessageService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IWorkspacesService private workspacesService: IWorkspacesService
|
||||
) {
|
||||
super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService, themeService);
|
||||
|
||||
this.tabDisposeables = [];
|
||||
this.editorLabels = [];
|
||||
}
|
||||
|
||||
protected initActions(services: IInstantiationService): void {
|
||||
super.initActions(this.createScopedInstantiationService());
|
||||
}
|
||||
|
||||
private createScopedInstantiationService(): IInstantiationService {
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
|
||||
|
||||
// We create a scoped instantiation service to override the behaviour when closing an inactive editor
|
||||
// Specifically we want to move focus back to the editor when an inactive editor is closed from anywhere
|
||||
// in the tabs title control (e.g. mouse middle click, context menu on tab). This is only needed for
|
||||
// the inactive editors because closing the active one will always cause a tab switch that sets focus.
|
||||
// We also want to block the tabs container to reveal the currently active tab because that makes it very
|
||||
// hard to close multiple inactive tabs next to each other.
|
||||
delegatingEditorService.setEditorCloseHandler((position, editor) => {
|
||||
const group = stacks.groupAt(position);
|
||||
if (group && stacks.isActive(group) && !group.isActive(editor)) {
|
||||
this.editorGroupService.focusGroup(group);
|
||||
}
|
||||
|
||||
this.blockRevealActiveTab = true;
|
||||
|
||||
return TPromise.as(void 0);
|
||||
});
|
||||
|
||||
return this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
|
||||
}
|
||||
|
||||
public setContext(group: IEditorGroup): void {
|
||||
super.setContext(group);
|
||||
|
||||
this.editorActionsToolbar.context = { group };
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
|
||||
this.titleContainer = parent;
|
||||
|
||||
// Tabs Container
|
||||
this.tabsContainer = document.createElement('div');
|
||||
this.tabsContainer.setAttribute('role', 'tablist');
|
||||
DOM.addClass(this.tabsContainer, 'tabs-container');
|
||||
|
||||
// Forward scrolling inside the container to our custom scrollbar
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.SCROLL, e => {
|
||||
if (DOM.hasClass(this.tabsContainer, 'scroll')) {
|
||||
this.scrollbar.setScrollPosition({
|
||||
scrollLeft: this.tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
// New file when double clicking on tabs container (but not tabs)
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DBLCLICK, e => {
|
||||
const target = e.target;
|
||||
if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
const group = this.context;
|
||||
if (group) {
|
||||
this.editorService.openEditor({ options: { pinned: true, index: group.count /* always at the end */ } } as IUntitledResourceInput).done(null, errors.onUnexpectedError); // untitled are always pinned
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Custom Scrollbar
|
||||
this.scrollbar = new ScrollableElement(this.tabsContainer, {
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: ScrollbarVisibility.Hidden,
|
||||
scrollYToX: true,
|
||||
useShadows: false,
|
||||
horizontalScrollbarSize: 3
|
||||
});
|
||||
|
||||
this.scrollbar.onScroll(e => {
|
||||
this.tabsContainer.scrollLeft = e.scrollLeft;
|
||||
});
|
||||
|
||||
this.titleContainer.appendChild(this.scrollbar.getDomNode());
|
||||
|
||||
// Drag over
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_OVER, (e: DragEvent) => {
|
||||
|
||||
// update the dropEffect, otherwise it would look like a "move" operation. but only if we are
|
||||
// not dragging a tab actually because there we support both moving as well as copying
|
||||
if (!TabsTitleControl.getDraggedEditor()) {
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
DOM.addClass(this.tabsContainer, 'scroll'); // enable support to scroll while dragging
|
||||
|
||||
const target = e.target;
|
||||
if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
|
||||
this.updateDropFeedback(this.tabsContainer, true);
|
||||
}
|
||||
}));
|
||||
|
||||
// Drag leave
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
this.updateDropFeedback(this.tabsContainer, false);
|
||||
DOM.removeClass(this.tabsContainer, 'scroll');
|
||||
}));
|
||||
|
||||
// Drag end
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_END, (e: DragEvent) => {
|
||||
this.updateDropFeedback(this.tabsContainer, false);
|
||||
DOM.removeClass(this.tabsContainer, 'scroll');
|
||||
}));
|
||||
|
||||
// Drop onto tabs container
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DROP, (e: DragEvent) => {
|
||||
this.updateDropFeedback(this.tabsContainer, false);
|
||||
DOM.removeClass(this.tabsContainer, 'scroll');
|
||||
|
||||
const target = e.target;
|
||||
if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
|
||||
const group = this.context;
|
||||
if (group) {
|
||||
const targetPosition = this.stacks.positionOfGroup(group);
|
||||
const targetIndex = group.count;
|
||||
|
||||
this.onDrop(e, group, targetPosition, targetIndex);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Editor Actions Container
|
||||
const editorActionsContainer = document.createElement('div');
|
||||
DOM.addClass(editorActionsContainer, 'editor-actions');
|
||||
this.titleContainer.appendChild(editorActionsContainer);
|
||||
|
||||
// Editor Actions Toolbar
|
||||
this.createEditorActionsToolBar(editorActionsContainer);
|
||||
}
|
||||
|
||||
private updateDropFeedback(element: HTMLElement, isDND: boolean, index?: number): void {
|
||||
const isTab = (typeof index === 'number');
|
||||
const isActiveTab = isTab && this.context && this.context.isActive(this.context.getEditor(index));
|
||||
|
||||
// Background
|
||||
const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : null;
|
||||
element.style.backgroundColor = isDND ? this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) : noDNDBackgroundColor;
|
||||
|
||||
// Outline
|
||||
const activeContrastBorderColor = this.getColor(activeContrastBorder);
|
||||
if (activeContrastBorderColor && isDND) {
|
||||
element.style.outlineWidth = '2px';
|
||||
element.style.outlineStyle = 'dashed';
|
||||
element.style.outlineColor = activeContrastBorderColor;
|
||||
element.style.outlineOffset = isTab ? '-5px' : '-3px';
|
||||
} else {
|
||||
element.style.outlineWidth = null;
|
||||
element.style.outlineStyle = null;
|
||||
element.style.outlineColor = activeContrastBorderColor;
|
||||
element.style.outlineOffset = null;
|
||||
}
|
||||
}
|
||||
|
||||
public allowDragging(element: HTMLElement): boolean {
|
||||
return (element.className === 'tabs-container');
|
||||
}
|
||||
|
||||
protected doUpdate(): void {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = this.context;
|
||||
|
||||
// Tabs container activity state
|
||||
const isGroupActive = this.stacks.isActive(group);
|
||||
if (isGroupActive) {
|
||||
DOM.addClass(this.titleContainer, 'active');
|
||||
} else {
|
||||
DOM.removeClass(this.titleContainer, 'active');
|
||||
}
|
||||
|
||||
// Compute labels and protect against duplicates
|
||||
const editorsOfGroup = this.context.getEditors();
|
||||
const labels = this.getUniqueTabLabels(editorsOfGroup);
|
||||
|
||||
// Tab label and styles
|
||||
editorsOfGroup.forEach((editor, index) => {
|
||||
const tabContainer = this.tabsContainer.children[index];
|
||||
if (tabContainer instanceof HTMLElement) {
|
||||
const isPinned = group.isPinned(index);
|
||||
const isTabActive = group.isActive(editor);
|
||||
const isDirty = editor.isDirty();
|
||||
|
||||
const label = labels[index];
|
||||
const name = label.name;
|
||||
const description = label.hasAmbiguousName && label.description ? label.description : '';
|
||||
const title = label.title || '';
|
||||
|
||||
// Container
|
||||
tabContainer.setAttribute('aria-label', `${name}, tab`);
|
||||
tabContainer.title = title;
|
||||
tabContainer.style.borderLeftColor = (index !== 0) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
|
||||
tabContainer.style.borderRightColor = (index === editorsOfGroup.length - 1) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
|
||||
tabContainer.style.outlineColor = this.getColor(activeContrastBorder);
|
||||
|
||||
const tabOptions = this.editorGroupService.getTabOptions();
|
||||
['off', 'left'].forEach(option => {
|
||||
const domAction = tabOptions.tabCloseButton === option ? DOM.addClass : DOM.removeClass;
|
||||
domAction(tabContainer, `close-button-${option}`);
|
||||
});
|
||||
|
||||
// Label
|
||||
const tabLabel = this.editorLabels[index];
|
||||
tabLabel.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { extraClasses: ['tab-label'], italic: !isPinned });
|
||||
|
||||
// Active state
|
||||
if (isTabActive) {
|
||||
DOM.addClass(tabContainer, 'active');
|
||||
tabContainer.setAttribute('aria-selected', 'true');
|
||||
tabContainer.style.backgroundColor = this.getColor(TAB_ACTIVE_BACKGROUND);
|
||||
tabLabel.element.style.color = this.getColor(isGroupActive ? TAB_ACTIVE_FOREGROUND : TAB_UNFOCUSED_ACTIVE_FOREGROUND);
|
||||
|
||||
// Use boxShadow for the active tab border because if we also have a editor group header
|
||||
// color, the two colors would collide and the tab border never shows up.
|
||||
// see https://github.com/Microsoft/vscode/issues/33111
|
||||
const activeTabBorderColor = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER : TAB_UNFOCUSED_ACTIVE_BORDER);
|
||||
if (activeTabBorderColor) {
|
||||
tabContainer.style.boxShadow = `${activeTabBorderColor} 0 -1px inset`;
|
||||
} else {
|
||||
tabContainer.style.boxShadow = null;
|
||||
}
|
||||
|
||||
this.activeTab = tabContainer;
|
||||
} else {
|
||||
DOM.removeClass(tabContainer, 'active');
|
||||
tabContainer.setAttribute('aria-selected', 'false');
|
||||
tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND);
|
||||
tabLabel.element.style.color = this.getColor(isGroupActive ? TAB_INACTIVE_FOREGROUND : TAB_UNFOCUSED_INACTIVE_FOREGROUND);
|
||||
tabContainer.style.boxShadow = null;
|
||||
}
|
||||
|
||||
// Dirty State
|
||||
if (isDirty) {
|
||||
DOM.addClass(tabContainer, 'dirty');
|
||||
} else {
|
||||
DOM.removeClass(tabContainer, 'dirty');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
this.updateEditorActionsToolbar();
|
||||
|
||||
// Ensure the active tab is always revealed
|
||||
this.layout();
|
||||
}
|
||||
|
||||
private getUniqueTabLabels(editors: IEditorInput[]): IEditorInputLabel[] {
|
||||
const labels: IEditorInputLabel[] = [];
|
||||
|
||||
const mapLabelToDuplicates = new Map<string, IEditorInputLabel[]>();
|
||||
const mapLabelAndDescriptionToDuplicates = new Map<string, IEditorInputLabel[]>();
|
||||
|
||||
// Build labels and descriptions for each editor
|
||||
editors.forEach(editor => {
|
||||
const name = editor.getName();
|
||||
let description = editor.getDescription();
|
||||
if (mapLabelAndDescriptionToDuplicates.has(`${name}${description}`)) {
|
||||
description = editor.getDescription(true); // try verbose description if name+description already exists
|
||||
}
|
||||
|
||||
const item: IEditorInputLabel = {
|
||||
name,
|
||||
description,
|
||||
title: editor.getTitle(Verbosity.LONG)
|
||||
};
|
||||
labels.push(item);
|
||||
|
||||
getOrSet(mapLabelToDuplicates, item.name, []).push(item);
|
||||
|
||||
if (typeof description === 'string') {
|
||||
getOrSet(mapLabelAndDescriptionToDuplicates, `${item.name}${item.description}`, []).push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Mark duplicates and shorten their descriptions
|
||||
mapLabelToDuplicates.forEach(duplicates => {
|
||||
if (duplicates.length > 1) {
|
||||
duplicates = duplicates.filter(d => {
|
||||
// we could have items with equal label and description. in that case it does not make much
|
||||
// sense to produce a shortened version of the label, so we ignore those kind of items
|
||||
return typeof d.description === 'string' && mapLabelAndDescriptionToDuplicates.get(`${d.name}${d.description}`).length === 1;
|
||||
});
|
||||
|
||||
if (duplicates.length > 1) {
|
||||
const shortenedDescriptions = shorten(duplicates.map(duplicate => duplicate.description));
|
||||
duplicates.forEach((duplicate, i) => {
|
||||
duplicate.description = shortenedDescriptions[i];
|
||||
duplicate.hasAmbiguousName = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
protected doRefresh(): void {
|
||||
const group = this.context;
|
||||
const editor = group && group.activeEditor;
|
||||
if (!editor) {
|
||||
this.clearTabs();
|
||||
|
||||
this.clearEditorActionsToolbar();
|
||||
|
||||
return; // return early if we are being closed
|
||||
}
|
||||
|
||||
// Handle Tabs
|
||||
this.handleTabs(group.count);
|
||||
DOM.removeClass(this.titleContainer, 'empty');
|
||||
|
||||
// Update Tabs
|
||||
this.doUpdate();
|
||||
}
|
||||
|
||||
private clearTabs(): void {
|
||||
DOM.clearNode(this.tabsContainer);
|
||||
|
||||
this.tabDisposeables = dispose(this.tabDisposeables);
|
||||
this.editorLabels = [];
|
||||
|
||||
DOM.addClass(this.titleContainer, 'empty');
|
||||
}
|
||||
|
||||
private handleTabs(tabsNeeded: number): void {
|
||||
const tabs = this.tabsContainer.children;
|
||||
const tabsCount = tabs.length;
|
||||
|
||||
// Nothing to do if count did not change
|
||||
if (tabsCount === tabsNeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need more tabs: create new ones
|
||||
if (tabsCount < tabsNeeded) {
|
||||
for (let i = tabsCount; i < tabsNeeded; i++) {
|
||||
this.tabsContainer.appendChild(this.createTab(i));
|
||||
}
|
||||
}
|
||||
|
||||
// We need less tabs: delete the ones we do not need
|
||||
else {
|
||||
for (let i = 0; i < tabsCount - tabsNeeded; i++) {
|
||||
(this.tabsContainer.lastChild as HTMLElement).remove();
|
||||
this.editorLabels.pop();
|
||||
this.tabDisposeables.pop().dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createTab(index: number): HTMLElement {
|
||||
|
||||
// Tab Container
|
||||
const tabContainer = document.createElement('div');
|
||||
tabContainer.draggable = true;
|
||||
tabContainer.tabIndex = 0;
|
||||
tabContainer.setAttribute('role', 'presentation'); // cannot use role "tab" here due to https://github.com/Microsoft/vscode/issues/8659
|
||||
DOM.addClass(tabContainer, 'tab');
|
||||
|
||||
// Tab Editor Label
|
||||
const editorLabel = this.instantiationService.createInstance(EditorLabel, tabContainer, void 0);
|
||||
this.editorLabels.push(editorLabel);
|
||||
|
||||
// Tab Close
|
||||
const tabCloseContainer = document.createElement('div');
|
||||
DOM.addClass(tabCloseContainer, 'tab-close');
|
||||
tabContainer.appendChild(tabCloseContainer);
|
||||
|
||||
const bar = new ActionBar(tabCloseContainer, { ariaLabel: nls.localize('araLabelTabActions', "Tab actions"), actionRunner: new TabActionRunner(() => this.context, index) });
|
||||
bar.push(this.closeEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeEditorAction) });
|
||||
|
||||
// Eventing
|
||||
const disposable = this.hookTabListeners(tabContainer, index);
|
||||
|
||||
this.tabDisposeables.push(combinedDisposable([disposable, bar, editorLabel]));
|
||||
|
||||
return tabContainer;
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
if (!this.activeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleContainerWidth = this.tabsContainer.offsetWidth;
|
||||
const totalContainerWidth = this.tabsContainer.scrollWidth;
|
||||
|
||||
// Update scrollbar
|
||||
this.scrollbar.setScrollDimensions({
|
||||
width: visibleContainerWidth,
|
||||
scrollWidth: totalContainerWidth
|
||||
});
|
||||
|
||||
// Return now if we are blocked to reveal the active tab and clear flag
|
||||
if (this.blockRevealActiveTab) {
|
||||
this.blockRevealActiveTab = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reveal the active one
|
||||
const containerScrollPosX = this.tabsContainer.scrollLeft;
|
||||
const activeTabPosX = this.activeTab.offsetLeft;
|
||||
const activeTabWidth = this.activeTab.offsetWidth;
|
||||
const activeTabFits = activeTabWidth <= visibleContainerWidth;
|
||||
|
||||
// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
|
||||
// Note: only try to do this if we actually have enough width to give to show the tab fully!
|
||||
if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) {
|
||||
this.scrollbar.setScrollPosition({
|
||||
scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
|
||||
});
|
||||
}
|
||||
|
||||
// Tab is overlflowng to the left or does not fit: Scroll it into view to the left
|
||||
else if (containerScrollPosX > activeTabPosX || !activeTabFits) {
|
||||
this.scrollbar.setScrollPosition({
|
||||
scrollLeft: this.activeTab.offsetLeft
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private hookTabListeners(tab: HTMLElement, index: number): IDisposable {
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
// Open on Click
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
tab.blur();
|
||||
|
||||
const { editor, position } = this.toTabContext(index);
|
||||
if (e.button === 0 /* Left Button */ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
|
||||
setTimeout(() => this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError)); // timeout to keep focus in editor after mouse up
|
||||
}
|
||||
}));
|
||||
|
||||
// Close on mouse middle click
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_UP, (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e);
|
||||
tab.blur();
|
||||
|
||||
if (e.button === 1 /* Middle Button*/ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
|
||||
this.closeEditorAction.run(this.toTabContext(index)).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
}));
|
||||
|
||||
// Context menu on Shift+F10
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.shiftKey && event.keyCode === KeyCode.F10) {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
|
||||
this.onContextMenu({ group, editor }, e, tab);
|
||||
}
|
||||
}));
|
||||
|
||||
// Keyboard accessibility
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let handled = false;
|
||||
|
||||
const { group, position, editor } = this.toTabContext(index);
|
||||
|
||||
// Run action on Enter/Space
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
handled = true;
|
||||
this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Navigate in editors
|
||||
else if ([KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.Home, KeyCode.End].some(kb => event.equals(kb))) {
|
||||
let targetIndex: number;
|
||||
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.UpArrow)) {
|
||||
targetIndex = index - 1;
|
||||
} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.DownArrow)) {
|
||||
targetIndex = index + 1;
|
||||
} else if (event.equals(KeyCode.Home)) {
|
||||
targetIndex = 0;
|
||||
} else {
|
||||
targetIndex = group.count - 1;
|
||||
}
|
||||
|
||||
const target = group.getEditor(targetIndex);
|
||||
if (target) {
|
||||
handled = true;
|
||||
this.editorService.openEditor(target, { preserveFocus: true }, position).done(null, errors.onUnexpectedError);
|
||||
(<HTMLElement>this.tabsContainer.childNodes[targetIndex]).focus();
|
||||
}
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
}
|
||||
|
||||
// moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar
|
||||
this.scrollbar.setScrollPosition({
|
||||
scrollLeft: this.tabsContainer.scrollLeft
|
||||
});
|
||||
}));
|
||||
|
||||
// Pin on double click
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DBLCLICK, (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
|
||||
this.editorGroupService.pinEditor(group, editor);
|
||||
}));
|
||||
|
||||
// Context menu
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.CONTEXT_MENU, (e: Event) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
|
||||
this.onContextMenu({ group, editor }, e, tab);
|
||||
}, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */));
|
||||
|
||||
// Drag start
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_START, (e: DragEvent) => {
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
|
||||
this.onEditorDragStart({ editor, group });
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
|
||||
// Insert transfer accordingly
|
||||
const fileResource = toResource(editor, { supportSideBySide: true, filter: 'file' });
|
||||
if (fileResource) {
|
||||
const resource = fileResource.toString();
|
||||
e.dataTransfer.setData('URL', resource); // enables cross window DND of tabs
|
||||
e.dataTransfer.setData('DownloadURL', [MIME_BINARY, editor.getName(), resource].join(':')); // enables support to drag a tab as file to desktop
|
||||
e.dataTransfer.setData('text/plain', getPathLabel(resource)); // enables dropping tab resource path into text controls
|
||||
}
|
||||
}));
|
||||
|
||||
// We need to keep track of DRAG_ENTER and DRAG_LEAVE events because a tab is not just a div without children,
|
||||
// it contains a label and a close button. HTML gives us DRAG_ENTER and DRAG_LEAVE events when hovering over
|
||||
// these children and this can cause flicker of the drop feedback. The workaround is to count the events and only
|
||||
// remove the drop feedback when the counter is 0 (see https://github.com/Microsoft/vscode/issues/14470)
|
||||
let counter = 0;
|
||||
|
||||
// Drag over
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
counter++;
|
||||
this.updateDropFeedback(tab, true, index);
|
||||
}));
|
||||
|
||||
// Drag leave
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
this.updateDropFeedback(tab, false, index);
|
||||
}
|
||||
}));
|
||||
|
||||
// Drag end
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_END, (e: DragEvent) => {
|
||||
counter = 0;
|
||||
this.updateDropFeedback(tab, false, index);
|
||||
|
||||
this.onEditorDragEnd();
|
||||
}));
|
||||
|
||||
// Drop
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DROP, (e: DragEvent) => {
|
||||
counter = 0;
|
||||
this.updateDropFeedback(tab, false, index);
|
||||
|
||||
const { group, position } = this.toTabContext(index);
|
||||
|
||||
this.onDrop(e, group, position, index);
|
||||
}));
|
||||
|
||||
return combinedDisposable(disposables);
|
||||
}
|
||||
|
||||
private isTabActionBar(element: HTMLElement): boolean {
|
||||
return !!DOM.findParentWithClass(element, 'monaco-action-bar', 'tab');
|
||||
}
|
||||
|
||||
private toTabContext(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
|
||||
const group = this.context;
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
const editor = group.getEditor(index);
|
||||
|
||||
return { group, position, editor };
|
||||
}
|
||||
|
||||
private onDrop(e: DragEvent, group: IEditorGroup, targetPosition: Position, targetIndex: number): void {
|
||||
this.updateDropFeedback(this.tabsContainer, false);
|
||||
DOM.removeClass(this.tabsContainer, 'scroll');
|
||||
|
||||
// Local DND
|
||||
const draggedEditor = TabsTitleControl.getDraggedEditor();
|
||||
if (draggedEditor) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
// Move editor to target position and index
|
||||
if (this.isMoveOperation(e, draggedEditor.group, group)) {
|
||||
this.editorGroupService.moveEditor(draggedEditor.editor, draggedEditor.group, group, { index: targetIndex });
|
||||
}
|
||||
|
||||
// Copy: just open editor at target index
|
||||
else {
|
||||
this.editorService.openEditor(draggedEditor.editor, { pinned: true, index: targetIndex }, targetPosition).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
this.onEditorDragEnd();
|
||||
}
|
||||
|
||||
// External DND
|
||||
else {
|
||||
this.handleExternalDrop(e, targetPosition, targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private handleExternalDrop(e: DragEvent, targetPosition: Position, targetIndex: number): void {
|
||||
const droppedResources = extractResources(e).filter(r => r.resource.scheme === 'file' || r.resource.scheme === 'untitled');
|
||||
if (droppedResources.length) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
handleWorkspaceExternalDrop(droppedResources, this.fileService, this.messageService, this.windowsService, this.windowService, this.workspacesService).then(handled => {
|
||||
if (handled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add external ones to recently open list
|
||||
const externalResources = droppedResources.filter(d => d.isExternal).map(d => d.resource);
|
||||
if (externalResources.length) {
|
||||
this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath));
|
||||
}
|
||||
|
||||
// Open in Editor
|
||||
this.windowService.focusWindow()
|
||||
.then(() => this.editorService.openEditors(droppedResources.map(d => {
|
||||
return {
|
||||
input: { resource: d.resource, options: { pinned: true, index: targetIndex } },
|
||||
position: targetPosition
|
||||
};
|
||||
}))).then(() => {
|
||||
this.editorGroupService.focusGroup(targetPosition);
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private isMoveOperation(e: DragEvent, source: IEditorGroup, target: IEditorGroup) {
|
||||
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
|
||||
|
||||
return !isCopy || source.id === target.id;
|
||||
}
|
||||
}
|
||||
|
||||
class TabActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private group: () => IEditorGroup, private index: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
public run(action: IAction, context?: any): TPromise<void> {
|
||||
const group = this.group();
|
||||
if (!group) {
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
|
||||
return super.run(action, { group, editor: group.getEditor(this.index) });
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
const activeContrastBorderColor = theme.getColor(activeContrastBorder);
|
||||
if (activeContrastBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover {
|
||||
outline: 1px solid;
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover {
|
||||
outline: 1px dashed;
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active > .tab-close .action-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover > .tab-close .action-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty > .tab-close .action-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover > .tab-close .action-label {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
377
src/vs/workbench/browser/parts/editor/textDiffEditor.ts
Normal file
@@ -0,0 +1,377 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/textdiffeditor';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import objects = require('vs/base/common/objects');
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import types = require('vs/base/common/types');
|
||||
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IFileEditorInput } from 'vs/workbench/common/editor';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
|
||||
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
|
||||
import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
|
||||
import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService';
|
||||
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
|
||||
/**
|
||||
* The text editor that leverages the diff text editor for the editing experience.
|
||||
*/
|
||||
export class TextDiffEditor extends BaseTextEditor {
|
||||
|
||||
public static ID = TEXT_DIFF_EDITOR_ID;
|
||||
|
||||
private diffNavigator: DiffNavigator;
|
||||
private nextDiffAction: NavigateAction;
|
||||
private previousDiffAction: NavigateAction;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@IModeService modeService: IModeService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
) {
|
||||
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, modeService, textFileService, editorGroupService);
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
if (this.input) {
|
||||
return this.input.getName();
|
||||
}
|
||||
|
||||
return nls.localize('textDiffEditor', "Text Diff Editor");
|
||||
}
|
||||
|
||||
public createEditorControl(parent: Builder, configuration: IEditorOptions): IDiffEditor {
|
||||
|
||||
// Actions
|
||||
this.nextDiffAction = new NavigateAction(this, true);
|
||||
this.previousDiffAction = new NavigateAction(this, false);
|
||||
|
||||
// Support navigation within the diff editor by overriding the editor service within
|
||||
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
|
||||
delegatingEditorService.setEditorOpenHandler((input: EditorInput, options?: EditorOptions, arg3?: any) => {
|
||||
|
||||
// Check if arg4 is a position argument that differs from this editors position
|
||||
if (types.isUndefinedOrNull(arg3) || arg3 === false || arg3 === this.position) {
|
||||
const activeDiffInput = <DiffEditorInput>this.input;
|
||||
if (input && options && activeDiffInput) {
|
||||
|
||||
// Input matches modified side of the diff editor: perform the action on modified side
|
||||
if (input.matches(activeDiffInput.modifiedInput)) {
|
||||
return this.setInput(this.input, options).then(() => this);
|
||||
}
|
||||
|
||||
// Input matches original side of the diff editor: perform the action on original side
|
||||
else if (input.matches(activeDiffInput.originalInput)) {
|
||||
const originalEditor = this.getControl().getOriginalEditor();
|
||||
if (options instanceof TextEditorOptions) {
|
||||
(<TextEditorOptions>options).apply(originalEditor, ScrollType.Smooth);
|
||||
|
||||
return TPromise.as(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
});
|
||||
|
||||
// Create a special child of instantiator that will delegate all calls to openEditor() to the same diff editor if the input matches with the modified one
|
||||
const diffEditorInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
|
||||
|
||||
return diffEditorInstantiator.createInstance(DiffEditorWidget, parent.getHTMLElement(), configuration);
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = this.input;
|
||||
super.setInput(input, options);
|
||||
|
||||
// Detect options
|
||||
const forceOpen = options && options.forceOpen;
|
||||
|
||||
// Same Input
|
||||
if (!forceOpen && input.matches(oldInput)) {
|
||||
|
||||
// TextOptions (avoiding instanceof here for a reason, do not change!)
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
textOptions.apply(<IDiffEditor>this.getControl(), ScrollType.Smooth);
|
||||
}
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
}
|
||||
|
||||
// Dispose previous diff navigator
|
||||
if (this.diffNavigator) {
|
||||
this.diffNavigator.dispose();
|
||||
}
|
||||
|
||||
// Different Input (Reload)
|
||||
return input.resolve(true).then(resolvedModel => {
|
||||
|
||||
// Assert Model Instance
|
||||
if (!(resolvedModel instanceof TextDiffEditorModel) && this.openAsBinary(input, options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading a diff takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Editor
|
||||
const diffEditor = <IDiffEditor>this.getControl();
|
||||
diffEditor.setModel((<TextDiffEditorModel>resolvedModel).textDiffEditorModel);
|
||||
|
||||
// Handle TextOptions
|
||||
let alwaysRevealFirst = true;
|
||||
if (options && types.isFunction((<TextEditorOptions>options).apply)) {
|
||||
const hadOptions = (<TextEditorOptions>options).apply(<IDiffEditor>diffEditor, ScrollType.Immediate);
|
||||
if (hadOptions) {
|
||||
alwaysRevealFirst = false; // Do not reveal if we are instructed to open specific line/col
|
||||
}
|
||||
}
|
||||
|
||||
// Listen on diff updated changes to reveal the first change
|
||||
this.diffNavigator = new DiffNavigator(diffEditor, {
|
||||
alwaysRevealFirst
|
||||
});
|
||||
this.diffNavigator.addListener(DiffNavigator.Events.UPDATED, () => {
|
||||
this.nextDiffAction.updateEnablement();
|
||||
this.previousDiffAction.updateEnablement();
|
||||
});
|
||||
}, error => {
|
||||
|
||||
// In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff.
|
||||
if (this.isFileBinaryError(error) && this.openAsBinary(input, options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise make sure the error bubbles up
|
||||
return TPromise.wrapError(error);
|
||||
});
|
||||
}
|
||||
|
||||
private openAsBinary(input: EditorInput, options: EditorOptions): boolean {
|
||||
if (input instanceof DiffEditorInput) {
|
||||
const originalInput = input.originalInput;
|
||||
const modifiedInput = input.modifiedInput;
|
||||
|
||||
const binaryDiffInput = new DiffEditorInput(input.getName(), input.getDescription(), originalInput, modifiedInput, true);
|
||||
|
||||
// Forward binary flag to input if supported
|
||||
if (types.isFunction(((originalInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary)) {
|
||||
((originalInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary();
|
||||
}
|
||||
|
||||
if (types.isFunction(((modifiedInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary)) {
|
||||
((modifiedInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary();
|
||||
}
|
||||
|
||||
this.editorService.openEditor(binaryDiffInput, options, this.position).done(null, onUnexpectedError);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions {
|
||||
const editorConfiguration = super.computeConfiguration(configuration);
|
||||
|
||||
// Handle diff editor specially by merging in diffEditor configuration
|
||||
if (types.isObject(configuration.diffEditor)) {
|
||||
objects.mixin(editorConfiguration, configuration.diffEditor);
|
||||
}
|
||||
|
||||
return editorConfiguration;
|
||||
}
|
||||
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
const options: IDiffEditorOptions = super.getConfigurationOverrides();
|
||||
|
||||
options.readOnly = this.isReadOnly();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
protected getAriaLabel(): string {
|
||||
let ariaLabel: string;
|
||||
const inputName = this.input && this.input.getName();
|
||||
if (this.isReadOnly()) {
|
||||
ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text compare editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text compare editor.");
|
||||
} else {
|
||||
ariaLabel = inputName ? nls.localize('editableEditorWithInputAriaLabel', "{0}. Text file compare editor.", inputName) : nls.localize('editableEditorAriaLabel', "Text file compare editor.");
|
||||
}
|
||||
|
||||
return ariaLabel;
|
||||
}
|
||||
|
||||
private isReadOnly(): boolean {
|
||||
const input = this.input;
|
||||
if (input instanceof DiffEditorInput) {
|
||||
const modifiedInput = input.modifiedInput;
|
||||
|
||||
return modifiedInput instanceof ResourceEditorInput;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private isFileBinaryError(error: Error[]): boolean;
|
||||
private isFileBinaryError(error: Error): boolean;
|
||||
private isFileBinaryError(error: any): boolean {
|
||||
if (types.isArray(error)) {
|
||||
const errors = <Error[]>error;
|
||||
return errors.some(e => this.isFileBinaryError(e));
|
||||
}
|
||||
|
||||
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
|
||||
// Dispose previous diff navigator
|
||||
if (this.diffNavigator) {
|
||||
this.diffNavigator.dispose();
|
||||
}
|
||||
|
||||
// Clear Model
|
||||
this.getControl().setModel(null);
|
||||
|
||||
// Pass to super
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public getDiffNavigator(): DiffNavigator {
|
||||
return this.diffNavigator;
|
||||
}
|
||||
|
||||
public getActions(): IAction[] {
|
||||
return [
|
||||
this.previousDiffAction,
|
||||
this.nextDiffAction
|
||||
];
|
||||
}
|
||||
|
||||
public getSecondaryActions(): IAction[] {
|
||||
const actions = super.getSecondaryActions();
|
||||
|
||||
// Action to toggle editor mode from inline to side by side
|
||||
const toggleEditorModeAction = new ToggleEditorModeAction(this);
|
||||
toggleEditorModeAction.order = 50; // Closer to the end
|
||||
|
||||
actions.push(...[
|
||||
toggleEditorModeAction
|
||||
]);
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public getControl(): IDiffEditor {
|
||||
return super.getControl() as IDiffEditor;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
// Dispose previous diff navigator
|
||||
if (this.diffNavigator) {
|
||||
this.diffNavigator.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class NavigateAction extends Action {
|
||||
static ID_NEXT = 'workbench.action.compareEditor.nextChange';
|
||||
static ID_PREV = 'workbench.action.compareEditor.previousChange';
|
||||
|
||||
private editor: TextDiffEditor;
|
||||
private next: boolean;
|
||||
|
||||
constructor(editor: TextDiffEditor, next: boolean) {
|
||||
super(next ? NavigateAction.ID_NEXT : NavigateAction.ID_PREV);
|
||||
|
||||
this.editor = editor;
|
||||
this.next = next;
|
||||
|
||||
this.label = this.next ? nls.localize('navigate.next.label', "Next Change") : nls.localize('navigate.prev.label', "Previous Change");
|
||||
this.class = this.next ? 'textdiff-editor-action next' : 'textdiff-editor-action previous';
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
if (this.next) {
|
||||
this.editor.getDiffNavigator().next();
|
||||
} else {
|
||||
this.editor.getDiffNavigator().previous();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public updateEnablement(): void {
|
||||
this.enabled = this.editor.getDiffNavigator().canNavigate();
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleEditorModeAction extends Action {
|
||||
private static ID = 'toggle.diff.editorMode';
|
||||
private static INLINE_LABEL = nls.localize('inlineDiffLabel', "Switch to Inline View");
|
||||
private static SIDEBYSIDE_LABEL = nls.localize('sideBySideDiffLabel', "Switch to Side by Side View");
|
||||
|
||||
constructor(private editor: TextDiffEditor) {
|
||||
super(ToggleEditorModeAction.ID);
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return ToggleEditorModeAction.isInlineMode(this.editor) ? ToggleEditorModeAction.SIDEBYSIDE_LABEL : ToggleEditorModeAction.INLINE_LABEL;
|
||||
}
|
||||
|
||||
public run(): TPromise<boolean> {
|
||||
const inlineModeActive = ToggleEditorModeAction.isInlineMode(this.editor);
|
||||
|
||||
const control = this.editor.getControl();
|
||||
control.updateOptions(<IDiffEditorOptions>{
|
||||
renderSideBySide: inlineModeActive
|
||||
});
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
private static isInlineMode(editor: TextDiffEditor): boolean {
|
||||
const control = editor.getControl();
|
||||
|
||||
return control && !control.renderSideBySide;
|
||||
}
|
||||
}
|
||||
334
src/vs/workbench/browser/parts/editor/textEditor.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import objects = require('vs/base/common/objects');
|
||||
import types = require('vs/base/common/types');
|
||||
import errors = require('vs/base/common/errors');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { EditorInput, EditorOptions, toResource } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorViewState, IEditor, isCommonCodeEditor, isCommonDiffEditor } from 'vs/editor/common/editorCommon';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Scope } from 'vs/workbench/common/memento';
|
||||
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState';
|
||||
|
||||
interface ITextEditorViewState {
|
||||
0?: IEditorViewState;
|
||||
1?: IEditorViewState;
|
||||
2?: IEditorViewState;
|
||||
}
|
||||
|
||||
export interface IEditorConfiguration {
|
||||
editor: object;
|
||||
diffEditor: object;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class of editors that leverage the text editor for the editing experience. This class is only intended to
|
||||
* be subclassed and not instantiated.
|
||||
*/
|
||||
export abstract class BaseTextEditor extends BaseEditor {
|
||||
private editorControl: IEditor;
|
||||
private _editorContainer: Builder;
|
||||
private hasPendingConfigurationChange: boolean;
|
||||
private lastAppliedEditorOptions: IEditorOptions;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@ITextResourceConfigurationService private _configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@IModeService private modeService: IModeService,
|
||||
@ITextFileService private _textFileService: ITextFileService,
|
||||
@IEditorGroupService protected editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
|
||||
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getConfiguration<IEditorConfiguration>(this.getResource()))));
|
||||
}
|
||||
|
||||
protected get instantiationService(): IInstantiationService {
|
||||
return this._instantiationService;
|
||||
}
|
||||
|
||||
protected get configurationService(): ITextResourceConfigurationService {
|
||||
return this._configurationService;
|
||||
}
|
||||
|
||||
protected get textFileService(): ITextFileService {
|
||||
return this._textFileService;
|
||||
}
|
||||
|
||||
private handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void {
|
||||
if (this.isVisible()) {
|
||||
this.updateEditorConfiguration(configuration);
|
||||
} else {
|
||||
this.hasPendingConfigurationChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
private consumePendingConfigurationChangeEvent(): void {
|
||||
if (this.hasPendingConfigurationChange) {
|
||||
this.updateEditorConfiguration();
|
||||
this.hasPendingConfigurationChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions {
|
||||
|
||||
// Specific editor options always overwrite user configuration
|
||||
const editorConfiguration: IEditorOptions = types.isObject(configuration.editor) ? objects.clone(configuration.editor) : Object.create(null);
|
||||
objects.assign(editorConfiguration, this.getConfigurationOverrides());
|
||||
|
||||
// ARIA label
|
||||
editorConfiguration.ariaLabel = this.computeAriaLabel();
|
||||
|
||||
return editorConfiguration;
|
||||
}
|
||||
|
||||
private computeAriaLabel(): string {
|
||||
let ariaLabel = this.getAriaLabel();
|
||||
|
||||
// Apply group information to help identify in which group we are
|
||||
if (ariaLabel && typeof this.position === 'number') {
|
||||
ariaLabel = nls.localize('editorLabelWithGroup', "{0}, Group {1}.", ariaLabel, this.position + 1);
|
||||
}
|
||||
|
||||
return ariaLabel;
|
||||
}
|
||||
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
const overrides = {};
|
||||
objects.assign(overrides, {
|
||||
overviewRulerLanes: 3,
|
||||
lineNumbersMinChars: 3,
|
||||
fixedOverflowWidgets: true
|
||||
});
|
||||
|
||||
return overrides;
|
||||
}
|
||||
|
||||
protected createEditor(parent: Builder): void {
|
||||
|
||||
// Editor for Text
|
||||
this._editorContainer = parent;
|
||||
this.editorControl = this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getConfiguration<IEditorConfiguration>(this.getResource())));
|
||||
|
||||
// Model & Language changes
|
||||
const codeEditor = getCodeEditor(this);
|
||||
if (codeEditor) {
|
||||
this.toUnbind.push(codeEditor.onDidChangeModelLanguage(e => this.updateEditorConfiguration()));
|
||||
this.toUnbind.push(codeEditor.onDidChangeModel(e => this.updateEditorConfiguration()));
|
||||
}
|
||||
|
||||
// Application & Editor focus change to respect auto save settings
|
||||
if (isCommonCodeEditor(this.editorControl)) {
|
||||
this.toUnbind.push(this.editorControl.onDidBlurEditor(() => this.onEditorFocusLost()));
|
||||
} else if (isCommonDiffEditor(this.editorControl)) {
|
||||
this.toUnbind.push(this.editorControl.getOriginalEditor().onDidBlurEditor(() => this.onEditorFocusLost()));
|
||||
this.toUnbind.push(this.editorControl.getModifiedEditor().onDidBlurEditor(() => this.onEditorFocusLost()));
|
||||
}
|
||||
|
||||
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusLost()));
|
||||
this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost()));
|
||||
}
|
||||
|
||||
private onEditorFocusLost(): void {
|
||||
this.maybeTriggerSaveAll(SaveReason.FOCUS_CHANGE);
|
||||
}
|
||||
|
||||
private onWindowFocusLost(): void {
|
||||
this.maybeTriggerSaveAll(SaveReason.WINDOW_CHANGE);
|
||||
}
|
||||
|
||||
private maybeTriggerSaveAll(reason: SaveReason): void {
|
||||
const mode = this.textFileService.getAutoSaveMode();
|
||||
|
||||
// Determine if we need to save all. In case of a window focus change we also save if auto save mode
|
||||
// is configured to be ON_FOCUS_CHANGE (editor focus change)
|
||||
if (
|
||||
(reason === SaveReason.WINDOW_CHANGE && (mode === AutoSaveMode.ON_FOCUS_CHANGE || mode === AutoSaveMode.ON_WINDOW_CHANGE)) ||
|
||||
(reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE)
|
||||
) {
|
||||
if (this.textFileService.isDirty()) {
|
||||
this.textFileService.saveAll(void 0, { reason }).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates and returns the text editor control to be used. Subclasses can override to
|
||||
* provide their own editor control that should be used (e.g. a DiffEditor).
|
||||
*
|
||||
* The passed in configuration object should be passed to the editor control when creating it.
|
||||
*/
|
||||
protected createEditorControl(parent: Builder, configuration: IEditorOptions): IEditor {
|
||||
|
||||
// Use a getter for the instantiation service since some subclasses might use scoped instantiation services
|
||||
return this.instantiationService.createInstance(CodeEditor, parent.getHTMLElement(), configuration);
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
return super.setInput(input, options).then(() => {
|
||||
|
||||
// Update editor options after having set the input. We do this because there can be
|
||||
// editor input specific options (e.g. an ARIA label depending on the input showing)
|
||||
this.updateEditorConfiguration();
|
||||
});
|
||||
}
|
||||
|
||||
public changePosition(position: Position): void {
|
||||
super.changePosition(position);
|
||||
|
||||
// Make sure to update ARIA label if the position of this editor changed
|
||||
if (this.editorControl) {
|
||||
this.editorControl.updateOptions({ ariaLabel: this.computeAriaLabel() });
|
||||
}
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, position: Position = null): void {
|
||||
|
||||
// Pass on to Editor
|
||||
if (visible) {
|
||||
this.consumePendingConfigurationChangeEvent();
|
||||
this.editorControl.onVisible();
|
||||
} else {
|
||||
this.editorControl.onHide();
|
||||
}
|
||||
|
||||
super.setEditorVisible(visible, position);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.editorControl.focus();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
|
||||
// Pass on to Editor
|
||||
this.editorControl.layout(dimension);
|
||||
}
|
||||
|
||||
public getControl(): IEditor {
|
||||
return this.editorControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the text editor view state under the given key.
|
||||
*/
|
||||
protected saveTextEditorViewState(key: string): void {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
let textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (!textEditorViewStateMemento) {
|
||||
textEditorViewStateMemento = Object.create(null);
|
||||
memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY] = textEditorViewStateMemento;
|
||||
}
|
||||
|
||||
const editorViewState = this.getControl().saveViewState();
|
||||
|
||||
let lastKnownViewState: ITextEditorViewState = textEditorViewStateMemento[key];
|
||||
if (!lastKnownViewState) {
|
||||
lastKnownViewState = Object.create(null);
|
||||
textEditorViewStateMemento[key] = lastKnownViewState;
|
||||
}
|
||||
|
||||
if (typeof this.position === 'number') {
|
||||
lastKnownViewState[this.position] = editorViewState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the text editor view state under the given key.
|
||||
*/
|
||||
protected clearTextEditorViewState(keys: string[]): void {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
const textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (textEditorViewStateMemento) {
|
||||
keys.forEach(key => delete textEditorViewStateMemento[key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the text editor view state for the given key and returns it.
|
||||
*/
|
||||
protected loadTextEditorViewState(key: string): IEditorViewState {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
const textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (textEditorViewStateMemento) {
|
||||
const viewState: ITextEditorViewState = textEditorViewStateMemento[key];
|
||||
if (viewState) {
|
||||
return viewState[this.position];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private updateEditorConfiguration(configuration = this.configurationService.getConfiguration<IEditorConfiguration>(this.getResource())): void {
|
||||
if (!this.editorControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editorConfiguration = this.computeConfiguration(configuration);
|
||||
|
||||
// Try to figure out the actual editor options that changed from the last time we updated the editor.
|
||||
// We do this so that we are not overwriting some dynamic editor settings (e.g. word wrap) that might
|
||||
// have been applied to the editor directly.
|
||||
let editorSettingsToApply = editorConfiguration;
|
||||
if (this.lastAppliedEditorOptions) {
|
||||
editorSettingsToApply = objects.distinct(this.lastAppliedEditorOptions, editorSettingsToApply);
|
||||
}
|
||||
|
||||
if (Object.keys(editorSettingsToApply).length > 0) {
|
||||
this.lastAppliedEditorOptions = editorConfiguration;
|
||||
this.editorControl.updateOptions(editorSettingsToApply);
|
||||
}
|
||||
}
|
||||
|
||||
protected getResource(): URI {
|
||||
const codeEditor = getCodeEditor(this);
|
||||
if (codeEditor) {
|
||||
const model = codeEditor.getModel();
|
||||
if (model) {
|
||||
return model.uri;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.input) {
|
||||
return toResource(this.input);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract getAriaLabel(): string;
|
||||
|
||||
public dispose(): void {
|
||||
this.lastAppliedEditorOptions = void 0;
|
||||
this.editorControl.destroy();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
202
src/vs/workbench/browser/parts/editor/textResourceEditor.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import types = require('vs/base/common/types');
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { TextEditorOptions, EditorModel, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
|
||||
/**
|
||||
* An editor implementation that is capable of showing the contents of resource inputs. Uses
|
||||
* the TextEditor widget to show the contents.
|
||||
*/
|
||||
export class TextResourceEditor extends BaseTextEditor {
|
||||
|
||||
public static ID = 'workbench.editors.textResourceEditor';
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@IModeService modeService: IModeService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
) {
|
||||
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, modeService, textFileService, editorGroupService);
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
if (this.input) {
|
||||
return this.input.getName();
|
||||
}
|
||||
|
||||
return nls.localize('textEditor', "Text Editor");
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = this.input;
|
||||
super.setInput(input, options);
|
||||
|
||||
// Detect options
|
||||
const forceOpen = options && options.forceOpen;
|
||||
|
||||
// Same Input
|
||||
if (!forceOpen && input.matches(oldInput)) {
|
||||
|
||||
// TextOptions (avoiding instanceof here for a reason, do not change!)
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
textOptions.apply(this.getControl(), ScrollType.Smooth);
|
||||
}
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
}
|
||||
|
||||
// Remember view settings if input changes
|
||||
this.saveTextEditorViewStateForInput(oldInput);
|
||||
|
||||
// Different Input (Reload)
|
||||
return input.resolve(true).then((resolvedModel: EditorModel) => {
|
||||
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BaseTextEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as text'));
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set Editor Model
|
||||
const textEditor = this.getControl();
|
||||
const textEditorModel = resolvedModel.textEditorModel;
|
||||
textEditor.setModel(textEditorModel);
|
||||
|
||||
// Apply Options from TextOptions
|
||||
let optionsGotApplied = false;
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
optionsGotApplied = textOptions.apply(textEditor, ScrollType.Immediate);
|
||||
}
|
||||
|
||||
// Otherwise restore View State
|
||||
if (!optionsGotApplied) {
|
||||
this.restoreViewState(input);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
}
|
||||
|
||||
protected restoreViewState(input: EditorInput) {
|
||||
if (input instanceof UntitledEditorInput || input instanceof ResourceEditorInput) {
|
||||
const viewState = this.loadTextEditorViewState(input.getResource().toString());
|
||||
if (viewState) {
|
||||
this.getControl().restoreViewState(viewState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
const options = super.getConfigurationOverrides();
|
||||
|
||||
options.readOnly = !(this.input instanceof UntitledEditorInput); // all resource editors are readonly except for the untitled one;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
protected getAriaLabel(): string {
|
||||
const input = this.input;
|
||||
const isReadonly = !(this.input instanceof UntitledEditorInput);
|
||||
|
||||
let ariaLabel: string;
|
||||
const inputName = input && input.getName();
|
||||
if (isReadonly) {
|
||||
ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text editor.");
|
||||
} else {
|
||||
ariaLabel = inputName ? nls.localize('untitledFileEditorWithInputAriaLabel', "{0}. Untitled file text editor.", inputName) : nls.localize('untitledFileEditorAriaLabel', "Untitled file text editor.");
|
||||
}
|
||||
|
||||
return ariaLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reveals the last line of this editor if it has a model set.
|
||||
*/
|
||||
public revealLastLine(): void {
|
||||
const codeEditor = <ICodeEditor>this.getControl();
|
||||
const model = codeEditor.getModel();
|
||||
|
||||
if (model) {
|
||||
const lastLine = model.getLineCount();
|
||||
codeEditor.revealPosition({ lineNumber: lastLine, column: model.getLineMaxColumn(lastLine) }, ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
|
||||
// Keep editor view state in settings to restore when coming back
|
||||
this.saveTextEditorViewStateForInput(this.input);
|
||||
|
||||
// Clear Model
|
||||
this.getControl().setModel(null);
|
||||
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
|
||||
// Save View State (only for untitled)
|
||||
if (this.input instanceof UntitledEditorInput) {
|
||||
this.saveTextEditorViewStateForInput(this.input);
|
||||
}
|
||||
|
||||
// Call Super
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
protected saveTextEditorViewStateForInput(input: EditorInput): void {
|
||||
if (!(input instanceof UntitledEditorInput) && !(input instanceof ResourceEditorInput)) {
|
||||
return; // only enabled for untitled and resource inputs
|
||||
}
|
||||
|
||||
const key = input.getResource().toString();
|
||||
|
||||
// Clear view state if input is disposed
|
||||
if (input.isDisposed()) {
|
||||
super.clearTextEditorViewState([key]);
|
||||
}
|
||||
|
||||
// Otherwise save it
|
||||
else {
|
||||
super.saveTextEditorViewState(key);
|
||||
|
||||
// Make sure to clean up when the input gets disposed
|
||||
once(input.onDispose)(() => {
|
||||
super.clearTextEditorViewState([key]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
556
src/vs/workbench/browser/parts/editor/titleControl.ts
Normal file
@@ -0,0 +1,556 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/titlecontrol';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Scope, IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { isCommonCodeEditor, isCommonDiffEditor } from 'vs/editor/common/editorCommon';
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource } from 'vs/workbench/common/editor';
|
||||
import { EventType as BaseEventType } from 'vs/base/common/events';
|
||||
import { IActionItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { CloseEditorsInGroupAction, SplitEditorAction, CloseEditorAction, KeepEditorAction, CloseOtherEditorsInGroupAction, CloseRightEditorsInGroupAction, ShowEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IMenuService, MenuId, IMenu, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IDraggedResource } from 'vs/base/browser/dnd';
|
||||
import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { extname } from 'vs/base/common/paths';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export interface IToolbarActions {
|
||||
primary: IAction[];
|
||||
secondary: IAction[];
|
||||
}
|
||||
|
||||
export interface ITitleAreaControl {
|
||||
setContext(group: IEditorGroup): void;
|
||||
hasContext(): boolean;
|
||||
allowDragging(element: HTMLElement): boolean;
|
||||
setDragged(dragged: boolean): void;
|
||||
create(parent: HTMLElement): void;
|
||||
getContainer(): HTMLElement;
|
||||
refresh(instant?: boolean): void;
|
||||
update(instant?: boolean): void;
|
||||
layout(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export abstract class TitleControl extends Themable implements ITitleAreaControl {
|
||||
|
||||
private static draggedEditor: IEditorIdentifier;
|
||||
|
||||
protected stacks: IEditorStacksModel;
|
||||
protected context: IEditorGroup;
|
||||
|
||||
protected dragged: boolean;
|
||||
|
||||
protected closeEditorAction: CloseEditorAction;
|
||||
protected pinEditorAction: KeepEditorAction;
|
||||
protected closeOtherEditorsAction: CloseOtherEditorsInGroupAction;
|
||||
protected closeRightEditorsAction: CloseRightEditorsInGroupAction;
|
||||
protected closeUnmodifiedEditorsInGroupAction: CloseUnmodifiedEditorsInGroupAction;
|
||||
protected closeEditorsInGroupAction: CloseEditorsInGroupAction;
|
||||
protected splitEditorAction: SplitEditorAction;
|
||||
protected showEditorsInGroupAction: ShowEditorsInGroupAction;
|
||||
|
||||
private parent: HTMLElement;
|
||||
|
||||
private currentPrimaryEditorActionIds: string[] = [];
|
||||
private currentSecondaryEditorActionIds: string[] = [];
|
||||
protected editorActionsToolbar: ToolBar;
|
||||
|
||||
private mapActionsToEditors: { [editorId: string]: IToolbarActions; };
|
||||
private scheduler: RunOnceScheduler;
|
||||
private refreshScheduled: boolean;
|
||||
|
||||
private resourceContext: ResourceContextKey;
|
||||
private disposeOnEditorActions: IDisposable[] = [];
|
||||
|
||||
private contextMenu: IMenu;
|
||||
|
||||
constructor(
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService protected editorGroupService: IEditorGroupService,
|
||||
@IContextKeyService protected contextKeyService: IContextKeyService,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@ITelemetryService protected telemetryService: ITelemetryService,
|
||||
@IMessageService protected messageService: IMessageService,
|
||||
@IMenuService protected menuService: IMenuService,
|
||||
@IQuickOpenService protected quickOpenService: IQuickOpenService,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.stacks = editorGroupService.getStacksModel();
|
||||
this.mapActionsToEditors = Object.create(null);
|
||||
|
||||
this.scheduler = new RunOnceScheduler(() => this.onSchedule(), 0);
|
||||
this.toUnbind.push(this.scheduler);
|
||||
|
||||
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
|
||||
|
||||
this.contextMenu = this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService);
|
||||
this.toUnbind.push(this.contextMenu);
|
||||
|
||||
this.initActions(this.instantiationService);
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
public static getDraggedEditor(): IEditorIdentifier {
|
||||
return TitleControl.draggedEditor;
|
||||
}
|
||||
|
||||
public setDragged(dragged: boolean): void {
|
||||
this.dragged = dragged;
|
||||
}
|
||||
|
||||
protected onEditorDragStart(editor: IEditorIdentifier): void {
|
||||
TitleControl.draggedEditor = editor;
|
||||
}
|
||||
|
||||
protected onEditorDragEnd(): void {
|
||||
TitleControl.draggedEditor = void 0;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.stacks.onModelChanged(e => this.onStacksChanged(e)));
|
||||
}
|
||||
|
||||
private onStacksChanged(e: IStacksModelChangeEvent): void {
|
||||
if (e.structural) {
|
||||
this.updateSplitActionEnablement();
|
||||
}
|
||||
}
|
||||
|
||||
private updateSplitActionEnablement(): void {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupCount = this.stacks.groups.length;
|
||||
|
||||
// Split editor
|
||||
this.splitEditorAction.enabled = groupCount < 3;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
this.update(true); // run an update when the theme changes to new styles
|
||||
}
|
||||
|
||||
private onSchedule(): void {
|
||||
if (this.refreshScheduled) {
|
||||
this.doRefresh();
|
||||
} else {
|
||||
this.doUpdate();
|
||||
}
|
||||
|
||||
this.refreshScheduled = false;
|
||||
}
|
||||
|
||||
public setContext(group: IEditorGroup): void {
|
||||
this.context = group;
|
||||
}
|
||||
|
||||
public hasContext(): boolean {
|
||||
return !!this.context;
|
||||
}
|
||||
|
||||
public update(instant?: boolean): void {
|
||||
if (instant) {
|
||||
this.scheduler.cancel();
|
||||
this.onSchedule();
|
||||
} else {
|
||||
this.scheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
public refresh(instant?: boolean) {
|
||||
this.refreshScheduled = true;
|
||||
|
||||
if (instant) {
|
||||
this.scheduler.cancel();
|
||||
this.onSchedule();
|
||||
} else {
|
||||
this.scheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): void {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public getContainer(): HTMLElement {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
protected abstract doRefresh(): void;
|
||||
|
||||
protected doUpdate(): void {
|
||||
this.doRefresh();
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
// Subclasses can opt in to react on layout
|
||||
}
|
||||
|
||||
public allowDragging(element: HTMLElement): boolean {
|
||||
return !DOM.findParentWithClass(element, 'monaco-action-bar', 'one-editor-silo');
|
||||
}
|
||||
|
||||
protected initActions(services: IInstantiationService): void {
|
||||
this.closeEditorAction = services.createInstance(CloseEditorAction, CloseEditorAction.ID, nls.localize('close', "Close"));
|
||||
this.closeOtherEditorsAction = services.createInstance(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, nls.localize('closeOthers', "Close Others"));
|
||||
this.closeRightEditorsAction = services.createInstance(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, nls.localize('closeRight', "Close to the Right"));
|
||||
this.closeEditorsInGroupAction = services.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"));
|
||||
this.closeUnmodifiedEditorsInGroupAction = services.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"));
|
||||
this.pinEditorAction = services.createInstance(KeepEditorAction, KeepEditorAction.ID, nls.localize('keepOpen', "Keep Open"));
|
||||
this.showEditorsInGroupAction = services.createInstance(ShowEditorsInGroupAction, ShowEditorsInGroupAction.ID, nls.localize('showOpenedEditors', "Show Opened Editors"));
|
||||
this.splitEditorAction = services.createInstance(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL);
|
||||
}
|
||||
|
||||
protected createEditorActionsToolBar(container: HTMLElement): void {
|
||||
this.editorActionsToolbar = new ToolBar(container, this.contextMenuService, {
|
||||
actionItemProvider: (action: Action) => this.actionItemProvider(action),
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
ariaLabel: nls.localize('araLabelEditorActions', "Editor actions"),
|
||||
getKeyBinding: (action) => this.getKeybinding(action)
|
||||
});
|
||||
|
||||
// Action Run Handling
|
||||
this.toUnbind.push(this.editorActionsToolbar.actionRunner.addListener(BaseEventType.RUN, (e: any) => {
|
||||
|
||||
// Check for Error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.messageService.show(Severity.Error, e.error);
|
||||
}
|
||||
|
||||
// Log in telemetry
|
||||
if (this.telemetryService) {
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'editorPart' });
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected actionItemProvider(action: Action): IActionItem {
|
||||
if (!this.context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const group = this.context;
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
const editor = this.editorService.getVisibleEditors()[position];
|
||||
|
||||
let actionItem: IActionItem;
|
||||
|
||||
// Check Active Editor
|
||||
if (editor instanceof BaseEditor) {
|
||||
actionItem = editor.getActionItem(action);
|
||||
}
|
||||
|
||||
// Check Registry
|
||||
if (!actionItem) {
|
||||
const actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
actionItem = actionBarRegistry.getActionItemForContext(Scope.EDITOR, { input: editor && editor.input, editor, position }, action);
|
||||
}
|
||||
|
||||
// Check extensions
|
||||
if (!actionItem) {
|
||||
actionItem = createActionItem(action, this.keybindingService, this.messageService);
|
||||
}
|
||||
|
||||
return actionItem;
|
||||
}
|
||||
|
||||
protected getEditorActions(identifier: IEditorIdentifier): IToolbarActions {
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
|
||||
const { group } = identifier;
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
|
||||
// Update the resource context
|
||||
this.resourceContext.set(group && toResource(group.activeEditor, { supportSideBySide: true }));
|
||||
|
||||
// Editor actions require the editor control to be there, so we retrieve it via service
|
||||
const control = this.editorService.getVisibleEditors()[position];
|
||||
if (control instanceof BaseEditor && control.input && typeof control.position === 'number') {
|
||||
|
||||
// Editor Control Actions
|
||||
let editorActions = this.mapActionsToEditors[control.getId()];
|
||||
if (!editorActions) {
|
||||
editorActions = { primary: control.getActions(), secondary: control.getSecondaryActions() };
|
||||
this.mapActionsToEditors[control.getId()] = editorActions;
|
||||
}
|
||||
primary.push(...editorActions.primary);
|
||||
secondary.push(...editorActions.secondary);
|
||||
|
||||
// MenuItems
|
||||
// TODO This isn't very proper but needed as we have failed to
|
||||
// use the correct context key service per editor only once. Don't
|
||||
// take this code as sample of how to work with menus
|
||||
this.disposeOnEditorActions = dispose(this.disposeOnEditorActions);
|
||||
const widget = control.getControl();
|
||||
const codeEditor = isCommonCodeEditor(widget) && widget || isCommonDiffEditor(widget) && widget.getModifiedEditor();
|
||||
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
|
||||
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
|
||||
this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(_ => this.update()));
|
||||
|
||||
fillInActions(titleBarMenu, { arg: this.resourceContext.get() }, { primary, secondary });
|
||||
}
|
||||
|
||||
return { primary, secondary };
|
||||
}
|
||||
|
||||
protected updateEditorActionsToolbar(): void {
|
||||
const group = this.context;
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = group && group.activeEditor;
|
||||
const isActive = this.stacks.isActive(group);
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
let primaryEditorActions: IAction[] = [];
|
||||
let secondaryEditorActions: IAction[] = [];
|
||||
if (isActive) {
|
||||
const editorActions = this.getEditorActions({ group, editor });
|
||||
primaryEditorActions = prepareActions(editorActions.primary);
|
||||
if (isActive && editor instanceof EditorInput && editor.supportsSplitEditor()) {
|
||||
this.updateSplitActionEnablement();
|
||||
primaryEditorActions.push(this.splitEditorAction);
|
||||
}
|
||||
secondaryEditorActions = prepareActions(editorActions.secondary);
|
||||
}
|
||||
|
||||
const tabOptions = this.editorGroupService.getTabOptions();
|
||||
if (tabOptions.showTabs) {
|
||||
if (secondaryEditorActions.length > 0) {
|
||||
secondaryEditorActions.push(new Separator());
|
||||
}
|
||||
secondaryEditorActions.push(this.showEditorsInGroupAction);
|
||||
secondaryEditorActions.push(new Separator());
|
||||
secondaryEditorActions.push(this.closeUnmodifiedEditorsInGroupAction);
|
||||
secondaryEditorActions.push(this.closeEditorsInGroupAction);
|
||||
}
|
||||
|
||||
const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
|
||||
if (!tabOptions.showTabs) {
|
||||
primaryEditorActionIds.push(this.closeEditorAction.id); // always show "Close" when tabs are disabled
|
||||
}
|
||||
|
||||
const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id);
|
||||
|
||||
if (
|
||||
!arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) ||
|
||||
!arrays.equals(secondaryEditorActionIds, this.currentSecondaryEditorActionIds) ||
|
||||
primaryEditorActions.some(action => action instanceof ExecuteCommandAction) || // execute command actions can have the same ID but different arguments
|
||||
secondaryEditorActions.some(action => action instanceof ExecuteCommandAction) // see also https://github.com/Microsoft/vscode/issues/16298
|
||||
) {
|
||||
this.editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();
|
||||
|
||||
if (!tabOptions.showTabs) {
|
||||
this.editorActionsToolbar.addPrimaryAction(this.closeEditorAction)();
|
||||
}
|
||||
|
||||
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
|
||||
this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
|
||||
}
|
||||
}
|
||||
|
||||
protected clearEditorActionsToolbar(): void {
|
||||
this.editorActionsToolbar.setActions([], [])();
|
||||
|
||||
this.currentPrimaryEditorActionIds = [];
|
||||
this.currentSecondaryEditorActionIds = [];
|
||||
}
|
||||
|
||||
protected onContextMenu(identifier: IEditorIdentifier, e: Event, node: HTMLElement): void {
|
||||
|
||||
// Update the resource context
|
||||
const currentContext = this.resourceContext.get();
|
||||
this.resourceContext.set(toResource(identifier.editor, { supportSideBySide: true }));
|
||||
|
||||
// Find target anchor
|
||||
let anchor: HTMLElement | { x: number, y: number } = node;
|
||||
if (e instanceof MouseEvent) {
|
||||
const event = new StandardMouseEvent(e);
|
||||
anchor = { x: event.posx, y: event.posy };
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(this.getContextMenuActions(identifier)),
|
||||
getActionsContext: () => identifier,
|
||||
getKeyBinding: (action) => this.getKeybinding(action),
|
||||
onHide: (cancel) => this.resourceContext.set(currentContext) // restore previous context
|
||||
});
|
||||
}
|
||||
|
||||
protected getKeybinding(action: IAction): ResolvedKeybinding {
|
||||
return this.keybindingService.lookupKeybinding(action.id);
|
||||
}
|
||||
|
||||
protected getKeybindingLabel(action: IAction): string {
|
||||
const keybinding = this.getKeybinding(action);
|
||||
|
||||
return keybinding ? keybinding.getLabel() : void 0;
|
||||
}
|
||||
|
||||
protected getContextMenuActions(identifier: IEditorIdentifier): IAction[] {
|
||||
const { editor, group } = identifier;
|
||||
|
||||
// Enablement
|
||||
this.closeOtherEditorsAction.enabled = group.count > 1;
|
||||
this.pinEditorAction.enabled = !group.isPinned(editor);
|
||||
this.closeRightEditorsAction.enabled = group.indexOf(editor) !== group.count - 1;
|
||||
|
||||
// Actions: For all editors
|
||||
const actions: IAction[] = [
|
||||
this.closeEditorAction,
|
||||
this.closeOtherEditorsAction
|
||||
];
|
||||
const tabOptions = this.editorGroupService.getTabOptions();
|
||||
|
||||
if (tabOptions.showTabs) {
|
||||
actions.push(this.closeRightEditorsAction);
|
||||
}
|
||||
|
||||
actions.push(this.closeUnmodifiedEditorsInGroupAction);
|
||||
actions.push(this.closeEditorsInGroupAction);
|
||||
|
||||
if (tabOptions.previewEditors) {
|
||||
actions.push(new Separator(), this.pinEditorAction);
|
||||
}
|
||||
|
||||
// Fill in contributed actions
|
||||
fillInActions(this.contextMenu, { arg: this.resourceContext.get() }, actions);
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
// Actions
|
||||
[
|
||||
this.splitEditorAction,
|
||||
this.showEditorsInGroupAction,
|
||||
this.closeEditorAction,
|
||||
this.closeRightEditorsAction,
|
||||
this.closeUnmodifiedEditorsInGroupAction,
|
||||
this.closeOtherEditorsAction,
|
||||
this.closeEditorsInGroupAction,
|
||||
this.pinEditorAction
|
||||
].forEach((action) => {
|
||||
action.dispose();
|
||||
});
|
||||
|
||||
// Toolbar
|
||||
this.editorActionsToolbar.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared function across some editor components to handle drag & drop of folders and workspace files
|
||||
* to open them in the window instead of the editor.
|
||||
*/
|
||||
export function handleWorkspaceExternalDrop(
|
||||
resources: IDraggedResource[],
|
||||
fileService: IFileService,
|
||||
messageService: IMessageService,
|
||||
windowsService: IWindowsService,
|
||||
windowService: IWindowService,
|
||||
workspacesService: IWorkspacesService
|
||||
): TPromise<boolean /* handled */> {
|
||||
|
||||
// Return early if there are no external resources
|
||||
const externalResources = resources.filter(d => d.isExternal).map(d => d.resource);
|
||||
if (!externalResources.length) {
|
||||
return TPromise.as(false);
|
||||
}
|
||||
|
||||
const externalWorkspaceResources: { workspaces: URI[], folders: URI[] } = {
|
||||
workspaces: [],
|
||||
folders: []
|
||||
};
|
||||
|
||||
return TPromise.join(externalResources.map(resource => {
|
||||
|
||||
// Check for Workspace
|
||||
if (extname(resource.fsPath) === `.${WORKSPACE_EXTENSION}`) {
|
||||
externalWorkspaceResources.workspaces.push(resource);
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Check for Folder
|
||||
return fileService.resolveFile(resource).then(stat => {
|
||||
if (stat.isDirectory) {
|
||||
externalWorkspaceResources.folders.push(stat.resource);
|
||||
}
|
||||
}, error => void 0);
|
||||
})).then(_ => {
|
||||
const { workspaces, folders } = externalWorkspaceResources;
|
||||
|
||||
// Return early if no external resource is a folder or workspace
|
||||
if (workspaces.length === 0 && folders.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pass focus to window
|
||||
windowService.focusWindow();
|
||||
|
||||
let workspacesToOpen: TPromise<string[]>;
|
||||
|
||||
// Open in separate windows if we drop workspaces or just one folder
|
||||
if (workspaces.length > 0 || folders.length === 1) {
|
||||
workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources.fsPath));
|
||||
}
|
||||
|
||||
// Multiple folders: Create new workspace with folders and open
|
||||
else if (folders.length > 1) {
|
||||
workspacesToOpen = workspacesService.createWorkspace([...folders].map(folder => folder.fsPath)).then(workspace => [workspace.configPath]);
|
||||
}
|
||||
|
||||
// Open
|
||||
workspacesToOpen.then(workspaces => {
|
||||
windowsService.openWindow(workspaces, { forceReuseWindow: true });
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
71
src/vs/workbench/browser/parts/editor/webviewEditor.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Scope } from 'vs/workbench/common/memento';
|
||||
|
||||
export interface HtmlPreviewEditorViewState {
|
||||
scrollYPercentage: number;
|
||||
}
|
||||
|
||||
interface HtmlPreviewEditorViewStates {
|
||||
0?: HtmlPreviewEditorViewState;
|
||||
1?: HtmlPreviewEditorViewState;
|
||||
2?: HtmlPreviewEditorViewState;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is only intended to be subclassed and not instantiated.
|
||||
*/
|
||||
export abstract class BaseWebviewEditor extends BaseEditor {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService,
|
||||
private storageService: IStorageService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
}
|
||||
|
||||
private get viewStateStorageKey(): string {
|
||||
return this.getId() + '.editorViewState';
|
||||
}
|
||||
|
||||
protected saveViewState(resource: URI | string, editorViewState: HtmlPreviewEditorViewState): void {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
let editorViewStateMemento = memento[this.viewStateStorageKey];
|
||||
if (!editorViewStateMemento) {
|
||||
editorViewStateMemento = Object.create(null);
|
||||
memento[this.viewStateStorageKey] = editorViewStateMemento;
|
||||
}
|
||||
|
||||
let fileViewState: HtmlPreviewEditorViewStates = editorViewStateMemento[resource.toString()];
|
||||
if (!fileViewState) {
|
||||
fileViewState = Object.create(null);
|
||||
editorViewStateMemento[resource.toString()] = fileViewState;
|
||||
}
|
||||
|
||||
if (typeof this.position === 'number') {
|
||||
fileViewState[this.position] = editorViewState;
|
||||
}
|
||||
}
|
||||
|
||||
protected loadViewState(resource: URI | string): HtmlPreviewEditorViewState | null {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
const editorViewStateMemento = memento[this.viewStateStorageKey];
|
||||
if (editorViewStateMemento) {
|
||||
const fileViewState: HtmlPreviewEditorViewStates = editorViewStateMemento[resource.toString()];
|
||||
if (fileViewState) {
|
||||
return fileViewState[this.position];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
17
src/vs/workbench/browser/parts/media/compositepart.css
Normal file
@@ -0,0 +1,17 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .part > .content > .composite {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .composite.title {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .composite.title > .title-actions {
|
||||
flex: 1;
|
||||
padding-left: 5px;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
1
src/vs/workbench/browser/parts/panel/media/close.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M.379 5.652l2.828-2.828 4.76 4.761 4.826-4.826 2.828 2.828-7.654 7.654L.379 5.652z" id="outline" style="display: none;"/><g id="iconBg"><path class="icon-vs-bg" d="M7.967 11.827L1.793 5.652l1.414-1.414 4.76 4.761 4.826-4.826 1.414 1.414-6.24 6.24z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 536 B |
1
src/vs/workbench/browser/parts/panel/media/down.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M.379 5.652l2.828-2.828 4.76 4.761 4.826-4.826 2.828 2.828-7.654 7.654L.379 5.652z" id="outline" style="display: none;"/><g id="iconBg"><path class="icon-vs-bg" d="M7.967 11.827L1.793 5.652l1.414-1.414 4.76 4.761 4.826-4.826 1.414 1.414-6.24 6.24z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 536 B |
83
src/vs/workbench/browser/parts/panel/media/panelpart.css
Normal file
@@ -0,0 +1,83 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench.nopanel > .part.panel {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel {
|
||||
z-index: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel .title {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
padding-right: 0px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/** Panel Switcher */
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar {
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:first-child .action-label {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label {
|
||||
text-transform: uppercase;
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
font-size: 11px;
|
||||
padding-bottom: 4px; /* puts the bottom border down */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label.checked {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
/** Actions */
|
||||
|
||||
.monaco-workbench .panel .monaco-action-bar .action-item.select-container {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench .panel .monaco-action-bar .action-item .select-box {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-workbench .hide-panel-action {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .maximize-panel-action {
|
||||
background: url('up.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .maximize-panel-action,
|
||||
.hc-black .monaco-workbench .maximize-panel-action {
|
||||
background: url('up-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .minimize-panel-action {
|
||||
background: url('down.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .minimize-panel-action,
|
||||
.hc-black .monaco-workbench .minimize-panel-action {
|
||||
background: url('down-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .hide-panel-action,
|
||||
.hc-black .monaco-workbench .hide-panel-action {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M7.967 2.759l7.654 7.654-2.828 2.828-4.826-4.826-4.76 4.761-2.828-2.828 7.588-7.589z" id="outline" style="display: none;"/><g id="iconBg"><path class="icon-vs-bg" d="M14.207 10.413l-1.414 1.414-4.826-4.826-4.76 4.761-1.414-1.414 6.174-6.175 6.24 6.24z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 540 B |
1
src/vs/workbench/browser/parts/panel/media/up.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M7.967 2.759l7.654 7.654-2.828 2.828-4.826-4.826-4.76 4.761-2.828-2.828 7.588-7.589z" id="outline" style="display: none;"/><g id="iconBg"><path class="icon-vs-bg" d="M14.207 10.413l-1.414 1.414-4.826-4.826-4.76 4.761-1.414-1.414 6.174-6.175 6.24 6.24z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 540 B |
156
src/vs/workbench/browser/parts/panel/panelActions.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/panelpart';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
||||
export class PanelAction extends Action {
|
||||
|
||||
constructor(
|
||||
private panel: IPanelIdentifier,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IPanelService private panelService: IPanelService
|
||||
) {
|
||||
super(panel.id, panel.name);
|
||||
|
||||
this.tooltip = nls.localize('panelActionTooltip', "{0} ({1})", panel.name, this.getKeybindingLabel(panel.commandId));
|
||||
}
|
||||
|
||||
public run(event): TPromise<any> {
|
||||
return this.panelService.openPanel(this.panel.id, true).then(() => this.activate());
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
private getKeybindingLabel(id: string): string {
|
||||
const keys = this.keybindingService.lookupKeybinding(id);
|
||||
|
||||
return keys ? keys.getLabel() : '';
|
||||
}
|
||||
}
|
||||
|
||||
export class ClosePanelAction extends Action {
|
||||
static ID = 'workbench.action.closePanel';
|
||||
static LABEL = nls.localize('closePanel', "Close Panel");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, name, 'hide-panel-action');
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
return this.partService.setPanelHidden(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class TogglePanelAction extends Action {
|
||||
static ID = 'workbench.action.togglePanel';
|
||||
static LABEL = nls.localize('togglePanel', "Toggle Panel");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, name, partService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel');
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
return this.partService.setPanelHidden(this.partService.isVisible(Parts.PANEL_PART));
|
||||
}
|
||||
}
|
||||
|
||||
class FocusPanelAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.focusPanel';
|
||||
public static LABEL = nls.localize('focusPanel', "Focus into Panel");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPanelService private panelService: IPanelService,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
|
||||
// Show panel
|
||||
if (!this.partService.isVisible(Parts.PANEL_PART)) {
|
||||
return this.partService.setPanelHidden(false);
|
||||
}
|
||||
|
||||
// Focus into active panel
|
||||
let panel = this.panelService.getActivePanel();
|
||||
if (panel) {
|
||||
panel.focus();
|
||||
}
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleMaximizedPanelAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleMaximizedPanel';
|
||||
public static LABEL = nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel");
|
||||
private static MAXIMIZE_LABEL = nls.localize('maximizePanel', "Maximize Panel Size");
|
||||
private static RESTORE_LABEL = nls.localize('minimizePanel', "Restore Panel Size");
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, label, partService.isPanelMaximized() ? 'minimize-panel-action' : 'maximize-panel-action');
|
||||
this.toDispose = [];
|
||||
this.toDispose.push(partService.onEditorLayout(() => {
|
||||
const maximized = this.partService.isPanelMaximized();
|
||||
this.class = maximized ? 'minimize-panel-action' : 'maximize-panel-action';
|
||||
this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL;
|
||||
}));
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
// Show panel
|
||||
return this.partService.setPanelHidden(false)
|
||||
.then(() => this.partService.toggleMaximizedPanel());
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelAction, TogglePanelAction.ID, TogglePanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View"));
|
||||
269
src/vs/workbench/browser/parts/panel/panelPart.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/panelpart';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Scope } from 'vs/workbench/browser/actions';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart';
|
||||
import { Panel, PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
|
||||
import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ClosePanelAction, PanelAction, ToggleMaximizedPanelAction } from 'vs/workbench/browser/parts/panel/panelActions';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
public static activePanelSettingsKey = 'workbench.panelpart.activepanelid';
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private blockOpeningPanel: boolean;
|
||||
private panelSwitcherBar: ActionBar;
|
||||
|
||||
private panelIdToActions: { [panelId: string]: PanelAction; };
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IMessageService messageService: IMessageService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IPartService partService: IPartService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(
|
||||
messageService,
|
||||
storageService,
|
||||
telemetryService,
|
||||
contextMenuService,
|
||||
partService,
|
||||
keybindingService,
|
||||
instantiationService,
|
||||
themeService,
|
||||
Registry.as<PanelRegistry>(PanelExtensions.Panels),
|
||||
PanelPart.activePanelSettingsKey,
|
||||
Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
|
||||
'panel',
|
||||
'panel',
|
||||
Scope.PANEL,
|
||||
null,
|
||||
id,
|
||||
{ hasTitle: true }
|
||||
);
|
||||
|
||||
this.panelIdToActions = Object.create(null);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Activate panel action on opening of a panel
|
||||
this.toUnbind.push(this.onDidPanelOpen(panel => this.updatePanelActions(panel.getId(), true)));
|
||||
|
||||
// Deactivate panel action on close
|
||||
this.toUnbind.push(this.onDidPanelClose(panel => this.updatePanelActions(panel.getId(), false)));
|
||||
}
|
||||
|
||||
private updatePanelActions(id: string, didOpen: boolean): void {
|
||||
if (this.panelIdToActions[id]) {
|
||||
didOpen ? this.panelIdToActions[id].activate() : this.panelIdToActions[id].deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
public get onDidPanelOpen(): Event<IPanel> {
|
||||
return this._onDidCompositeOpen.event;
|
||||
}
|
||||
|
||||
public get onDidPanelClose(): Event<IPanel> {
|
||||
return this._onDidCompositeClose.event;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
const container = this.getContainer();
|
||||
container.style('background-color', this.getColor(PANEL_BACKGROUND));
|
||||
|
||||
const title = this.getTitleArea();
|
||||
title.style('border-top-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder));
|
||||
}
|
||||
|
||||
public openPanel(id: string, focus?: boolean): TPromise<Panel> {
|
||||
if (this.blockOpeningPanel) {
|
||||
return TPromise.as(null); // Workaround against a potential race condition
|
||||
}
|
||||
|
||||
// First check if panel is hidden and show if so
|
||||
let promise = TPromise.as<any>(null);
|
||||
if (!this.partService.isVisible(Parts.PANEL_PART)) {
|
||||
try {
|
||||
this.blockOpeningPanel = true;
|
||||
promise = this.partService.setPanelHidden(false);
|
||||
} finally {
|
||||
this.blockOpeningPanel = false;
|
||||
}
|
||||
}
|
||||
|
||||
return promise.then(() => this.openComposite(id, focus));
|
||||
}
|
||||
|
||||
public getPanels(): IPanelIdentifier[] {
|
||||
return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels()
|
||||
.sort((v1, v2) => v1.order - v2.order);
|
||||
}
|
||||
|
||||
protected getActions(): IAction[] {
|
||||
return [
|
||||
this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL),
|
||||
this.instantiationService.createInstance(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL)
|
||||
];
|
||||
}
|
||||
|
||||
public getActivePanel(): IPanel {
|
||||
return this.getActiveComposite();
|
||||
}
|
||||
|
||||
public getLastActivePanelId(): string {
|
||||
return this.getLastActiveCompositetId();
|
||||
}
|
||||
|
||||
public hideActivePanel(): TPromise<void> {
|
||||
return this.hideActiveComposite().then(composite => void 0);
|
||||
}
|
||||
|
||||
protected createTitleLabel(parent: Builder): ICompositeTitleLabel {
|
||||
let titleArea = $(parent).div({
|
||||
'class': ['panel-switcher-container']
|
||||
});
|
||||
|
||||
// Show a panel switcher
|
||||
this.panelSwitcherBar = new ActionBar(titleArea, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
ariaLabel: nls.localize('panelSwitcherBarAriaLabel', "Active Panel Switcher"),
|
||||
animated: false
|
||||
});
|
||||
this.toUnbind.push(this.panelSwitcherBar);
|
||||
|
||||
this.fillPanelSwitcher();
|
||||
|
||||
return {
|
||||
updateTitle: (id, title, keybinding) => {
|
||||
const action = this.panelIdToActions[id];
|
||||
if (action) {
|
||||
action.label = title;
|
||||
}
|
||||
},
|
||||
updateStyles: () => {
|
||||
// Handled via theming participant
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private fillPanelSwitcher(): void {
|
||||
const panels = this.getPanels();
|
||||
|
||||
this.panelSwitcherBar.push(panels.map(panel => {
|
||||
const action = this.instantiationService.createInstance(PanelAction, panel);
|
||||
|
||||
this.panelIdToActions[panel.id] = action;
|
||||
this.toUnbind.push(action);
|
||||
|
||||
return action;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Panel Background: since panels can host editors, we apply a background rule if the panel background
|
||||
// color is different from the editor background color. This is a bit of a hack though. The better way
|
||||
// would be to have a way to push the background color onto each editor widget itself somehow.
|
||||
const panelBackground = theme.getColor(PANEL_BACKGROUND);
|
||||
if (panelBackground && panelBackground !== theme.getColor(editorBackground)) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .content .monaco-editor,
|
||||
.monaco-workbench > .part.panel > .content .monaco-editor .margin,
|
||||
.monaco-workbench > .part.panel > .content .monaco-editor .monaco-editor-background {
|
||||
background-color: ${panelBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Title Active
|
||||
const titleActive = theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND);
|
||||
const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER);
|
||||
if (titleActive || titleActiveBorder) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:hover .action-label,
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label.checked {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${titleActiveBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Title Inactive
|
||||
const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND);
|
||||
if (titleInactive) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label {
|
||||
color: ${titleInactive};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Title focus
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:focus {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${focusBorderColor} !important;
|
||||
border-bottom: 1px solid;
|
||||
outline: none;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
if (outline) {
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label.checked,
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:hover {
|
||||
outline-color: ${outline};
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:hover:not(.checked) {
|
||||
outline-style: dashed;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#C5C5C5" cx="8" cy="8" r="4"/></svg>
|
||||
|
After Width: | Height: | Size: 167 B |
1
src/vs/workbench/browser/parts/quickopen/media/dirty.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#424242" cx="8" cy="8" r="4"/></svg>
|
||||
|
After Width: | Height: | Size: 167 B |
24
src/vs/workbench/browser/parts/quickopen/media/quickopen.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.none,
|
||||
.vs-dark .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.none {
|
||||
width: 16px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.dirty {
|
||||
width: 14px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.dirty {
|
||||
background-image: url('dirty.svg');
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.dirty,
|
||||
.vs-dark .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.dirty {
|
||||
background-image: url('dirty-inverse.svg');
|
||||
}
|
||||
1323
src/vs/workbench/browser/parts/quickopen/quickOpenController.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { RemoveFromEditorHistoryAction } from 'vs/workbench/browser/parts/quickopen/quickOpenController';
|
||||
import { QuickOpenSelectNextAction, QuickOpenSelectPreviousAction, inQuickOpenContext, getQuickNavigateHandler, QuickOpenNavigateNextAction, QuickOpenNavigatePreviousAction, defaultQuickOpenContext, QUICKOPEN_ACTION_ID, QUICKOPEN_ACION_LABEL } from 'vs/workbench/browser/parts/quickopen/quickopen';
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.closeQuickOpen',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: inQuickOpenContext,
|
||||
primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape],
|
||||
handler: accessor => {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
quickOpenService.close();
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.acceptSelectedQuickOpenItem',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: inQuickOpenContext,
|
||||
primary: null,
|
||||
handler: accessor => {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
quickOpenService.accept();
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.focusQuickOpen',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: inQuickOpenContext,
|
||||
primary: null,
|
||||
handler: accessor => {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
quickOpenService.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const registry = <IWorkbenchActionRegistry>Registry.as(ActionExtensions.WorkbenchActions);
|
||||
|
||||
const globalQuickOpenKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_E], mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: null } };
|
||||
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: QUICKOPEN_ACTION_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: undefined,
|
||||
primary: globalQuickOpenKeybinding.primary,
|
||||
secondary: globalQuickOpenKeybinding.secondary,
|
||||
mac: globalQuickOpenKeybinding.mac
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: { id: QUICKOPEN_ACTION_ID, title: QUICKOPEN_ACION_LABEL }
|
||||
});
|
||||
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: null, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingsRegistry.WEIGHT.workbenchContrib(50)), 'Select Next in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: null, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingsRegistry.WEIGHT.workbenchContrib(50)), 'Select Previous in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenNavigateNextAction, QuickOpenNavigateNextAction.ID, QuickOpenNavigateNextAction.LABEL), 'Navigate Next in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenNavigatePreviousAction, QuickOpenNavigatePreviousAction.ID, QuickOpenNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveFromEditorHistoryAction, RemoveFromEditorHistoryAction.ID, RemoveFromEditorHistoryAction.LABEL), 'Remove From History');
|
||||
|
||||
const quickOpenNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigateNextInFilePickerId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
handler: getQuickNavigateHandler(quickOpenNavigateNextInFilePickerId, true),
|
||||
when: defaultQuickOpenContext,
|
||||
primary: globalQuickOpenKeybinding.primary,
|
||||
secondary: globalQuickOpenKeybinding.secondary,
|
||||
mac: globalQuickOpenKeybinding.mac
|
||||
});
|
||||
|
||||
const quickOpenNavigatePreviousInFilePickerId = 'workbench.action.quickOpenNavigatePreviousInFilePicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigatePreviousInFilePickerId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInFilePickerId, false),
|
||||
when: defaultQuickOpenContext,
|
||||
primary: globalQuickOpenKeybinding.primary | KeyMod.Shift,
|
||||
secondary: [globalQuickOpenKeybinding.secondary[0] | KeyMod.Shift],
|
||||
mac: {
|
||||
primary: globalQuickOpenKeybinding.mac.primary | KeyMod.Shift,
|
||||
secondary: null
|
||||
}
|
||||
});
|
||||
125
src/vs/workbench/browser/parts/quickopen/quickopen.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const inQuickOpenContext = ContextKeyExpr.has('inQuickOpen');
|
||||
export const defaultQuickOpenContextKey = 'inFilesPicker';
|
||||
export const defaultQuickOpenContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(defaultQuickOpenContextKey));
|
||||
|
||||
export const QUICKOPEN_ACTION_ID = 'workbench.action.quickOpen';
|
||||
export const QUICKOPEN_ACION_LABEL = nls.localize('quickOpen', "Go to File...");
|
||||
|
||||
CommandsRegistry.registerCommand(QUICKOPEN_ACTION_ID, function (accessor: ServicesAccessor, prefix: string = null) {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
|
||||
return quickOpenService.show(typeof prefix === 'string' ? prefix : null).then(() => {
|
||||
return void 0;
|
||||
});
|
||||
});
|
||||
|
||||
export class BaseQuickOpenNavigateAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private next: boolean,
|
||||
private quickNavigate: boolean,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(event?: any): TPromise<any> {
|
||||
const keys = this.keybindingService.lookupKeybindings(this.id);
|
||||
const quickNavigate = this.quickNavigate ? { keybindings: keys } : void 0;
|
||||
|
||||
this.quickOpenService.navigate(this.next, quickNavigate);
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHandler {
|
||||
return accessor => {
|
||||
const keybindingService = accessor.get(IKeybindingService);
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
|
||||
const keys = keybindingService.lookupKeybindings(id);
|
||||
const quickNavigate = { keybindings: keys };
|
||||
|
||||
quickOpenService.navigate(next, quickNavigate);
|
||||
};
|
||||
}
|
||||
|
||||
export class QuickOpenNavigateNextAction extends BaseQuickOpenNavigateAction {
|
||||
|
||||
public static ID = 'workbench.action.quickOpenNavigateNext';
|
||||
public static LABEL = nls.localize('quickNavigateNext', "Navigate Next in Quick Open");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label, true, true, quickOpenService, keybindingService);
|
||||
}
|
||||
}
|
||||
|
||||
export class QuickOpenNavigatePreviousAction extends BaseQuickOpenNavigateAction {
|
||||
|
||||
public static ID = 'workbench.action.quickOpenNavigatePrevious';
|
||||
public static LABEL = nls.localize('quickNavigatePrevious', "Navigate Previous in Quick Open");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label, false, true, quickOpenService, keybindingService);
|
||||
}
|
||||
}
|
||||
|
||||
export class QuickOpenSelectNextAction extends BaseQuickOpenNavigateAction {
|
||||
|
||||
public static ID = 'workbench.action.quickOpenSelectNext';
|
||||
public static LABEL = nls.localize('quickSelectNext', "Select Next in Quick Open");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label, true, false, quickOpenService, keybindingService);
|
||||
}
|
||||
}
|
||||
|
||||
export class QuickOpenSelectPreviousAction extends BaseQuickOpenNavigateAction {
|
||||
|
||||
public static ID = 'workbench.action.quickOpenSelectPrevious';
|
||||
public static LABEL = nls.localize('quickSelectPrevious', "Select Previous in Quick Open");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label, false, false, quickOpenService, keybindingService);
|
||||
}
|
||||
}
|
||||
57
src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css
Normal file
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .sidebar > .content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench.nosidebar > .sidebar {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.monaco-workbench > .sidebar > .title > .title-label span {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.monaco-workbench .viewlet .collapsible.header .title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-workbench .viewlet .collapsible.header .actions {
|
||||
width: 0; /* not using display: none for keyboard a11y reasons */
|
||||
}
|
||||
|
||||
.monaco-workbench .viewlet .split-view-view:hover > .header .actions,
|
||||
.monaco-workbench .viewlet .collapsible.header.focused .actions {
|
||||
width: initial;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench .viewlet .collapsible.header .actions .action-label {
|
||||
width: 28px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 0;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.monaco-workbench .viewlet .collapsible.header .actions .action-label .label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .viewlet .collapsible.header.collapsed .actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .viewlet .collapsible.header .action-label {
|
||||
margin-right: 0.2em;
|
||||
background-repeat: no-repeat;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
184
src/vs/workbench/browser/parts/sidebar/sidebarPart.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/sidebarpart';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { CompositePart } from 'vs/workbench/browser/parts/compositePart';
|
||||
import { Viewlet, ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { Scope } from 'vs/workbench/browser/actions';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import { ToggleSidebarVisibilityAction } from 'vs/workbench/browser/actions/toggleSidebarVisibility';
|
||||
|
||||
export class SidebarPart extends CompositePart<Viewlet> {
|
||||
|
||||
public static activeViewletSettingsKey = 'workbench.sidebar.activeviewletid';
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private blockOpeningViewlet: boolean;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IMessageService messageService: IMessageService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IPartService partService: IPartService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(
|
||||
messageService,
|
||||
storageService,
|
||||
telemetryService,
|
||||
contextMenuService,
|
||||
partService,
|
||||
keybindingService,
|
||||
instantiationService,
|
||||
themeService,
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets),
|
||||
SidebarPart.activeViewletSettingsKey,
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).getDefaultViewletId(),
|
||||
'sideBar',
|
||||
'viewlet',
|
||||
Scope.VIEWLET,
|
||||
SIDE_BAR_TITLE_FOREGROUND,
|
||||
id,
|
||||
{ hasTitle: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }
|
||||
);
|
||||
}
|
||||
|
||||
public get onDidViewletOpen(): Event<IViewlet> {
|
||||
return this._onDidCompositeOpen.event as Event<IViewlet>;
|
||||
}
|
||||
|
||||
public get onDidViewletClose(): Event<IViewlet> {
|
||||
return this._onDidCompositeClose.event as Event<IViewlet>;
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
// Part container
|
||||
const container = this.getContainer();
|
||||
|
||||
container.style('background-color', this.getColor(SIDE_BAR_BACKGROUND));
|
||||
container.style('color', this.getColor(SIDE_BAR_FOREGROUND));
|
||||
|
||||
const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder);
|
||||
const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT;
|
||||
container.style('border-right-width', borderColor && isPositionLeft ? '1px' : null);
|
||||
container.style('border-right-style', borderColor && isPositionLeft ? 'solid' : null);
|
||||
container.style('border-right-color', isPositionLeft ? borderColor : null);
|
||||
container.style('border-left-width', borderColor && !isPositionLeft ? '1px' : null);
|
||||
container.style('border-left-style', borderColor && !isPositionLeft ? 'solid' : null);
|
||||
container.style('border-left-color', !isPositionLeft ? borderColor : null);
|
||||
}
|
||||
|
||||
public openViewlet(id: string, focus?: boolean): TPromise<Viewlet> {
|
||||
if (this.blockOpeningViewlet) {
|
||||
return TPromise.as(null); // Workaround against a potential race condition
|
||||
}
|
||||
|
||||
// First check if sidebar is hidden and show if so
|
||||
let promise = TPromise.as<void>(null);
|
||||
if (!this.partService.isVisible(Parts.SIDEBAR_PART)) {
|
||||
try {
|
||||
this.blockOpeningViewlet = true;
|
||||
promise = this.partService.setSideBarHidden(false);
|
||||
} finally {
|
||||
this.blockOpeningViewlet = false;
|
||||
}
|
||||
}
|
||||
|
||||
return promise.then(() => this.openComposite(id, focus)) as TPromise<Viewlet>;
|
||||
}
|
||||
|
||||
public getActiveViewlet(): IViewlet {
|
||||
return <IViewlet>this.getActiveComposite();
|
||||
}
|
||||
|
||||
public getLastActiveViewletId(): string {
|
||||
return this.getLastActiveCompositetId();
|
||||
}
|
||||
|
||||
public hideActiveViewlet(): TPromise<void> {
|
||||
return this.hideActiveComposite().then(composite => void 0);
|
||||
}
|
||||
|
||||
protected getTitleAreaContextMenuActions(): IAction[] {
|
||||
const contextMenuActions = super.getTitleAreaContextMenuActions();
|
||||
if (contextMenuActions.length) {
|
||||
contextMenuActions.push(new Separator());
|
||||
}
|
||||
contextMenuActions.push(this.createHideSideBarAction());
|
||||
return contextMenuActions;
|
||||
}
|
||||
|
||||
private createHideSideBarAction(): IAction {
|
||||
return <IAction>{
|
||||
id: ToggleSidebarVisibilityAction.ID,
|
||||
label: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
|
||||
enabled: true,
|
||||
run: () => this.partService.setSideBarHidden(true)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FocusSideBarAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.focusSideBar';
|
||||
public static LABEL = nls.localize('focusSideBar', "Focus into Side Bar");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
|
||||
// Show side bar
|
||||
if (!this.partService.isVisible(Parts.SIDEBAR_PART)) {
|
||||
return this.partService.setSideBarHidden(false);
|
||||
}
|
||||
|
||||
// Focus into active viewlet
|
||||
let viewlet = this.viewletService.getActiveViewlet();
|
||||
if (viewlet) {
|
||||
viewlet.focus();
|
||||
}
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSideBarAction, FocusSideBarAction.ID, FocusSideBarAction.LABEL, {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_0
|
||||
}), 'View: Focus into Side Bar', nls.localize('viewCategory', "View"));
|
||||
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .part.statusbar {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
font-size: 12px;
|
||||
padding: 0 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item {
|
||||
display: inline-block;
|
||||
line-height: 22px;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item.left > :first-child {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item.right > :first-child {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item a:not([disabled]):not(.disabled) {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-entry > span {
|
||||
cursor: default;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-entry > span,
|
||||
.monaco-workbench > .part.statusbar > .statusbar-entry > a:not([disabled]) {
|
||||
padding: 0 5px 0 5px;
|
||||
white-space: pre; /* gives some degree of styling */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-entry span.octicon {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item a:hover:not([disabled]):not(.disabled) {
|
||||
text-decoration: none;
|
||||
}
|
||||
58
src/vs/workbench/browser/parts/statusbar/statusbar.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as statusbarService from 'vs/platform/statusbar/common/statusbar';
|
||||
import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IStatusbarItem {
|
||||
render(element: HTMLElement): IDisposable;
|
||||
}
|
||||
|
||||
export import StatusbarAlignment = statusbarService.StatusbarAlignment;
|
||||
|
||||
export class StatusbarItemDescriptor {
|
||||
|
||||
public syncDescriptor: SyncDescriptor0<IStatusbarItem>;
|
||||
public alignment: StatusbarAlignment;
|
||||
public priority: number;
|
||||
|
||||
constructor(ctor: IConstructorSignature0<IStatusbarItem>, alignment?: StatusbarAlignment, priority?: number) {
|
||||
this.syncDescriptor = createSyncDescriptor(ctor);
|
||||
this.alignment = alignment || StatusbarAlignment.LEFT;
|
||||
this.priority = priority || 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IStatusbarRegistry {
|
||||
registerStatusbarItem(descriptor: StatusbarItemDescriptor): void;
|
||||
items: StatusbarItemDescriptor[];
|
||||
}
|
||||
|
||||
class StatusbarRegistry implements IStatusbarRegistry {
|
||||
|
||||
private _items: StatusbarItemDescriptor[];
|
||||
|
||||
constructor() {
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
public get items(): StatusbarItemDescriptor[] {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
public registerStatusbarItem(descriptor: StatusbarItemDescriptor): void {
|
||||
this._items.push(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
export const Extensions = {
|
||||
Statusbar: 'workbench.contributions.statusbar'
|
||||
};
|
||||
|
||||
Registry.add(Extensions.Statusbar, new StatusbarRegistry());
|
||||
352
src/vs/workbench/browser/parts/statusbar/statusbarPart.ts
Normal file
@@ -0,0 +1,352 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/statusbarpart';
|
||||
import dom = require('vs/base/browser/dom');
|
||||
import nls = require('vs/nls');
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { StatusbarAlignment, IStatusbarRegistry, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IStatusbarService, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isThemeColor } from 'vs/editor/common/editorCommon';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
export class StatusbarPart extends Part implements IStatusbarService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private static PRIORITY_PROP = 'priority';
|
||||
private static ALIGNMENT_PROP = 'alignment';
|
||||
|
||||
private statusItemsContainer: Builder;
|
||||
private statusMsgDispose: IDisposable;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkspaceRoots(() => this.updateStyles()));
|
||||
}
|
||||
|
||||
public addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable {
|
||||
|
||||
// Render entry in status bar
|
||||
const el = this.doCreateStatusItem(alignment, priority);
|
||||
const item = this.instantiationService.createInstance(StatusBarEntryItem, entry);
|
||||
const toDispose = item.render(el);
|
||||
|
||||
// Insert according to priority
|
||||
const container = this.statusItemsContainer.getHTMLElement();
|
||||
const neighbours = this.getEntries(alignment);
|
||||
let inserted = false;
|
||||
for (let i = 0; i < neighbours.length; i++) {
|
||||
const neighbour = neighbours[i];
|
||||
const nPriority = $(neighbour).getProperty(StatusbarPart.PRIORITY_PROP);
|
||||
if (
|
||||
alignment === StatusbarAlignment.LEFT && nPriority < priority ||
|
||||
alignment === StatusbarAlignment.RIGHT && nPriority > priority
|
||||
) {
|
||||
container.insertBefore(el, neighbour);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
container.appendChild(el);
|
||||
}
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
$(el).destroy();
|
||||
|
||||
if (toDispose) {
|
||||
toDispose.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private getEntries(alignment: StatusbarAlignment): HTMLElement[] {
|
||||
const entries: HTMLElement[] = [];
|
||||
|
||||
const container = this.statusItemsContainer.getHTMLElement();
|
||||
const children = container.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const childElement = <HTMLElement>children.item(i);
|
||||
if ($(childElement).getProperty(StatusbarPart.ALIGNMENT_PROP) === alignment) {
|
||||
entries.push(childElement);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
this.statusItemsContainer = $(parent);
|
||||
|
||||
// Fill in initial items that were contributed from the registry
|
||||
const registry = Registry.as<IStatusbarRegistry>(Extensions.Statusbar);
|
||||
|
||||
const leftDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.LEFT).sort((a, b) => b.priority - a.priority);
|
||||
const rightDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.RIGHT).sort((a, b) => a.priority - b.priority);
|
||||
|
||||
const descriptors = rightDescriptors.concat(leftDescriptors); // right first because they float
|
||||
|
||||
this.toUnbind.push(...descriptors.map(descriptor => {
|
||||
const item = this.instantiationService.createInstance(descriptor.syncDescriptor);
|
||||
const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority);
|
||||
|
||||
const dispose = item.render(el);
|
||||
this.statusItemsContainer.append(el);
|
||||
|
||||
return dispose;
|
||||
}));
|
||||
|
||||
return this.statusItemsContainer;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
const container = this.getContainer();
|
||||
|
||||
container.style('color', this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND));
|
||||
container.style('background-color', this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND));
|
||||
|
||||
const borderColor = this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_BORDER : STATUS_BAR_NO_FOLDER_BORDER) || this.getColor(contrastBorder);
|
||||
container.style('border-top-width', borderColor ? '1px' : null);
|
||||
container.style('border-top-style', borderColor ? 'solid' : null);
|
||||
container.style('border-top-color', borderColor);
|
||||
}
|
||||
|
||||
private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
dom.addClass(el, 'statusbar-item');
|
||||
|
||||
if (alignment === StatusbarAlignment.RIGHT) {
|
||||
dom.addClass(el, 'right');
|
||||
} else {
|
||||
dom.addClass(el, 'left');
|
||||
}
|
||||
|
||||
$(el).setProperty(StatusbarPart.PRIORITY_PROP, priority);
|
||||
$(el).setProperty(StatusbarPart.ALIGNMENT_PROP, alignment);
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
public setStatusMessage(message: string, autoDisposeAfter: number = -1, delayBy: number = 0): IDisposable {
|
||||
if (this.statusMsgDispose) {
|
||||
this.statusMsgDispose.dispose(); // dismiss any previous
|
||||
}
|
||||
|
||||
// Create new
|
||||
let statusDispose: IDisposable;
|
||||
let showHandle = setTimeout(() => {
|
||||
statusDispose = this.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
|
||||
showHandle = null;
|
||||
}, delayBy);
|
||||
let hideHandle: number;
|
||||
|
||||
// Dispose function takes care of timeouts and actual entry
|
||||
const dispose = {
|
||||
dispose: () => {
|
||||
if (showHandle) {
|
||||
clearTimeout(showHandle);
|
||||
}
|
||||
|
||||
if (hideHandle) {
|
||||
clearTimeout(hideHandle);
|
||||
}
|
||||
|
||||
if (statusDispose) {
|
||||
statusDispose.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
this.statusMsgDispose = dispose;
|
||||
|
||||
if (typeof autoDisposeAfter === 'number' && autoDisposeAfter > 0) {
|
||||
hideHandle = setTimeout(() => dispose.dispose(), autoDisposeAfter);
|
||||
}
|
||||
|
||||
return dispose;
|
||||
}
|
||||
}
|
||||
|
||||
let manageExtensionAction: ManageExtensionAction;
|
||||
class StatusBarEntryItem implements IStatusbarItem {
|
||||
private entry: IStatusbarEntry;
|
||||
|
||||
constructor(
|
||||
entry: IStatusbarEntry,
|
||||
@ICommandService private commandService: ICommandService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
this.entry = entry;
|
||||
|
||||
if (!manageExtensionAction) {
|
||||
manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
}
|
||||
|
||||
public render(el: HTMLElement): IDisposable {
|
||||
let toDispose: IDisposable[] = [];
|
||||
dom.addClass(el, 'statusbar-entry');
|
||||
|
||||
// Text Container
|
||||
let textContainer: HTMLElement;
|
||||
if (this.entry.command) {
|
||||
textContainer = document.createElement('a');
|
||||
|
||||
$(textContainer).on('click', () => this.executeCommand(this.entry.command, this.entry.arguments), toDispose);
|
||||
} else {
|
||||
textContainer = document.createElement('span');
|
||||
}
|
||||
|
||||
// Label
|
||||
new OcticonLabel(textContainer).text = this.entry.text;
|
||||
|
||||
// Tooltip
|
||||
if (this.entry.tooltip) {
|
||||
$(textContainer).title(this.entry.tooltip);
|
||||
}
|
||||
|
||||
// Color
|
||||
let color = this.entry.color;
|
||||
if (color) {
|
||||
if (isThemeColor(color)) {
|
||||
let colorId = color.id;
|
||||
color = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString();
|
||||
toDispose.push(this.themeService.onThemeChange(theme => {
|
||||
let colorValue = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString();
|
||||
$(textContainer).color(colorValue);
|
||||
}));
|
||||
}
|
||||
$(textContainer).color(color);
|
||||
}
|
||||
|
||||
// Context Menu
|
||||
if (this.entry.extensionId) {
|
||||
$(textContainer).on('contextmenu', e => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => el,
|
||||
getActionsContext: () => this.entry.extensionId,
|
||||
getActions: () => TPromise.as([manageExtensionAction])
|
||||
});
|
||||
}, toDispose);
|
||||
}
|
||||
|
||||
el.appendChild(textContainer);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
toDispose = dispose(toDispose);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private executeCommand(id: string, args?: any[]) {
|
||||
args = args || [];
|
||||
|
||||
// Lookup built in commands
|
||||
const builtInActionDescriptor = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).getWorkbenchAction(id);
|
||||
if (builtInActionDescriptor) {
|
||||
const action = this.instantiationService.createInstance(builtInActionDescriptor.syncDescriptor);
|
||||
|
||||
if (action.enabled) {
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'status bar' });
|
||||
(action.run() || TPromise.as(null)).done(() => {
|
||||
action.dispose();
|
||||
}, (err) => this.messageService.show(Severity.Error, toErrorMessage(err)));
|
||||
} else {
|
||||
this.messageService.show(Severity.Warning, nls.localize('canNotRun', "Command '{0}' is currently not enabled and can not be run.", action.label || id));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Maintain old behaviour of always focusing the editor here
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
const codeEditor = getCodeEditor(activeEditor);
|
||||
if (codeEditor) {
|
||||
codeEditor.focus();
|
||||
}
|
||||
|
||||
// Fallback to the command service for any other case
|
||||
this.commandService.executeCommand(id, ...args).done(undefined, err => this.messageService.show(Severity.Error, toErrorMessage(err)));
|
||||
}
|
||||
}
|
||||
|
||||
class ManageExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('statusbar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
public run(extensionId: string): TPromise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', extensionId);
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
|
||||
if (statusBarItemHoverBackground) {
|
||||
collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item a:hover:not([disabled]):not(.disabled) { background-color: ${statusBarItemHoverBackground}; }`);
|
||||
}
|
||||
|
||||
const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
|
||||
if (statusBarItemActiveBackground) {
|
||||
collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item a:active:not([disabled]):not(.disabled) { background-color: ${statusBarItemActiveBackground}; }`);
|
||||
}
|
||||
|
||||
const statusBarProminentItemBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND);
|
||||
if (statusBarProminentItemBackground) {
|
||||
collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`);
|
||||
}
|
||||
|
||||
const statusBarProminentItemHoverBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND);
|
||||
if (statusBarProminentItemHoverBackground) {
|
||||
collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item a.status-bar-info:hover:not([disabled]):not(.disabled) { background-color: ${statusBarProminentItemHoverBackground}; }`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .part.titlebar {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
padding: 0 70px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
zoom: 1; /* prevent zooming */
|
||||
line-height: 22px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.titlebar > .window-title {
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
line-height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-app-region: drag;
|
||||
zoom: 1; /* prevent zooming */
|
||||
}
|
||||
376
src/vs/workbench/browser/parts/titlebar/titlebarPart.ts
Normal file
@@ -0,0 +1,376 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/titlebarpart';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, $, Dimension } from 'vs/base/browser/builder';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
|
||||
import { getZoomFactor } from 'vs/base/browser/browser';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IIntegrityService } from 'vs/platform/integrity/common/integrity';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import nls = require('vs/nls');
|
||||
import * as labels from 'vs/base/common/labels';
|
||||
import { EditorInput, toResource } from 'vs/workbench/common/editor';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { Verbosity } from 'vs/platform/editor/common/editor';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private static NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
|
||||
private static NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
|
||||
private static TITLE_DIRTY = '\u25cf ';
|
||||
private static TITLE_SEPARATOR = ' — ';
|
||||
|
||||
private titleContainer: Builder;
|
||||
private title: Builder;
|
||||
private pendingTitle: string;
|
||||
private initialTitleFontSize: number;
|
||||
private representedFileName: string;
|
||||
|
||||
private isInactive: boolean;
|
||||
|
||||
private titleTemplate: string;
|
||||
private isPure: boolean;
|
||||
private activeEditorListeners: IDisposable[];
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IIntegrityService private integrityService: IIntegrityService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this.isPure = true;
|
||||
this.activeEditorListeners = [];
|
||||
|
||||
this.init();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
|
||||
// Read initial config
|
||||
this.onConfigurationChanged();
|
||||
|
||||
// Initial window title when loading is done
|
||||
this.partService.joinCreation().done(() => this.setTitle(this.getWindowTitle()));
|
||||
|
||||
// Integrity for window title
|
||||
this.integrityService.isPure().then(r => {
|
||||
if (!r.isPure) {
|
||||
this.isPure = false;
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onBlur()));
|
||||
this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.FOCUS, () => this.onFocus()));
|
||||
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged(true)));
|
||||
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkspaceRoots(() => this.onDidChangeWorkspaceRoots()));
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkspaceName(() => this.onDidChangeWorkspaceName()));
|
||||
}
|
||||
|
||||
private onBlur(): void {
|
||||
this.isInactive = true;
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onFocus(): void {
|
||||
this.isInactive = false;
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onDidChangeWorkspaceRoots(): void {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
|
||||
private onDidChangeWorkspaceName(): void {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
|
||||
private onConfigurationChanged(update?: boolean): void {
|
||||
const currentTitleTemplate = this.titleTemplate;
|
||||
this.titleTemplate = this.configurationService.lookup<string>('window.title').value;
|
||||
|
||||
if (update && currentTitleTemplate !== this.titleTemplate) {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
}
|
||||
|
||||
private onEditorsChanged(): void {
|
||||
|
||||
// Dispose old listeners
|
||||
dispose(this.activeEditorListeners);
|
||||
this.activeEditorListeners = [];
|
||||
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
const activeInput = activeEditor ? activeEditor.input : void 0;
|
||||
|
||||
// Calculate New Window Title
|
||||
this.setTitle(this.getWindowTitle());
|
||||
|
||||
// Apply listener for dirty and label changes
|
||||
if (activeInput instanceof EditorInput) {
|
||||
this.activeEditorListeners.push(activeInput.onDidChangeDirty(() => {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}));
|
||||
|
||||
this.activeEditorListeners.push(activeInput.onDidChangeLabel(() => {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private getWindowTitle(): string {
|
||||
let title = this.doGetWindowTitle();
|
||||
if (!title) {
|
||||
title = this.environmentService.appNameLong;
|
||||
}
|
||||
|
||||
if (!this.isPure) {
|
||||
title = `${title} ${TitlebarPart.NLS_UNSUPPORTED}`;
|
||||
}
|
||||
|
||||
// Extension Development Host gets a special title to identify itself
|
||||
if (this.environmentService.isExtensionDevelopment) {
|
||||
title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title}`;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible template values:
|
||||
*
|
||||
* {activeEditorLong}: e.g. /Users/Development/myProject/myFolder/myFile.txt
|
||||
* {activeEditorMedium}: e.g. myFolder/myFile.txt
|
||||
* {activeEditorShort}: e.g. myFile.txt
|
||||
* {rootName}: e.g. myFolder1, myFolder2, myFolder3
|
||||
* {rootPath}: e.g. /Users/Development/myProject
|
||||
* {folderName}: e.g. myFolder
|
||||
* {folderPath}: e.g. /Users/Development/myFolder
|
||||
* {appName}: e.g. VS Code
|
||||
* {dirty}: indiactor
|
||||
* {separator}: conditional separator
|
||||
*/
|
||||
private doGetWindowTitle(): string {
|
||||
const input = this.editorService.getActiveEditorInput();
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
|
||||
// Compute root resource
|
||||
// Single Root Workspace: always the single root workspace in this case
|
||||
// Multi Root Workspace: workspace configuration file
|
||||
let root: URI;
|
||||
if (this.contextService.hasMultiFolderWorkspace()) {
|
||||
root = workspace.configuration;
|
||||
} else if (this.contextService.hasFolderWorkspace()) {
|
||||
root = workspace.roots[0];
|
||||
}
|
||||
|
||||
// Compute folder resource
|
||||
// Single Root Workspace: always the root single workspace in this case
|
||||
// Multi Root Workspace: root folder of the currently active file if any
|
||||
let folder: URI;
|
||||
if (workspace) {
|
||||
if (workspace.roots.length === 1) {
|
||||
folder = workspace.roots[0];
|
||||
} else {
|
||||
folder = this.contextService.getRoot(toResource(input, { supportSideBySide: true, filter: 'file' }));
|
||||
}
|
||||
}
|
||||
|
||||
// Variables
|
||||
const activeEditorShort = input ? input.getTitle(Verbosity.SHORT) : '';
|
||||
const activeEditorMedium = input ? input.getTitle(Verbosity.MEDIUM) : activeEditorShort;
|
||||
const activeEditorLong = input ? input.getTitle(Verbosity.LONG) : activeEditorMedium;
|
||||
const rootName = workspace ? workspace.name : '';
|
||||
const rootPath = workspace ? labels.getPathLabel(root, void 0, this.environmentService) : '';
|
||||
const folderName = folder ? (paths.basename(folder.fsPath) || folder.fsPath) : '';
|
||||
const folderPath = folder ? labels.getPathLabel(folder, void 0, this.environmentService) : '';
|
||||
const dirty = input && input.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
|
||||
const appName = this.environmentService.appNameLong;
|
||||
const separator = TitlebarPart.TITLE_SEPARATOR;
|
||||
|
||||
return labels.template(this.titleTemplate, {
|
||||
activeEditorShort,
|
||||
activeEditorLong,
|
||||
activeEditorMedium,
|
||||
rootName,
|
||||
rootPath,
|
||||
folderName,
|
||||
folderPath,
|
||||
dirty,
|
||||
appName,
|
||||
separator: { label: separator }
|
||||
});
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
this.titleContainer = $(parent);
|
||||
|
||||
// Title
|
||||
this.title = $(this.titleContainer).div({ class: 'window-title' });
|
||||
if (this.pendingTitle) {
|
||||
this.title.text(this.pendingTitle);
|
||||
}
|
||||
|
||||
// Maximize/Restore on doubleclick
|
||||
this.titleContainer.on(DOM.EventType.DBLCLICK, (e) => {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
this.onTitleDoubleclick();
|
||||
});
|
||||
|
||||
// Context menu on title
|
||||
this.title.on([DOM.EventType.CONTEXT_MENU, DOM.EventType.MOUSE_DOWN], (e: MouseEvent) => {
|
||||
if (e.type === DOM.EventType.CONTEXT_MENU || e.metaKey) {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
this.onContextMenu(e);
|
||||
}
|
||||
});
|
||||
|
||||
return this.titleContainer;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
// Part container
|
||||
const container = this.getContainer();
|
||||
if (container) {
|
||||
container.style('color', this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND));
|
||||
container.style('background-color', this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND));
|
||||
|
||||
const titleBorder = this.getColor(TITLE_BAR_BORDER);
|
||||
container.style('border-bottom', titleBorder ? `1px solid ${titleBorder}` : null);
|
||||
}
|
||||
}
|
||||
|
||||
private onTitleDoubleclick(): void {
|
||||
this.windowService.onWindowTitleDoubleClick().then(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
private onContextMenu(e: MouseEvent): void {
|
||||
|
||||
// Find target anchor
|
||||
const event = new StandardMouseEvent(e);
|
||||
const anchor = { x: event.posx, y: event.posy };
|
||||
|
||||
// Show menu
|
||||
const actions = this.getContextMenuActions();
|
||||
if (actions.length) {
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(actions),
|
||||
onHide: () => actions.forEach(a => a.dispose())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getContextMenuActions(): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
if (this.representedFileName) {
|
||||
const segments = this.representedFileName.split(paths.sep);
|
||||
for (let i = segments.length; i > 0; i--) {
|
||||
const isFile = (i === segments.length);
|
||||
|
||||
let pathOffset = i;
|
||||
if (!isFile) {
|
||||
pathOffset++; // for segments which are not the file name we want to open the folder
|
||||
}
|
||||
|
||||
const path = segments.slice(0, pathOffset).join(paths.sep);
|
||||
|
||||
let label = paths.basename(path);
|
||||
if (!isFile) {
|
||||
label = paths.basename(paths.dirname(path));
|
||||
}
|
||||
|
||||
actions.push(new ShowItemInFolderAction(path, label || paths.sep, this.windowsService));
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public setTitle(title: string): void {
|
||||
|
||||
// Always set the native window title to identify us properly to the OS
|
||||
window.document.title = title;
|
||||
|
||||
// Apply if we can
|
||||
if (this.title) {
|
||||
this.title.text(title);
|
||||
} else {
|
||||
this.pendingTitle = title;
|
||||
}
|
||||
}
|
||||
|
||||
public setRepresentedFilename(path: string): void {
|
||||
|
||||
// Apply to window
|
||||
this.windowService.setRepresentedFilename(path);
|
||||
|
||||
// Keep for context menu
|
||||
this.representedFileName = path;
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// To prevent zooming we need to adjust the font size with the zoom factor
|
||||
if (typeof this.initialTitleFontSize !== 'number') {
|
||||
this.initialTitleFontSize = parseInt(this.titleContainer.getComputedStyle().fontSize, 10);
|
||||
}
|
||||
this.titleContainer.style({ fontSize: `${this.initialTitleFontSize / getZoomFactor()}px` });
|
||||
|
||||
return super.layout(dimension);
|
||||
}
|
||||
}
|
||||
|
||||
class ShowItemInFolderAction extends Action {
|
||||
|
||||
constructor(private path: string, label: string, private windowsService: IWindowsService) {
|
||||
super('showItemInFolder.action.id', label);
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
return this.windowsService.showItemInFolder(this.path);
|
||||
}
|
||||
}
|
||||
380
src/vs/workbench/browser/quickopen.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import types = require('vs/base/common/types');
|
||||
import errors = require('vs/base/common/errors');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenEntry, IHighlight, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { EditorOptions, EditorInput } from 'vs/workbench/common/editor';
|
||||
import { IResourceInput, IEditorInput, IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
|
||||
export interface IWorkbenchQuickOpenConfiguration {
|
||||
workbench: {
|
||||
quickOpen: {
|
||||
closeOnFocusLost: boolean;
|
||||
},
|
||||
commandPalette: {
|
||||
history: number;
|
||||
preserveInput: boolean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export class QuickOpenHandler {
|
||||
|
||||
/**
|
||||
* A quick open handler returns results for a given input string. The resolved promise
|
||||
* returns an instance of quick open model. It is up to the handler to keep and reuse an
|
||||
* instance of the same model across multiple calls. This helps in situations where the user is
|
||||
* narrowing down a search and the model is just filtering some items out.
|
||||
*
|
||||
* As such, returning the same model instance across multiple searches will yield best
|
||||
* results in terms of performance when many items are shown.
|
||||
*/
|
||||
public getResults(searchValue: string): TPromise<IModel<any>> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ARIA label to apply when this quick open handler is active in quick open.
|
||||
*/
|
||||
public getAriaLabel(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra CSS class name to add to the quick open widget to do custom styling of entries.
|
||||
*/
|
||||
public getClass(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the handler can run in the current environment. Return a string if the handler cannot run but has
|
||||
* a good message to show in this case.
|
||||
*/
|
||||
public canRun(): boolean | string {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hints to the outside that this quick open handler typically returns results fast.
|
||||
*/
|
||||
public hasShortResponseTime(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the handler wishes the quick open widget to automatically select the first result entry or an entry
|
||||
* based on a specific prefix match.
|
||||
*/
|
||||
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates to the handler that the quick open widget has been opened.
|
||||
*/
|
||||
public onOpen(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates to the handler that the quick open widget has been closed. Allows to free up any resources as needed.
|
||||
* The parameter canceled indicates if the quick open widget was closed with an entry being run or not.
|
||||
*/
|
||||
public onClose(canceled: boolean): void {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to return a label that will be placed to the side of the results from this handler or null if none.
|
||||
*/
|
||||
public getGroupLabel(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to return a label that will be used when there are no results found
|
||||
*/
|
||||
public getEmptyLabel(searchString: string): string {
|
||||
if (searchString.length > 0) {
|
||||
return nls.localize('noResultsMatching', "No results matching");
|
||||
}
|
||||
return nls.localize('noResultsFound2', "No results found");
|
||||
}
|
||||
}
|
||||
|
||||
export interface QuickOpenHandlerHelpEntry {
|
||||
prefix: string;
|
||||
description: string;
|
||||
needsEditor: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A lightweight descriptor of a quick open handler.
|
||||
*/
|
||||
export class QuickOpenHandlerDescriptor extends AsyncDescriptor<QuickOpenHandler> {
|
||||
public prefix: string;
|
||||
public description: string;
|
||||
public contextKey: string;
|
||||
public isDefault: boolean;
|
||||
public helpEntries: QuickOpenHandlerHelpEntry[];
|
||||
public instantProgress: boolean;
|
||||
|
||||
private id: string;
|
||||
|
||||
constructor(moduleId: string, ctorName: string, prefix: string, contextKey: string, description: string, instantProgress?: boolean);
|
||||
constructor(moduleId: string, ctorName: string, prefix: string, contextKey: string, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean);
|
||||
constructor(moduleId: string, ctorName: string, prefix: string, contextKey: string, param: any, instantProgress: boolean = false) {
|
||||
super(moduleId, ctorName);
|
||||
|
||||
this.id = moduleId + ctorName;
|
||||
this.prefix = prefix;
|
||||
this.contextKey = contextKey;
|
||||
this.instantProgress = instantProgress;
|
||||
|
||||
if (types.isString(param)) {
|
||||
this.description = param;
|
||||
} else {
|
||||
this.helpEntries = param;
|
||||
}
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
export const Extensions = {
|
||||
Quickopen: 'workbench.contributions.quickopen'
|
||||
};
|
||||
|
||||
export interface IQuickOpenRegistry {
|
||||
|
||||
/**
|
||||
* Registers a quick open handler to the platform.
|
||||
*/
|
||||
registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void;
|
||||
|
||||
/**
|
||||
* Registers a default quick open handler to fallback to.
|
||||
*/
|
||||
registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void;
|
||||
|
||||
/**
|
||||
* Get all registered quick open handlers
|
||||
*/
|
||||
getQuickOpenHandlers(): QuickOpenHandlerDescriptor[];
|
||||
|
||||
/**
|
||||
* Get a specific quick open handler for a given prefix.
|
||||
*/
|
||||
getQuickOpenHandler(prefix: string): QuickOpenHandlerDescriptor;
|
||||
|
||||
/**
|
||||
* Returns the default quick open handler.
|
||||
*/
|
||||
getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor;
|
||||
}
|
||||
|
||||
class QuickOpenRegistry implements IQuickOpenRegistry {
|
||||
private handlers: QuickOpenHandlerDescriptor[];
|
||||
private defaultHandler: QuickOpenHandlerDescriptor;
|
||||
|
||||
constructor() {
|
||||
this.handlers = [];
|
||||
}
|
||||
|
||||
public registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void {
|
||||
this.handlers.push(descriptor);
|
||||
|
||||
// sort the handlers by decreasing prefix length, such that longer
|
||||
// prefixes take priority: 'ext' vs 'ext install' - the latter should win
|
||||
this.handlers.sort((h1, h2) => h2.prefix.length - h1.prefix.length);
|
||||
}
|
||||
|
||||
public registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void {
|
||||
this.defaultHandler = descriptor;
|
||||
}
|
||||
|
||||
public getQuickOpenHandlers(): QuickOpenHandlerDescriptor[] {
|
||||
return this.handlers.slice(0);
|
||||
}
|
||||
|
||||
public getQuickOpenHandler(text: string): QuickOpenHandlerDescriptor {
|
||||
return text ? arrays.first(this.handlers, h => strings.startsWith(text, h.prefix), null) : null;
|
||||
}
|
||||
|
||||
public getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor {
|
||||
return this.defaultHandler;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Quickopen, new QuickOpenRegistry());
|
||||
|
||||
export interface IEditorQuickOpenEntry {
|
||||
|
||||
/**
|
||||
* The editor input used for this entry when opening.
|
||||
*/
|
||||
getInput(): IResourceInput | IEditorInput;
|
||||
|
||||
/**
|
||||
* The editor options used for this entry when opening.
|
||||
*/
|
||||
getOptions(): IEditorOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass of quick open entry that will open an editor with input and options when running.
|
||||
*/
|
||||
export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuickOpenEntry {
|
||||
|
||||
constructor(private _editorService: IWorkbenchEditorService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public get editorService() {
|
||||
return this._editorService;
|
||||
}
|
||||
|
||||
public getInput(): IResourceInput | IEditorInput {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getOptions(): IEditorOptions {
|
||||
return null;
|
||||
}
|
||||
|
||||
public run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
const hideWidget = (mode === Mode.OPEN);
|
||||
|
||||
if (mode === Mode.OPEN || mode === Mode.OPEN_IN_BACKGROUND) {
|
||||
let sideBySide = context.keymods.indexOf(KeyMod.CtrlCmd) >= 0;
|
||||
|
||||
let openInBackgroundOptions: IEditorOptions;
|
||||
if (mode === Mode.OPEN_IN_BACKGROUND) {
|
||||
openInBackgroundOptions = { pinned: true, preserveFocus: true };
|
||||
}
|
||||
|
||||
let input = this.getInput();
|
||||
if (input instanceof EditorInput) {
|
||||
let opts = this.getOptions();
|
||||
if (opts) {
|
||||
opts = objects.mixin(opts, openInBackgroundOptions, true);
|
||||
} else if (openInBackgroundOptions) {
|
||||
opts = EditorOptions.create(openInBackgroundOptions);
|
||||
}
|
||||
|
||||
this.editorService.openEditor(input, opts, sideBySide).done(null, errors.onUnexpectedError);
|
||||
} else {
|
||||
const resourceInput = <IResourceInput>input;
|
||||
|
||||
if (openInBackgroundOptions) {
|
||||
resourceInput.options = objects.assign(resourceInput.options || Object.create(null), openInBackgroundOptions);
|
||||
}
|
||||
|
||||
this.editorService.openEditor(resourceInput, sideBySide).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
return hideWidget;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass of quick open entry group that provides access to editor input and options.
|
||||
*/
|
||||
export class EditorQuickOpenEntryGroup extends QuickOpenEntryGroup implements IEditorQuickOpenEntry {
|
||||
|
||||
public getInput(): IEditorInput | IResourceInput {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getOptions(): IEditorOptions {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Infrastructure for quick open commands
|
||||
|
||||
export interface ICommand {
|
||||
aliases: string[];
|
||||
getResults(input: string): TPromise<QuickOpenEntry[]>;
|
||||
getEmptyLabel(input: string): string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
class CommandEntry extends QuickOpenEntry {
|
||||
|
||||
constructor(private quickOpenService: IQuickOpenService, private prefix: string, private command: ICommand, highlights: IHighlight[]) {
|
||||
super(highlights);
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public getIcon(): string {
|
||||
return this.command.icon || null;
|
||||
}
|
||||
|
||||
public getLabel(): string {
|
||||
return this.command.aliases[0];
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, command", this.getLabel());
|
||||
}
|
||||
|
||||
public run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.PREVIEW) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.quickOpenService.show(`${this.prefix} ${this.command.aliases[0]} `);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICommandQuickOpenHandlerOptions {
|
||||
prefix: string;
|
||||
commands: ICommand[];
|
||||
defaultCommand?: ICommand;
|
||||
}
|
||||
|
||||
export class QuickOpenAction extends Action {
|
||||
private prefix: string;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
prefix: string,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.prefix = prefix;
|
||||
this.enabled = !!this.quickOpenService;
|
||||
}
|
||||
|
||||
public run(context?: any): TPromise<void> {
|
||||
|
||||
// Show with prefix
|
||||
this.quickOpenService.show(this.prefix);
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
280
src/vs/workbench/browser/viewlet.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import errors = require('vs/base/common/errors');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ITree, IFocusEvent, ISelectionEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
|
||||
export abstract class Viewlet extends Composite implements IViewlet {
|
||||
|
||||
public getOptimalWidth(): number {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper subtype of viewlet for those that use a tree inside.
|
||||
*/
|
||||
export abstract class ViewerViewlet extends Viewlet {
|
||||
|
||||
protected viewer: ITree;
|
||||
|
||||
private viewerContainer: Builder;
|
||||
private wasLayouted: boolean;
|
||||
|
||||
public create(parent: Builder): TPromise<void> {
|
||||
super.create(parent);
|
||||
|
||||
// Container for Viewer
|
||||
this.viewerContainer = parent.div();
|
||||
|
||||
// Viewer
|
||||
this.viewer = this.createViewer(this.viewerContainer);
|
||||
|
||||
// Eventing
|
||||
this.toUnbind.push(this.viewer.addListener('selection', (e: ISelectionEvent) => this.onSelection(e)));
|
||||
this.toUnbind.push(this.viewer.addListener('focus', (e: IFocusEvent) => this.onFocus(e)));
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an element in the viewer receives selection.
|
||||
*/
|
||||
public abstract onSelection(e: ISelectionEvent): void;
|
||||
|
||||
/**
|
||||
* Called when an element in the viewer receives focus.
|
||||
*/
|
||||
public abstract onFocus(e: IFocusEvent): void;
|
||||
|
||||
/**
|
||||
* Returns true if this viewlet is currently visible and false otherwise.
|
||||
*/
|
||||
public abstract createViewer(viewerContainer: Builder): ITree;
|
||||
|
||||
/**
|
||||
* Returns the viewer that is contained in this viewlet.
|
||||
*/
|
||||
public getViewer(): ITree {
|
||||
return this.viewer;
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
let promise: TPromise<void>;
|
||||
|
||||
if (visible) {
|
||||
promise = super.setVisible(visible);
|
||||
this.getViewer().onVisible();
|
||||
} else {
|
||||
this.getViewer().onHidden();
|
||||
promise = super.setVisible(visible);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (!this.viewer) {
|
||||
return; // return early if viewlet has not yet been created
|
||||
}
|
||||
|
||||
// Make sure the current selected element is revealed
|
||||
const selection = this.viewer.getSelection();
|
||||
if (selection.length > 0) {
|
||||
this.reveal(selection[0], 0.5).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.viewer.DOMFocus();
|
||||
}
|
||||
|
||||
public reveal(element: any, relativeTop?: number): TPromise<void> {
|
||||
if (!this.viewer) {
|
||||
return TPromise.as(null); // return early if viewlet has not yet been created
|
||||
}
|
||||
|
||||
// The viewer cannot properly reveal without being layed out, so force it if not yet done
|
||||
if (!this.wasLayouted) {
|
||||
this.viewer.layout();
|
||||
}
|
||||
|
||||
// Now reveal
|
||||
return this.viewer.reveal(element, relativeTop);
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
if (!this.viewer) {
|
||||
return; // return early if viewlet has not yet been created
|
||||
}
|
||||
|
||||
// Pass on to Viewer
|
||||
this.wasLayouted = true;
|
||||
this.viewer.layout(dimension.height);
|
||||
}
|
||||
|
||||
public getControl(): ITree {
|
||||
return this.viewer;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
// Dispose Viewer
|
||||
if (this.viewer) {
|
||||
this.viewer.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A viewlet descriptor is a leightweight descriptor of a viewlet in the workbench.
|
||||
*/
|
||||
export class ViewletDescriptor extends CompositeDescriptor<Viewlet> {
|
||||
|
||||
constructor(
|
||||
moduleId: string,
|
||||
ctorName: string,
|
||||
id: string,
|
||||
name: string,
|
||||
cssClass?: string,
|
||||
order?: number,
|
||||
private _extensionId?: string
|
||||
) {
|
||||
super(moduleId, ctorName, id, name, cssClass, order);
|
||||
|
||||
if (_extensionId) {
|
||||
this.appendStaticArguments([id]); // Pass viewletId to external viewlet, which doesn't know its id until runtime.
|
||||
}
|
||||
}
|
||||
|
||||
public get extensionId(): string {
|
||||
return this._extensionId;
|
||||
}
|
||||
}
|
||||
|
||||
export const Extensions = {
|
||||
Viewlets: 'workbench.contributions.viewlets'
|
||||
};
|
||||
|
||||
export class ViewletRegistry extends CompositeRegistry<Viewlet> {
|
||||
private defaultViewletId: string;
|
||||
|
||||
/**
|
||||
* Registers a viewlet to the platform.
|
||||
*/
|
||||
public registerViewlet(descriptor: ViewletDescriptor): void {
|
||||
super.registerComposite(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the viewlet descriptor for the given id or null if none.
|
||||
*/
|
||||
public getViewlet(id: string): ViewletDescriptor {
|
||||
return this.getComposite(id) as ViewletDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of registered viewlets known to the platform.
|
||||
*/
|
||||
public getViewlets(): ViewletDescriptor[] {
|
||||
return this.getComposites() as ViewletDescriptor[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id of the viewlet that should open on startup by default.
|
||||
*/
|
||||
public setDefaultViewletId(id: string): void {
|
||||
this.defaultViewletId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the viewlet that should open on startup by default.
|
||||
*/
|
||||
public getDefaultViewletId(): string {
|
||||
return this.defaultViewletId;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Viewlets, new ViewletRegistry());
|
||||
|
||||
/**
|
||||
* A reusable action to toggle a viewlet with a specific id.
|
||||
*/
|
||||
export class ToggleViewletAction extends Action {
|
||||
private viewletId: string;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
viewletId: string,
|
||||
@IViewletService protected viewletService: IViewletService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, name);
|
||||
|
||||
this.viewletId = viewletId;
|
||||
this.enabled = !!this.viewletService && !!this.editorService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
|
||||
// Pass focus to viewlet if not open or focused
|
||||
if (this.otherViewletShowing() || !this.sidebarHasFocus()) {
|
||||
return this.viewletService.openViewlet(this.viewletId, true);
|
||||
}
|
||||
|
||||
// Otherwise pass focus to editor if possible
|
||||
const editor = this.editorService.getActiveEditor();
|
||||
if (editor) {
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
private otherViewletShowing(): boolean {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
return !activeViewlet || activeViewlet.getId() !== this.viewletId;
|
||||
}
|
||||
|
||||
private sidebarHasFocus(): boolean {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
const activeElement = document.activeElement;
|
||||
|
||||
return activeViewlet && activeElement && DOM.isAncestor(activeElement, (<Viewlet>activeViewlet).getContainer().getHTMLElement());
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse All action
|
||||
export class CollapseAction extends Action {
|
||||
|
||||
constructor(viewer: ITree, enabled: boolean, clazz: string) {
|
||||
super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, (context: any) => {
|
||||
if (viewer.getHighlight()) {
|
||||
return TPromise.as(null); // Global action disabled if user is in edit mode from another action
|
||||
}
|
||||
|
||||
viewer.collapseAll();
|
||||
viewer.clearSelection();
|
||||
viewer.clearFocus();
|
||||
viewer.DOMFocus();
|
||||
viewer.focusFirst();
|
||||
|
||||
return TPromise.as(null);
|
||||
});
|
||||
}
|
||||
}
|
||||