Merge VS Code 1.26.1 (#2394)

* Squash merge commits for 1.26 (#1) (#2323)

* Polish tag search as per feedback (#55269)

* Polish tag search as per feedback

* Updated regex

* Allow users to opt-out of features that send online requests in the background (#55097)

* settings sweep #54690

* Minor css tweaks to enable eoverflow elipsis in more places (#55277)

* fix an issue with titlebarheight when not scaling with zoom

* Settings descriptions update #54690

* fixes #55209

* Settings editor - many padding fixes

* More space above level 2 label

* Fixing Cannot debug npm script using Yarn #55103

* Settings editor - show ellipsis when description overflows

* Settings editor - ... fix measuring around links, relayout

* Setting descriptions

* Settings editor - fix ... for some short lines, fix select container width

* Settings editor - overlay trees so scrollable shadow is full width

* Fix #54133 - missing extension settings after reload

* Settings color token description tweak

* Settings editor - disable overflow indicator temporarily, needs to be faster

* Added command to Run the selected npm script

* fixes #54452

* fixes #54929

* fixes #55248

* prefix command with extension name

* Contribute run selected to the context menu

* node-debug@1.26.6

* Allow terminal rendererType to be swapped out at runtime

Part of #53274
Fixes #55344

* Settings editor - fix not focusing search when restoring editor
setInput must be actually async. Will be fixed naturally when we aren't using winJS promises...

* Settings editor - TOC should only expand the section with a selected item

* Bump node-debug2

* Settings editor - Tree focus outlines

* Settings editor - don't blink the scrollbar when toc selection changes
And hide TOC correctly when the editor is narrow

* Settings editor - header rows should not be selectable

* fixes #54877

* change debug assignee to isi

* Settings sweep (#54690)

* workaround for #55051

* Settings sweep (#54690)

* settings sweep

#54690

* Don't try closing tags when you type > after another >

* Describe what implementation code lens does

Fixes #55370

* fix javadoc formatter setting description

* fixes #55325

* update to officical TS version

* Settings editor - Even more padding, use semibold instead of bold

* Fix #55357 - fix TOC twistie

* fixes #55288

* explorer: refresh on di change file system provider registration

fixes #53256

* Disable push to Linux repo to test standalone publisher

* New env var to notify log level to extensions #54001

* Disable snippets in extension search (when not in suggest dropdown) (#55281)

* Disable snippits in extension search (when not in suggest dropdown)

* Add monaco input contributions

* Fix bug preventing snippetSuggestions from taking effect in sub-editors

* Latest emmet helper to fix #52366

* Fix comment updates for threads within same file

* Allow extensions to log telemetry to log files #54001

* Pull latest css grammar

* files.exclude control - use same style for "add" vs "edit"

* files.exclude control - focus/keyboard behavior

* don't show menubar too early

* files.exclude - better styling

* Place cursor at end of extensions search box on autofill (#55254)

* Place cursor at end of extensions search box on autofill

* Use position instead of selection

* fix linux build issue (empty if block)

* Settings editor - fix extension category prefixes

* Settings editor - add simple ellipsis for first line that overflows, doesn't cover case when first line does not overflow but there is more text, TODO

* File/Text search provider docs

* Fixes #52655

* Include epoch (#55008)

* Fixes #53385

* Fixes #49480

*  VS Code Insiders (Users) not opening Fixes #55353

* Better handling of the case when the extension host fails to start

* Fixes #53966

*  Remove confusing Start from wordPartLeft commands ID

* vscode-xterm@3.6.0-beta12

Fixes #55488

* Initial size is set to infinity!! Fixes #55461

* Polish embeddedEditorBackground

* configuration service misses event

* Fix #55224 - fix duplicate results in multiroot workspace from splitting the diskseach query

* Select all not working in issue reporter on mac, fixes #55424

* Disable fuzzy matching for extensions autosuggest (#55498)

* Fix clipping of extensions search border in some third party themes (#55504)

* fixes #55538

* Fix bug causing an aria alert to not be shown the third time
 (and odd numbers thereafter)

* Settings editor - work around rendering glitch with webkit-line-clamp

* Settings editor - revert earlier '...' changes

* Settings editor - move enumDescription to its own div, because it disturbs -webkit-line-clamp for some reason

* Settings editor - better overflow indicator

* Don't show existing filters in autocomplete (#55495)

* Dont show existing filters in autocomplete

* Simplify

* Settings Editor: Add aria labels for input elements Fixes: #54836 (#55543)

* fixes #55223

* Update vscode-css-languageservice to 3.0.10-next.1

* Fix #55509 - settings navigation

* Fix #55519

* Fix #55520

* FIx #55524

* Fix #55556 - include wordSeparators in all search queries, so findTextInFiles can respect isWordMatch correctly

* oss updates for endgame

* Fix unit tests

* fixes #55522

* Avoid missing manifest error from bubbling up #54757

* Settings format crawl

* Search provider - Fix FileSearchProvider to return array, not progress

* Fix #55598

* Settings editor - fix NPE rendering settings with no description

* dont render inden guides in search box (#55600)

* fixes #55454

* More settings crawl

* Another change for #55598 - maxResults applies to FileSearch and TextSearch but not FileIndex

* Fix FileSearchProvider unit tests for progress change

* fixes #55561

* Settings description update for #54690

* Update setting descriptions for online services

* Minor edits

* fixes #55513

* fixes #55451

* Fix #55612 - fix findTextInFiles cancellation

* fixes #55539

* More setting description tweaks

* Setting to disable online experiments #54354

* fixes #55507

* fixes #55515

* Show online services action only in Insiders for now

* Settings editor - change toc behavior default to 'filter'

* Settings editor - nicer filter count style during search

* Fix #55617 - search viewlet icons

* Settings editor - better styling for element count indicator

* SearchProvider - fix NPE when searching extraFileResources

* Allow extends to work without json suffix

Fixes #16905

* Remove accessability options logic entirely

Follow up on #55451

* use latest version of DAP

* fixes #55490

* fixes #55122

* fixes #52332

* Avoid assumptions about git: URIs (fixes #36236)

* relative path for descriptions

* resourece: get rid of isFile context key

fixes #48275

* Register previous ids for compatibility (#53497)

* more tuning for #48275

* no need to always re-read "files explorer"

fixes #52003

* read out active composites properly

fixes #51967

* Update link colors for hc theme to meet color contrast ratio, fixes #55651

Also updated link color for `textLinkActiveForeground` to be the same as `textLinkForeground` as it wasn't properly updated

* detect 'winpty-agent.exe'; fixes #55672

* node-debug@1.26.7

* reset counter on new label

* Settings editor - fix multiple setting links in one description

* Settings editor - color code blocks in setting descriptions, fix #55532

* Settings editor - hover color in TOC

* Settings editor - fix navigation NPE

* Settings editor - fix text control width

* Settings editor - maybe fix #55684

* Fix bug causing cursor to not move on paste

* fixes #53582

* Use ctrlCmd instead of ctrl for go down from search box

* fixes #55264

* fixes #55456

* filter for spcaes before triggering search (#55611)

* Fix #55698 - don't lose filtered TOC counts when refreshing TOC

* fixes #55421

* fixes #28979

* fixes #55576

* only add check for updates to windows/linux help

* readonly files: append decoration to label

fixes #53022

* debug: do not show toolbar while initialising

fixes #55026

* Opening launch.json should not activate debug extensions

fixes #55029

* fixes #55435

* fixes #55434

* fixes #55439

* trigger menu only on altkey up

* Fix #50555 - fix settings editor memory leak

* Fix #55712 - no need to focus 'a' anymore when restoring control focus after tree render

* fixes #55335

* proper fix for readonly model

fixes #53022

* improve FoldingRangeKind spec (for #55686)

* Use class with static fields (fixes #55494)

* Fixes #53671

* fixes #54630

* [html] should disable ionic suggestions by default. Currently forces deprecated Ionic v1 suggestions in .html files while typing. Fixes #53324

* cleanup deps

* debug issues back to andre

* update electron for smoketest

* Fix #55757 - prevent settings tabs from overflowing

* Fix #53897 - revert setting menu defaults to old editor

* Add enum descriptions to `typescript.preferences.importModuleSpecifier`

* Fix #55767 - leaking style elements from settings editor

* Fix #55521 - prevent flashing when clicking in exclude control

* Update Git modified color for contrast ratio, fixes #53140

* Revert "Merge branch 'master' of github.com:Microsoft/vscode"

This reverts commit bf46b6bfbae0cab99c2863e1244a916181fa9fbc, reversing
changes made to e275a424483dfb4ed33b428c97d5e2c441d6b917.

* Revert "Revert "Merge branch 'master' of github.com:Microsoft/vscode""

This reverts commit 53949d963f39e40757557c6526332354a31d9154.

* don't ask to install an incomplete menu

* Fix NPE in terminal AccessibilityManager

Fixes #55744

* don't display fallback menu unless we've closed the last window

* fixes #55547

* Fix smoke tests for extension search box

* Update OSSREADME.json for Electron 2.0.5

* Update distro

Includes Chromium license changes

* fix #55455

* fix #55865

* fixes #55893

* Fix bug causing workspace recommendations to go away upon ignoring a recommendation (#55805)

* Fix bug causing workspace recommendations to go away upon ignoring a recommendation

* ONly show on @recommended or @recommended:workspace

* Make more consistant

* Fix #55911

* Understand json activity (#55926)

* Understand json file activity

* Refactoring

* adding composer.json

* Distro update for experiments

* use terminal.processId for auto-attach; fixes #55918

* Reject invalid URI with vscode.openFolder (for #55891)

* improve win32 setup system vs user detection

fixes #55840

fixes #55840

delay winreg import

related to #55840

show notification earlier

related to #55840

fix #55840

update inno setup message

related to #55840

* Fix #55593 - this code only operates on local paths, so use fsPath and Uri.file instead

* Bring back the old menu due to electron 2.0 issues (#55913)

* add the old menu back for native menus

* make menu labels match

* `vscode.openFolder`: treat missing URI schema gracefully (for #55891)

* delay EH reattach; fixes #55955

* Mark all json files under appSettingsHome as settings

* Use localized strings for telemetry opt-out

* Exception when saving file editor opened from remote file provider (fixes #55051)

* Remove terminal menu from stable

Fixes 56003

* VSCode Insiders crashes on open with TypeError: Cannot read property 'lastIndexOf' of undefined. Fixes #54933

* improve fix for #55891

* fix #55916

* Improve #55891

* increase EH debugging restart delay; fixes #55955

* Revert "Don't include non-resource entries in history quick pick"

This reverts commit 37209a838e9f7e9abe6dc53ed73cdf1e03b72060.

* Diff editor: horizontal scrollbar height is smaller (fixes #56062)

* improve openFolder uri fix (correctly treat backslashes)

* fixes #56116
repair ipc for native menubar keybindings

* Fix #56240 - Open the JSON settings editor instead of the UI editor

* Fix #55536

* uriDisplay: if no formatter is registered fall back to getPathlabel

fixes #56104

* VSCode hangs when opening python file. Fixes #56377

* VS Code Hangs When Opening Specific PowerShell File. Fixes #56430

* Fix #56433 - search extraFileResources even when no folders open

* Workaround #55649

* Fix in master #56371

* Fix tests #56371

* Fix in master #56317

* increase version to 1.26.1

* Fixes #56387: Handle SIGPIPE in extension host

* fixes #56185

* Fix merge issues (part 1)

* Fix build breaks (part 1)

* Build breaks (part 2)

* Build breaks (part 3)

* More build breaks (part 4)

* Fix build breaks (part 5)

* WIP

* Fix menus

* Render query result and message panels (#2363)

* Put back query editor hot exit changes

* Fix grid changes that broke profiler (#2365)

* Update APIs for saving query editor state

* Fix restore view state for profiler and edit data

* Updating custom default themes to support 4.5:1 contrast ratio

* Test updates

* Fix Extension Manager and Windows Setup

* Update license headers

* Add appveyor and travis files back

* Fix hidden modal dropdown issue
This commit is contained in:
Karl Burtram
2018-09-04 14:55:00 -07:00
committed by GitHub
parent 3763278366
commit 81329fa7fa
2638 changed files with 118456 additions and 64012 deletions

View File

@@ -21,8 +21,9 @@ import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colo
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActivityAction, ActivityActionItem, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
import { ActivityAction, ActivityActionItem, ICompositeBarColors, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import URI from 'vs/base/common/uri';
export class ViewletActivityAction extends ActivityAction {
@@ -39,7 +40,7 @@ export class ViewletActivityAction extends ActivityAction {
super(activity);
}
public run(event: any): TPromise<any> {
run(event: any): TPromise<any> {
if (event instanceof MouseEvent && event.button === 2) {
return TPromise.as(false); // do not run on right click
}
@@ -85,7 +86,7 @@ export class ToggleViewletAction extends Action {
super(_viewlet.id, _viewlet.name);
}
public run(): TPromise<any> {
run(): TPromise<any> {
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
const activeViewlet = this.viewletService.getActiveViewlet();
@@ -116,7 +117,7 @@ export class GlobalActivityActionItem extends ActivityActionItem {
super(action, { draggable: false, colors, icon: true }, themeService);
}
public render(container: HTMLElement): void {
render(container: HTMLElement): void {
super.render(container);
// Context menus are triggered on mouse down so that an item can be picked
@@ -158,6 +159,41 @@ export class GlobalActivityActionItem extends ActivityActionItem {
}
}
export class PlaceHolderViewletActivityAction extends ViewletActivityAction {
constructor(
id: string, iconUrl: URI,
@IViewletService viewletService: IViewletService,
@IPartService partService: IPartService,
@ITelemetryService telemetryService: ITelemetryService
) {
super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, partService, telemetryService);
const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar
DOM.createCSSRule(iconClass, `-webkit-mask: url('${iconUrl || ''}') no-repeat 50% 50%`);
this.enabled = false;
}
setActivity(activity: IActivity): void {
this.activity = activity;
this.enabled = true;
}
}
export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction {
constructor(id: string, compositeBar: ICompositeBar) {
super({ id, name: id, cssClass: void 0 }, compositeBar);
this.enabled = false;
}
setActivity(activity: IActivity): void {
this.label = activity.name;
this.enabled = true;
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
// Styling with Outline color (e.g. high contrast theme)

View File

@@ -7,85 +7,105 @@
import 'vs/css!./media/activitybarpart';
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { illegalArgument } from 'vs/base/common/errors';
import { $ } from 'vs/base/browser/builder';
import { Action } from 'vs/base/common/actions';
import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity';
import { Registry } from 'vs/platform/registry/common/platform';
import { Part } from 'vs/workbench/browser/part';
import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
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 { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
// {{SQL CARBON EDIT}}
import { isMacintosh } from 'vs/base/common/platform';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { scheduleAtNextAnimationFrame, Dimension } from 'vs/base/browser/dom';
import { Color } from 'vs/base/common/color';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ViewLocation, ViewsRegistry } from 'vs/workbench/common/views';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import URI from 'vs/base/common/uri';
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { Dimension } from 'vs/base/browser/dom';
interface IPlaceholderComposite {
id: string;
iconUrl: URI;
}
export class ActivitybarPart extends Part {
private static readonly ACTION_HEIGHT = 50;
private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets';
private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets';
private static readonly COLORS = {
backgroundColor: ACTIVITY_BAR_FOREGROUND,
badgeBackground: ACTIVITY_BAR_BADGE_BACKGROUND,
badgeForeground: ACTIVITY_BAR_BADGE_FOREGROUND,
dragAndDropBackground: ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND
};
private static readonly ACTION_HEIGHT = 50;
public _serviceBrand: any;
private dimension: Dimension;
private globalActionBar: ActionBar;
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; };
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; } = Object.create(null);
private placeholderComposites: IPlaceholderComposite[] = [];
private compositeBar: CompositeBar;
private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null);
constructor(
id: string,
@IViewletService private viewletService: IViewletService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService,
@IPartService private partService: IPartService,
@IThemeService themeService: IThemeService,
// {{SQL CARBON EDIT}}
@IStorageService private storageService: IStorageService
@ILifecycleService private lifecycleService: ILifecycleService,
@IStorageService private storageService: IStorageService,
@IExtensionService private extensionService: IExtensionService
) {
super(id, { hasTitle: false }, themeService);
this.globalActivityIdToActions = Object.create(null);
this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, {
icon: true,
storageId: ActivitybarPart.PINNED_VIEWLETS,
orientation: ActionsOrientation.VERTICAL,
composites: this.viewletService.getViewlets(),
openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true),
getActivityAction: (compositeId: string) => this.instantiationService.createInstance(ViewletActivityAction, this.viewletService.getViewlet(compositeId)),
getCompositePinnedAction: (compositeId: string) => new ToggleCompositePinnedAction(this.viewletService.getViewlet(compositeId), this.compositeBar),
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)),
getContextMenuActions: () => [this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))],
getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
hidePart: () => this.partService.setSideBarHidden(true),
compositeSize: 50,
colors: ActivitybarPart.COLORS,
overflowActionSize: ActivitybarPart.ACTION_HEIGHT
});
}));
const previousState = this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, void 0);
if (previousState) {
let parsedPreviousState = <IPlaceholderComposite[]>JSON.parse(previousState);
parsedPreviousState.forEach((s) => {
if (typeof s.iconUrl === 'object') {
s.iconUrl = URI.revive(s.iconUrl);
} else {
s.iconUrl = void 0;
}
});
this.placeholderComposites = parsedPreviousState;
} else {
this.placeholderComposites = this.compositeBar.getCompositesFromStorage().map(id => (<IPlaceholderComposite>{ id, iconUrl: void 0 }));
}
this.registerListeners();
this.updateCompositebar();
this.updatePlaceholderComposites();
}
// {{SQL CARBON EDIT}}
@@ -101,27 +121,30 @@ export class ActivitybarPart extends Part {
}
private registerListeners(): void {
this.toUnbind.push(this.viewletService.onDidViewletRegister(() => this.updateCompositebar()));
this.toUnbind.push(ViewsRegistry.onViewsRegistered(() => this.updateCompositebar()));
this.toUnbind.push(ViewsRegistry.onViewsDeregistered(() => this.updateCompositebar()));
this._register(this.viewletService.onDidViewletRegister(() => this.updateCompositebar()));
// Activate viewlet action on opening of a viewlet
this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId())));
this._register(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId())));
// Deactivate viewlet action on close
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
this.toUnbind.push(this.viewletService.onDidViewletEnablementChange(({ id, enabled }) => {
this._register(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
this._register(this.viewletService.onDidViewletEnablementChange(({ id, enabled }) => {
if (enabled) {
this.compositeBar.addComposite(this.viewletService.getViewlet(id), true);
this.compositeBar.addComposite(this.viewletService.getViewlet(id));
} else {
this.compositeBar.removeComposite(id);
this.removeComposite(id);
}
}));
this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions()));
}
public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
private onDidRegisterExtensions(): void {
this.removeNotExistingPlaceholderComposites();
this.updateCompositebar();
}
showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
if (this.viewletService.getViewlet(viewletOrActionId)) {
return this.compositeBar.showActivity(viewletOrActionId, badge, clazz, priority);
}
@@ -144,7 +167,7 @@ export class ActivitybarPart extends Part {
return toDisposable(() => action.setBadge(undefined));
}
public createContentArea(parent: HTMLElement): HTMLElement {
createContentArea(parent: HTMLElement): HTMLElement {
const $el = $(parent);
const $result = $('.content').appendTo($el);
@@ -154,10 +177,31 @@ export class ActivitybarPart extends Part {
// Top Actionbar with action items for each viewlet action
this.createGlobalActivityActionBar($('.global-activity').appendTo($result).getHTMLElement());
// TODO@Ben: workaround for https://github.com/Microsoft/vscode/issues/45700
// It looks like there are rendering glitches on macOS with Chrome 61 when
// using --webkit-mask with a background color that is different from the image
// The workaround is to promote the element onto its own drawing layer. We do
// this only after the workbench has loaded because otherwise there is ugly flicker.
if (isMacintosh) {
this.lifecycleService.when(LifecyclePhase.Running).then(() => {
scheduleAtNextAnimationFrame(() => { // another delay...
scheduleAtNextAnimationFrame(() => { // ...to prevent more flickering on startup
registerThemingParticipant((theme, collector) => {
const activityBarForeground = theme.getColor(ACTIVITY_BAR_FOREGROUND);
if (activityBarForeground && !activityBarForeground.equals(Color.white)) {
// only apply this workaround if the color is different from the image one (white)
collector.addRule('.monaco-workbench .activitybar > .content .monaco-action-bar .action-label { will-change: transform; }');
}
});
});
});
});
}
return $result.getHTMLElement();
}
public updateStyles(): void {
updateStyles(): void {
super.updateStyles();
// Part container
@@ -176,22 +220,6 @@ export class ActivitybarPart extends Part {
container.style('border-left-color', !isPositionLeft ? borderColor : null);
}
private showContextMenu(e: MouseEvent): void {
const event = new StandardMouseEvent(e);
const actions: Action[] = this.viewletService.getViewlets()
.filter(viewlet => this.canShow(viewlet))
.map(viewlet => this.instantiationService.createInstance(ToggleCompositePinnedAction, viewlet, this.compositeBar));
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, y: event.posy }; },
getActions: () => TPromise.as(actions),
onHide: () => dispose(actions)
});
}
private createGlobalActivityActionBar(container: HTMLElement): void {
const activityRegistry = Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions);
const descriptors = activityRegistry.getActivities();
@@ -199,13 +227,12 @@ export class ActivitybarPart extends Part {
.map(d => this.instantiationService.createInstance(d))
.map(a => new GlobalActivityAction(a));
this.globalActionBar = new ActionBar(container, {
this.globalActionBar = this._register(new ActionBar(container, {
actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, ActivitybarPart.COLORS),
orientation: ActionsOrientation.VERTICAL,
ariaLabel: nls.localize('globalActions', "Global Actions"),
animated: false
});
this.toUnbind.push(this.globalActionBar);
}));
actions.forEach(a => {
this.globalActivityIdToActions[a.id] = a;
@@ -213,34 +240,91 @@ export class ActivitybarPart extends Part {
});
}
private getCompositeActions(compositeId: string): { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } {
let compositeActions = this.compositeActions[compositeId];
if (!compositeActions) {
const viewlet = this.viewletService.getViewlet(compositeId);
if (viewlet) {
compositeActions = {
activityAction: this.instantiationService.createInstance(ViewletActivityAction, viewlet),
pinnedAction: new ToggleCompositePinnedAction(viewlet, this.compositeBar)
};
} else {
const placeHolderComposite = this.placeholderComposites.filter(c => c.id === compositeId)[0];
compositeActions = {
activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, placeHolderComposite.iconUrl),
pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)
};
}
this.compositeActions[compositeId] = compositeActions;
}
return compositeActions;
}
private updateCompositebar(): void {
const viewlets = this.viewletService.getViewlets();
for (const viewlet of viewlets) {
const canShow = this.canShow(viewlet);
if (canShow) {
this.compositeBar.addComposite(viewlet, false);
} else {
this.compositeBar.removeComposite(viewlet.id);
this.compositeBar.addComposite(viewlet);
// Pin it by default if it is new => it does not has a placeholder
if (this.placeholderComposites.every(c => c.id !== viewlet.id)) {
this.compositeBar.pin(viewlet.id);
}
this.enableCompositeActions(viewlet);
const activeViewlet = this.viewletService.getActiveViewlet();
if (activeViewlet && activeViewlet.getId() === viewlet.id) {
this.compositeBar.pin(viewlet.id);
this.compositeBar.activateComposite(viewlet.id);
}
}
}
private canShow(viewlet: ViewletDescriptor): boolean {
const viewLocation = ViewLocation.get(viewlet.id);
if (viewLocation) {
return ViewsRegistry.getViews(viewLocation).length > 0;
private updatePlaceholderComposites(): void {
const viewlets = this.viewletService.getViewlets();
for (const { id } of this.placeholderComposites) {
if (viewlets.every(viewlet => viewlet.id !== id)) {
this.compositeBar.addComposite({ id, name: id, order: void 0 });
}
}
return true;
}
public getPinned(): string[] {
private removeNotExistingPlaceholderComposites(): void {
const viewlets = this.viewletService.getViewlets();
for (const { id } of this.placeholderComposites) {
if (viewlets.every(viewlet => viewlet.id !== id)) {
this.removeComposite(id);
}
}
}
private removeComposite(compositeId: string): void {
this.compositeBar.removeComposite(compositeId);
const compositeActions = this.compositeActions[compositeId];
if (compositeActions) {
compositeActions.activityAction.dispose();
compositeActions.pinnedAction.dispose();
delete this.compositeActions[compositeId];
}
}
private enableCompositeActions(viewlet: ViewletDescriptor): void {
const { activityAction, pinnedAction } = this.getCompositeActions(viewlet.id);
if (activityAction instanceof PlaceHolderViewletActivityAction) {
activityAction.setActivity(viewlet);
}
if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) {
pinnedAction.setActivity(viewlet);
}
}
getPinned(): string[] {
return this.viewletService.getViewlets().map(v => v.id).filter(id => this.compositeBar.isPinned(id));
}
/**
* Layout title, content and status area in the given dimension.
*/
public layout(dimension: Dimension): Dimension[] {
layout(dimension: Dimension): Dimension[] {
if (!this.partService.isVisible(Parts.ACTIVITYBAR_PART)) {
return [dimension];
}
@@ -260,22 +344,10 @@ export class ActivitybarPart extends Part {
return sizes;
}
public shutdown(): void {
this.compositeBar.shutdown();
shutdown(): void {
const state = this.viewletService.getViewlets().map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl }));
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL);
super.shutdown();
}
public dispose(): void {
if (this.compositeBar) {
this.compositeBar.dispose();
this.compositeBar = null;
}
if (this.globalActionBar) {
this.globalActionBar.dispose();
this.globalActionBar = null;
}
super.dispose();
}
}
}

View File

@@ -23,7 +23,7 @@ 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 { ScopedProgressService } 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';
@@ -51,6 +51,12 @@ export interface ICompositeTitleLabel {
}
export abstract class CompositePart<T extends Composite> extends Part {
protected _onDidCompositeOpen = this._register(new Emitter<IComposite>());
protected _onDidCompositeClose = this._register(new Emitter<IComposite>());
protected toolBar: ToolBar;
private instantiatedCompositeListeners: IDisposable[];
private mapCompositeToCompositeContainer: { [compositeId: string]: Builder; };
private mapActionsBindingToComposite: { [compositeId: string]: () => void; };
@@ -59,13 +65,10 @@ export abstract class CompositePart<T extends Composite> extends Part {
private lastActiveCompositeId: string;
private instantiatedComposites: Composite[];
private titleLabel: ICompositeTitleLabel;
protected toolBar: ToolBar;
private progressBar: ProgressBar;
private contentAreaSize: Dimension;
private telemetryActionsListener: IDisposable;
private currentCompositeOpenToken: string;
protected _onDidCompositeOpen = new Emitter<IComposite>();
protected _onDidCompositeClose = new Emitter<IComposite>();
constructor(
private notificationService: INotificationService,
@@ -178,7 +181,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
// Instantiate composite from registry otherwise
const compositeDescriptor = this.registry.getComposite(id);
if (compositeDescriptor) {
const progressService = this.instantiationService.createInstance(WorkbenchProgressService, this.progressBar, compositeDescriptor.id, isActive);
const progressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive);
const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService]));
const composite = compositeDescriptor.instantiate(compositeInstantiationService);
@@ -400,7 +403,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
});
}
public createTitleArea(parent: HTMLElement): HTMLElement {
createTitleArea(parent: HTMLElement): HTMLElement {
// Title Area Container
const titleArea = $(parent).div({
@@ -416,11 +419,11 @@ export abstract class CompositePart<T extends Composite> extends Part {
}, div => {
// Toolbar
this.toolBar = new ToolBar(div.getHTMLElement(), this.contextMenuService, {
this.toolBar = this._register(new ToolBar(div.getHTMLElement(), this.contextMenuService, {
actionItemProvider: action => this.actionItemProvider(action as Action),
orientation: ActionsOrientation.HORIZONTAL,
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id)
});
}));
});
return titleArea.getHTMLElement();
@@ -431,7 +434,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
$(parent).div({
'class': 'title-label'
}, div => {
titleLabel = div.span();
titleLabel = div.element('h2');
});
const $this = this;
@@ -463,12 +466,12 @@ export abstract class CompositePart<T extends Composite> extends Part {
return undefined;
}
public createContentArea(parent: HTMLElement): HTMLElement {
createContentArea(parent: HTMLElement): HTMLElement {
return $(parent).div({
'class': 'content'
}, div => {
this.progressBar = new ProgressBar(div.getHTMLElement());
this.toUnbind.push(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar = this._register(new ProgressBar(div.getHTMLElement()));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
}).getHTMLElement();
}
@@ -477,7 +480,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
this.notificationService.error(types.isString(error) ? new Error(error) : error);
}
public getProgressIndicator(id: string): IProgressService {
getProgressIndicator(id: string): IProgressService {
return this.mapProgressServiceToComposite[id];
}
@@ -489,7 +492,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return [];
}
public layout(dimension: Dimension): Dimension[] {
layout(dimension: Dimension): Dimension[] {
// Pass to super
const sizes = super.layout(dimension);
@@ -503,13 +506,13 @@ export abstract class CompositePart<T extends Composite> extends Part {
return sizes;
}
public shutdown(): void {
shutdown(): void {
this.instantiatedComposites.forEach(i => i.shutdown());
super.shutdown();
}
public dispose(): void {
dispose(): void {
this.mapCompositeToCompositeContainer = null;
this.mapProgressServiceToComposite = null;
this.mapActionsBindingToComposite = null;
@@ -519,13 +522,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
}
this.instantiatedComposites = [];
this.instantiatedCompositeListeners = dispose(this.instantiatedCompositeListeners);
this.progressBar.dispose();
this.toolBar.dispose();
// Super Dispose
super.dispose();
}
}

View File

@@ -6,146 +6,182 @@
'use strict';
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { Action, IAction } from 'vs/base/common/actions';
import { illegalArgument } from 'vs/base/common/errors';
import * as arrays from 'vs/base/common/arrays';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ActionBar, IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Event, Emitter } from 'vs/base/common/event';
import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
import { TPromise } from 'vs/base/common/winjs.base';
import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Widget } from 'vs/base/browser/ui/widget';
export interface ICompositeBarOptions {
icon: boolean;
storageId: string;
orientation: ActionsOrientation;
composites: { id: string, name: string, order: number }[];
colors: ICompositeBarColors;
compositeSize: number;
overflowActionSize: number;
getActivityAction: (compositeId: string) => ActivityAction;
getCompositePinnedAction: (compositeId: string) => Action;
getOnCompositeClickAction: (compositeId: string) => Action;
getContextMenuActions: () => Action[];
openComposite: (compositeId: string) => TPromise<any>;
getDefaultCompositeId: () => string;
hidePart: () => TPromise<any>;
}
interface CompositeState {
id: string;
pinned: boolean;
}
export class CompositeBar implements ICompositeBar {
private readonly _onDidContextMenu: Emitter<MouseEvent>;
export class CompositeBar extends Widget implements ICompositeBar {
private dimension: Dimension;
private toDispose: IDisposable[];
private compositeSwitcherBar: ActionBar;
private compositeOverflowAction: CompositeOverflowActivityAction;
private compositeOverflowActionItem: CompositeOverflowActivityActionItem;
private compositeIdToActions: { [compositeId: string]: ActivityAction; };
private compositeIdToActionItems: { [compositeId: string]: IActionItem; };
private compositeIdToActivityStack: { [compositeId: string]: ICompositeActivity[]; };
private model: CompositeBarModel;
private storedState: ISerializedCompositeBarItem[];
private visibleComposites: string[];
private compositeSizeInBar: Map<string, number>;
private initialCompositesStates: CompositeState[];
private pinnedComposites: string[];
private activeCompositeId: string;
private activeUnpinnedCompositeId: string;
constructor(
private options: ICompositeBarOptions,
@IInstantiationService private instantiationService: IInstantiationService,
@IStorageService private storageService: IStorageService,
@IContextMenuService private contextMenuService: IContextMenuService
) {
this.toDispose = [];
this.compositeIdToActionItems = Object.create(null);
this.compositeIdToActions = Object.create(null);
this.compositeIdToActivityStack = Object.create(null);
super();
this.model = new CompositeBarModel(options);
this.storedState = this.loadCompositeItemsFromStorage();
this.visibleComposites = [];
this.compositeSizeInBar = new Map<string, number>();
this._onDidContextMenu = new Emitter<MouseEvent>();
this.initialCompositesStates = this.loadCompositesStates();
this.pinnedComposites = this.initialCompositesStates
.filter(c => c.pinned)
.map(c => c.id)
.filter(id => this.options.composites.some(c => c.id === id));
}
public get onDidContextMenu(): Event<MouseEvent> {
return this._onDidContextMenu.event;
getCompositesFromStorage(): string[] {
return this.storedState.map(s => s.id);
}
public addComposite(compositeData: { id: string; name: string, order: number }, activate: boolean): void {
if (this.options.composites.filter(c => c.id === compositeData.id).length) {
return;
}
this.options.composites.push(compositeData);
create(parent: HTMLElement): HTMLElement {
const actionBarDiv = parent.appendChild($('.composite-bar'));
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
actionItemProvider: (action: Action) => {
if (action instanceof CompositeOverflowActivityAction) {
return this.compositeOverflowActionItem;
}
const item = this.model.findItem(action.id);
return item && this.instantiationService.createInstance(CompositeActionItem, action, item.pinnedAction, this.options.colors, this.options.icon, this);
},
orientation: this.options.orientation,
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
animated: false,
}));
const compositeState = this.initialCompositesStates.filter(c => c.id === compositeData.id)[0];
if (!compositeState /* new composites are pinned by default */ || compositeState.pinned) {
let index;
if (compositeState) {
index = this.initialCompositesStates.indexOf(compositeState);
} else {
index = 0;
while (index < this.options.composites.length && this.options.composites[index].order < compositeData.order) {
index++;
// Contextmenu for composites
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
// Allow to drop at the end to move composites to the end
this._register(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => {
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
if (draggedCompositeId) {
EventHelper.stop(e, true);
CompositeActionItem.clearDraggedComposite();
const targetItem = this.model.items[this.model.items.length - 1];
if (targetItem && targetItem.id !== draggedCompositeId) {
this.move(draggedCompositeId, targetItem.id);
}
}
this.pin(compositeData.id, true, index, activate);
}
}));
return actionBarDiv;
}
public removeComposite(id: string): void {
if (this.options.composites.filter(c => c.id === id).length === 0) {
layout(dimension: Dimension): void {
this.dimension = dimension;
if (dimension.height === 0 || dimension.width === 0) {
// Do not layout if not visible. Otherwise the size measurment would be computed wrongly
return;
}
this.options.composites = this.options.composites.filter(c => c.id !== id);
this.unpin(id);
this.pullComposite(id);
// Only at the end deactivate composite so the unpin and pull properly finish
this.deactivateComposite(id);
if (this.compositeSizeInBar.size === 0) {
// Compute size of each composite by getting the size from the css renderer
// Size is later used for overflow computation
this.computeSizes(this.model.items);
}
this.updateCompositeSwitcher();
}
public activateComposite(id: string): void {
if (this.compositeIdToActions[id]) {
if (this.compositeIdToActions[this.activeCompositeId]) {
this.compositeIdToActions[this.activeCompositeId].deactivate();
addComposite({ id, name, order }: { id: string; name: string, order: number }): void {
const state = this.storedState.filter(s => s.id === id)[0];
const pinned = state ? state.pinned : true;
let index = order >= 0 ? order : this.model.items.length;
if (state) {
// Find the index by looking its previous item
index = 0;
for (let i = this.storedState.indexOf(state) - 1; i >= 0; i--) {
const previousItemId = this.storedState[i].id;
const previousItemIndex = this.model.findIndex(previousItemId);
if (previousItemIndex !== -1) {
index = previousItemIndex + 1;
break;
}
}
this.compositeIdToActions[id].activate();
}
this.activeCompositeId = id;
const activeUnpinnedCompositeShouldClose = this.activeUnpinnedCompositeId && this.activeUnpinnedCompositeId !== id;
const activeUnpinnedCompositeShouldShow = !this.pinnedComposites.some(pid => pid === id);
if (activeUnpinnedCompositeShouldShow || activeUnpinnedCompositeShouldClose) {
// Add to the model
if (this.model.add(id, name, order, index)) {
this.computeSizes([this.model.findItem(id)]);
if (pinned) {
this.pin(id);
} else {
this.updateCompositeSwitcher();
}
}
}
removeComposite(id: string): void {
// If it pinned, unpin it first
if (this.isPinned(id)) {
this.unpin(id);
}
// Remove from the model
if (this.model.remove(id)) {
this.updateCompositeSwitcher();
}
}
public deactivateComposite(id: string): void {
if (this.compositeIdToActions[id]) {
this.compositeIdToActions[id].deactivate();
}
if (this.activeCompositeId === id) {
this.activeCompositeId = undefined;
}
if (this.activeUnpinnedCompositeId === id) {
this.updateCompositeSwitcher();
this.activeUnpinnedCompositeId = undefined;
activateComposite(id: string): void {
const previousActiveItem = this.model.activeItem;
if (this.model.activate(id)) {
// Update if current composite is neither visible nor pinned
// or previous active composite is not pinned
if (this.visibleComposites.indexOf(id) === - 1 || !this.model.activeItem.pinned || (previousActiveItem && !previousActiveItem.pinned)) {
this.updateCompositeSwitcher();
}
}
}
public showActivity(compositeId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
deactivateComposite(id: string): void {
const previousActiveItem = this.model.activeItem;
if (this.model.deactivate()) {
if (previousActiveItem && !previousActiveItem.pinned) {
this.updateCompositeSwitcher();
}
}
}
showActivity(compositeId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
if (!badge) {
throw illegalArgument('badge');
}
@@ -155,100 +191,90 @@ export class CompositeBar implements ICompositeBar {
}
const activity: ICompositeActivity = { badge, clazz, priority };
const stack = this.compositeIdToActivityStack[compositeId] || (this.compositeIdToActivityStack[compositeId] = []);
for (let i = 0; i <= stack.length; i++) {
if (i === stack.length) {
stack.push(activity);
break;
} else if (stack[i].priority <= priority) {
stack.splice(i, 0, activity);
break;
}
}
this.updateActivity(compositeId);
return {
dispose: () => {
const stack = this.compositeIdToActivityStack[compositeId];
if (!stack) {
return;
}
const idx = stack.indexOf(activity);
if (idx < 0) {
return;
}
stack.splice(idx, 1);
if (stack.length === 0) {
delete this.compositeIdToActivityStack[compositeId];
}
this.updateActivity(compositeId);
}
};
this.model.addActivity(compositeId, activity);
return toDisposable(() => this.model.removeActivity(compositeId, activity));
}
private updateActivity(compositeId: string) {
const action = this.compositeIdToActions[compositeId];
if (!action) {
return;
}
pin(compositeId: string, open?: boolean): void {
if (this.model.setPinned(compositeId, true)) {
this.updateCompositeSwitcher();
const stack = this.compositeIdToActivityStack[compositeId];
// reset
if (!stack || !stack.length) {
action.setBadge(undefined);
}
// update
else {
const [{ badge, clazz }] = stack;
action.setBadge(badge);
if (clazz) {
action.class = clazz;
if (open) {
this.options.openComposite(compositeId)
.done(() => this.activateComposite(compositeId)); // Activate after opening
}
}
}
public create(parent: HTMLElement): HTMLElement {
const actionBarDiv = parent.appendChild($('.composite-bar'));
this.compositeSwitcherBar = new ActionBar(actionBarDiv, {
actionItemProvider: (action: Action) => action instanceof CompositeOverflowActivityAction ? this.compositeOverflowActionItem : this.compositeIdToActionItems[action.id],
orientation: this.options.orientation,
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
animated: false,
});
this.toDispose.push(this.compositeSwitcherBar);
unpin(compositeId: string): void {
if (this.model.setPinned(compositeId, false)) {
// Contextmenu for composites
this.toDispose.push(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => {
EventHelper.stop(e, true);
this._onDidContextMenu.fire(e);
}));
this.updateCompositeSwitcher();
// Allow to drop at the end to move composites to the end
this.toDispose.push(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => {
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
if (draggedCompositeId) {
EventHelper.stop(e, true);
CompositeActionItem.clearDraggedComposite();
const defaultCompositeId = this.options.getDefaultCompositeId();
const targetId = this.pinnedComposites[this.pinnedComposites.length - 1];
if (targetId !== draggedCompositeId) {
this.move(draggedCompositeId, this.pinnedComposites[this.pinnedComposites.length - 1]);
}
// Case: composite is not the active one or the active one is a different one
// Solv: we do nothing
if (!this.model.activeItem || this.model.activeItem.id !== compositeId) {
return;
}
}));
return actionBarDiv;
// Deactivate itself
this.deactivateComposite(compositeId);
// Case: composite is not the default composite and default composite is still showing
// Solv: we open the default composite
if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
this.options.openComposite(defaultCompositeId);
}
// Case: we closed the last visible composite
// Solv: we hide the part
else if (this.visibleComposites.length === 1) {
this.options.hidePart();
}
// Case: we closed the default composite
// Solv: we open the next visible composite from top
else {
this.options.openComposite(this.visibleComposites.filter(cid => cid !== compositeId)[0]);
}
}
}
public getAction(compositeId): ActivityAction {
return this.compositeIdToActions[compositeId];
isPinned(compositeId: string): boolean {
const item = this.model.findItem(compositeId);
return item && item.pinned;
}
move(compositeId: string, toCompositeId: string): void {
if (this.model.move(compositeId, toCompositeId)) {
// timeout helps to prevent artifacts from showing up
setTimeout(() => this.updateCompositeSwitcher(), 0);
}
}
getAction(compositeId): ActivityAction {
const item = this.model.findItem(compositeId);
return item && item.activityAction;
}
private computeSizes(items: ICompositeBarItem[]): void {
const size = this.options.compositeSize;
if (size) {
items.forEach(composite => this.compositeSizeInBar.set(composite.id, size));
} else {
if (this.dimension && this.dimension.height !== 0 && this.dimension.width !== 0) {
// Compute sizes only if visible. Otherwise the size measurment would be computed wrongly.
const currentItemsLength = this.compositeSwitcherBar.items.length;
this.compositeSwitcherBar.push(items.map(composite => composite.activityAction));
items.map((composite, index) => this.compositeSizeInBar.set(composite.id, this.options.orientation === ActionsOrientation.VERTICAL
? this.compositeSwitcherBar.getHeight(currentItemsLength + index)
: this.compositeSwitcherBar.getWidth(currentItemsLength + index)
));
items.forEach(() => this.compositeSwitcherBar.pull(this.compositeSwitcherBar.items.length - 1));
}
}
}
private updateCompositeSwitcher(): void {
@@ -256,15 +282,10 @@ export class CompositeBar implements ICompositeBar {
return; // We have not been rendered yet so there is nothing to update.
}
let compositesToShow = this.pinnedComposites.slice(0); // never modify original array
// Always show the active composite even if it is marked to be hidden
if (this.activeCompositeId && !compositesToShow.some(id => id === this.activeCompositeId)) {
this.activeUnpinnedCompositeId = this.activeCompositeId;
compositesToShow = compositesToShow.concat(this.activeUnpinnedCompositeId);
} else {
this.activeUnpinnedCompositeId = void 0;
}
let compositesToShow = this.model.items.filter(item =>
item.pinned
|| (this.model.activeItem && this.model.activeItem.id === item.id) /* Show the active composite even if it is not pinned */
).map(item => item.id);
// Ensure we are not showing more composites than we have height for
let overflows = false;
@@ -288,19 +309,20 @@ export class CompositeBar implements ICompositeBar {
if (size > limit) {
size -= this.compositeSizeInBar.get(compositesToShow.pop());
}
// We always try show the active composite
if (this.activeCompositeId && compositesToShow.length && compositesToShow.indexOf(this.activeCompositeId) === -1) {
if (this.model.activeItem && compositesToShow.every(compositeId => compositeId !== this.model.activeItem.id)) {
const removedComposite = compositesToShow.pop();
size = size - this.compositeSizeInBar.get(removedComposite) + this.compositeSizeInBar.get(this.activeCompositeId);
compositesToShow.push(this.activeCompositeId);
size = size - this.compositeSizeInBar.get(removedComposite) + this.compositeSizeInBar.get(this.model.activeItem.id);
compositesToShow.push(this.model.activeItem.id);
}
// The active composite might have bigger size than the removed composite, check for overflow again
if (size > limit) {
compositesToShow.length ? compositesToShow.splice(compositesToShow.length - 2, 1) : compositesToShow.pop();
}
const visibleComposites = Object.keys(this.compositeIdToActions);
const visibleCompositesChange = !arrays.equals(compositesToShow, visibleComposites);
const visibleCompositesChange = !arrays.equals(compositesToShow, this.visibleComposites);
// Pull out overflow action if there is a composite change so that we can add it to the end later
if (this.compositeOverflowAction && visibleCompositesChange) {
@@ -313,37 +335,35 @@ export class CompositeBar implements ICompositeBar {
this.compositeOverflowActionItem = null;
}
// Pull out composites that overflow, got hidden or changed position
visibleComposites.forEach((compositeId, index) => {
if (compositesToShow.indexOf(compositeId) !== index) {
this.pullComposite(compositeId);
// Pull out composites that overflow or got hidden
const compositesToRemove: number[] = [];
this.visibleComposites.forEach((compositeId, index) => {
if (compositesToShow.indexOf(compositeId) === -1) {
compositesToRemove.push(index);
}
});
compositesToRemove.reverse().forEach(index => {
const actionItem = this.compositeSwitcherBar.items[index];
this.compositeSwitcherBar.pull(index);
actionItem.dispose();
this.visibleComposites.splice(index, 1);
});
// Built actions for composites to show
const newCompositesToShow = compositesToShow
.filter(compositeId => !this.compositeIdToActions[compositeId])
.map(compositeId => this.toAction(compositeId));
// Update when we have new composites to show
if (newCompositesToShow.length) {
// Add to composite switcher
this.compositeSwitcherBar.push(newCompositesToShow, { label: true, icon: this.options.icon });
// Make sure to activate the active one
if (this.activeCompositeId) {
const activeCompositeEntry = this.compositeIdToActions[this.activeCompositeId];
if (activeCompositeEntry) {
activeCompositeEntry.activate();
// Update the positions of the composites
compositesToShow.forEach((compositeId, newIndex) => {
const currentIndex = this.visibleComposites.indexOf(compositeId);
if (newIndex !== currentIndex) {
if (currentIndex !== -1) {
const actionItem = this.compositeSwitcherBar.items[currentIndex];
this.compositeSwitcherBar.pull(currentIndex);
actionItem.dispose();
this.visibleComposites.splice(currentIndex, 1);
}
}
// Make sure to restore activity
Object.keys(this.compositeIdToActions).forEach(compositeId => {
this.updateActivity(compositeId);
});
}
this.compositeSwitcherBar.push(this.model.findItem(compositeId).activityAction, { label: true, icon: this.options.icon, index: newIndex });
this.visibleComposites.splice(newIndex, 0, compositeId);
}
});
// Add overflow action as needed
if ((visibleCompositesChange && overflows) || this.compositeSwitcherBar.length() === 0) {
@@ -352,210 +372,254 @@ export class CompositeBar implements ICompositeBar {
CompositeOverflowActivityActionItem,
this.compositeOverflowAction,
() => this.getOverflowingComposites(),
() => this.activeCompositeId,
(compositeId: string) => this.compositeIdToActivityStack[compositeId] && this.compositeIdToActivityStack[compositeId][0].badge,
() => this.model.activeItem ? this.model.activeItem.id : void 0,
(compositeId: string) => {
const item = this.model.findItem(compositeId);
return item && item.activity[0] && item.activity[0].badge;
},
this.options.getOnCompositeClickAction,
this.options.colors
);
this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });
}
// Persist
this.saveCompositeItems();
}
private getOverflowingComposites(): { id: string, name: string }[] {
let overflowingIds = this.pinnedComposites;
if (this.activeUnpinnedCompositeId) {
overflowingIds = overflowingIds.concat(this.activeUnpinnedCompositeId);
}
const visibleComposites = Object.keys(this.compositeIdToActions);
let overflowingIds = this.model.items.filter(item => item.pinned).map(item => item.id);
overflowingIds = overflowingIds.filter(compositeId => visibleComposites.indexOf(compositeId) === -1);
return this.options.composites.filter(c => overflowingIds.indexOf(c.id) !== -1);
// Show the active composite even if it is not pinned
if (this.model.activeItem && !this.model.activeItem.pinned) {
overflowingIds.push(this.model.activeItem.id);
}
overflowingIds = overflowingIds.filter(compositeId => this.visibleComposites.indexOf(compositeId) === -1);
return this.model.items.filter(c => overflowingIds.indexOf(c.id) !== -1);
}
private getVisibleComposites(): string[] {
return Object.keys(this.compositeIdToActions);
}
private pullComposite(compositeId: string): void {
const index = Object.keys(this.compositeIdToActions).indexOf(compositeId);
if (index >= 0) {
this.compositeSwitcherBar.pull(index);
const action = this.compositeIdToActions[compositeId];
action.dispose();
delete this.compositeIdToActions[compositeId];
const actionItem = this.compositeIdToActionItems[action.id];
actionItem.dispose();
delete this.compositeIdToActionItems[action.id];
private showContextMenu(e: MouseEvent): void {
EventHelper.stop(e, true);
const event = new StandardMouseEvent(e);
const actions: IAction[] = this.model.items
.map(({ id, name, activityAction }) => (<IAction>{
id,
label: name,
checked: this.isPinned(id),
enabled: activityAction.enabled,
run: () => {
if (this.isPinned(id)) {
this.unpin(id);
} else {
this.pin(id, true);
}
}
}));
const otherActions = this.options.getContextMenuActions();
if (otherActions.length) {
actions.push(new Separator());
actions.push(...otherActions);
}
}
private toAction(compositeId: string): ActivityAction {
if (this.compositeIdToActions[compositeId]) {
return this.compositeIdToActions[compositeId];
}
const compositeActivityAction = this.options.getActivityAction(compositeId);
const pinnedAction = this.options.getCompositePinnedAction(compositeId);
this.compositeIdToActionItems[compositeId] = this.instantiationService.createInstance(CompositeActionItem, compositeActivityAction, pinnedAction, this.options.colors, this.options.icon, this);
this.compositeIdToActions[compositeId] = compositeActivityAction;
return compositeActivityAction;
}
public unpin(compositeId: string): void {
if (!this.isPinned(compositeId)) {
return;
}
const defaultCompositeId = this.options.getDefaultCompositeId();
const visibleComposites = this.getVisibleComposites();
let unpinPromise: TPromise<any>;
// remove from pinned
const index = this.pinnedComposites.indexOf(compositeId);
this.pinnedComposites.splice(index, 1);
// Case: composite is not the active one or the active one is a different one
// Solv: we do nothing
if (!this.activeCompositeId || this.activeCompositeId !== compositeId) {
unpinPromise = TPromise.as(null);
}
// Case: composite is not the default composite and default composite is still showing
// Solv: we open the default composite
else if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
unpinPromise = this.options.openComposite(defaultCompositeId);
}
// Case: we closed the last visible composite
// Solv: we hide the part
else if (visibleComposites.length === 1) {
unpinPromise = this.options.hidePart();
}
// Case: we closed the default composite
// Solv: we open the next visible composite from top
else {
unpinPromise = this.options.openComposite(visibleComposites.filter(cid => cid !== compositeId)[0]);
}
unpinPromise.then(() => {
this.updateCompositeSwitcher();
});
// Persist
this.saveCompositesStates();
}
public isPinned(compositeId: string): boolean {
return this.pinnedComposites.indexOf(compositeId) >= 0;
}
public pin(compositeId: string, update = true, index = this.pinnedComposites.length, activate: boolean = true): void {
if (this.isPinned(compositeId)) {
return;
}
const activatePromise = activate ? this.options.openComposite(compositeId) : TPromise.as(null);
activatePromise.then(() => {
this.pinnedComposites.splice(index, 0, compositeId);
this.pinnedComposites = arrays.distinct(this.pinnedComposites);
if (update) {
this.updateCompositeSwitcher();
}
// Persist
this.saveCompositesStates();
this.contextMenuService.showContextMenu({
getAnchor: () => { return { x: event.posx, y: event.posy }; },
getActions: () => TPromise.as(actions),
});
}
public move(compositeId: string, toCompositeId: string): void {
// Make sure both composites are known to this composite bar
if (this.options.composites.filter(c => c.id === compositeId || c.id === toCompositeId).length !== 2) {
return;
}
// Make sure a moved composite gets pinned
if (!this.isPinned(compositeId)) {
this.pin(compositeId, false /* defer update, we take care of it */);
}
const fromIndex = this.pinnedComposites.indexOf(compositeId);
const toIndex = this.pinnedComposites.indexOf(toCompositeId);
this.pinnedComposites.splice(fromIndex, 1);
this.pinnedComposites.splice(toIndex, 0, compositeId);
// Clear composites that are impacted by the move
const visibleComposites = Object.keys(this.compositeIdToActions);
for (let i = Math.min(fromIndex, toIndex); i < visibleComposites.length; i++) {
this.pullComposite(visibleComposites[i]);
}
// timeout helps to prevent artifacts from showing up
setTimeout(() => {
this.updateCompositeSwitcher();
}, 0);
// Persist
this.saveCompositesStates();
}
public layout(dimension: Dimension): void {
this.dimension = dimension;
if (dimension.height === 0 || dimension.width === 0) {
// Do not layout if not visible. Otherwise the size measurment would be computed wrongly
return;
}
if (this.compositeSizeInBar.size === 0) {
// Compute size of each composite by getting the size from the css renderer
// Size is later used for overflow computation
this.compositeSwitcherBar.clear();
this.compositeSwitcherBar.push(this.options.composites.map(c => this.options.getActivityAction(c.id)));
this.options.composites.map((c, index) => this.compositeSizeInBar.set(c.id, this.options.orientation === ActionsOrientation.VERTICAL
? this.compositeSwitcherBar.getHeight(index)
: this.compositeSwitcherBar.getWidth(index)
));
this.compositeSwitcherBar.clear();
}
this.updateCompositeSwitcher();
}
private loadCompositesStates(): CompositeState[] {
const storedStates = <Array<string | CompositeState>>JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, '[]'));
const isOldData = storedStates && storedStates.length && typeof storedStates[0] === 'string';
const compositeStates = <CompositeState[]>storedStates.map(c =>
private loadCompositeItemsFromStorage(): ISerializedCompositeBarItem[] {
const storedStates = <Array<string | ISerializedCompositeBarItem>>JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, '[]'));
const compositeStates = <ISerializedCompositeBarItem[]>storedStates.map(c =>
typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true } : c);
if (!isOldData) { /* Add new composites only if it is new data */
const newComposites = this.options.composites.filter(c => compositeStates.every(s => s.id !== c.id));
newComposites.sort((c1, c2) => c1.order < c2.order ? -1 : 1);
newComposites.forEach(c => compositeStates.push({ id: c.id, pinned: true /* new composites are pinned by default */ }));
}
return compositeStates;
}
private saveCompositesStates(): void {
const toSave = this.pinnedComposites.map(id => (<CompositeState>{ id, pinned: true }));
for (const composite of this.options.composites) {
if (this.pinnedComposites.indexOf(composite.id) === -1) { // Unpinned composites
toSave.push({ id: composite.id, pinned: false });
}
}
this.storageService.store(this.options.storageId, JSON.stringify(toSave), StorageScope.GLOBAL);
}
public shutdown(): void {
this.saveCompositesStates();
}
public dispose(): void {
this.toDispose = dispose(this.toDispose);
private saveCompositeItems(): void {
this.storedState = this.model.toJSON();
this.storageService.store(this.options.storageId, JSON.stringify(this.storedState), StorageScope.GLOBAL);
}
}
interface ISerializedCompositeBarItem {
id: string;
pinned: boolean;
order: number;
}
interface ICompositeBarItem extends ISerializedCompositeBarItem {
name: string;
activityAction: ActivityAction;
pinnedAction: Action;
activity: ICompositeActivity[];
}
class CompositeBarModel {
readonly items: ICompositeBarItem[] = [];
activeItem: ICompositeBarItem;
constructor(private options: ICompositeBarOptions) { }
private createCompositeBarItem(id: string, name: string, order: number, pinned: boolean): ICompositeBarItem {
const options = this.options;
return {
id, name, pinned, order, activity: [],
get activityAction() {
return options.getActivityAction(id);
},
get pinnedAction() {
return options.getCompositePinnedAction(id);
}
};
}
add(id: string, name: string, order: number, index: number): boolean {
const item = this.findItem(id);
if (item) {
item.order = order;
item.name = name;
return false;
} else {
if (index === void 0) {
index = 0;
while (index < this.items.length && this.items[index].order < order) {
index++;
}
}
this.items.splice(index, 0, this.createCompositeBarItem(id, name, order, false));
return true;
}
}
remove(id: string): boolean {
for (let index = 0; index < this.items.length; index++) {
if (this.items[index].id === id) {
this.items.splice(index, 1);
return true;
}
}
return false;
}
move(compositeId: string, toCompositeId: string): boolean {
const fromIndex = this.findIndex(compositeId);
const toIndex = this.findIndex(toCompositeId);
// Make sure both items are known to the model
if (fromIndex === -1 || toIndex === -1) {
return false;
}
const sourceItem = this.items.splice(fromIndex, 1)[0];
this.items.splice(toIndex, 0, sourceItem);
// Make sure a moved composite gets pinned
sourceItem.pinned = true;
return true;
}
setPinned(id: string, pinned: boolean): boolean {
for (let index = 0; index < this.items.length; index++) {
const item = this.items[index];
if (item.id === id) {
if (item.pinned !== pinned) {
item.pinned = pinned;
return true;
}
return false;
}
}
return false;
}
addActivity(id: string, activity: ICompositeActivity): boolean {
const item = this.findItem(id);
if (item) {
const stack = item.activity;
for (let i = 0; i <= stack.length; i++) {
if (i === stack.length) {
stack.push(activity);
break;
} else if (stack[i].priority <= activity.priority) {
stack.splice(i, 0, activity);
break;
}
}
this.updateActivity(id);
return true;
}
return false;
}
removeActivity(id: string, activity: ICompositeActivity): boolean {
const item = this.findItem(id);
if (item) {
const index = item.activity.indexOf(activity);
if (index !== -1) {
item.activity.splice(index, 1);
this.updateActivity(id);
return true;
}
}
return false;
}
updateActivity(id: string): void {
const item = this.findItem(id);
if (item) {
if (item.activity.length) {
const [{ badge, clazz }] = item.activity;
item.activityAction.setBadge(badge, clazz);
}
else {
item.activityAction.setBadge(undefined);
}
}
}
activate(id: string): boolean {
if (!this.activeItem || this.activeItem.id !== id) {
if (this.activeItem) {
this.deactivate();
}
for (let index = 0; index < this.items.length; index++) {
const item = this.items[index];
if (item.id === id) {
this.activeItem = item;
this.activeItem.activityAction.activate();
return true;
}
}
}
return false;
}
deactivate(): boolean {
if (this.activeItem) {
this.activeItem.activityAction.deactivate();
this.activeItem = void 0;
return true;
}
return false;
}
findItem(id: string): ICompositeBarItem {
return this.items.filter(item => item.id === id)[0];
}
findIndex(id: string): number {
for (let index = 0; index < this.items.length; index++) {
if (this.items[index].id === id) {
return index;
}
}
return -1;
}
toJSON(): ISerializedCompositeBarItem[] {
return this.items.map(({ id, pinned, order }) => ({ id, pinned, order }));
}
}

View File

@@ -12,7 +12,7 @@ import * as dom from 'vs/base/browser/dom';
import { Builder, $ } from 'vs/base/browser/builder';
import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { dispose, IDisposable, empty, toDisposable } from 'vs/base/common/lifecycle';
import { dispose, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
@@ -52,9 +52,15 @@ export interface ICompositeBar {
}
export class ActivityAction extends Action {
private _onDidChangeActivity = new Emitter<this>();
get onDidChangeActivity(): Event<this> { return this._onDidChangeActivity.event; }
private _onDidChangeBadge = new Emitter<this>();
get onDidChangeBadge(): Event<this> { return this._onDidChangeBadge.event; }
private badge: IBadge;
private clazz: string | undefined;
private _onDidChangeBadge = new Emitter<this>();
constructor(private _activity: IActivity) {
super(_activity.id, _activity.name, _activity.cssClass);
@@ -62,39 +68,47 @@ export class ActivityAction extends Action {
this.badge = null;
}
public get activity(): IActivity {
get activity(): IActivity {
return this._activity;
}
public get onDidChangeBadge(): Event<this> {
return this._onDidChangeBadge.event;
set activity(activity: IActivity) {
this._activity = activity;
this._onDidChangeActivity.fire(this);
}
public activate(): void {
activate(): void {
if (!this.checked) {
this._setChecked(true);
}
}
public deactivate(): void {
deactivate(): void {
if (this.checked) {
this._setChecked(false);
}
}
public getBadge(): IBadge {
getBadge(): IBadge {
return this.badge;
}
public getClass(): string | undefined {
getClass(): string | undefined {
return this.clazz;
}
public setBadge(badge: IBadge, clazz?: string): void {
setBadge(badge: IBadge, clazz?: string): void {
this.badge = badge;
this.clazz = clazz;
this._onDidChangeBadge.fire(this);
}
dispose(): void {
this._onDidChangeActivity.dispose();
this._onDidChangeBadge.dispose();
super.dispose();
}
}
export interface ICompositeBarColors {
@@ -116,7 +130,7 @@ export class ActivityActionItem extends BaseActionItem {
protected options: IActivityActionItemOptions;
private $badgeContent: Builder;
private badgeDisposable: IDisposable = empty;
private badgeDisposable: IDisposable = Disposable.None;
private mouseUpTimeout: number;
constructor(
@@ -127,7 +141,8 @@ export class ActivityActionItem extends BaseActionItem {
super(null, action, options);
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose);
action.onDidChangeActivity(this.updateActivity, this, this._callOnDispose);
action.onDidChangeBadge(this.updateBadge, this, this._callOnDispose);
}
protected get activity(): IActivity {
@@ -159,14 +174,13 @@ export class ActivityActionItem extends BaseActionItem {
}
}
public render(container: HTMLElement): void {
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
role: 'button'
});
// Try hard to prevent keyboard only focus feedback when using mouse
@@ -186,19 +200,13 @@ export class ActivityActionItem extends BaseActionItem {
// Label
this.$label = $('a.action-label').appendTo(this.builder);
if (this.activity.cssClass) {
this.$label.addClass(this.activity.cssClass);
}
if (!this.options.icon) {
this.$label.text(this.getAction().label);
}
this.$badge = this.builder.clone().div({ 'class': 'badge' }, badge => {
this.$badgeContent = badge.div({ 'class': 'badge-content' });
});
this.$badge.hide();
this.updateActivity();
this.updateStyles();
}
@@ -206,13 +214,23 @@ export class ActivityActionItem extends BaseActionItem {
this.updateStyles();
}
protected updateBadge(badge: IBadge, clazz?: string): void {
if (!this.$badge || !this.$badgeContent) {
protected updateActivity(): void {
this.updateLabel();
this.updateTitle(this.activity.name);
this.updateBadge();
}
protected updateBadge(): void {
const action = this.getAction();
if (!this.$badge || !this.$badgeContent || !(action instanceof ActivityAction)) {
return;
}
const badge = action.getBadge();
const clazz = action.getClass();
this.badgeDisposable.dispose();
this.badgeDisposable = empty;
this.badgeDisposable = Disposable.None;
this.$badgeContent.empty();
this.$badge.hide();
@@ -266,7 +284,19 @@ export class ActivityActionItem extends BaseActionItem {
} else {
title = this.activity.name;
}
this.updateTitle(title);
}
private updateLabel(): void {
if (this.activity.cssClass) {
this.$label.addClass(this.activity.cssClass);
}
if (!this.options.icon) {
this.$label.text(this.getAction().label);
}
}
private updateTitle(title: string): void {
[this.$label, this.$badge, this.$container].forEach(b => {
if (b) {
b.attr('aria-label', title);
@@ -275,14 +305,7 @@ export class ActivityActionItem extends BaseActionItem {
});
}
private handleBadgeChangeEvenet(): void {
const action = this.getAction();
if (action instanceof ActivityAction) {
this.updateBadge(action.getBadge(), action.getClass());
}
}
public dispose(): void {
dispose(): void {
super.dispose();
if (this.mouseUpTimeout) {
@@ -305,7 +328,7 @@ export class CompositeOverflowActivityAction extends ActivityAction {
});
}
public run(event: any): TPromise<any> {
run(event: any): TPromise<any> {
this.showMenu();
return TPromise.as(true);
@@ -328,7 +351,7 @@ export class CompositeOverflowActivityActionItem extends ActivityActionItem {
super(action, { icon: true, colors }, themeService);
}
public showMenu(): void {
showMenu(): void {
if (this.actions) {
dispose(this.actions);
}
@@ -365,7 +388,7 @@ export class CompositeOverflowActivityActionItem extends ActivityActionItem {
});
}
public dispose(): void {
dispose(): void {
super.dispose();
this.actions = dispose(this.actions);
@@ -380,7 +403,7 @@ class ManageExtensionAction extends Action {
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
}
public run(id: string): TPromise<any> {
run(id: string): TPromise<any> {
return this.commandService.executeCommand('_extensions.manage', id);
}
}
@@ -411,6 +434,8 @@ export class CompositeActionItem extends ActivityActionItem {
if (!CompositeActionItem.manageExtensionAction) {
CompositeActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
}
compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = null; this.updateActivity(); }, this, this._callOnDispose);
}
protected get activity(): IActivity {
@@ -442,9 +467,12 @@ export class CompositeActionItem extends ActivityActionItem {
return null;
}
public render(container: HTMLElement): void {
render(container: HTMLElement): void {
super.render(container);
this._updateChecked();
this._updateEnabled();
this.$container.on('contextmenu', e => {
dom.EventHelper.stop(e, true);
@@ -524,7 +552,7 @@ export class CompositeActionItem extends ActivityActionItem {
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
}
public static getDraggedCompositeId(): string {
static getDraggedCompositeId(): string {
return CompositeActionItem.draggedCompositeId;
}
@@ -532,7 +560,7 @@ export class CompositeActionItem extends ActivityActionItem {
CompositeActionItem.draggedCompositeId = compositeId;
}
public static clearDraggedComposite(): void {
static clearDraggedComposite(): void {
CompositeActionItem.draggedCompositeId = void 0;
}
@@ -558,24 +586,28 @@ export class CompositeActionItem extends ActivityActionItem {
});
}
public focus(): void {
focus(): void {
this.$container.domFocus();
}
protected _updateClass(): void {
if (this.cssClass) {
this.$badge.removeClass(this.cssClass);
this.$label.removeClass(this.cssClass);
}
this.cssClass = this.getAction().class;
this.$badge.addClass(this.cssClass);
if (this.cssClass) {
this.$label.addClass(this.cssClass);
}
}
protected _updateChecked(): void {
if (this.getAction().checked) {
this.$container.addClass('checked');
this.$container.attr('aria-label', nls.localize('compositeActive', "{0} active", this.$container.getHTMLElement().title));
} else {
this.$container.removeClass('checked');
this.$container.attr('aria-label', this.$container.getHTMLElement().title);
}
}
@@ -587,7 +619,7 @@ export class CompositeActionItem extends ActivityActionItem {
}
}
public dispose(): void {
dispose(): void {
super.dispose();
CompositeActionItem.clearDraggedComposite();
@@ -607,7 +639,7 @@ export class ToggleCompositePinnedAction extends Action {
this.checked = this.activity && this.compositeBar.isPinned(this.activity.id);
}
public run(context: string): TPromise<any> {
run(context: string): TPromise<any> {
const id = this.activity ? this.activity.id : context;
if (this.compositeBar.isPinned(id)) {

View File

@@ -6,16 +6,24 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { Panel } from 'vs/workbench/browser/panel';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { IEditor, Position } from 'vs/platform/editor/common/editor';
import { EditorInput, EditorOptions, IEditor, GroupIdentifier, IEditorMemento } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { LRUCache } from 'vs/base/common/map';
import URI from 'vs/base/common/uri';
import { once, Event } from 'vs/base/common/event';
import { isEmptyObject } from 'vs/base/common/types';
import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
/**
* 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.
* Editors are layed out in the editor part of the workbench in editor groups. Multiple editors can be
* open at the same 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
@@ -24,30 +32,53 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
* 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) {
private static readonly EDITOR_MEMENTOS: Map<string, EditorMemento<any>> = new Map<string, EditorMemento<any>>();
readonly minimumWidth = DEFAULT_EDITOR_MIN_DIMENSIONS.width;
readonly maximumWidth = DEFAULT_EDITOR_MAX_DIMENSIONS.width;
readonly minimumHeight = DEFAULT_EDITOR_MIN_DIMENSIONS.height;
readonly maximumHeight = DEFAULT_EDITOR_MAX_DIMENSIONS.height;
readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; }> = Event.None;
protected _input: EditorInput;
private _options: EditorOptions;
private _group: IEditorGroup;
constructor(
id: string,
telemetryService: ITelemetryService,
themeService: IThemeService
) {
super(id, telemetryService, themeService);
}
public get input(): EditorInput {
get input(): EditorInput {
return this._input;
}
public get options(): EditorOptions {
get options(): EditorOptions {
return this._options;
}
get group(): IEditorGroup {
return this._group;
}
/**
* 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.
* Sets the given input with the options to the editor. The input is guaranteed
* to be different from the previous input that was set using the input.matches()
* method.
*
* The provided cancellation token should be used to test if the operation
* was cancelled.
*/
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
this._input = input;
this._options = options;
@@ -55,17 +86,28 @@ export abstract class BaseEditor extends Panel implements IEditor {
}
/**
* Called to indicate to the editor that the input should be cleared and resources associated with the
* input should be freed.
* Called to indicate to the editor that the input should be cleared and
* resources associated with the input should be freed.
*/
public clearInput(): void {
clearInput(): void {
this._input = null;
this._options = null;
}
public create(parent: HTMLElement): void; // create is sync for editors
public create(parent: HTMLElement): TPromise<void>;
public create(parent: HTMLElement): TPromise<void> {
/**
* Note: Clients should not call this method, the workbench calls this
* method. Calling it otherwise may result in unexpected behavior.
*
* Sets the given options to the editor. Clients should apply the options
* to the current input.
*/
setOptions(options: EditorOptions): void {
this._options = options;
}
create(parent: HTMLElement): void; // create is sync for editors
create(parent: HTMLElement): TPromise<void>;
create(parent: HTMLElement): TPromise<void> {
const res = super.create(parent);
// Create Editor
@@ -79,50 +121,183 @@ export abstract class BaseEditor extends Panel implements IEditor {
*/
protected abstract createEditor(parent: HTMLElement): void;
/**
* Subclasses can set this to false if it does not make sense to center editor input.
*/
public supportsCenteredLayout(): boolean {
return true;
}
/**
* 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> {
setVisible(visible: boolean, group?: IEditorGroup): void; // setVisible is sync for editors
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void>;
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void> {
const promise = super.setVisible(visible);
// Propagate to Editor
this.setEditorVisible(visible, position);
this.setEditorVisible(visible, group);
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.
* Indicates that the editor control got visible or hidden in a specific group. A
* editor instance will only ever be visible in one editor group.
*
* @param visible the state of visibility of this editor
* @param group the editor group this editor is in.
*/
public changePosition(position: Position): void {
this._position = position;
protected setEditorVisible(visible: boolean, group: IEditorGroup): void {
this._group = group;
}
/**
* The position this editor is showing in or null if none.
*/
public get position(): Position {
return this._position;
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
const mementoKey = `${this.getId()}${key}`;
let editorMemento = BaseEditor.EDITOR_MEMENTOS.get(mementoKey);
if (!editorMemento) {
editorMemento = new EditorMemento(this.getId(), key, this.getMemento(storageService), limit, editorGroupService);
BaseEditor.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
}
return editorMemento;
}
public dispose(): void {
shutdown(): void {
// Shutdown all editor memento for this editor type
BaseEditor.EDITOR_MEMENTOS.forEach(editorMemento => {
if (editorMemento.id === this.getId()) {
editorMemento.shutdown();
}
});
super.shutdown();
}
dispose(): void {
this._input = null;
this._options = null;
// Super Dispose
super.dispose();
}
}
interface MapGroupToMemento<T> {
[group: number]: T;
}
export class EditorMemento<T> implements IEditorMemento<T> {
private cache: LRUCache<string, MapGroupToMemento<T>>;
private cleanedUp = false;
constructor(
private _id: string,
private key: string,
private memento: object,
private limit: number,
private editorGroupService: IEditorGroupsService
) { }
get id(): string {
return this._id;
}
saveState(group: IEditorGroup, resource: URI, state: T): void;
saveState(group: IEditorGroup, editor: EditorInput, state: T): void;
saveState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void {
const resource = this.doGetResource(resourceOrEditor);
if (!resource || !group) {
return; // we are not in a good state to save any state for a resource
}
const cache = this.doLoad();
let mementoForResource = cache.get(resource.toString());
if (!mementoForResource) {
mementoForResource = Object.create(null) as MapGroupToMemento<T>;
cache.set(resource.toString(), mementoForResource);
}
mementoForResource[group.id] = state;
// Automatically clear when editor input gets disposed if any
if (resourceOrEditor instanceof EditorInput) {
once(resourceOrEditor.onDispose)(() => {
this.clearState(resource);
});
}
}
loadState(group: IEditorGroup, resource: URI): T;
loadState(group: IEditorGroup, editor: EditorInput): T;
loadState(group: IEditorGroup, resourceOrEditor: URI | EditorInput): T {
const resource = this.doGetResource(resourceOrEditor);
if (!resource || !group) {
return void 0; // we are not in a good state to load any state for a resource
}
const cache = this.doLoad();
const mementoForResource = cache.get(resource.toString());
if (mementoForResource) {
return mementoForResource[group.id];
}
return void 0;
}
clearState(resource: URI): void;
clearState(editor: EditorInput): void;
clearState(resourceOrEditor: URI | EditorInput): void {
const resource = this.doGetResource(resourceOrEditor);
if (resource) {
const cache = this.doLoad();
cache.delete(resource.toString());
}
}
private doGetResource(resourceOrEditor: URI | EditorInput): URI {
if (resourceOrEditor instanceof EditorInput) {
return resourceOrEditor.getResource();
}
return resourceOrEditor;
}
private doLoad(): LRUCache<string, MapGroupToMemento<T>> {
if (!this.cache) {
this.cache = new LRUCache<string, MapGroupToMemento<T>>(this.limit);
// Restore from serialized map state
const rawEditorMemento = this.memento[this.key];
if (Array.isArray(rawEditorMemento)) {
this.cache.fromJSON(rawEditorMemento);
}
}
return this.cache;
}
shutdown(): void {
const cache = this.doLoad();
// Cleanup once during shutdown
if (!this.cleanedUp) {
this.cleanUp();
this.cleanedUp = true;
}
this.memento[this.key] = cache.toJSON();
}
private cleanUp(): void {
const cache = this.doLoad();
// Remove groups from states that no longer exist
cache.forEach((mapGroupToMemento, resource) => {
Object.keys(mapGroupToMemento).forEach(group => {
const groupId: GroupIdentifier = Number(group);
if (!this.editorGroupService.getGroup(groupId)) {
delete mapGroupToMemento[groupId];
if (isEmptyObject(mapGroupToMemento)) {
cache.delete(resource);
}
}
});
});
}
}

View File

@@ -18,7 +18,7 @@ import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/bina
*/
export class BinaryResourceDiffEditor extends SideBySideEditor {
public static readonly ID = BINARY_DIFF_EDITOR_ID;
static readonly ID = BINARY_DIFF_EDITOR_ID;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@@ -28,7 +28,7 @@ export class BinaryResourceDiffEditor extends SideBySideEditor {
super(telemetryService, instantiationService, themeService);
}
public getMetadata(): string {
getMetadata(): string {
const master = this.masterEditor;
const details = this.detailsEditor;

View File

@@ -19,6 +19,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer';
import URI from 'vs/base/common/uri';
import { Dimension } from 'vs/base/browser/dom';
import { IFileService } from 'vs/platform/files/common/files';
import { CancellationToken } from 'vs/base/common/cancellation';
export interface IOpenCallbacks {
openInternal: (input: EditorInput, options: EditorOptions) => void;
@@ -30,7 +32,8 @@ export interface IOpenCallbacks {
*/
export abstract class BaseBinaryResourceEditor extends BaseEditor {
private readonly _onMetadataChanged: Emitter<void>;
private readonly _onMetadataChanged: Emitter<void> = this._register(new Emitter<void>());
get onMetadataChanged(): Event<void> { return this._onMetadataChanged.event; }
private callbacks: IOpenCallbacks;
private metadata: string;
@@ -42,21 +45,15 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
id: string,
callbacks: IOpenCallbacks,
telemetryService: ITelemetryService,
themeService: IThemeService
themeService: IThemeService,
@IFileService private readonly _fileService: IFileService,
) {
super(id, telemetryService, themeService);
this._onMetadataChanged = new Emitter<void>();
this.toUnbind.push(this._onMetadataChanged);
this.callbacks = callbacks;
}
public get onMetadataChanged(): Event<void> {
return this._onMetadataChanged.event;
}
public getTitle(): string {
getTitle(): string {
return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer");
}
@@ -70,35 +67,28 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
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 });
this.scrollbar = this._register(new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto }));
parent.appendChild(this.scrollbar.getDomNode());
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
return super.setInput(input, options, token).then(() => {
return input.resolve().then(model => {
// Return early for same input unless we force to open
const forceOpen = options && options.forceOpen;
if (!forceOpen && input.matches(this.input)) {
return TPromise.wrap<void>(null);
}
// Otherwise set input and resolve
return super.setInput(input, options).then(() => {
return input.resolve(true).then(model => {
// Check for cancellation
if (token.isCancellationRequested) {
return void 0;
}
// Assert Model instance
if (!(model 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
this.resourceViewerContext = ResourceViewer.show(
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() },
this._fileService,
this.binaryContainer.getHTMLElement(),
this.scrollbar,
resource => this.callbacks.openInternal(input, options),
@@ -106,25 +96,22 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
meta => this.handleMetadataChanged(meta)
);
return TPromise.as<void>(null);
return void 0;
});
});
}
private handleMetadataChanged(meta: string): void {
this.metadata = meta;
this._onMetadataChanged.fire();
}
public getMetadata(): string {
getMetadata(): string {
return this.metadata;
}
public supportsCenteredLayout(): boolean {
return false;
}
public clearInput(): void {
clearInput(): void {
// Clear Meta
this.handleMetadataChanged(null);
@@ -135,7 +122,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
super.clearInput();
}
public layout(dimension: Dimension): void {
layout(dimension: Dimension): void {
// Pass on to Binary Container
this.binaryContainer.size(dimension.width, dimension.height);
@@ -145,15 +132,14 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
}
}
public focus(): void {
focus(): void {
this.binaryContainer.domFocus();
}
public dispose(): void {
dispose(): void {
// Destroy Container
this.binaryContainer.destroy();
this.scrollbar.dispose();
super.dispose();
}

View File

@@ -0,0 +1,147 @@
/*---------------------------------------------------------------------------------------------
* 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 { BreadcrumbsWidget } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
import { IDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { GroupIdentifier } from 'vs/workbench/common/editor';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Emitter, Event } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { localize } from 'vs/nls';
export const IBreadcrumbsService = createDecorator<IBreadcrumbsService>('IEditorBreadcrumbsService');
export interface IBreadcrumbsService {
_serviceBrand: any;
register(group: GroupIdentifier, widget: BreadcrumbsWidget): IDisposable;
getWidget(group: GroupIdentifier): BreadcrumbsWidget;
}
export class BreadcrumbsService implements IBreadcrumbsService {
_serviceBrand: any;
private readonly _map = new Map<number, BreadcrumbsWidget>();
register(group: number, widget: BreadcrumbsWidget): IDisposable {
if (this._map.has(group)) {
throw new Error(`group (${group}) has already a widget`);
}
this._map.set(group, widget);
return {
dispose: () => this._map.delete(group)
};
}
getWidget(group: number): BreadcrumbsWidget {
return this._map.get(group);
}
}
registerSingleton(IBreadcrumbsService, BreadcrumbsService);
//#region config
export abstract class BreadcrumbsConfig<T> {
name: string;
value: T;
onDidChange: Event<T>;
abstract dispose(): void;
private constructor() {
// internal
}
static IsEnabled = BreadcrumbsConfig._stub<boolean>('breadcrumbs.enabled');
static UseQuickPick = BreadcrumbsConfig._stub<boolean>('breadcrumbs.useQuickPick');
static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath');
static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath');
private static _stub<T>(name: string): { bindTo(service: IConfigurationService): BreadcrumbsConfig<T> } {
return {
bindTo(service) {
let value: T = service.getValue(name);
let onDidChange = new Emitter<T>();
let listener = service.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(name)) {
value = service.getValue(name);
onDidChange.fire(value);
}
});
return {
name,
get value() {
return value;
},
set value(newValue: T) {
service.updateValue(name, newValue);
value = newValue;
},
onDidChange: onDidChange.event,
dispose(): void {
listener.dispose();
onDidChange.dispose();
}
};
}
};
}
}
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
id: 'breadcrumbs',
title: localize('title', "Breadcrumb Navigation"),
order: 101,
type: 'object',
properties: {
'breadcrumbs.enabled': {
description: localize('enabled', "Enable/disable navigation breadcrumbs"),
type: 'boolean',
default: false
},
// 'breadcrumbs.useQuickPick': {
// description: localize('useQuickPick', "Use quick pick instead of breadcrumb-pickers."),
// type: 'boolean',
// default: false
// },
'breadcrumbs.filePath': {
description: localize('filepath', "Controls whether and how file paths are shown in the breadcrumbs view."),
type: 'string',
default: 'on',
enum: ['on', 'off', 'last'],
enumDescriptions: [
localize('filepath.on', "Show the file path in the breadcrumbs view."),
localize('filepath.off', "Do not show the file path in the breadcrumbs view."),
localize('filepath.last', "Only show the last element of the file path in the breadcrumbs view."),
]
},
'breadcrumbs.symbolPath': {
description: localize('symbolpath', "Controls whether and how symbols are shown in the breadcrumbs view."),
type: 'string',
default: 'on',
enum: ['on', 'off', 'last'],
enumDescriptions: [
localize('symbolpath.on', "Show all symbols in the breadcrumbs view."),
localize('symbolpath.off', "Do not show symbols in the breadcrumbs view."),
localize('symbolpath.last', "Only show the current symbol in the breadcrumbs view."),
]
},
}
});
//#endregion

View File

@@ -0,0 +1,501 @@
/*---------------------------------------------------------------------------------------------
* 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 * as dom from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { isEqual } from 'vs/base/common/resources';
import 'vs/css!./media/breadcrumbscontrol';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
import { symbolKindToCssClass } from 'vs/editor/common/modes';
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { FileKind, IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { FileLabel } from 'vs/workbench/browser/labels';
import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { createBreadcrumbsPicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
import { IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { tail } from 'vs/base/common/arrays';
import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
class Item extends BreadcrumbsItem {
private readonly _disposables: IDisposable[] = [];
constructor(
readonly element: BreadcrumbElement,
readonly options: IBreadcrumbsControlOptions,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super();
}
dispose(): void {
dispose(this._disposables);
}
equals(other: BreadcrumbsItem): boolean {
if (!(other instanceof Item)) {
return false;
}
if (this.element instanceof FileElement && other.element instanceof FileElement) {
return isEqual(this.element.uri, other.element.uri);
}
if (this.element instanceof TreeElement && other.element instanceof TreeElement) {
return this.element.id === other.element.id;
}
return false;
}
render(container: HTMLElement): void {
if (this.element instanceof FileElement) {
// file/folder
let label = this._instantiationService.createInstance(FileLabel, container, {});
label.setFile(this.element.uri, {
hidePath: true,
hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons,
fileKind: this.element.kind,
fileDecorations: { colors: this.options.showDecorationColors, badges: false },
});
dom.addClass(container, FileKind[this.element.kind].toLowerCase());
this._disposables.push(label);
} else if (this.element instanceof OutlineModel) {
// has outline element but not in one
let label = document.createElement('div');
label.innerHTML = '&hellip;';
label.className = 'hint-more';
container.appendChild(label);
} else if (this.element instanceof OutlineGroup) {
// provider
let label = new IconLabel(container);
label.setValue(this.element.provider.displayName);
this._disposables.push(label);
} else if (this.element instanceof OutlineElement) {
// symbol
if (this.options.showSymbolIcons) {
let icon = document.createElement('div');
icon.className = symbolKindToCssClass(this.element.symbol.kind);
container.appendChild(icon);
dom.addClass(container, 'shows-symbol-icon');
}
let label = new IconLabel(container);
let title = this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE');
label.setValue(title);
this._disposables.push(label);
}
}
}
export interface IBreadcrumbsControlOptions {
showFileIcons: boolean;
showSymbolIcons: boolean;
showDecorationColors: boolean;
extraClasses: string[];
}
export class BreadcrumbsControl {
static HEIGHT = 25;
static readonly Payload_Reveal = {};
static readonly Payload_RevealAside = {};
static readonly Payload_Pick = {};
static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false);
static CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false);
private readonly _ckBreadcrumbsVisible: IContextKey<boolean>;
private readonly _ckBreadcrumbsActive: IContextKey<boolean>;
private readonly _cfUseQuickPick: BreadcrumbsConfig<boolean>;
readonly domNode: HTMLDivElement;
private readonly _widget: BreadcrumbsWidget;
private _disposables = new Array<IDisposable>();
private _breadcrumbsDisposables = new Array<IDisposable>();
private _breadcrumbsPickerShowing = false;
constructor(
container: HTMLElement,
private readonly _options: IBreadcrumbsControlOptions,
private readonly _editorGroup: EditorGroupView,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IContextViewService private readonly _contextViewService: IContextViewService,
@IEditorService private readonly _editorService: IEditorService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IThemeService private readonly _themeService: IThemeService,
@IQuickOpenService private readonly _quickOpenService: IQuickOpenService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IFileService private readonly _fileService: IFileService,
@IBreadcrumbsService breadcrumbsService: IBreadcrumbsService,
) {
this.domNode = document.createElement('div');
dom.addClass(this.domNode, 'breadcrumbs-control');
dom.addClasses(this.domNode, ..._options.extraClasses);
dom.append(container, this.domNode);
this._widget = new BreadcrumbsWidget(this.domNode);
this._widget.onDidSelectItem(this._onSelectEvent, this, this._disposables);
this._widget.onDidFocusItem(this._onFocusEvent, this, this._disposables);
this._widget.onDidChangeFocus(this._updateCkBreadcrumbsActive, this, this._disposables);
this._disposables.push(attachBreadcrumbsStyler(this._widget, this._themeService));
this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService);
this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService);
this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService);
this._disposables.push(breadcrumbsService.register(this._editorGroup.id, this._widget));
}
dispose(): void {
this._disposables = dispose(this._disposables);
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
this._ckBreadcrumbsVisible.reset();
this._ckBreadcrumbsActive.reset();
this._cfUseQuickPick.dispose();
this._widget.dispose();
this.domNode.remove();
}
layout(dim: dom.Dimension): void {
this._widget.layout(dim);
}
isHidden(): boolean {
return dom.hasClass(this.domNode, 'hidden');
}
hide(): void {
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
this._ckBreadcrumbsVisible.set(false);
dom.toggleClass(this.domNode, 'hidden', true);
}
update(): boolean {
const input = this._editorGroup.activeEditor;
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
if (!input || !input.getResource() || (input.getResource().scheme !== Schemas.untitled && !this._fileService.canHandleResource(input.getResource()))) {
// cleanup and return when there is no input or when
// we cannot handle this input
if (!this.isHidden()) {
this.hide();
return true;
} else {
return false;
}
}
dom.toggleClass(this.domNode, 'hidden', false);
this._ckBreadcrumbsVisible.set(true);
let control = this._editorGroup.activeControl.getControl() as ICodeEditor;
let model = new EditorBreadcrumbsModel(input.getResource(), isCodeEditor(control) ? control : undefined, this._workspaceService, this._configurationService);
dom.toggleClass(this.domNode, 'relative-path', model.isRelative());
let updateBreadcrumbs = () => {
let items = model.getElements().map(element => new Item(element, this._options, this._instantiationService));
this._widget.setItems(items);
this._widget.reveal(items[items.length - 1]);
};
let listener = model.onDidUpdate(updateBreadcrumbs);
updateBreadcrumbs();
this._breadcrumbsDisposables = [model, listener];
// close picker on hide/update
this._breadcrumbsDisposables.push({
dispose: () => {
if (this._breadcrumbsPickerShowing) {
this._contextViewService.hideContextView(this);
}
}
});
return true;
}
private _onFocusEvent(event: IBreadcrumbsItemEvent): void {
if (event.item && this._breadcrumbsPickerShowing) {
return this._widget.setSelection(event.item);
}
}
private _onSelectEvent(event: IBreadcrumbsItemEvent): void {
if (!event.item) {
return;
}
this._editorGroup.focus();
const { element } = event.item as Item;
const group = this._getEditorGroup(event.payload);
if (group !== undefined) {
// reveal the item
this._widget.setFocused(undefined);
this._widget.setSelection(undefined);
this._revealInEditor(event, element, group);
return;
}
if (this._cfUseQuickPick.value) {
// using quick pick
this._widget.setFocused(undefined);
this._widget.setSelection(undefined);
this._quickOpenService.show(element instanceof TreeElement ? '@' : '');
return;
}
// show picker
let picker: BreadcrumbsPicker;
this._contextViewService.showContextView({
render: (parent: HTMLElement) => {
picker = createBreadcrumbsPicker(this._instantiationService, parent, element);
let listener = picker.onDidPickElement(data => {
this._contextViewService.hideContextView(this);
this._revealInEditor(event, data.target, this._getEditorGroup(data.payload && data.payload.originalEvent));
});
this._breadcrumbsPickerShowing = true;
this._updateCkBreadcrumbsActive();
return combinedDisposable([listener, picker]);
},
getAnchor() {
let pickerHeight = 330;
let pickerWidth = Math.max(220, dom.getTotalWidth(event.node));
let pickerArrowSize = 8;
let pickerArrowOffset: number;
let data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement);
let y = data.top + data.height - pickerArrowSize;
let x = data.left;
if (x + pickerWidth >= window.innerWidth) {
x = window.innerWidth - pickerWidth;
}
if (event.payload instanceof StandardMouseEvent) {
pickerArrowOffset = event.payload.posx - x - pickerArrowSize;
} else {
pickerArrowOffset = (data.left + (data.width * .3)) - x;
}
picker.layout(pickerHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset));
picker.setInput(element);
return { x, y };
},
onHide: (data) => {
this._breadcrumbsPickerShowing = false;
this._updateCkBreadcrumbsActive();
if (data === this) {
this._widget.setFocused(undefined);
this._widget.setSelection(undefined);
}
}
});
}
private _updateCkBreadcrumbsActive(): void {
const value = this._widget.isDOMFocused() || this._breadcrumbsPickerShowing;
this._ckBreadcrumbsActive.set(value);
}
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): void {
if (element instanceof FileElement) {
if (element.kind === FileKind.FILE) {
// open file in editor
this._editorService.openEditor({ resource: element.uri }, group);
} else {
// show next picker
let items = this._widget.getItems();
let idx = items.indexOf(event.item);
this._widget.setFocused(items[idx + 1]);
this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick);
}
} else if (element instanceof OutlineElement) {
// open symbol in editor
let model = OutlineModel.get(element);
this._editorService.openEditor({
resource: model.textModel.uri,
options: { selection: Range.collapseToStart(element.symbol.selectionRange) }
}, group);
}
}
private _getEditorGroup(data: StandardMouseEvent | object): SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined {
if (data === BreadcrumbsControl.Payload_RevealAside || (data instanceof StandardMouseEvent && data.altKey)) {
return SIDE_GROUP;
} else if (data === BreadcrumbsControl.Payload_Reveal || (data instanceof StandardMouseEvent && data.metaKey)) {
return ACTIVE_GROUP;
} else {
return undefined;
}
}
}
//#region commands
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: 'breadcrumbs.focusAndSelect',
title: localize('cmd.focus', "Focus Breadcrumbs")
}
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: 'breadcrumbs.toggle',
title: localize('cmd.toggle', "Toggle Breadcrumbs")
}
});
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
group: '5_editor',
order: 99,
command: {
id: 'breadcrumbs.toggle',
title: localize('cmd.toggle', "Toggle Breadcrumbs")
}
});
CommandsRegistry.registerCommand('breadcrumbs.toggle', accessor => {
let config = accessor.get(IConfigurationService);
let value = BreadcrumbsConfig.IsEnabled.bindTo(config).value;
BreadcrumbsConfig.IsEnabled.bindTo(config).value = !value;
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focus',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON,
when: BreadcrumbsControl.CK_BreadcrumbsVisible,
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
const item = tail(widget.getItems());
widget.setFocused(item);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focusAndSelect',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT,
when: BreadcrumbsControl.CK_BreadcrumbsVisible,
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
const item = tail(widget.getItems());
widget.setFocused(item);
widget.setSelection(item, BreadcrumbsControl.Payload_Pick);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focusNext',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.RightArrow,
secondary: [KeyMod.CtrlCmd | KeyCode.RightArrow],
mac: {
primary: KeyCode.RightArrow,
secondary: [KeyMod.Alt | KeyCode.RightArrow],
},
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
breadcrumbs.getWidget(groups.activeGroup.id).focusNext();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.focusPrevious',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.LeftArrow,
secondary: [KeyMod.CtrlCmd | KeyCode.LeftArrow],
mac: {
primary: KeyCode.LeftArrow,
secondary: [KeyMod.Alt | KeyCode.LeftArrow],
},
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
breadcrumbs.getWidget(groups.activeGroup.id).focusPrev();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.selectFocused',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Enter,
secondary: [KeyCode.DownArrow],
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_Pick);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.revealFocused',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Space,
secondary: [KeyMod.CtrlCmd | KeyCode.Enter],
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_Reveal);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.selectEditor',
weight: KeybindingWeight.WorkbenchContrib + 1,
primary: KeyCode.Escape,
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
breadcrumbs.getWidget(groups.activeGroup.id).setFocused(undefined);
breadcrumbs.getWidget(groups.activeGroup.id).setSelection(undefined);
groups.activeGroup.activeControl.focus();
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.revealFocusedFromTreeAside',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyCode.Enter,
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey),
handler(accessor) {
const groups = accessor.get(IEditorGroupsService);
const breadcrumbs = accessor.get(IBreadcrumbsService);
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_RevealAside);
}
});
//#endregion

View File

@@ -0,0 +1,231 @@
/*---------------------------------------------------------------------------------------------
* 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 { equals } from 'vs/base/common/arrays';
import { TimeoutTimer } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { size } from 'vs/base/common/collections';
import { onUnexpectedError } from 'vs/base/common/errors';
import { debounceEvent, Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as paths from 'vs/base/common/paths';
import { isEqual } from 'vs/base/common/resources';
import URI from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IPosition } from 'vs/editor/common/core/position';
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { Schemas } from 'vs/base/common/network';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { FileKind } from 'vs/platform/files/common/files';
export class FileElement {
constructor(
readonly uri: URI,
readonly kind: FileKind
) { }
}
export type BreadcrumbElement = FileElement | OutlineModel | OutlineGroup | OutlineElement;
type FileInfo = { path: FileElement[], folder: IWorkspaceFolder };
export class EditorBreadcrumbsModel {
private readonly _disposables: IDisposable[] = [];
private readonly _fileInfo: FileInfo;
private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
private _outlineElements: (OutlineModel | OutlineGroup | OutlineElement)[] = [];
private _outlineDisposables: IDisposable[] = [];
private _onDidUpdate = new Emitter<this>();
readonly onDidUpdate: Event<this> = this._onDidUpdate.event;
constructor(
private readonly _uri: URI,
private readonly _editor: ICodeEditor | undefined,
@IWorkspaceContextService workspaceService: IWorkspaceContextService,
@IConfigurationService configurationService: IConfigurationService,
) {
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService);
this._disposables.push(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));
this._disposables.push(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));
this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(this._uri, workspaceService);
this._bindToEditor();
this._onDidUpdate.fire(this);
}
dispose(): void {
this._cfgFilePath.dispose();
this._cfgSymbolPath.dispose();
dispose(this._disposables);
}
isRelative(): boolean {
return Boolean(this._fileInfo.folder);
}
getElements(): ReadonlyArray<BreadcrumbElement> {
let result: BreadcrumbElement[] = [];
// file path elements
if (this._cfgFilePath.value === 'on') {
result = result.concat(this._fileInfo.path);
} else if (this._cfgFilePath.value === 'last' && this._fileInfo.path.length > 0) {
result = result.concat(this._fileInfo.path.slice(-1));
}
// symbol path elements
if (this._cfgSymbolPath.value === 'on') {
result = result.concat(this._outlineElements);
} else if (this._cfgSymbolPath.value === 'last' && this._outlineElements.length > 0) {
result = result.concat(this._outlineElements.slice(-1));
}
return result;
}
private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo {
if (uri.scheme === Schemas.untitled) {
return {
folder: undefined,
path: []
};
}
let info: FileInfo = {
folder: workspaceService.getWorkspaceFolder(uri),
path: []
};
while (uri.path !== '/') {
if (info.folder && isEqual(info.folder.uri, uri)) {
break;
}
info.path.unshift(new FileElement(uri, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER));
uri = uri.with({ path: paths.dirname(uri.path) });
}
if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER));
}
return info;
}
private _bindToEditor(): void {
if (!this._editor) {
return;
}
// update as model changes
this._disposables.push(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline()));
this._disposables.push(this._editor.onDidChangeModel(_ => this._updateOutline()));
this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._updateOutline()));
this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline(true)));
this._updateOutline();
// stop when editor dies
this._disposables.push(this._editor.onDidDispose(() => this._outlineDisposables = dispose(this._outlineDisposables)));
}
private _updateOutline(didChangeContent?: boolean): void {
this._outlineDisposables = dispose(this._outlineDisposables);
if (!didChangeContent) {
this._updateOutlineElements([]);
}
const buffer = this._editor.getModel();
if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) {
return;
}
const source = new CancellationTokenSource();
const versionIdThen = buffer.getVersionId();
const timeout = new TimeoutTimer();
this._outlineDisposables.push({
dispose: () => {
source.cancel();
source.dispose();
timeout.dispose();
}
});
OutlineModel.create(buffer, source.token).then(model => {
if (TreeElement.empty(model)) {
// empty -> no outline elements
this._updateOutlineElements([]);
} else {
// copy the model
model = model.adopt();
this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => {
timeout.cancelAndSet(() => {
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId()) {
this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
}
}, 150);
}));
}
}).catch(err => {
this._updateOutlineElements([]);
onUnexpectedError(err);
});
}
private _getOutlineElements(model: OutlineModel, position: IPosition): (OutlineModel | OutlineGroup | OutlineElement)[] {
if (!model) {
return [];
}
let item: OutlineGroup | OutlineElement = model.getItemEnclosingPosition(position);
if (!item) {
return [model];
}
let chain: (OutlineGroup | OutlineElement)[] = [];
while (item) {
chain.push(item);
let parent = item.parent;
if (parent instanceof OutlineModel) {
break;
}
if (parent instanceof OutlineGroup && size(parent.parent.children) === 1) {
break;
}
item = parent;
}
return chain.reverse();
}
private _updateOutlineElements(elements: (OutlineModel | OutlineGroup | OutlineElement)[]): void {
if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
this._outlineElements = elements;
this._onDidUpdate.fire(this);
}
}
private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean {
if (a === b) {
return true;
} else if (!a || !b) {
return false;
} else {
return a.id === b.id;
}
}
}

View File

@@ -0,0 +1,367 @@
/*---------------------------------------------------------------------------------------------
* 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 * as dom from 'vs/base/browser/dom';
import { compareFileNames } from 'vs/base/common/comparers';
import { Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { dirname, isEqual } from 'vs/base/common/resources';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDataSource, IRenderer, ISelectionEvent, ISorter, ITree } from 'vs/base/parts/tree/browser/tree';
import 'vs/css!./media/breadcrumbscontrol';
import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { OutlineDataSource, OutlineItemComparator, OutlineRenderer } from 'vs/editor/contrib/documentSymbols/outlineTree';
import { localize } from 'vs/nls';
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
import { HighlightingWorkbenchTree, IHighlightingTreeConfiguration, IHighlightingRenderer } from 'vs/platform/list/browser/listService';
import { IThemeService, DARK } from 'vs/platform/theme/common/themeService';
import { FileLabel } from 'vs/workbench/browser/labels';
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { onUnexpectedError } from 'vs/base/common/errors';
import { breadcrumbsPickerBackground } from 'vs/platform/theme/common/colorRegistry';
import { FuzzyScore, createMatches, fuzzyScore } from 'vs/base/common/filters';
import { IWorkspaceContextService, IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker {
let ctor: IConstructorSignature1<HTMLElement, BreadcrumbsPicker> = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker;
return instantiationService.createInstance(ctor, parent);
}
export abstract class BreadcrumbsPicker {
protected readonly _disposables = new Array<IDisposable>();
protected readonly _domNode: HTMLDivElement;
protected readonly _arrow: HTMLDivElement;
protected readonly _treeContainer: HTMLDivElement;
protected readonly _tree: HighlightingWorkbenchTree;
protected readonly _focus: dom.IFocusTracker;
private readonly _onDidPickElement = new Emitter<{ target: any, payload: any }>();
readonly onDidPickElement: Event<{ target: any, payload: any }> = this._onDidPickElement.event;
constructor(
parent: HTMLElement,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IThemeService protected readonly _themeService: IThemeService,
) {
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons';
parent.appendChild(this._domNode);
this._focus = dom.trackFocus(this._domNode);
this._focus.onDidBlur(_ => this._onDidPickElement.fire({ target: undefined, payload: undefined }), undefined, this._disposables);
const theme = this._themeService.getTheme();
const color = theme.getColor(breadcrumbsPickerBackground);
this._arrow = document.createElement('div');
this._arrow.style.width = '0';
this._arrow.style.borderStyle = 'solid';
this._arrow.style.borderWidth = '8px';
this._arrow.style.borderColor = `transparent transparent ${color.toString()}`;
this._domNode.appendChild(this._arrow);
this._treeContainer = document.createElement('div');
this._treeContainer.style.background = color.toString();
this._treeContainer.style.paddingTop = '2px';
this._treeContainer.style.boxShadow = `0px 5px 8px ${(theme.type === DARK ? color.darken(.6) : color.darken(.2))}`;
this._domNode.appendChild(this._treeContainer);
const treeConifg = this._completeTreeConfiguration({ dataSource: undefined, renderer: undefined });
this._tree = this._instantiationService.createInstance(
HighlightingWorkbenchTree,
this._treeContainer,
treeConifg,
{ useShadows: false },
{ placeholder: localize('placeholder', "Find") }
);
this._disposables.push(this._tree.onDidChangeSelection(e => {
if (e.payload !== this._tree) {
const target = this._getTargetFromSelectionEvent(e);
if (!target) {
return;
}
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
this._onDidPickElement.fire({ target, payload: e.payload });
}, 0);
}
}));
this._domNode.focus();
}
dispose(): void {
dispose(this._disposables);
this._onDidPickElement.dispose();
this._tree.dispose();
this._focus.dispose();
}
setInput(input: any): void {
let actualInput = this._getInput(input);
this._tree.setInput(actualInput).then(() => {
let selection = this._getInitialSelection(this._tree, input);
if (selection) {
this._tree.reveal(selection, .5).then(() => {
this._tree.setSelection([selection], this._tree);
this._tree.setFocus(selection);
this._tree.domFocus();
});
} else {
this._tree.focusFirst();
this._tree.setSelection([this._tree.getFocus()], this._tree);
this._tree.domFocus();
}
}, onUnexpectedError);
}
layout(height: number, width: number, arrowSize: number, arrowOffset: number) {
this._domNode.style.height = `${height}px`;
this._domNode.style.width = `${width}px`;
this._arrow.style.borderWidth = `${arrowSize}px`;
this._arrow.style.marginLeft = `${arrowOffset}px`;
this._treeContainer.style.height = `${height - 2 * arrowSize}px`;
this._treeContainer.style.width = `${width}px`;
this._tree.layout();
}
protected abstract _getInput(input: BreadcrumbElement): any;
protected abstract _getInitialSelection(tree: ITree, input: BreadcrumbElement): any;
protected abstract _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration;
protected abstract _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined;
}
//#region - Files
export class FileDataSource implements IDataSource {
private readonly _parents = new WeakMap<object, IWorkspaceFolder | IFileStat>();
constructor(
@IFileService private readonly _fileService: IFileService,
) { }
getId(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): string {
if (URI.isUri(element)) {
return element.toString();
} else if (IWorkspace.isIWorkspace(element)) {
return element.id;
} else if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
return element.uri.toString();
} else {
return element.resource.toString();
}
}
hasChildren(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): boolean {
return URI.isUri(element) || IWorkspace.isIWorkspace(element) || IWorkspaceFolder.isIWorkspaceFolder(element) || element.isDirectory;
}
getChildren(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): TPromise<IWorkspaceFolder[] | IFileStat[]> {
if (IWorkspace.isIWorkspace(element)) {
return TPromise.as(element.folders).then(folders => {
for (let child of folders) {
this._parents.set(element, child);
}
return folders;
});
}
let uri: URI;
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
uri = element.uri;
} else if (URI.isUri(element)) {
uri = element;
} else {
uri = element.resource;
}
return this._fileService.resolveFile(uri).then(stat => {
for (let child of stat.children) {
this._parents.set(stat, child);
}
return stat.children;
});
}
getParent(tree: ITree, element: IWorkspace | URI | IWorkspaceFolder | IFileStat): TPromise<IWorkspaceFolder | IFileStat> {
return TPromise.as(this._parents.get(element));
}
}
export class FileRenderer implements IRenderer, IHighlightingRenderer {
private readonly _scores = new Map<string, FuzzyScore>();
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IConfigurationService private readonly _configService: IConfigurationService,
) { }
getHeight(tree: ITree, element: any): number {
return 22;
}
getTemplateId(tree: ITree, element: any): string {
return 'FileStat';
}
renderTemplate(tree: ITree, templateId: string, container: HTMLElement) {
return this._instantiationService.createInstance(FileLabel, container, { supportHighlights: true });
}
renderElement(tree: ITree, element: IFileStat | IWorkspaceFolder, templateId: string, templateData: FileLabel): void {
let fileDecorations = this._configService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
let resource: URI;
let fileKind: FileKind;
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
resource = element.uri;
fileKind = FileKind.ROOT_FOLDER;
} else {
resource = element.resource;
fileKind = element.isDirectory ? FileKind.FOLDER : FileKind.FILE;
}
templateData.setFile(resource, {
fileKind,
hidePath: true,
fileDecorations: fileDecorations,
matches: createMatches((this._scores.get(resource.toString()) || [, []])[1])
});
}
disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void {
templateData.dispose();
}
updateHighlights(tree: ITree, pattern: string): any {
let nav = tree.getNavigator(undefined, false);
let topScore: FuzzyScore;
let topElement: any;
while (nav.next()) {
let element = nav.current() as IFileStat | IWorkspaceFolder;
let score = fuzzyScore(pattern, element.name, undefined, true);
this._scores.set(IWorkspaceFolder.isIWorkspaceFolder(element) ? element.uri.toString() : element.resource.toString(), score);
if (!topScore || score && topScore[0] < score[0]) {
topScore = score;
topElement = element;
}
}
return topElement;
}
}
export class FileSorter implements ISorter {
compare(tree: ITree, a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number {
if (IWorkspaceFolder.isIWorkspaceFolder(a) && IWorkspaceFolder.isIWorkspaceFolder(b)) {
return a.index - b.index;
} else {
if ((a as IFileStat).isDirectory === (b as IFileStat).isDirectory) {
// same type -> compare on names
return compareFileNames(a.name, b.name);
} else if ((a as IFileStat).isDirectory) {
return -1;
} else {
return 1;
}
}
}
}
export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
constructor(
parent: HTMLElement,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
) {
super(parent, instantiationService, themeService);
}
protected _getInput(input: BreadcrumbElement): any {
let { uri, kind } = (input as FileElement);
if (kind === FileKind.ROOT_FOLDER) {
return this._workspaceService.getWorkspace();
} else {
return dirname(uri);
}
}
protected _getInitialSelection(tree: ITree, input: BreadcrumbElement): any {
let { uri } = (input as FileElement);
let nav = tree.getNavigator();
while (nav.next()) {
let cur = nav.current();
let candidate = IWorkspaceFolder.isIWorkspaceFolder(cur) ? cur.uri : (cur as IFileStat).resource;
if (isEqual(uri, candidate)) {
return cur;
}
}
return undefined;
}
protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration {
// todo@joh reuse explorer implementations?
config.dataSource = this._instantiationService.createInstance(FileDataSource);
config.renderer = this._instantiationService.createInstance(FileRenderer);
config.sorter = new FileSorter();
return config;
}
protected _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined {
let [first] = e.selection;
if (first && !IWorkspaceFolder.isIWorkspaceFolder(first) && !(first as IFileStat).isDirectory) {
return new FileElement((first as IFileStat).resource, FileKind.FILE);
}
}
}
//#endregion
//#region - Symbols
class HighlightingOutlineRenderer extends OutlineRenderer implements IHighlightingRenderer {
updateHighlights(tree: ITree, pattern: string): any {
let model = OutlineModel.get(tree.getInput());
return model.updateMatches(pattern);
}
}
export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
protected _getInput(input: BreadcrumbElement): any {
let element = input as TreeElement;
let model = OutlineModel.get(element);
model.updateMatches('');
return model;
}
protected _getInitialSelection(_tree: ITree, input: BreadcrumbElement): any {
return input instanceof OutlineModel ? undefined : input;
}
protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration {
config.dataSource = this._instantiationService.createInstance(OutlineDataSource);
config.renderer = this._instantiationService.createInstance(HighlightingOutlineRenderer);
config.sorter = new OutlineItemComparator();
return config;
}
protected _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined {
if (e.payload && e.payload.didClickOnTwistie) {
return;
}
let [first] = e.selection;
if (first instanceof OutlineElement) {
return first;
}
}
}
//#endregion

View File

@@ -28,19 +28,25 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import {
CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideAction, RevertAndCloseEditorAction,
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction,
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction,
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
ShowEditorsInGroupThreeAction, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorToSecondGroupAction, MoveEditorToThirdGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup
CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideFromQuickOpenAction, RevertAndCloseEditorAction,
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, ResetGroupSizesAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup,
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction,
OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup,
OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction,
MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup,
ShowEditorsInActiveGroupAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction,
SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction,
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction
} 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 { IEditorService } 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 { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { isMacintosh } from 'vs/base/common/platform';
import { GroupOnePicker, GroupTwoPicker, GroupThreePicker, AllEditorsPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
import { AllEditorsPicker, ActiveEditorGroupPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
import { Schemas } from 'vs/base/common/network';
// Register String Editor
@@ -103,10 +109,9 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
constructor(
@ITextFileService private textFileService: ITextFileService
) {
}
) { }
public serialize(editorInput: EditorInput): string {
serialize(editorInput: EditorInput): string {
if (!this.textFileService.isHotExitEnabled) {
return null; // never restore untitled unless hot exit is enabled
}
@@ -120,7 +125,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
let resource = untitledEditorInput.getResource();
if (untitledEditorInput.hasAssociatedFilePath) {
resource = URI.file(resource.fsPath); // untitled with associated file path use the file schema
resource = resource.with({ scheme: Schemas.file }); // untitled with associated file path use the file schema
}
const serialized: ISerializedUntitledEditorInput = {
@@ -133,7 +138,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
return JSON.stringify(serialized);
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput {
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);
@@ -141,7 +146,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
const language = deserialized.modeId;
const encoding = deserialized.encoding;
return accessor.get(IWorkbenchEditorService).createInput({ resource, filePath, language, encoding }) as UntitledEditorInput;
return accessor.get(IEditorService).createInput({ resource, filePath, language, encoding }) as UntitledEditorInput;
});
}
}
@@ -162,7 +167,7 @@ interface ISerializedSideBySideEditorInput {
// Register Side by Side Editor Input Factory
class SideBySideEditorInputFactory implements IEditorInputFactory {
public serialize(editorInput: EditorInput): string {
serialize(editorInput: EditorInput): string {
const input = <SideBySideEditorInput>editorInput;
if (input.details && input.master) {
@@ -190,7 +195,7 @@ class SideBySideEditorInputFactory implements IEditorInputFactory {
return null;
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
@@ -223,25 +228,25 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, Chang
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding');
export class QuickOpenActionContributor extends ActionBarContributor {
private openToSideActionInstance: OpenToSideAction;
private openToSideActionInstance: OpenToSideFromQuickOpenAction;
constructor(@IInstantiationService private instantiationService: IInstantiationService) {
super();
}
public hasActions(context: any): boolean {
hasActions(context: any): boolean {
const entry = this.getEntry(context);
return !!entry;
}
public getActions(context: any): IAction[] {
getActions(context: any): IAction[] {
const actions: Action[] = [];
const entry = this.getEntry(context);
if (entry) {
if (!this.openToSideActionInstance) {
this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideAction);
this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideFromQuickOpenAction);
} else {
this.openToSideActionInstance.updateClass();
}
@@ -269,50 +274,20 @@ const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExp
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
GroupOnePicker,
GroupOnePicker.ID,
editorCommands.NAVIGATE_IN_GROUP_ONE_PREFIX,
ActiveEditorGroupPicker,
ActiveEditorGroupPicker.ID,
editorCommands.NAVIGATE_IN_ACTIVE_GROUP_PREFIX,
editorPickerContextKey,
[
{
prefix: editorCommands.NAVIGATE_IN_GROUP_ONE_PREFIX,
prefix: editorCommands.NAVIGATE_IN_ACTIVE_GROUP_PREFIX,
needsEditor: false,
description: nls.localize('groupOnePicker', "Show Editors in First Group")
},
{
prefix: editorCommands.NAVIGATE_IN_GROUP_TWO_PREFIX,
needsEditor: false,
description: nls.localize('groupTwoPicker', "Show Editors in Second Group")
},
{
prefix: editorCommands.NAVIGATE_IN_GROUP_THREE_PREFIX,
needsEditor: false,
description: nls.localize('groupThreePicker', "Show Editors in Third Group")
description: nls.localize('groupOnePicker', "Show Editors in Active Group")
}
]
)
);
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
GroupTwoPicker,
GroupTwoPicker.ID,
editorCommands.NAVIGATE_IN_GROUP_TWO_PREFIX,
editorPickerContextKey,
[]
)
);
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
GroupThreePicker,
GroupThreePicker.ID,
editorCommands.NAVIGATE_IN_GROUP_THREE_PREFIX,
editorPickerContextKey,
[]
)
);
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
AllEditorsPicker,
@@ -333,48 +308,73 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
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(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_9 }), 'View: Open Last Editor in Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9], mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9] } }), 'View: Open Last Editor in Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFirstEditorInGroup, OpenFirstEditorInGroup.ID, OpenFirstEditorInGroup.LABEL), 'View: Open First 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(ShowEditorsInActiveGroupAction, ShowEditorsInActiveGroupAction.ID, ShowEditorsInActiveGroupAction.LABEL), 'View: Show Editors in Active 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), 'File: Clear Recently Opened', nls.localize('file', "File"));
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(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors in Group to the Left', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorOrthogonalAction, SplitEditorOrthogonalAction.ID, SplitEditorOrthogonalAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_BACKSLASH) }), 'View: Split Editor Orthogonal', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorLeftAction, SplitEditorLeftAction.ID, SplitEditorLeftAction.LABEL), 'View: Split Editor Left', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorRightAction, SplitEditorRightAction.ID, SplitEditorRightAction.LABEL), 'View: Split Editor Right', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorUpAction, SplitEditorUpAction.ID, SplitEditorUpAction.LABEL), 'Split Editor Up', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorDownAction, SplitEditorDownAction.ID, SplitEditorDownAction.LABEL), 'View: Split Editor Down', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editors of Two Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join Editors of All Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(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(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Sidebar', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Minimize Other Editor Groups', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category);
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(MoveGroupUpAction, MoveGroupUpAction.ID, MoveGroupUpAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupDownAction, MoveGroupDownAction.ID, MoveGroupDownAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToSecondGroupAction, MoveEditorToSecondGroupAction.ID, MoveEditorToSecondGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_2, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_2 } }), 'View: Move Editor into Second Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToThirdGroupAction, MoveEditorToThirdGroupAction.ID, MoveEditorToThirdGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_3, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_3 } }), 'View: Move Editor into Third Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Previous Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Next Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToLastGroupAction, MoveEditorToLastGroupAction.ID, MoveEditorToLastGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_9, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_9 } }), 'View: Move Editor into Last Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToLeftGroupAction, MoveEditorToLeftGroupAction.ID, MoveEditorToLeftGroupAction.LABEL), 'View: Move Editor into Left Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToRightGroupAction, MoveEditorToRightGroupAction.ID, MoveEditorToRightGroupAction.LABEL), 'View: Move Editor into Right Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToAboveGroupAction, MoveEditorToAboveGroupAction.ID, MoveEditorToAboveGroupAction.LABEL), 'View: Move Editor into Above Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToBelowGroupAction, MoveEditorToBelowGroupAction.ID, MoveEditorToBelowGroupAction.LABEL), 'View: Move Editor into Below Group', 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(FocusLastGroupAction, FocusLastGroupAction.ID, FocusLastGroupAction.LABEL), 'View: Focus Last Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL), 'View: Focus Previous Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL), 'View: Focus Next Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLeftGroup, FocusLeftGroup.ID, FocusLeftGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Left Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusRightGroup, FocusRightGroup.ID, FocusRightGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Right Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusAboveGroup, FocusAboveGroup.ID, FocusAboveGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.UpArrow) }), 'View: Focus Above Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusBelowGroup, FocusBelowGroup.ID, FocusBelowGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.DownArrow) }), 'View: Focus Below Editor Group', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupLeftAction, NewEditorGroupLeftAction.ID, NewEditorGroupLeftAction.LABEL), 'View: New Editor Group to the Left', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupRightAction, NewEditorGroupRightAction.ID, NewEditorGroupRightAction.LABEL), 'View: New Editor Group to the Right', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupAboveAction, NewEditorGroupAboveAction.ID, NewEditorGroupAboveAction.LABEL), 'View: New Editor Group Above', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupBelowAction, NewEditorGroupBelowAction.ID, NewEditorGroupBelowAction.LABEL), 'View: New Editor Group Below', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward');
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back');
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last');
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History');
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History');
registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutSingleAction, EditorLayoutSingleAction.ID, EditorLayoutSingleAction.LABEL), 'View: Single Column Editor Layout', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsAction, EditorLayoutTwoColumnsAction.ID, EditorLayoutTwoColumnsAction.LABEL), 'View: Two Columns Editor Layout', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeColumnsAction, EditorLayoutThreeColumnsAction.ID, EditorLayoutThreeColumnsAction.LABEL), 'View: Three Columns Editor Layout', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsAction, EditorLayoutTwoRowsAction.ID, EditorLayoutTwoRowsAction.LABEL), 'View: Two Rows Editor Layout', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeRowsAction, EditorLayoutThreeRowsAction.ID, EditorLayoutThreeRowsAction.LABEL), 'View: Three Rows Editor Layout', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoByTwoGridAction, EditorLayoutTwoByTwoGridAction.ID, EditorLayoutTwoByTwoGridAction.LABEL), 'View: Grid Editor Layout (2x2)', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsRightAction, EditorLayoutTwoRowsRightAction.ID, EditorLayoutTwoRowsRightAction.LABEL), 'View: Two Rows Right Editor Layout', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category);
// Register Editor Picker Actions including quick navigate support
const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } };
@@ -385,7 +385,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUs
const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickOpenNavigateNextInEditorPickerId,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true),
when: editorPickerContext,
primary: openNextEditorKeybinding.primary,
@@ -395,30 +395,36 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickOpenNavigatePreviousInEditorPickerId,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false),
when: editorPickerContext,
primary: openPreviousEditorKeybinding.primary,
mac: openPreviousEditorKeybinding.mac
});
// Editor Commands
editorCommands.setup();
// Touch Bar
if (isMacintosh) {
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath } },
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')) } },
group: 'navigation'
});
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath } },
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')) } },
group: 'navigation'
});
}
// Empty Editor Group Context Menu
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '2_split', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '2_split', order: 20 });
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '2_split', order: 30 });
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '2_split', order: 40 });
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.CLOSE_EDITOR_GROUP_COMMAND_ID, title: nls.localize('close', "Close") }, group: '3_close', order: 10, when: ContextKeyExpr.has('multipleEditorGroups') });
// Editor Title Context Menu
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close") }, group: '1_close', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others") }, group: '1_close', order: 20 });
@@ -426,6 +432,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open") }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '5_split', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '5_split', order: 40 });
// Editor Title Menu
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_INLINE_MODE, title: nls.localize('toggleInlineView', "Toggle Inline View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') });
@@ -433,9 +443,236 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
interface IEditorToolItem { id: string; title: string; iconDark: string; iconLight: string; }
function appendEditorToolItem(primary: IEditorToolItem, alternative: IEditorToolItem, when: ContextKeyExpr, order: number): void {
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: primary.id,
title: primary.title,
iconLocation: {
dark: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${primary.iconDark}`)),
light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${primary.iconLight}`))
}
},
alt: {
id: alternative.id,
title: alternative.title,
iconLocation: {
dark: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconDark}`)),
light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconLight}`))
}
},
group: 'navigation',
when,
order
});
}
// Editor Title Menu: Split Editor
appendEditorToolItem(
{
id: SplitEditorAction.ID,
title: nls.localize('splitEditorRight', "Split Editor Right"),
iconDark: 'split-editor-horizontal-inverse.svg',
iconLight: 'split-editor-horizontal.svg'
}, {
id: editorCommands.SPLIT_EDITOR_DOWN,
title: nls.localize('splitEditorDown', "Split Editor Down"),
iconDark: 'split-editor-vertical-inverse.svg',
iconLight: 'split-editor-vertical.svg'
},
ContextKeyExpr.not('splitEditorsVertically'),
100000 /* towards the end */
);
appendEditorToolItem(
{
id: SplitEditorAction.ID,
title: nls.localize('splitEditorDown', "Split Editor Down"),
iconDark: 'split-editor-vertical-inverse.svg',
iconLight: 'split-editor-vertical.svg'
}, {
id: editorCommands.SPLIT_EDITOR_RIGHT,
title: nls.localize('splitEditorRight', "Split Editor Right"),
iconDark: 'split-editor-horizontal-inverse.svg',
iconLight: 'split-editor-horizontal.svg'
},
ContextKeyExpr.has('splitEditorsVertically'),
100000 // towards the end
);
// Editor Title Menu: Close Group (tabs disabled)
appendEditorToolItem(
{
id: editorCommands.CLOSE_EDITOR_COMMAND_ID,
title: nls.localize('close', "Close"),
iconDark: 'close-big-inverse-alt.svg',
iconLight: 'close-big-alt.svg'
}, {
id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
title: nls.localize('closeAll', "Close All"),
iconDark: 'closeall-editors-inverse.svg',
iconLight: 'closeall-editors.svg'
},
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')),
1000000 // towards the end
);
appendEditorToolItem(
{
id: editorCommands.CLOSE_EDITOR_COMMAND_ID,
title: nls.localize('close', "Close"),
iconDark: 'close-dirty-inverse-alt.svg',
iconLight: 'close-dirty-alt.svg'
}, {
id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
title: nls.localize('closeAll', "Close All"),
iconDark: 'closeall-editors-inverse.svg',
iconLight: 'closeall-editors.svg'
},
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')),
1000000 // towards the end
);
// Editor Commands for Command Palette
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepEditor', "Keep Editor"), category }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOtherEditors', "Close Other Editors"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOtherEditors', "Close Other Editors in Group"), category } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), category } });
// File menu
MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, {
group: '1_editor',
command: {
id: ReopenClosedEditorAction.ID,
title: nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, {
group: 'z_clear',
command: {
id: ClearRecentFilesAction.ID,
title: nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened")
},
order: 1
});
// Layout menu
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
group: '2_appearance',
title: nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout"),
submenu: MenuId.MenubarLayoutMenu,
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '1_split',
command: {
id: editorCommands.SPLIT_EDITOR_UP,
title: nls.localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '1_split',
command: {
id: editorCommands.SPLIT_EDITOR_DOWN,
title: nls.localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '1_split',
command: {
id: editorCommands.SPLIT_EDITOR_LEFT,
title: nls.localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '1_split',
command: {
id: editorCommands.SPLIT_EDITOR_RIGHT,
title: nls.localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right")
},
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '2_layouts',
command: {
id: EditorLayoutSingleAction.ID,
title: nls.localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '2_layouts',
command: {
id: EditorLayoutTwoColumnsAction.ID,
title: nls.localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '2_layouts',
command: {
id: EditorLayoutThreeColumnsAction.ID,
title: nls.localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns")
},
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '2_layouts',
command: {
id: EditorLayoutTwoRowsAction.ID,
title: nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows")
},
order: 5
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '2_layouts',
command: {
id: EditorLayoutThreeRowsAction.ID,
title: nls.localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows")
},
order: 6
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '2_layouts',
command: {
id: EditorLayoutTwoByTwoGridAction.ID,
title: nls.localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)")
},
order: 7
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '2_layouts',
command: {
id: EditorLayoutTwoRowsRightAction.ID,
title: nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right")
},
order: 8
});
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: '2_layouts',
command: {
id: EditorLayoutTwoColumnsBottomAction.ID,
title: nls.localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom")
},
order: 9
});

View File

@@ -0,0 +1,163 @@
/*---------------------------------------------------------------------------------------------
* 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 { GroupIdentifier, IWorkbenchEditorConfiguration, IWorkbenchEditorPartConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent } from 'vs/workbench/common/editor';
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Dimension } from 'vs/base/browser/dom';
import { Event } from 'vs/base/common/event';
import { assign } from 'vs/base/common/objects';
import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { ISerializableView } from 'vs/base/browser/ui/grid/grid';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export const EDITOR_TITLE_HEIGHT = 35;
export const DEFAULT_EDITOR_MIN_DIMENSIONS = new Dimension(220, 70);
export const DEFAULT_EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
export interface IEditorPartOptions extends IWorkbenchEditorPartConfiguration {
iconTheme?: string;
}
export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = {
showTabs: true,
tabCloseButton: 'right',
tabSizing: 'fit',
showIcons: true,
enablePreview: true,
openPositioning: 'right',
openSideBySideDirection: 'right',
closeEmptyGroups: true,
labelFormat: 'default',
iconTheme: 'vs-seti'
};
export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean {
return event.affectsConfiguration('workbench.editor') || event.affectsConfiguration('workbench.iconTheme');
}
export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEditorPartOptions {
const options: IEditorPartOptions = assign(Object.create(null), DEFAULT_EDITOR_PART_OPTIONS);
if (!config || !config.workbench) {
return options;
}
if (typeof config.workbench.iconTheme === 'string') {
options.iconTheme = config.workbench.iconTheme;
}
if (config.workbench.editor) {
assign(options, config.workbench.editor);
}
return options;
}
export interface IEditorPartOptionsChangeEvent {
oldPartOptions: IEditorPartOptions;
newPartOptions: IEditorPartOptions;
}
export interface IEditorOpeningEvent extends IEditorIdentifier {
options?: IEditorOptions;
/**
* Allows to prevent the opening of an editor by providing a callback
* that will be executed instead. By returning another editor promise
* it is possible to override the opening with another editor. It is ok
* to return a promise that resolves to NULL to prevent the opening
* altogether.
*/
prevent(callback: () => Thenable<any>): void;
}
export interface IEditorGroupsAccessor {
readonly groups: IEditorGroupView[];
readonly activeGroup: IEditorGroupView;
readonly partOptions: IEditorPartOptions;
readonly onDidEditorPartOptionsChange: Event<IEditorPartOptionsChangeEvent>;
getGroup(identifier: GroupIdentifier): IEditorGroupView;
getGroups(order: GroupsOrder): IEditorGroupView[];
activateGroup(identifier: IEditorGroupView | GroupIdentifier): IEditorGroupView;
addGroup(location: IEditorGroupView | GroupIdentifier, direction: GroupDirection, options?: IAddGroupOptions): IEditorGroupView;
mergeGroup(group: IEditorGroupView | GroupIdentifier, target: IEditorGroupView | GroupIdentifier, options?: IMergeGroupOptions): IEditorGroupView;
moveGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView;
copyGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView;
removeGroup(group: IEditorGroupView | GroupIdentifier): void;
}
export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup {
readonly group: EditorGroup;
readonly whenRestored: TPromise<void>;
readonly disposed: boolean;
readonly onDidFocus: Event<void>;
readonly onWillDispose: Event<void>;
readonly onWillOpenEditor: Event<IEditorOpeningEvent>;
readonly onDidOpenEditorFail: Event<IEditorInput>;
readonly onWillCloseEditor: Event<IEditorCloseEvent>;
readonly onDidCloseEditor: Event<IEditorCloseEvent>;
isEmpty(): boolean;
setActive(isActive: boolean): void;
setLabel(label: string): void;
relayout(): void;
shutdown(): void;
}
export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEditor?: IEditorInput, presetOptions?: EditorOptions): EditorOptions {
const activeGroupCodeEditor = group.activeControl ? getCodeEditor(group.activeControl.getControl()) : void 0;
if (activeGroupCodeEditor) {
if (!expectedActiveEditor || expectedActiveEditor.matches(group.activeEditor)) {
return TextEditorOptions.fromEditor(activeGroupCodeEditor, presetOptions);
}
}
return presetOptions || new EditorOptions();
}
/**
* A sub-interface of IEditorService to hide some workbench-core specific
* events from clients.
*/
export interface EditorServiceImpl extends IEditorService {
/**
* Emitted when an editor is closed.
*/
readonly onDidCloseEditor: Event<IEditorCloseEvent>;
/**
* Emitted when an editor failed to open.
*/
readonly onDidOpenEditorFail: Event<IEditorIdentifier>;
}
/**
* A sub-interface of IEditorGroupsService to hide some workbench-core specific
* methods from clients.
*/
export interface EditorGroupsServiceImpl extends IEditorGroupsService {
/**
* A promise that resolves when groups have been restored.
*/
readonly whenRestored: TPromise<void>;
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,14 +6,11 @@
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, EditorInput, IEditorIdentifier, IEditorCommandsContext } from 'vs/workbench/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditor, Position, POSITIONS, Direction, IEditorInput } from 'vs/platform/editor/common/editor';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditor, IEditorInput } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
import { EditorStacksModel, EditorGroup } from 'vs/workbench/common/editor/editorStacksModel';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
@@ -22,26 +19,37 @@ import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IListService } from 'vs/platform/list/browser/listService';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { distinct } from 'vs/base/common/arrays';
import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
export const CLOSE_EDITORS_AND_GROUP_COMMAND_ID = 'workbench.action.closeEditorsAndGroup';
export const CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID = 'workbench.action.closeEditorsToTheRight';
export const CLOSE_EDITOR_COMMAND_ID = 'workbench.action.closeActiveEditor';
export const CLOSE_EDITOR_GROUP_COMMAND_ID = 'workbench.action.closeGroup';
export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOtherEditors';
export const MOVE_ACTIVE_EDITOR_COMMAND_ID = 'moveActiveEditor';
export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
export const TOGGLE_DIFF_INLINE_MODE = 'toggle.diff.editorMode';
export const NAVIGATE_IN_GROUP_ONE_PREFIX = 'edt one ';
export const NAVIGATE_IN_GROUP_TWO_PREFIX = 'edt two ';
export const NAVIGATE_IN_GROUP_THREE_PREFIX = 'edt three ';
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp';
export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown';
export const SPLIT_EDITOR_LEFT = 'workbench.action.splitEditorLeft';
export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight';
export function setup(): void {
registerActiveEditorMoveCommand();
registerDiffEditorCommands();
registerOpenEditorAtIndexCommands();
registerEditorCommands();
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active ';
export interface ActiveEditorMoveArguments {
to?: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
by?: 'tab' | 'group';
value?: number;
}
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
@@ -49,17 +57,15 @@ const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean
return false;
}
const activeEditorMoveArg: ActiveEditorMoveArguments = arg;
if (!types.isString(activeEditorMoveArg.to)) {
if (!types.isString(arg.to)) {
return false;
}
if (!types.isUndefined(activeEditorMoveArg.by) && !types.isString(activeEditorMoveArg.by)) {
if (!types.isUndefined(arg.by) && !types.isString(arg.by)) {
return false;
}
if (!types.isUndefined(activeEditorMoveArg.value) && !types.isNumber(activeEditorMoveArg.value)) {
if (!types.isUndefined(arg.value) && !types.isNumber(arg.value)) {
return false;
}
@@ -68,8 +74,8 @@ const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean
function registerActiveEditorMoveCommand(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: EditorCommands.MoveActiveEditor,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
id: MOVE_ACTIVE_EDITOR_COMMAND_ID,
weight: KeybindingWeight.WorkbenchContrib,
when: EditorContextKeys.editorTextFocus,
primary: null,
handler: (accessor, args: any) => moveActiveEditor(args, accessor),
@@ -78,7 +84,7 @@ function registerActiveEditorMoveCommand(): void {
args: [
{
name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
description: nls.localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move. By tab or by group.\n\t* 'value': Number value providing how many positions or an absolute position to move."),
description: nls.localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."),
constraint: isActiveEditorMoveArg
}
]
@@ -86,98 +92,153 @@ function registerActiveEditorMoveCommand(): void {
});
}
function moveActiveEditor(args: ActiveEditorMoveArguments = {}, accessor: ServicesAccessor): void {
args.to = args.to || ActiveEditorMovePositioning.RIGHT;
args.by = args.by || ActiveEditorMovePositioningBy.TAB;
args.value = types.isUndefined(args.value) ? 1 : args.value;
function moveActiveEditor(args: ActiveEditorMoveArguments = Object.create(null), accessor: ServicesAccessor): void {
args.to = args.to || 'right';
args.by = args.by || 'tab';
args.value = typeof args.value === 'number' ? args.value : 1;
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
if (activeEditor) {
const activeControl = accessor.get(IEditorService).activeControl;
if (activeControl) {
switch (args.by) {
case ActiveEditorMovePositioningBy.TAB:
return moveActiveTab(args, activeEditor, accessor);
case ActiveEditorMovePositioningBy.GROUP:
return moveActiveEditorToGroup(args, activeEditor, accessor);
case 'tab':
return moveActiveTab(args, activeControl, accessor);
case 'group':
return moveActiveEditorToGroup(args, activeControl, 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);
function moveActiveTab(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void {
const group = control.group;
let index = group.getIndexOfEditor(control.input);
switch (args.to) {
case ActiveEditorMovePositioning.FIRST:
case 'first':
index = 0;
break;
case ActiveEditorMovePositioning.LAST:
index = editorGroup.count - 1;
case 'last':
index = group.count - 1;
break;
case ActiveEditorMovePositioning.LEFT:
case 'left':
index = index - args.value;
break;
case ActiveEditorMovePositioning.RIGHT:
case 'right':
index = index + args.value;
break;
case ActiveEditorMovePositioning.CENTER:
index = Math.round(editorGroup.count / 2) - 1;
case 'center':
index = Math.round(group.count / 2) - 1;
break;
case ActiveEditorMovePositioning.POSITION:
case 'position':
index = args.value - 1;
break;
}
index = index < 0 ? 0 : index >= editorGroup.count ? editorGroup.count - 1 : index;
editorGroupsService.moveEditor(activeEditor.input, editorGroup, editorGroup, { index });
index = index < 0 ? 0 : index >= group.count ? group.count - 1 : index;
group.moveEditor(control.input, group, { index });
}
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
let newPosition = activeEditor.position;
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void {
const editorGroupService = accessor.get(IEditorGroupsService);
const configurationService = accessor.get(IConfigurationService);
const sourceGroup = control.group;
let targetGroup: IEditorGroup;
switch (args.to) {
case ActiveEditorMovePositioning.LEFT:
newPosition = newPosition - 1;
case 'left':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.LEFT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.LEFT);
}
break;
case ActiveEditorMovePositioning.RIGHT:
newPosition = newPosition + 1;
case 'right':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.RIGHT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.RIGHT);
}
break;
case ActiveEditorMovePositioning.FIRST:
newPosition = Position.ONE;
case 'up':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.UP }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.UP);
}
break;
case ActiveEditorMovePositioning.LAST:
newPosition = Position.THREE;
case 'down':
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.DOWN }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.DOWN);
}
break;
case ActiveEditorMovePositioning.CENTER:
newPosition = Position.TWO;
case 'first':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.FIRST }, sourceGroup);
break;
case ActiveEditorMovePositioning.POSITION:
newPosition = args.value - 1;
case 'last':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.LAST }, sourceGroup);
break;
case 'previous':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.PREVIOUS }, sourceGroup);
break;
case 'next':
targetGroup = editorGroupService.findGroup({ location: GroupLocation.NEXT }, sourceGroup);
if (!targetGroup) {
targetGroup = editorGroupService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService));
}
break;
case 'center':
targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupService.count / 2) - 1];
break;
case 'position':
targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[args.value - 1];
break;
}
newPosition = POSITIONS.indexOf(newPosition) !== -1 ? newPosition : activeEditor.position;
accessor.get(IEditorGroupService).moveEditor(activeEditor.input, activeEditor.position, newPosition);
if (targetGroup) {
sourceGroup.moveEditor(control.input, targetGroup);
targetGroup.focus();
}
}
function registerEditorGroupsLayoutCommand(): void {
CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, (accessor: ServicesAccessor, args: EditorGroupLayout) => {
if (!args || typeof args !== 'object') {
return;
}
const editorGroupService = accessor.get(IEditorGroupsService);
editorGroupService.applyLayout(args);
});
}
export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
const target = editorGroupService.activeGroup;
editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).forEach(group => {
if (group === target) {
return; // keep target
}
editorGroupService.mergeGroup(group, target);
});
}
function registerDiffEditorCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.compareEditor.nextChange',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: TextCompareEditorVisible,
weight: KeybindingWeight.WorkbenchContrib,
when: TextCompareEditorVisibleContext,
primary: null,
handler: accessor => navigateInDiffEditor(accessor, true)
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.compareEditor.previousChange',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: TextCompareEditorVisible,
weight: KeybindingWeight.WorkbenchContrib,
when: TextCompareEditorVisibleContext,
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);
const editorService = accessor.get(IEditorService);
const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor);
if (candidates.length > 0) {
next ? (<TextDiffEditor>candidates[0]).getDiffNavigator().next() : (<TextDiffEditor>candidates[0]).getDiffNavigator().previous();
@@ -186,25 +247,17 @@ function registerDiffEditorCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: TOGGLE_DIFF_INLINE_MODE,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: void 0,
handler: (accessor, resource, context: IEditorCommandsContext) => {
const editorService = accessor.get(IWorkbenchEditorService);
const editorGroupService = accessor.get(IEditorGroupService);
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
let editor: IEditor;
if (context) {
const position = positionAndInput(editorGroupService, editorService, context).position;
editor = editorService.getVisibleEditors()[position];
} else {
editor = editorService.getActiveEditor();
}
if (editor instanceof TextDiffEditor) {
const control = editor.getControl();
const isInlineMode = !control.renderSideBySide;
control.updateOptions(<IDiffEditorOptions>{
const { control } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
if (control instanceof TextDiffEditor) {
const widget = control.getControl();
const isInlineMode = !widget.renderSideBySide;
widget.updateOptions(<IDiffEditorOptions>{
renderSideBySide: isInlineMode
});
}
@@ -221,19 +274,16 @@ function registerOpenEditorAtIndexCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.openEditorAtIndex' + visibleIndex,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.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);
const editorService = accessor.get(IEditorService);
const activeControl = editorService.activeControl;
if (activeControl) {
const editor = activeControl.group.getEditor(editorIndex);
if (editor) {
return editorService.openEditor(editor).then(() => void 0);
}
@@ -262,169 +312,243 @@ function registerOpenEditorAtIndexCommands(): void {
}
}
function registerEditorCommands() {
function registerFocusEditorGroupAtIndexCommands(): void {
// Keybindings to focus a specific group (2-8) in the editor area
for (let groupIndex = 1; groupIndex < 8; groupIndex++) {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: toCommandId(groupIndex),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: KeyMod.CtrlCmd | toKeyCode(groupIndex),
handler: accessor => {
const editorGroupService = accessor.get(IEditorGroupsService);
const configurationService = accessor.get(IConfigurationService);
// To keep backwards compatibility (pre-grid), allow to focus a group
// that does not exist as long as it is the next group after the last
// opened group. Otherwise we return.
if (groupIndex > editorGroupService.count) {
return;
}
// Group exists: just focus
const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
if (groups[groupIndex]) {
return groups[groupIndex].focus();
}
// Group does not exist: create new by splitting the active one of the last group
const direction = preferredSideBySideGroupDirection(configurationService);
const lastGroup = editorGroupService.findGroup({ location: GroupLocation.LAST });
const newGroup = editorGroupService.addGroup(lastGroup, direction);
// Focus
newGroup.focus();
}
});
}
function toCommandId(index: number): string {
switch (index) {
case 1: return 'workbench.action.focusSecondEditorGroup';
case 2: return 'workbench.action.focusThirdEditorGroup';
case 3: return 'workbench.action.focusFourthEditorGroup';
case 4: return 'workbench.action.focusFifthEditorGroup';
case 5: return 'workbench.action.focusSixthEditorGroup';
case 6: return 'workbench.action.focusSeventhEditorGroup';
case 7: return 'workbench.action.focusEighthEditorGroup';
}
return void 0;
}
function toKeyCode(index: number): KeyCode {
switch (index) {
case 1: return KeyCode.KEY_2;
case 2: return KeyCode.KEY_3;
case 3: return KeyCode.KEY_4;
case 4: return KeyCode.KEY_5;
case 5: return KeyCode.KEY_6;
case 6: return KeyCode.KEY_7;
case 7: return KeyCode.KEY_8;
}
return void 0;
}
}
export function splitEditor(editorGroupService: IEditorGroupsService, direction: GroupDirection, context?: IEditorCommandsContext): void {
let sourceGroup: IEditorGroup;
if (context && typeof context.groupId === 'number') {
sourceGroup = editorGroupService.getGroup(context.groupId);
} else {
sourceGroup = editorGroupService.activeGroup;
}
// Add group
const newGroup = editorGroupService.addGroup(sourceGroup, direction);
// Split editor (if it can be split)
let editorToCopy: IEditorInput;
if (context && typeof context.editorIndex === 'number') {
editorToCopy = sourceGroup.getEditor(context.editorIndex);
} else {
editorToCopy = sourceGroup.activeEditor;
}
if (editorToCopy && (editorToCopy as EditorInput).supportsSplitEditor()) {
sourceGroup.copyEditor(editorToCopy, newGroup);
}
// Focus
newGroup.focus();
}
function registerSplitEditorCommands() {
[
{ id: SPLIT_EDITOR_UP, direction: GroupDirection.UP },
{ id: SPLIT_EDITOR_DOWN, direction: GroupDirection.DOWN },
{ id: SPLIT_EDITOR_LEFT, direction: GroupDirection.LEFT },
{ id: SPLIT_EDITOR_RIGHT, direction: GroupDirection.RIGHT }
].forEach(({ id, direction }) => {
CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
});
});
}
function registerCloseEditorCommands() {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_SAVED_EDITORS_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
handler: (accessor, resource: URI | object, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const model = editorGroupService.getStacksModel();
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
if (contexts.length === 0 && model.activeGroup) {
// If command is triggered from the command palette use the active group
contexts.push({ groupId: model.activeGroup.id });
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
const activeGroup = editorGroupService.activeGroup;
if (contexts.length === 0) {
contexts.push({ groupId: activeGroup.id }); // active group as fallback
}
let positionOne: { savedOnly: boolean } = void 0;
let positionTwo: { savedOnly: boolean } = void 0;
let positionThree: { savedOnly: boolean } = void 0;
contexts.forEach(c => {
switch (model.positionOfGroup(model.getGroup(c.groupId))) {
case Position.ONE: positionOne = { savedOnly: true }; break;
case Position.TWO: positionTwo = { savedOnly: true }; break;
case Position.THREE: positionThree = { savedOnly: true }; break;
}
});
return editorService.closeEditors({ positionOne, positionTwo, positionThree });
return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
));
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
handler: (accessor, resource: URI | object, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
const distinctGroupIds = distinct(contexts.map(c => c.groupId));
const model = editorGroupService.getStacksModel();
if (distinctGroupIds.length) {
return editorService.closeEditors(distinctGroupIds.map(gid => model.positionOfGroup(model.getGroup(gid))));
}
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
return editorService.closeEditors(activeEditor.position);
if (distinctGroupIds.length === 0) {
distinctGroupIds.push(editorGroupService.activeGroup.id);
}
return TPromise.as(false);
return TPromise.join(distinctGroupIds.map(groupId =>
editorGroupService.getGroup(groupId).closeAllEditors()
));
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_EDITOR_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
handler: (accessor, resource: URI | object, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const groupIds = distinct(contexts.map(context => context.groupId));
const model = editorGroupService.getStacksModel();
const editorsToClose = new Map<Position, IEditorInput[]>();
groupIds.forEach(groupId => {
const group = model.getGroup(groupId);
const position = model.positionOfGroup(group);
if (position >= 0) {
const inputs = contexts.map(c => {
if (c && groupId === c.groupId && types.isNumber(c.editorIndex)) {
return group.getEditor(c.editorIndex);
}
return group.activeEditor;
}).filter(input => !!input);
if (inputs.length) {
editorsToClose.set(position, inputs);
}
}
});
if (editorsToClose.size === 0) {
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
return editorService.closeEditor(activeEditor.position, activeEditor.input);
}
const activeGroup = editorGroupService.activeGroup;
if (contexts.length === 0 && activeGroup.activeEditor) {
contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) }); // active editor as fallback
}
return editorService.closeEditors({
positionOne: editorsToClose.get(Position.ONE),
positionTwo: editorsToClose.get(Position.TWO),
positionThree: editorsToClose.get(Position.THREE)
});
const groupIds = distinct(contexts.map(context => context.groupId));
return TPromise.join(groupIds.map(groupId => {
const group = editorGroupService.getGroup(groupId);
const editors = contexts
.filter(context => context.groupId === groupId)
.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
return group.closeEditors(editors);
}));
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_EDITOR_GROUP_COMMAND_ID,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
const commandsContext = getCommandsContext(resourceOrContext, context);
let group: IEditorGroup;
if (commandsContext && typeof commandsContext.groupId === 'number') {
group = editorGroupService.getGroup(commandsContext.groupId);
} else {
group = editorGroupService.activeGroup;
}
editorGroupService.removeGroup(group);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: void 0,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
handler: (accessor, resource: URI | object, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const model = editorGroupService.getStacksModel();
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
if (contexts.length === 0) {
// Cover the case when run from command palette
const activeGroup = model.activeGroup;
const activeEditor = editorService.getActiveEditorInput();
if (activeGroup && activeEditor) {
contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.indexOf(activeEditor) });
}
const activeGroup = editorGroupService.activeGroup;
if (contexts.length === 0 && activeGroup.activeEditor) {
contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) }); // active editor as fallback
}
const groupIds = distinct(contexts.map(context => context.groupId));
const editorsToClose = new Map<Position, IEditorInput[]>();
groupIds.forEach(groupId => {
const group = model.getGroup(groupId);
const inputsToSkip = contexts.map(c => {
if (c.groupId === groupId && types.isNumber(c.editorIndex)) {
return group.getEditor(c.editorIndex);
}
return void 0;
}).filter(input => !!input);
return TPromise.join(groupIds.map(groupId => {
const group = editorGroupService.getGroup(groupId);
const editors = contexts
.filter(context => context.groupId === groupId)
.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
const toClose = group.getEditors().filter(input => inputsToSkip.indexOf(input) === -1);
editorsToClose.set(model.positionOfGroup(group), toClose);
});
return editorService.closeEditors({
positionOne: editorsToClose.get(Position.ONE),
positionTwo: editorsToClose.get(Position.TWO),
positionThree: editorsToClose.get(Position.THREE)
});
return group.closeEditors(editorsToClose);
}));
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: void 0,
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
const { position, input } = positionAndInput(editorGroupService, editorService, context);
if (typeof position === 'number' && input) {
return editorService.closeEditors(position, { except: input, direction: Direction.RIGHT });
const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
if (group && editor) {
return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
}
return TPromise.as(false);
@@ -433,17 +557,15 @@ function registerEditorCommands() {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: KEEP_EDITOR_COMMAND_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
const { position, input } = positionAndInput(editorGroupService, editorService, context);
if (typeof position === 'number' && input) {
return editorGroupService.pinEditor(position, input);
const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
if (group && editor) {
return group.pinEditor(editor);
}
return TPromise.as(false);
@@ -452,88 +574,98 @@ function registerEditorCommands() {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: SHOW_EDITORS_IN_GROUP,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: void 0,
primary: void 0,
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
const quickOpenService = accessor.get(IQuickOpenService);
const stacks = editorGroupService.getStacksModel();
const groupCount = stacks.groups.length;
if (groupCount <= 1) {
if (editorGroupService.count <= 1) {
return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
}
const { position } = positionAndInput(editorGroupService, editorService, context);
switch (position) {
case Position.TWO:
return quickOpenService.show(NAVIGATE_IN_GROUP_TWO_PREFIX);
case Position.THREE:
return quickOpenService.show(NAVIGATE_IN_GROUP_THREE_PREFIX);
const commandsContext = getCommandsContext(resourceOrContext, context);
if (commandsContext && typeof commandsContext.groupId === 'number') {
editorGroupService.activateGroup(editorGroupService.getGroup(commandsContext.groupId)); // we need the group to be active
}
return quickOpenService.show(NAVIGATE_IN_GROUP_ONE_PREFIX);
return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.printStacksModel',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor) {
console.log(`${accessor.get(IEditorGroupService).getStacksModel().toString()}\n\n`);
},
when: void 0,
primary: void 0
});
CommandsRegistry.registerCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, (accessor: ServicesAccessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupsService);
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.validateStacksModel',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor) {
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
},
when: void 0,
primary: void 0
const { group } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
if (group) {
return group.closeAllEditors().then(() => {
if (group.count === 0 && editorGroupService.getGroup(group.id) /* could be gone by now */) {
editorGroupService.removeGroup(group); // only remove group if it is now empty
}
});
}
return void 0;
});
}
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorCommandsContext): { position: Position, input: IEditorInput } {
// Resolve from context
const model = editorGroupService.getStacksModel();
const group = context ? model.getGroup(context.groupId) : undefined;
let position = group ? model.positionOfGroup(group) : undefined;
let input = group && types.isNumber(context.editorIndex) ? group.getEditor(context.editorIndex) : undefined;
// If position or input are not passed in take the position and input of the active editor.
const active = editorService.getActiveEditor();
if (active) {
position = typeof position === 'number' ? position : active.position;
input = input ? input : <EditorInput>active.input;
function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext {
if (URI.isUri(resourceOrContext)) {
return context;
}
return { position, input };
if (resourceOrContext && typeof resourceOrContext.groupId === 'number') {
return resourceOrContext;
}
if (context && typeof context.groupId === 'number') {
return context;
}
return void 0;
}
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService): IEditorCommandsContext[] {
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
// Resolve from context
let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined;
let editor = group && typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : undefined;
let control = group ? group.activeControl : undefined;
// Fallback to active group as needed
if (!group) {
group = editorGroupService.activeGroup;
editor = <EditorInput>group.activeEditor;
control = group.activeControl;
}
return { group, editor, control };
}
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] {
// First check for a focused list to return the selected items from
const list = listService.lastFocusedList;
if (list instanceof List && list.isDOMFocused()) {
const elementToContext = (element: IEditorIdentifier | EditorGroup) =>
element instanceof EditorGroup ? { groupId: element.id, editorIndex: undefined } : { groupId: element.group.id, editorIndex: element.group.indexOf(element.editor) };
const onlyEditorGroupAndEditor = (e: IEditorIdentifier | EditorGroup) => e instanceof EditorGroup || ('editor' in e && 'group' in e);
const elementToContext = (element: IEditorIdentifier | IEditorGroup) => {
if (isEditorGroup(element)) {
return { groupId: element.id, editorIndex: void 0 };
}
const focusedElements: (IEditorIdentifier | EditorGroup)[] = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
// need to take into account when editor context is { group: group }
const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : undefined;
return { groupId: element.groupId, editorIndex: editorGroupService.getGroup(element.groupId).getIndexOfEditor(element.editor) };
};
const onlyEditorGroupAndEditor = (e: IEditorIdentifier | IEditorGroup) => isEditorGroup(e) || isEditorIdentifier(e);
const focusedElements: (IEditorIdentifier | IEditorGroup)[] = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : void 0; // need to take into account when editor context is { group: group }
if (focus) {
const selection: (IEditorIdentifier | EditorGroup)[] = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
const selection: (IEditorIdentifier | IEditorGroup)[] = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
// Only respect selection if it contains focused element
if (selection && selection.some(s => s instanceof EditorGroup ? s.id === focus.groupId : s.group.id === focus.groupId && s.group.indexOf(s.editor) === focus.editorIndex)) {
if (selection && selection.some(s => isEditorGroup(s) ? s.id === focus.groupId : s.groupId === focus.groupId && editorGroupService.getGroup(s.groupId).getIndexOfEditor(s.editor) === focus.editorIndex)) {
return selection.map(elementToContext);
}
@@ -544,3 +676,25 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon
// Otherwise go with passed in context
return !!editorContext ? [editorContext] : [];
}
function isEditorGroup(thing: any): thing is IEditorGroup {
const group = thing as IEditorGroup;
return group && typeof group.id === 'number' && Array.isArray(group.editors);
}
function isEditorIdentifier(thing: any): thing is IEditorIdentifier {
const identifier = thing as IEditorIdentifier;
return identifier && typeof identifier.groupId === 'number';
}
export function setup(): void {
registerActiveEditorMoveCommand();
registerEditorGroupsLayoutCommand();
registerDiffEditorCommands();
registerOpenEditorAtIndexCommands();
registerCloseEditorCommands();
registerFocusEditorGroupAtIndexCommands();
registerSplitEditorCommands();
}

View File

@@ -0,0 +1,247 @@
/*---------------------------------------------------------------------------------------------
* 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 { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { Dimension, show, hide, addClass } from 'vs/base/browser/dom';
import { Registry } from 'vs/platform/registry/common/platform';
import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } from 'vs/workbench/browser/editor';
import { TPromise } from 'vs/base/common/winjs.base';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
import { toWinJsPromise } from 'vs/base/common/async';
import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
import { Event, Emitter } from 'vs/base/common/event';
export interface IOpenEditorResult {
readonly control: BaseEditor;
readonly editorChanged: boolean;
}
export class EditorControl extends Disposable {
get minimumWidth() { return this._activeControl ? this._activeControl.minimumWidth : DEFAULT_EDITOR_MIN_DIMENSIONS.width; }
get minimumHeight() { return this._activeControl ? this._activeControl.minimumHeight : DEFAULT_EDITOR_MIN_DIMENSIONS.height; }
get maximumWidth() { return this._activeControl ? this._activeControl.maximumWidth : DEFAULT_EDITOR_MAX_DIMENSIONS.width; }
get maximumHeight() { return this._activeControl ? this._activeControl.maximumHeight : DEFAULT_EDITOR_MAX_DIMENSIONS.height; }
private _onDidFocus: Emitter<void> = this._register(new Emitter<void>());
get onDidFocus(): Event<void> { return this._onDidFocus.event; }
private _onDidSizeConstraintsChange = this._register(new Emitter<{ width: number; height: number; }>());
get onDidSizeConstraintsChange(): Event<{ width: number; height: number; }> { return this._onDidSizeConstraintsChange.event; }
private _activeControl: BaseEditor;
private controls: BaseEditor[] = [];
private activeControlDisposeables: IDisposable[] = [];
private dimension: Dimension;
private editorOperation: LongRunningOperation;
constructor(
private parent: HTMLElement,
private groupView: IEditorGroupView,
@IPartService private partService: IPartService,
@IInstantiationService private instantiationService: IInstantiationService,
@IProgressService progressService: IProgressService
) {
super();
this.editorOperation = this._register(new LongRunningOperation(progressService));
}
get activeControl(): BaseEditor {
return this._activeControl;
}
openEditor(editor: EditorInput, options?: EditorOptions): TPromise<IOpenEditorResult> {
// Editor control
const descriptor = Registry.as<IEditorRegistry>(EditorExtensions.Editors).getEditor(editor);
const control = this.doShowEditorControl(descriptor, options);
// Set input
return this.doSetInput(control, editor, options).then((editorChanged => (({ control, editorChanged } as IOpenEditorResult))));
}
private doShowEditorControl(descriptor: IEditorDescriptor, options: EditorOptions): BaseEditor {
// Return early if the currently active editor control can handle the input
if (this._activeControl && descriptor.describes(this._activeControl)) {
return this._activeControl;
}
// Hide active one first
this.doHideActiveEditorControl();
// Create editor
const control = this.doCreateEditorControl(descriptor);
// Set editor as active
this.doSetActiveControl(control);
// Show editor
this.parent.appendChild(control.getContainer());
show(control.getContainer());
// Indicate to editor that it is now visible
control.setVisible(true, this.groupView);
// Layout
if (this.dimension) {
control.layout(this.dimension);
}
return control;
}
private doCreateEditorControl(descriptor: IEditorDescriptor): BaseEditor {
// Instantiate editor
const control = this.doInstantiateEditorControl(descriptor);
// Create editor container as needed
if (!control.getContainer()) {
const controlInstanceContainer = document.createElement('div');
addClass(controlInstanceContainer, 'editor-instance');
controlInstanceContainer.id = descriptor.getId();
control.create(controlInstanceContainer);
}
return control;
}
private doInstantiateEditorControl(descriptor: IEditorDescriptor): BaseEditor {
// Return early if already instantiated
const existingControl = this.controls.filter(control => descriptor.describes(control))[0];
if (existingControl) {
return existingControl;
}
// Otherwise instantiate new
const control = this._register(descriptor.instantiate(this.instantiationService));
this.controls.push(control);
return control;
}
private doSetActiveControl(control: BaseEditor) {
this._activeControl = control;
// Clear out previous active control listeners
this.activeControlDisposeables = dispose(this.activeControlDisposeables);
// Listen to control changes
if (control) {
this.activeControlDisposeables.push(control.onDidSizeConstraintsChange(e => this._onDidSizeConstraintsChange.fire(e)));
this.activeControlDisposeables.push(control.onDidFocus(() => this._onDidFocus.fire()));
}
// Indicate that size constraints could have changed due to new editor
this._onDidSizeConstraintsChange.fire();
}
private doSetInput(control: BaseEditor, editor: EditorInput, options: EditorOptions): TPromise<boolean> {
// If the input did not change, return early and only apply the options
// unless the options instruct us to force open it even if it is the same
const forceReload = options && options.forceReload;
const inputMatches = control.input && control.input.matches(editor);
if (inputMatches && !forceReload) {
// Forward options
control.setOptions(options);
// Still focus as needed
const focus = !options || !options.preserveFocus;
if (focus) {
control.focus();
}
return TPromise.as(false);
}
// Show progress while setting input after a certain timeout. If the workbench is opening
// be more relaxed about progress showing by increasing the delay a little bit to reduce flicker.
const operation = this.editorOperation.start(this.partService.isCreated() ? 800 : 3200);
// Call into editor control
const editorWillChange = !inputMatches;
return toWinJsPromise(control.setInput(editor, options, operation.token)).then(() => {
// Focus (unless prevented or another operation is running)
if (operation.isCurrent()) {
const focus = !options || !options.preserveFocus;
if (focus) {
control.focus();
}
}
// Operation done
operation.stop();
return editorWillChange;
}, e => {
// Operation done
operation.stop();
return TPromise.wrapError(e);
});
}
private doHideActiveEditorControl(): void {
if (!this._activeControl) {
return;
}
// Stop any running operation
this.editorOperation.stop();
// Remove control from parent and hide
const controlInstanceContainer = this._activeControl.getContainer();
this.parent.removeChild(controlInstanceContainer);
hide(controlInstanceContainer);
// Indicate to editor control
this._activeControl.clearInput();
this._activeControl.setVisible(false, this.groupView);
// Clear active control
this.doSetActiveControl(null);
}
closeEditor(editor: EditorInput): void {
if (this._activeControl && editor.matches(this._activeControl.input)) {
this.doHideActiveEditorControl();
}
}
layout(dimension: Dimension): void {
this.dimension = dimension;
if (this._activeControl && this.dimension) {
this._activeControl.layout(this.dimension);
}
}
shutdown(): void {
// Forward to all editor controls
this.controls.forEach(editor => editor.shutdown());
}
dispose(): void {
this.activeControlDisposeables = dispose(this.activeControlDisposeables);
super.dispose();
}
}

View File

@@ -0,0 +1,534 @@
/*---------------------------------------------------------------------------------------------
* 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/editordroptarget';
import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass, removeClass } from 'vs/base/browser/dom';
import { IEditorGroupsAccessor, EDITOR_TITLE_HEIGHT, IEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor';
import { EDITOR_DRAG_AND_DROP_BACKGROUND, Themable } from 'vs/workbench/common/theme';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { isMacintosh } from 'vs/base/common/platform';
import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/group/common/editorGroupsService';
import { toDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
interface IDropOperation {
splitDirection?: GroupDirection;
}
class DropOverlay extends Themable {
private static OVERLAY_ID = 'monaco-workbench-editor-drop-overlay';
private container: HTMLElement;
private overlay: HTMLElement;
private currentDropOperation: IDropOperation;
private _disposed: boolean;
private readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
private readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
constructor(
private accessor: IEditorGroupsAccessor,
private groupView: IEditorGroupView,
themeService: IThemeService,
private instantiationService: IInstantiationService
) {
super(themeService);
this.create();
}
get disposed(): boolean {
return this._disposed;
}
private create(): void {
const overlayOffsetHeight = this.getOverlayOffsetHeight();
// Container
this.container = document.createElement('div');
this.container.id = DropOverlay.OVERLAY_ID;
this.container.style.top = `${overlayOffsetHeight}px`;
// Parent
this.groupView.element.appendChild(this.container);
addClass(this.groupView.element, 'dragged-over');
this._register(toDisposable(() => {
this.groupView.element.removeChild(this.container);
removeClass(this.groupView.element, 'dragged-over');
}));
// Overlay
this.overlay = document.createElement('div');
addClass(this.overlay, 'editor-group-overlay-indicator');
this.container.appendChild(this.overlay);
// Overlay Event Handling
this.registerListeners();
// Styles
this.updateStyles();
}
protected updateStyles(): void {
// Overlay drop background
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND);
// Overlay contrast border (if any)
const activeContrastBorderColor = this.getColor(activeContrastBorder);
this.overlay.style.outlineColor = activeContrastBorderColor;
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : null;
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : null;
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : null;
}
private registerListeners(): void {
this._register(new DragAndDropObserver(this.container, {
onDragEnter: e => void 0,
onDragOver: e => {
const isDraggingGroup = this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype);
const isDraggingEditor = this.editorTransfer.hasData(DraggedEditorIdentifier.prototype);
// Update the dropEffect to "copy" if there is no local data to be dragged because
// in that case we can only copy the data into and not move it from its source
if (!isDraggingEditor && !isDraggingGroup) {
e.dataTransfer.dropEffect = 'copy';
}
// Find out if operation is valid
const isCopy = isDraggingGroup ? this.isCopyOperation(e) : isDraggingEditor ? this.isCopyOperation(e, this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier) : true;
if (!isCopy) {
const sourceGroupView = this.findSourceGroupView();
if (sourceGroupView === this.groupView) {
if (isDraggingGroup || (isDraggingEditor && sourceGroupView.count < 2)) {
this.hideOverlay();
return; // do not allow to drop group/editor on itself if this results in an empty group
}
}
}
// Position overlay
this.positionOverlay(e.offsetX, e.offsetY, isDraggingGroup);
},
onDragLeave: e => this.dispose(),
onDragEnd: e => this.dispose(),
onDrop: e => {
EventHelper.stop(e, true);
// Dispose overlay
this.dispose();
// Handle drop if we have a valid operation
if (this.currentDropOperation) {
this.handleDrop(e, this.currentDropOperation.splitDirection);
}
}
}));
this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => {
// Under some circumstances we have seen reports where the drop overlay is not being
// cleaned up and as such the editor area remains under the overlay so that you cannot
// type into the editor anymore. This seems related to using VMs and DND via host and
// guest OS, though some users also saw it without VMs.
// To protect against this issue we always destroy the overlay as soon as we detect a
// mouse event over it. The delay is used to guarantee we are not interfering with the
// actual DROP event that can also trigger a mouse over event.
setTimeout(() => {
this.dispose();
}, 300);
}));
}
private findSourceGroupView(): IEditorGroupView {
// Check for group transfer
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
return this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)[0].identifier);
}
// Check for editor transfer
else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
return this.accessor.getGroup(this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier.groupId);
}
return void 0;
}
private handleDrop(event: DragEvent, splitDirection?: GroupDirection): void {
// Determine target group
const ensureTargetGroup = () => {
let targetGroup: IEditorGroupView;
if (typeof splitDirection === 'number') {
targetGroup = this.accessor.addGroup(this.groupView, splitDirection);
} else {
targetGroup = this.groupView;
}
return targetGroup;
};
// Check for group transfer
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
const draggedEditorGroup = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)[0].identifier;
// Return if the drop is a no-op
const sourceGroup = this.accessor.getGroup(draggedEditorGroup);
if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) {
return;
}
// Split to new group
let targetGroup: IEditorGroupView;
if (typeof splitDirection === 'number') {
if (this.isCopyOperation(event)) {
targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection);
} else {
targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection);
}
}
// Merge into existing group
else {
if (this.isCopyOperation(event)) {
targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView, { mode: MergeGroupMode.COPY_EDITORS });
} else {
targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView);
}
}
this.accessor.activateGroup(targetGroup);
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
}
// Check for editor transfer
else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier;
const targetGroup = ensureTargetGroup();
// Return if the drop is a no-op
const sourceGroup = this.accessor.getGroup(draggedEditor.groupId);
if (sourceGroup === targetGroup) {
return;
}
// Open in target group
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true }));
targetGroup.openEditor(draggedEditor.editor, options);
// Ensure target has focus
targetGroup.focus();
// Close in source group unless we copy
const copyEditor = this.isCopyOperation(event, draggedEditor);
if (!copyEditor) {
sourceGroup.closeEditor(draggedEditor.editor);
}
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
}
// Check for URI transfer
else {
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true /* open workspace instead of file if dropped */ });
dropHandler.handleDrop(event, () => ensureTargetGroup(), targetGroup => targetGroup.focus());
}
}
private isCopyOperation(e: DragEvent, draggedEditor?: IEditorIdentifier): boolean {
if (draggedEditor && !(draggedEditor.editor as EditorInput).supportsSplitEditor()) {
return false;
}
return (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
}
private positionOverlay(mousePosX: number, mousePosY: number, isDraggingGroup: boolean): void {
const preferSplitVertically = this.accessor.partOptions.openSideBySideDirection === 'right';
const editorControlWidth = this.groupView.element.clientWidth;
const editorControlHeight = this.groupView.element.clientHeight - this.getOverlayOffsetHeight();
let edgeWidthThresholdFactor: number;
if (isDraggingGroup) {
edgeWidthThresholdFactor = preferSplitVertically ? 0.3 : 0.1; // give larger threshold when dragging group depending on preferred split direction
} else {
edgeWidthThresholdFactor = 0.1; // 10% threshold to split if dragging editors
}
let edgeHeightThresholdFactor: number;
if (isDraggingGroup) {
edgeHeightThresholdFactor = preferSplitVertically ? 0.1 : 0.3; // give larger threshold when dragging group depending on preferred split direction
} else {
edgeHeightThresholdFactor = 0.1; // 10% threshold to split if dragging editors
}
const edgeWidthThreshold = editorControlWidth * edgeWidthThresholdFactor;
const edgeHeightThreshold = editorControlHeight * edgeHeightThresholdFactor;
const splitWidthThreshold = editorControlWidth / 3; // offer to split left/right at 33%
const splitHeightThreshold = editorControlHeight / 3; // offer to split up/down at 33%
// Enable to debug the drop threshold square
// let child = this.overlay.children.item(0) as HTMLElement || this.overlay.appendChild(document.createElement('div'));
// child.style.backgroundColor = 'red';
// child.style.position = 'absolute';
// child.style.width = (groupViewWidth - (2 * edgeWidthThreshold)) + 'px';
// child.style.height = (groupViewHeight - (2 * edgeHeightThreshold)) + 'px';
// child.style.left = edgeWidthThreshold + 'px';
// child.style.top = edgeHeightThreshold + 'px';
// No split if mouse is above certain threshold in the center of the view
let splitDirection: GroupDirection;
if (
mousePosX > edgeWidthThreshold && mousePosX < editorControlWidth - edgeWidthThreshold &&
mousePosY > edgeHeightThreshold && mousePosY < editorControlHeight - edgeHeightThreshold
) {
splitDirection = void 0;
}
// Offer to split otherwise
else {
// User prefers to split vertically: offer a larger hitzone
// for this direction like so:
// ----------------------------------------------
// | | SPLIT UP | |
// | SPLIT |-----------------------| SPLIT |
// | | MERGE | |
// | LEFT |-----------------------| RIGHT |
// | | SPLIT DOWN | |
// ----------------------------------------------
if (preferSplitVertically) {
if (mousePosX < splitWidthThreshold) {
splitDirection = GroupDirection.LEFT;
} else if (mousePosX > splitWidthThreshold * 2) {
splitDirection = GroupDirection.RIGHT;
} else if (mousePosY < editorControlHeight / 2) {
splitDirection = GroupDirection.UP;
} else {
splitDirection = GroupDirection.DOWN;
}
}
// User prefers to split horizontally: offer a larger hitzone
// for this direction like so:
// ----------------------------------------------
// | SPLIT UP |
// |--------------------------------------------|
// | SPLIT LEFT | MERGE | SPLIT RIGHT |
// |--------------------------------------------|
// | SPLIT DOWN |
// ----------------------------------------------
else {
if (mousePosY < splitHeightThreshold) {
splitDirection = GroupDirection.UP;
} else if (mousePosY > splitHeightThreshold * 2) {
splitDirection = GroupDirection.DOWN;
} else if (mousePosX < editorControlWidth / 2) {
splitDirection = GroupDirection.LEFT;
} else {
splitDirection = GroupDirection.RIGHT;
}
}
}
// Draw overlay based on split direction
switch (splitDirection) {
case GroupDirection.UP:
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '50%' });
break;
case GroupDirection.DOWN:
this.doPositionOverlay({ top: '50%', left: '0', width: '100%', height: '50%' });
break;
case GroupDirection.LEFT:
this.doPositionOverlay({ top: '0', left: '0', width: '50%', height: '100%' });
break;
case GroupDirection.RIGHT:
this.doPositionOverlay({ top: '0', left: '50%', width: '50%', height: '100%' });
break;
default:
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' });
}
// Make sure the overlay is visible now
this.overlay.style.opacity = '1';
// Enable transition after a timeout to prevent initial animation
setTimeout(() => addClass(this.overlay, 'overlay-move-transition'), 0);
// Remember as current split direction
this.currentDropOperation = { splitDirection };
}
private doPositionOverlay(options: { top: string, left: string, width: string, height: string }): void {
// Container
const offsetHeight = this.getOverlayOffsetHeight();
if (offsetHeight) {
this.container.style.height = `calc(100% - ${offsetHeight}px)`;
} else {
this.container.style.height = '100%';
}
// Overlay
this.overlay.style.top = options.top;
this.overlay.style.left = options.left;
this.overlay.style.width = options.width;
this.overlay.style.height = options.height;
}
private getOverlayOffsetHeight(): number {
if (!this.groupView.isEmpty() && this.accessor.partOptions.showTabs) {
return EDITOR_TITLE_HEIGHT; // show overlay below title if group shows tabs
}
return 0;
}
private hideOverlay(): void {
// Reset overlay
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' });
this.overlay.style.opacity = '0';
removeClass(this.overlay, 'overlay-move-transition');
// Reset current operation
this.currentDropOperation = void 0;
}
contains(element: HTMLElement): boolean {
return element === this.container || element === this.overlay;
}
dispose(): void {
super.dispose();
this._disposed = true;
}
}
export class EditorDropTarget extends Themable {
private _overlay: DropOverlay;
private counter = 0;
private readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
private readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
constructor(
private accessor: IEditorGroupsAccessor,
private container: HTMLElement,
@IThemeService themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(themeService);
this.registerListeners();
}
private get overlay(): DropOverlay {
if (this._overlay && !this._overlay.disposed) {
return this._overlay;
}
return void 0;
}
private registerListeners(): void {
this._register(addDisposableListener(this.container, EventType.DRAG_ENTER, e => this.onDragEnter(e)));
this._register(addDisposableListener(this.container, EventType.DRAG_LEAVE, () => this.onDragLeave()));
[this.container, window].forEach(node => this._register(addDisposableListener(node as HTMLElement, EventType.DRAG_END, () => this.onDragEnd())));
}
private onDragEnter(event: DragEvent): void {
this.counter++;
// Validate transfer
if (
!this.editorTransfer.hasData(DraggedEditorIdentifier.prototype) &&
!this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype) &&
!event.dataTransfer.types.length // see https://github.com/Microsoft/vscode/issues/25789
) {
event.dataTransfer.dropEffect = 'none';
return; // unsupported transfer
}
// Signal DND start
this.updateContainer(true);
const target = event.target as HTMLElement;
if (target) {
// Somehow we managed to move the mouse quickly out of the current overlay, so destroy it
if (this.overlay && !this.overlay.contains(target)) {
this.disposeOverlay();
}
// Create overlay over target
if (!this.overlay) {
const targetGroupView = this.findTargetGroupView(target);
if (targetGroupView) {
this._overlay = new DropOverlay(this.accessor, targetGroupView, this.themeService, this.instantiationService);
}
}
}
}
private onDragLeave(): void {
this.counter--;
if (this.counter === 0) {
this.updateContainer(false);
}
}
private onDragEnd(): void {
this.counter = 0;
this.updateContainer(false);
this.disposeOverlay();
}
private findTargetGroupView(child: HTMLElement): IEditorGroupView {
const groups = this.accessor.groups;
for (let i = 0; i < groups.length; i++) {
const groupView = groups[i];
if (isAncestor(child, groupView.element)) {
return groupView;
}
}
return void 0;
}
private updateContainer(isDraggedOver: boolean): void {
toggleClass(this.container, 'dragged-over', isDraggedOver);
}
dispose(): void {
super.dispose();
this.disposeOverlay();
}
private disposeOverlay(): void {
if (this.overlay) {
this.overlay.dispose();
this._overlay = void 0;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@ import 'vs/css!./media/editorpicker';
import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import URI from 'vs/base/common/uri';
import * as errors from 'vs/base/common/errors';
import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel';
@@ -16,61 +15,55 @@ 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 { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditorInput, toResource, IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor';
import { EditorInput, toResource } from 'vs/workbench/common/editor';
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
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
@IModelService private modelService: IModelService
) {
super();
this.stacks = editorGroupService.getStacksModel();
}
public getLabelOptions(): IIconLabelValueOptions {
getLabelOptions(): IIconLabelValueOptions {
return {
extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()),
italic: this._group.isPreview(this.editor)
italic: !this._group.isPinned(this.editor)
};
}
public getLabel(): string {
getLabel(): string {
return this.editor.getName();
}
public getIcon(): string {
getIcon(): string {
return this.editor.isDirty() ? 'dirty' : '';
}
public get group(): IEditorGroup {
get group(): IEditorGroup {
return this._group;
}
public getResource(): URI {
getResource(): URI {
return toResource(this.editor, { supportSideBySide: true });
}
public getAriaLabel(): string {
getAriaLabel(): string {
return nls.localize('entryAriaLabel', "{0}, editor group picker", this.getLabel());
}
public getDescription(): string {
getDescription(): string {
return this.editor.getDescription();
}
public run(mode: Mode, context: IEntryRunContext): boolean {
run(mode: Mode, context: IEntryRunContext): boolean {
if (mode === Mode.OPEN) {
return this.runOpen(context);
}
@@ -79,7 +72,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup {
}
private runOpen(context: IEntryRunContext): boolean {
this.editorService.openEditor(this.editor, null, this.stacks.positionOfGroup(this.group)).done(null, errors.onUnexpectedError);
this._group.openEditor(this.editor);
return true;
}
@@ -90,15 +83,15 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
constructor(
@IInstantiationService protected instantiationService: IInstantiationService,
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IEditorGroupService protected editorGroupService: IEditorGroupService
@IEditorService protected editorService: IEditorService,
@IEditorGroupsService protected editorGroupService: IEditorGroupsService
) {
super();
this.scorerCache = Object.create(null);
}
public getResults(searchValue: string): TPromise<QuickOpenModel> {
getResults(searchValue: string): TPromise<QuickOpenModel> {
const editorEntries = this.getEditorEntries();
if (!editorEntries.length) {
return TPromise.as(null);
@@ -123,11 +116,11 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
});
// Sorting
const stacks = this.editorGroupService.getStacksModel();
if (query.value) {
const groups = this.editorGroupService.getGroups(GroupsOrder.CREATION_TIME);
entries.sort((e1, e2) => {
if (e1.group !== e2.group) {
return stacks.positionOfGroup(e1.group) - stacks.positionOfGroup(e2.group);
return groups.indexOf(e1.group) - groups.indexOf(e2.group); // older groups first
}
return compareItemsByScore(e1, e2, query, true, QuickOpenItemAccessor, this.scorerCache);
@@ -135,11 +128,11 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
}
// Grouping (for more than one group)
if (stacks.groups.length > 1) {
if (this.editorGroupService.count > 1) {
let lastGroup: IEditorGroup;
entries.forEach(e => {
if (!lastGroup || lastGroup !== e.group) {
e.setGroupLabel(nls.localize('groupLabel', "Group: {0}", e.group.label));
e.setGroupLabel(e.group.label);
e.setShowBorder(!!lastGroup);
lastGroup = e.group;
}
@@ -149,28 +142,26 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
return TPromise.as(new QuickOpenModel(entries));
}
public onClose(canceled: boolean): void {
onClose(canceled: boolean): void {
this.scorerCache = Object.create(null);
}
protected abstract getEditorEntries(): EditorPickerEntry[];
}
export abstract class EditorGroupPicker extends BaseEditorPicker {
export class ActiveEditorGroupPicker extends BaseEditorPicker {
static readonly ID = 'workbench.picker.activeEditors';
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));
return this.group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map((editor, index) => this.instantiationService.createInstance(EditorPickerEntry, editor, this.group));
}
protected abstract getPosition(): Position;
private get group(): IEditorGroup {
return this.editorGroupService.activeGroup;
}
public getEmptyLabel(searchString: string): string {
getEmptyLabel(searchString: string): string {
if (searchString) {
return nls.localize('noResultsFoundInGroup', "No matching opened editor found in group");
}
@@ -178,76 +169,45 @@ export abstract class EditorGroupPicker extends BaseEditorPicker {
return nls.localize('noOpenedEditors', "List of opened editors is currently empty in group");
}
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
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
};
}
const editors = this.group.count;
return {
autoFocusFirstEntry: group.count === 1,
autoFocusSecondEntry: group.count > 1
autoFocusFirstEntry: editors === 1,
autoFocusSecondEntry: editors > 1
};
}
}
export class GroupOnePicker extends EditorGroupPicker {
public static readonly ID = 'workbench.picker.editors.one';
protected getPosition(): Position {
return Position.ONE;
}
}
export class GroupTwoPicker extends EditorGroupPicker {
public static readonly ID = 'workbench.picker.editors.two';
protected getPosition(): Position {
return Position.TWO;
}
}
export class GroupThreePicker extends EditorGroupPicker {
public static readonly ID = 'workbench.picker.editors.three';
protected getPosition(): Position {
return Position.THREE;
}
}
export class AllEditorsPicker extends BaseEditorPicker {
public static readonly ID = 'workbench.picker.editors';
static readonly ID = 'workbench.picker.editors';
protected getEditorEntries(): EditorPickerEntry[] {
const entries: EditorPickerEntry[] = [];
const stacks = this.editorGroupService.getStacksModel();
stacks.groups.forEach((group, position) => {
group.getEditors().forEach((editor, index) => {
this.editorGroupService.getGroups(GroupsOrder.CREATION_TIME).forEach(group => {
group.editors.forEach(editor => {
entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group));
});
});
@@ -255,7 +215,7 @@ export class AllEditorsPicker extends BaseEditorPicker {
return entries;
}
public getEmptyLabel(searchString: string): string {
getEmptyLabel(searchString: string): string {
if (searchString) {
return nls.localize('noResultsFound', "No matching opened editor found");
}
@@ -263,7 +223,7 @@ export class AllEditorsPicker extends BaseEditorPicker {
return nls.localize('noOpenedEditorsAllGroups', "List of opened editors is currently empty");
}
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
if (searchValue) {
return {
autoFocusFirstEntry: true

View File

@@ -20,7 +20,7 @@ import { language, LANGUAGE_DEFAULT, AccessibilitySupport } from 'vs/base/common
import * as browser from 'vs/base/browser/browser';
import { IMode } from 'vs/editor/common/modes';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor';
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput } from 'vs/workbench/common/editor';
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorAction } from 'vs/editor/common/editorCommon';
@@ -30,21 +30,19 @@ import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/
import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/indentation';
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
import { IEditor as IBaseEditor, IEditorInput } from 'vs/platform/editor/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { SUPPORTED_ENCODINGS, IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { getCodeEditor as getEditorWidget, getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
@@ -54,7 +52,7 @@ import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { widgetShadow, editorWidgetBackground, foreground, darken, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { deepClone } from 'vs/base/common/objects';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Button } from 'vs/base/browser/ui/button/button';
import { Schemas } from 'vs/base/common/network';
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
@@ -64,11 +62,11 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr
class SideBySideEditorEncodingSupport implements IEncodingSupport {
constructor(private master: IEncodingSupport, private details: IEncodingSupport) { }
public getEncoding(): string {
getEncoding(): string {
return this.master.getEncoding(); // always report from modified (right hand) side
}
public setEncoding(encoding: string, mode: EncodingMode): void {
setEncoding(encoding: string, mode: EncodingMode): void {
[this.master, this.details].forEach(s => s.setEncoding(encoding, mode));
}
}
@@ -133,7 +131,7 @@ class StateChange {
this.metadata = false;
}
public combine(other: StateChange) {
combine(other: StateChange) {
this.indentation = this.indentation || other.indentation;
this.selectionStatus = this.selectionStatus || other.selectionStatus;
this.mode = this.mode || other.mode;
@@ -158,28 +156,28 @@ interface StateDelta {
class State {
private _selectionStatus: string;
public get selectionStatus(): string { return this._selectionStatus; }
get selectionStatus(): string { return this._selectionStatus; }
private _mode: string;
public get mode(): string { return this._mode; }
get mode(): string { return this._mode; }
private _encoding: string;
public get encoding(): string { return this._encoding; }
get encoding(): string { return this._encoding; }
private _EOL: string;
public get EOL(): string { return this._EOL; }
get EOL(): string { return this._EOL; }
private _indentation: string;
public get indentation(): string { return this._indentation; }
get indentation(): string { return this._indentation; }
private _tabFocusMode: boolean;
public get tabFocusMode(): boolean { return this._tabFocusMode; }
get tabFocusMode(): boolean { return this._tabFocusMode; }
private _screenReaderMode: boolean;
public get screenReaderMode(): boolean { return this._screenReaderMode; }
get screenReaderMode(): boolean { return this._screenReaderMode; }
private _metadata: string;
public get metadata(): string { return this._metadata; }
get metadata(): string { return this._metadata; }
constructor() {
this._selectionStatus = null;
@@ -191,7 +189,7 @@ class State {
this._metadata = null;
}
public update(update: StateDelta): StateChange {
update(update: StateDelta): StateChange {
const e = new StateChange();
let somethingChanged = false;
@@ -282,7 +280,6 @@ function hide(el: HTMLElement): void {
}
export class EditorStatus implements IStatusbarItem {
private state: State;
private element: HTMLElement;
private tabFocusModeElement: HTMLElement;
@@ -300,8 +297,7 @@ export class EditorStatus implements IStatusbarItem {
private screenReaderExplanation: ScreenReaderDetectedExplanation;
constructor(
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IEditorService private editorService: IEditorService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IInstantiationService private instantiationService: IInstantiationService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@@ -314,7 +310,7 @@ export class EditorStatus implements IStatusbarItem {
this.state = new State();
}
public render(container: HTMLElement): IDisposable {
render(container: HTMLElement): IDisposable {
this.element = append(container, $('.editor-statusbar-item'));
this.tabFocusModeElement = append(this.element, $('a.editor-status-tabfocusmode.status-bar-info'));
@@ -370,7 +366,7 @@ export class EditorStatus implements IStatusbarItem {
}
}
},
this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()),
this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()),
this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
@@ -545,73 +541,81 @@ export class EditorStatus implements IStatusbarItem {
TabFocus.setTabFocusMode(false);
}
private onEditorsChanged(): void {
const activeEditor = this.editorService.getActiveEditor();
const control = getEditorWidget(activeEditor);
private updateStatusBar(): void {
const activeControl = this.editorService.activeControl;
const activeCodeEditor = activeControl ? getCodeEditor(activeControl.getControl()) : void 0;
// Update all states
this.onScreenReaderModeChange(control);
this.onSelectionChange(control);
this.onModeChange(control);
this.onEOLChange(control);
this.onEncodingChange(activeEditor);
this.onIndentationChange(control);
this.onMetadataChange(activeEditor);
this.onScreenReaderModeChange(activeCodeEditor);
this.onSelectionChange(activeCodeEditor);
this.onModeChange(activeCodeEditor);
this.onEOLChange(activeCodeEditor);
this.onEncodingChange(activeControl);
this.onIndentationChange(activeCodeEditor);
this.onMetadataChange(activeControl);
// Dispose old active editor listeners
dispose(this.activeEditorListeners);
// Attach new listeners to active editor
if (control) {
if (activeCodeEditor) {
// Hook Listener for Configuration changes
this.activeEditorListeners.push(control.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
this.activeEditorListeners.push(activeCodeEditor.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
if (event.accessibilitySupport) {
this.onScreenReaderModeChange(control);
this.onScreenReaderModeChange(activeCodeEditor);
}
}));
// Hook Listener for Selection changes
this.activeEditorListeners.push(control.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
this.onSelectionChange(control);
this.activeEditorListeners.push(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
this.onSelectionChange(activeCodeEditor);
}));
// Hook Listener for mode changes
this.activeEditorListeners.push(control.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
this.onModeChange(control);
this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
this.onModeChange(activeCodeEditor);
}));
// Hook Listener for content changes
this.activeEditorListeners.push(control.onDidChangeModelContent((e) => {
this.onEOLChange(control);
this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelContent((e) => {
this.onEOLChange(activeCodeEditor);
let selections = activeCodeEditor.getSelections();
for (let i = 0; i < e.changes.length; i++) {
if (selections.some(selection => Range.areIntersecting(selection, e.changes[i].range))) {
this.onSelectionChange(activeCodeEditor);
break;
}
}
}));
// Hook Listener for content options changes
this.activeEditorListeners.push(control.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
this.onIndentationChange(control);
this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
this.onIndentationChange(activeCodeEditor);
}));
}
// Handle binary editors
else if (activeEditor instanceof BaseBinaryResourceEditor || activeEditor instanceof BinaryResourceDiffEditor) {
else if (activeControl instanceof BaseBinaryResourceEditor || activeControl instanceof BinaryResourceDiffEditor) {
const binaryEditors: BaseBinaryResourceEditor[] = [];
if (activeEditor instanceof BinaryResourceDiffEditor) {
const details = activeEditor.getDetailsEditor();
if (activeControl instanceof BinaryResourceDiffEditor) {
const details = activeControl.getDetailsEditor();
if (details instanceof BaseBinaryResourceEditor) {
binaryEditors.push(details);
}
const master = activeEditor.getMasterEditor();
const master = activeControl.getMasterEditor();
if (master instanceof BaseBinaryResourceEditor) {
binaryEditors.push(master);
}
} else {
binaryEditors.push(activeEditor);
binaryEditors.push(activeControl);
}
binaryEditors.forEach(editor => {
this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
this.onMetadataChange(activeEditor);
this.onMetadataChange(activeControl);
}));
});
}
@@ -742,7 +746,7 @@ export class EditorStatus implements IStatusbarItem {
this.updateState(info);
}
private onEncodingChange(e: IBaseEditor): void {
private onEncodingChange(e?: IBaseEditor): void {
if (e && !this.isActiveEditor(e)) {
return;
}
@@ -750,7 +754,7 @@ export class EditorStatus implements IStatusbarItem {
const info: StateDelta = { encoding: null };
// We only support text based editors
if (getEditorWidget(e)) {
if (e && (isCodeEditor(e.getControl()) || isDiffEditor(e.getControl()))) {
const encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(e.input);
if (encodingSupport) {
const rawEncoding = encodingSupport.getEncoding();
@@ -767,11 +771,11 @@ export class EditorStatus implements IStatusbarItem {
}
private onResourceEncodingChange(resource: uri): void {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor) {
const activeResource = toResource(activeEditor.input, { supportSideBySide: true });
const activeControl = this.editorService.activeControl;
if (activeControl) {
const activeResource = toResource(activeControl.input, { supportSideBySide: true });
if (activeResource && activeResource.toString() === resource.toString()) {
return this.onEncodingChange(<IBaseEditor>activeEditor); // only update if the encoding changed for the active resource
return this.onEncodingChange(<IBaseEditor>activeControl); // only update if the encoding changed for the active resource
}
}
}
@@ -782,10 +786,10 @@ export class EditorStatus implements IStatusbarItem {
this.updateState(info);
}
private isActiveEditor(e: IBaseEditor): boolean {
const activeEditor = this.editorService.getActiveEditor();
private isActiveEditor(control: IBaseEditor): boolean {
const activeControl = this.editorService.activeControl;
return activeEditor && e && activeEditor === e;
return activeControl && activeControl === control;
}
}
@@ -798,7 +802,7 @@ function isWritableCodeEditor(codeEditor: ICodeEditor): boolean {
}
function isWritableBaseEditor(e: IBaseEditor): boolean {
return isWritableCodeEditor(getEditorWidget(e));
return e && isWritableCodeEditor(getCodeEditor(e.getControl()));
}
export class ShowLanguageExtensionsAction extends Action {
@@ -822,15 +826,15 @@ export class ShowLanguageExtensionsAction extends Action {
export class ChangeModeAction extends Action {
public static readonly ID = 'workbench.action.editor.changeLanguageMode';
public static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
static readonly ID = 'workbench.action.editor.changeLanguageMode';
static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
constructor(
actionId: string,
actionLabel: string,
@IModeService private modeService: IModeService,
@IModelService private modelService: IModelService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorService private editorService: IEditorService,
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IPreferencesService private preferencesService: IPreferencesService,
@@ -840,15 +844,14 @@ export class ChangeModeAction extends Action {
super(actionId, actionLabel);
}
public run(): TPromise<any> {
let activeEditor = this.editorService.getActiveEditor();
const editorWidget = getEditorWidget(activeEditor);
if (!editorWidget) {
run(): TPromise<any> {
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
if (!activeTextEditorWidget) {
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
const textModel = editorWidget.getModel();
const resource = toResource(activeEditor.input, { supportSideBySide: true });
const textModel = activeTextEditorWidget.getModel();
const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true });
let hasLanguageSupport = !!resource;
if (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
@@ -946,17 +949,16 @@ export class ChangeModeAction extends Action {
}
// Change mode for active editor
activeEditor = this.editorService.getActiveEditor();
const codeOrDiffEditor = getCodeOrDiffEditor(activeEditor);
const activeEditor = this.editorService.activeControl;
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
const models: ITextModel[] = [];
if (codeOrDiffEditor.codeEditor) {
const codeEditorModel = codeOrDiffEditor.codeEditor.getModel();
if (isCodeEditor(activeTextEditorWidget)) {
const codeEditorModel = activeTextEditorWidget.getModel();
if (codeEditorModel) {
models.push(codeEditorModel);
}
}
if (codeOrDiffEditor.diffEditor) {
const diffEditorModel = codeOrDiffEditor.diffEditor.getModel();
} else if (isDiffEditor(activeTextEditorWidget)) {
const diffEditorModel = activeTextEditorWidget.getModel();
if (diffEditorModel) {
if (diffEditorModel.original) {
models.push(diffEditorModel.original);
@@ -1045,42 +1047,42 @@ export interface IChangeEOLEntry extends IPickOpenEntry {
class ChangeIndentationAction extends Action {
public static readonly ID = 'workbench.action.editor.changeIndentation';
public static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
static readonly ID = 'workbench.action.editor.changeIndentation';
static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
constructor(
actionId: string,
actionLabel: string,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorService private editorService: IEditorService,
@IQuickOpenService private quickOpenService: IQuickOpenService
) {
super(actionId, actionLabel);
}
public run(): TPromise<any> {
const activeEditor = this.editorService.getActiveEditor();
const control = getEditorWidget(activeEditor);
if (!control) {
run(): TPromise<any> {
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
if (!activeTextEditorWidget) {
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
if (!isWritableCodeEditor(control)) {
if (!isWritableCodeEditor(activeTextEditorWidget)) {
return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
}
const picks = [
control.getAction(IndentUsingSpaces.ID),
control.getAction(IndentUsingTabs.ID),
control.getAction(DetectIndentation.ID),
control.getAction(IndentationToSpacesAction.ID),
control.getAction(IndentationToTabsAction.ID),
control.getAction(TrimTrailingWhitespaceAction.ID)
activeTextEditorWidget.getAction(IndentUsingSpaces.ID),
activeTextEditorWidget.getAction(IndentUsingTabs.ID),
activeTextEditorWidget.getAction(DetectIndentation.ID),
activeTextEditorWidget.getAction(IndentationToSpacesAction.ID),
activeTextEditorWidget.getAction(IndentationToTabsAction.ID),
activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID)
].map((a: IEditorAction) => {
return {
id: a.id,
label: a.label,
detail: (language === LANGUAGE_DEFAULT) ? null : a.alias,
run: () => {
control.focus();
activeTextEditorWidget.focus();
a.run();
}
};
@@ -1095,30 +1097,29 @@ class ChangeIndentationAction extends Action {
export class ChangeEOLAction extends Action {
public static readonly ID = 'workbench.action.editor.changeEOL';
public static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
static readonly ID = 'workbench.action.editor.changeEOL';
static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
constructor(
actionId: string,
actionLabel: string,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorService private editorService: IEditorService,
@IQuickOpenService private quickOpenService: IQuickOpenService
) {
super(actionId, actionLabel);
}
public run(): TPromise<any> {
let activeEditor = this.editorService.getActiveEditor();
const editorWidget = getEditorWidget(activeEditor);
if (!editorWidget) {
run(): TPromise<any> {
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
if (!activeTextEditorWidget) {
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
if (!isWritableCodeEditor(editorWidget)) {
if (!isWritableCodeEditor(activeTextEditorWidget)) {
return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
}
const textModel = editorWidget.getModel();
const textModel = activeTextEditorWidget.getModel();
const EOLOptions: IChangeEOLEntry[] = [
{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
@@ -1129,11 +1130,10 @@ export class ChangeEOLAction extends Action {
return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then(eol => {
if (eol) {
activeEditor = this.editorService.getActiveEditor();
const editorWidget = getEditorWidget(activeEditor);
if (editorWidget && isWritableCodeEditor(editorWidget)) {
const textModel = editorWidget.getModel();
textModel.setEOL(eol.eol);
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget);
if (activeCodeEditor && isWritableCodeEditor(activeCodeEditor)) {
const textModel = activeCodeEditor.getModel();
textModel.pushEOL(eol.eol);
}
}
});
@@ -1142,13 +1142,13 @@ export class ChangeEOLAction extends Action {
export class ChangeEncodingAction extends Action {
public static readonly ID = 'workbench.action.editor.changeEncoding';
public static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
static readonly ID = 'workbench.action.editor.changeEncoding';
static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
constructor(
actionId: string,
actionLabel: string,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorService private editorService: IEditorService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
@IFileService private fileService: IFileService
@@ -1156,13 +1156,13 @@ export class ChangeEncodingAction extends Action {
super(actionId, actionLabel);
}
public run(): TPromise<any> {
let activeEditor = this.editorService.getActiveEditor();
if (!getEditorWidget(activeEditor) || !activeEditor.input) {
run(): TPromise<any> {
if (!getCodeEditor(this.editorService.activeTextEditorWidget)) {
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeEditor.input);
let activeControl = this.editorService.activeControl;
let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input);
if (!encodingSupport) {
return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
}
@@ -1181,7 +1181,7 @@ export class ChangeEncodingAction extends Action {
if (encodingSupport instanceof UntitledEditorInput) {
pickActionPromise = TPromise.as(saveWithEncodingPick);
} else if (!isWritableBaseEditor(activeEditor)) {
} else if (!isWritableBaseEditor(activeControl)) {
pickActionPromise = TPromise.as(reopenWithEncodingPick);
} else {
pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
@@ -1192,7 +1192,7 @@ export class ChangeEncodingAction extends Action {
return void 0;
}
const resource = toResource(activeEditor.input, { supportSideBySide: true });
const resource = toResource(activeControl.input, { supportSideBySide: true });
return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */)
.then(() => {
@@ -1249,8 +1249,8 @@ export class ChangeEncodingAction extends Action {
autoFocus: { autoFocusIndex: typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : void 0 }
}).then(encoding => {
if (encoding) {
activeEditor = this.editorService.getActiveEditor();
encodingSupport = toEditorWithEncodingSupport(activeEditor.input);
activeControl = this.editorService.activeControl;
encodingSupport = toEditorWithEncodingSupport(activeControl.input);
if (encodingSupport && encodingSupport.getEncoding() !== encoding.id) {
encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
}
@@ -1274,7 +1274,7 @@ class ScreenReaderDetectedExplanation extends Themable {
super(themeService);
}
public get visible(): boolean {
get visible(): boolean {
return this._visible;
}
@@ -1294,7 +1294,7 @@ class ScreenReaderDetectedExplanation extends Themable {
}
}
public show(anchorElement: HTMLElement): void {
show(anchorElement: HTMLElement): void {
this._visible = true;
this.contextViewService.showContextView({
@@ -1318,7 +1318,7 @@ class ScreenReaderDetectedExplanation extends Themable {
});
}
public hide(): void {
hide(): void {
this.contextViewService.hideContextView();
}

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* 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.editor>.content .editor-group-container .breadcrumbs-control.hidden {
display: none;
}
.monaco-workbench>.part.editor>.content .editor-group-container:not(.active) .breadcrumbs-control {
opacity: .8;
}
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.selected .monaco-icon-label,
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.focused .monaco-icon-label {
text-decoration-line: underline;
}
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.selected .hint-more,
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.focused .hint-more {
text-decoration-line: underline;
}
/* todo@joh move somewhere else */
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree>.input {
padding: 5px 9px;
position: relative;
box-sizing: border-box;
height: 36px;
}
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree>.tree {
height: calc(100% - 36px);
}
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree.inactive>.input {
display: none;
}
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree.inactive>.tree {
height: 100%;
}
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree .monaco-highlighted-label .highlight{
font-weight: bold;
}

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 253 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 253 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 253 B

View File

@@ -1 +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>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#424242"/></svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 253 B

View File

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

After

Width:  |  Height:  |  Size: 167 B

View File

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

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -1 +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>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.428 8L12 10.573 10.572 12 8 9.428 5.428 12 4 10.573 6.572 8 4 5.428 5.427 4 8 6.572 10.573 4 12 5.428 9.428 8z" fill="#E8E8E8"/></svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 241 B

View File

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

View File

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

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -1 +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>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.428 8L12 10.573 10.572 12 8 9.428 5.428 12 4 10.573 6.572 8 4 5.428 5.427 4 8 6.572 10.573 4 12 5.428 9.428 8z" fill="#424242"/></svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 241 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><path fill="#1E1E1E" d="M16 12h-2v2h-2v2H0V5h2V3h2V1h12v11z"/><g fill="#C5C5C5"><path d="M3 5h9v8h1V4H3zM5 2v1h9v8h1V2zM1 6v9h10V6H1zm8 7H7.5L6 11.5 4.5 13H3l2.3-2.3L3 8.5h1.5L6 10l1.5-1.5H9l-2.3 2.3L9 13z"/></g></svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><path fill="#F6F6F6" d="M16 12h-2v2h-2v2H0V5h2V3h2V1h12v11z"/><g fill="#424242"><path d="M3 5h9v8h1V4H3zM5 2v1h9v8h1V2zM1 6v9h10V6H1zm8 7H7.5L6 11.5 4.5 13H3l2.3-2.3L3 8.5h1.5L6 10l1.5-1.5H9l-2.3 2.3L9 13z"/></g></svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@@ -1,92 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 .one-editor-silo.centered .editor-container {
border-style: none solid;
border-width: 0px 1px;
}
#monaco-workbench-editor-move-overlay,
#monaco-workbench-editor-drop-overlay {
position: absolute;
left: 0;
width: 100%;
z-index: 10000;
}
#monaco-workbench-editor-drop-overlay {
opacity: 0; /* initially not visible until moving around */
}
.monaco-workbench > .editor > .content > .one-editor-silo {
position: absolute;
box-sizing: border-box; /* use border box to be able to draw a border as separator between editors */
}
.monaco-workbench > .editor > .content > .one-editor-silo.editor-one {
left: 0;
top: 0;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
right: 0;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
bottom: 0;
}
.monaco-workbench > .editor > .content > .one-editor-silo.dragging {
z-index: 70;
box-sizing: content-box;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
border-left: 1px solid;
border-right: 1px solid;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
border-top: 1px solid;
border-bottom: 1px solid;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
border-left: 1px solid;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
border-top: 1px solid;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.draggedunder {
transition: left 200ms ease-out;
}
.monaco-workbench > .editor > .content.vertical-layout > .editor-three.draggedunder {
transition-property: right;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.draggedunder {
transition: top 200ms ease-out;
}
.monaco-workbench > .editor > .content.horizontal-layout > .editor-three.draggedunder {
transition-property: bottom;
}
.monaco-workbench > .editor > .content > .one-editor-silo > .container {
height: 100%;
}
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
height: calc(100% - 35px); /* Editor is below editor title */
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* 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-drop-overlay {
position: absolute;
z-index: 10000;
width: 100%;
height: 100%;
left: 0;
}
#monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* very important to not take events away from the parent */
opacity: 0; /* hidden initially */
transition: opacity 150ms ease-out;
}
#monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator.overlay-move-transition {
transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out;
}

View File

@@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Container */
.monaco-workbench > .part.editor > .content .editor-group-container {
height: 100%;
}
.monaco-workbench > .part.editor > .content .editor-group-container.empty {
opacity: 0.5; /* dimmed to indicate inactive state */
}
.monaco-workbench > .part.editor > .content .editor-group-container.empty.active,
.monaco-workbench > .part.editor > .content .editor-group-container.empty.dragged-over {
opacity: 1; /* indicate active/dragged-over group through undimmed state */
}
/* Letterpress */
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-letterpress {
display: none; /* only visible when empty */
}
.monaco-workbench > .part.editor > .content .editor-group-container.empty > .editor-group-letterpress {
display: block;
margin: auto;
width: 100%;
height: calc(100% - 70px); /* centered below toolbar */
max-width: 260px;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: 70% 70%;
}
.monaco-workbench > .part.editor > .content.empty .editor-group-container.empty > .editor-group-letterpress {
background-size: 100% 100%; /* larger for empty editor part */
height: 100%; /* no toolbar in this case */
}
/* Title */
.monaco-workbench > .part.editor > .content .editor-group-container > .title {
position: relative;
display: flex;
flex-wrap: nowrap;
box-sizing: border-box;
overflow: hidden;
justify-content: space-between;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs {
flex-wrap: wrap;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.title-border-bottom::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--title-border-bottom-color);
width: 100%;
height: 1px;
}
.monaco-workbench > .part.editor > .content .editor-group-container.empty > .title {
display: none;
}
/* Toolbar */
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar {
display: none;
}
.monaco-workbench > .part.editor > .content:not(.empty) .editor-group-container.empty > .editor-group-container-toolbar {
display: block;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar .action-label {
display: block;
height: 35px;
line-height: 35px;
min-width: 28px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
}
.vs .monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group {
background-image: url('close-big.svg');
}
.vs-dark .monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group,
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group {
background-image: url('close-big-inverse.svg');
}
/* Editor */
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-container {
height: calc(100% - 35px); /* below title control */
}
.monaco-workbench > .part.editor > .content .editor-group-container.empty > .editor-container {
display: none;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-container > .editor-instance {
height: 100%;
}
.monaco-workbench > .part.editor > .content .grid-view-container {
width: 100%;
height: 100%;
}

View File

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

View File

@@ -70,10 +70,10 @@
}
.monaco-shell.vs .screen-reader-detected-explanation .cancel {
background: url('close-big.svg') center center no-repeat;
background: url('close-statusview.svg') center center no-repeat;
}
.monaco-shell.vs-dark .screen-reader-detected-explanation .cancel,
.monaco-shell.hc-black .screen-reader-detected-explanation .cancel {
background: url('close-big-dark.svg') center center no-repeat;
background: url('close-statusview-inverse.svg') center center no-repeat;
}

View File

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

View File

@@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* 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.editor > .content .editor-group-container > .title > .label-container {
display: flex;
justify-content: flex-start;
align-items: center;
overflow: hidden;
flex: auto;
}
/* Title Label */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label {
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
position: relative;
padding-left: 20px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label {
flex: none;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .monaco-icon-label::before {
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
}
/* Breadcrumbs */
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control {
flex: 1 50%;
overflow: hidden;
padding: 0 6px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
font-size: 0.9em;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.preview .monaco-breadcrumb-item {
font-style: italic;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
content: '/';
opacity: 1;
height: inherit;
width: inherit;
background-image: none;
}
/* {{SQL CARBON EDIT}} */
.monaco-workbench.windows > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
content: '/';
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::before,
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder + .monaco-breadcrumb-item::before,
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.relative-path .monaco-breadcrumb-item:nth-child(2)::before {
/* workspace folder, item following workspace folder, or relative path -> hide first seperator */
display: none;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::after {
/* use dot separator for workspace folder */
content: '•';
padding: 0 4px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
padding-right: 4px; /* does not have trailing separator*/
}
/* Title Actions */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-actions {
display: flex;
flex: initial;
opacity: 0.5;
height: 35px;
}
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .title-actions {
opacity: 1;
}

View File

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View File

@@ -1 +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>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>SplitScreenVertical_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM7,13H2V5H7Zm7,0H9V5h5Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 519 B

View File

@@ -1 +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>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>SplitScreenVertical_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM7,13H2V5H7Zm7,0H9V5h5Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 519 B

View File

@@ -1 +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>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>SplitScreenHorizontal_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM14,13H2V10H14Zm0-5H2V5H14Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 525 B

View File

@@ -1 +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>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>SplitScreenHorizontal_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM14,13H2V10H14Zm0-5H2V5H14Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 525 B

View File

@@ -1,20 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 822 B

View File

@@ -1,20 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 822 B

View File

@@ -1,228 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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;
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.sizing-shrink.has-icon-theme.close-button-right,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off {
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit {
width: 120px;
min-width: fit-content;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink {
min-width: 60px;
flex-basis: 0; /* all tabs are even */
flex-grow: 1; /* all tabs grow even */
max-width: fit-content;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.close-button-off::after {
content: "";
display: flex;
flex: 0;
width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/Microsoft/vscode/issues/45728) */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.close-button-left {
min-width: 80px; /* make more room for close button when it shows to the left */
padding-right: 5px; /* we need less room when sizing is shrink */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dragged {
will-change: transform; /* forces tab to be drawn on a separate layer (fixes https://github.com/Microsoft/vscode/issues/18733) */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dragged-over div {
pointer-events: none; /* prevents cursor flickering (fixes https://github.com/Microsoft/vscode/issues/38753) */
}
.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.sizing-shrink .tab-label {
position: relative;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
content: "";
position: absolute;
right: 0;
height: 100%;
width: 5px;
opacity: 1;
padding: 0;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-description-container {
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
text-overflow: clip;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
text-overflow: ellipsis;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before {
height: 16px; /* tweak the icon size of the editor labels when icons are enabled */
}
/* 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-right.sizing-shrink > .tab-close {
flex: 0;
overflow: hidden; /* let the close button be pushed out of view when sizing is set to shrink to make more room... */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty.close-button-right.sizing-shrink > .tab-close,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close {
overflow: visible; /* ...but still show the close button on hover and when dirty */
}
.monaco-workbench > .part.editor > .content > .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: 10px; /* give a little bit more room if close button is off */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.close-button-off {
padding-right: 5px; /* we need less room when sizing is shrink */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
background-repeat: no-repeat;
background-position-y: center;
background-position-x: calc(100% - 6px); /* to the right of the tab label */
padding-right: 28px; /* make room for dirty indication when we are running without close button */
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
background-image: url('close-dirty.svg');
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
background-image: url('close-dirty-inverse.svg');
}
/* Editor Actions */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions {
cursor: default;
flex: initial;
padding-left: 4px;
}

View File

@@ -0,0 +1,281 @@
/*---------------------------------------------------------------------------------------------
* 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 .editor-group-container > .title.tabs > .monaco-scrollable-element {
flex: 1;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .monaco-scrollable-element .scrollbar {
z-index: 3; /* on top of tabs */
cursor: default;
}
/* Tabs Container */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container {
display: flex;
height: 35px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container.scroll {
overflow: scroll !important;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar {
display: none;
}
/* Tab */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab {
position: relative;
display: flex;
white-space: nowrap;
cursor: pointer;
height: 35px;
box-sizing: border-box;
padding-left: 10px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-right,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off {
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit {
width: 120px;
min-width: fit-content;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink {
min-width: 60px;
flex-basis: 0; /* all tabs are even */
flex-grow: 1; /* all tabs grow even */
max-width: fit-content;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off::after {
content: '';
display: flex;
flex: 0;
width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/Microsoft/vscode/issues/45728) */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left {
min-width: 80px; /* make more room for close button when it shows to the left */
padding-right: 5px; /* we need less room when sizing is shrink */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged {
will-change: transform; /* forces tab to be drawn on a separate layer (fixes https://github.com/Microsoft/vscode/issues/18733) */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged-over div {
pointer-events: none; /* prevents cursor flickering (fixes https://github.com/Microsoft/vscode/issues/38753) */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left {
flex-direction: row-reverse;
padding-left: 0;
padding-right: 10px;
}
/* Tab border top/bottom */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-top-container,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-bottom-container {
display: none; /* hidden by default until a color is provided (see below) */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 6; /* over possible title border */
pointer-events: none;
background-color: var(--tab-border-top-color);
width: 100%;
height: 1px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container {
display: block;
position: absolute;
bottom: 0;
left: 0;
z-index: 6; /* over possible title border */
pointer-events: none;
background-color: var(--tab-border-bottom-color);
width: 100%;
height: 1px;
}
/* Tab Label */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label {
margin-top: auto;
margin-bottom: auto;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink .tab-label {
position: relative;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
content: '';
position: absolute;
right: 0;
height: 100%;
width: 5px;
opacity: 1;
padding: 0;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-description-container {
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
text-overflow: clip;
}
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
text-overflow: ellipsis;
}
.monaco-workbench > .part.editor > .content .editor-group-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 .editor-group-container > .title .tabs-container > .tab > .tab-close {
margin-top: auto;
margin-bottom: auto;
width: 28px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close {
flex: 0;
overflow: hidden; /* let the close button be pushed out of view when sizing is set to shrink to make more room... */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.close-button-right.sizing-shrink > .tab-close,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close {
overflow: visible; /* ...but still show the close button on hover and when dirty */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close {
display: none; /* hide the close action bar when we are configured to hide it */
}
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */
opacity: 1;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
opacity: 0.5;
}
.monaco-workbench > .part.editor > .content .editor-group-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 .editor-group-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 .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action,
.hc-black .monaco-workbench > .part.editor > .content .editor-group-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 .editor-group-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 .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover,
.hc-black .monaco-workbench > .part.editor > .content .editor-group-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 .editor-group-container > .title .tabs-container > .tab.close-button-off {
padding-right: 10px; /* give a little bit more room if close button is off */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off {
padding-right: 5px; /* we need less room when sizing is shrink */
}
.monaco-workbench > .part.editor > .content .editor-group-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 .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty {
background-image: url('close-dirty.svg');
}
.vs-dark .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty,
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty {
background-image: url('close-dirty-inverse.svg');
}
/* Editor Actions */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .editor-actions {
cursor: default;
flex: initial;
padding-left: 4px;
height: 35px;
}
/* Breadcrumbs */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control {
flex: 1 100%;
height: 25px;
cursor: default;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label::before {
height: 18px; /* tweak the icon size of the editor labels when icons are enabled */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
max-width: 260px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
padding-right: 8px;
}
/* .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:not(:last-child):not(:hover):not(.focused):not(.file) {
min-width: 33px;
} */

View File

@@ -23,12 +23,12 @@
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
opacity: 1;
background: url('Paragraph_16x_nohalo.svg') center center no-repeat;
background: url('paragraph.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace,
.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
opacity: 1;
background: url('Paragraph_16x_nohalo_inversep.svg') center center no-repeat;
background: url('paragraph-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked {

View File

@@ -5,36 +5,31 @@
/* 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 {
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label {
white-space: nowrap;
flex: 1;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo.centered > .container > .title .title-label {
flex-direction: row;
justify-content: center;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label a,
.monaco-workbench > .part.editor > .content .editor-group-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 {
.monaco-workbench > .part.editor > .content .editor-group-container > .title .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label a,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label a,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label h2,
.monaco-workbench > .part.editor > .content .editor-group-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 {
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-actions .action-label,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .editor-actions .action-label {
display: block;
height: 35px;
line-height: 35px;
@@ -44,69 +39,38 @@
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 {
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .title-actions .action-label,
.hc-black .monaco-workbench > .part.editor > .content .editor-group-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 {
.monaco-workbench > .part.editor > .content .editor-group-container > .title .editor-actions .action-label .label,
.monaco-workbench > .part.editor > .content .editor-group-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 {
.monaco-workbench > .part.editor > .content .editor-group-container > .title {
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 {
.monaco-workbench > .part.editor > .content .editor-group-container > .title .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 {
.vs-dark .monaco-workbench > .part.editor > .content .editor-group-container > .title .close-editor-action,
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .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;
}
/* Drag and Drop Feedback */
.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;
}
.monaco-editor-group-drag-image {
display: inline-block;
padding: 1px 7px;
border-radius: 10px;
font-size: 12px;
position: absolute;
}

View File

@@ -5,147 +5,222 @@
'use strict';
import 'vs/css!./media/notabstitle';
import * as errors from 'vs/base/common/errors';
import { toResource } from 'vs/workbench/common/editor';
import * as DOM from 'vs/base/browser/dom';
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
import 'vs/css!./media/notabstitlecontrol';
import { toResource, Verbosity, IEditorInput } from 'vs/workbench/common/editor';
import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl';
import { ResourceLabel } 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';
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
import { addDisposableListener, EventType, addClass, EventHelper, removeClass, toggleClass } from 'vs/base/browser/dom';
import { IEditorPartOptions, EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor';
import { IAction } from 'vs/base/common/actions';
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
export class NoTabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private editorLabel: ResourceLabel;
private lastRenderedActiveEditor: IEditorInput;
public create(parent: HTMLElement): void {
super.create(parent);
protected create(parent: HTMLElement): void {
this.titleContainer = parent;
this.titleContainer.draggable = true;
//Container listeners
this.registerContainerListeners();
// Gesture Support
Gesture.addTarget(this.titleContainer);
// 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)));
// Detect touch
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
const labelContainer = document.createElement('div');
addClass(labelContainer, 'label-container');
this.titleContainer.appendChild(labelContainer);
// Editor Label
this.editorLabel = this.instantiationService.createInstance(ResourceLabel, this.titleContainer, void 0);
this.toUnbind.push(this.editorLabel);
this.toUnbind.push(this.editorLabel.onClick(e => this.onTitleLabelClick(e)));
this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, void 0));
this._register(this.editorLabel.onClick(e => this.onTitleLabelClick(e)));
// Breadcrumbs
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, extraClasses: ['no-tabs-breadcrumbs'] });
// Right Actions Container
const actionsContainer = document.createElement('div');
DOM.addClass(actionsContainer, 'title-actions');
addClass(actionsContainer, 'title-actions');
this.titleContainer.appendChild(actionsContainer);
// Editor actions toolbar
this.createEditorActionsToolBar(actionsContainer);
}
private registerContainerListeners(): void {
// Group dragging
this.enableGroupDragging(this.titleContainer);
// Pin on double click
this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
// Detect mouse click
this._register(addDisposableListener(this.titleContainer, EventType.CLICK, (e: MouseEvent) => this.onTitleClick(e)));
// Detect touch
this._register(addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
// 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)));
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => this.onContextMenu({ group: this.context, editor: this.context.activeEditor }, e, this.titleContainer)));
this._register(addDisposableListener(this.titleContainer, EventType.CONTEXT_MENU, (e: Event) => this.onContextMenu(this.group.activeEditor, e, this.titleContainer)));
this._register(addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => this.onContextMenu(this.group.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
}
EventHelper.stop(e, false);
// delayed to let the onTitleClick() come first which can cause a focus change which can close quick open
setTimeout(() => this.quickOpenService.show());
}
private onTitleDoubleClick(e: MouseEvent): void {
DOM.EventHelper.stop(e);
if (!this.context) {
return;
}
EventHelper.stop(e);
const group = this.context;
this.editorGroupService.pinEditor(group, group.activeEditor);
this.group.pinEditor();
}
private onTitleClick(e: MouseEvent | GestureEvent): void {
if (!this.context) {
return;
}
const group = this.context;
// Close editor on middle mouse click
if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) {
this.closeOneEditorAction.run({ groupId: group.id, editorIndex: group.indexOf(group.activeEditor) }).done(null, errors.onUnexpectedError);
}
// Focus editor group unless:
// - click on toolbar: should trigger actions within
// - mouse click: do not focus group if there are more than one as it otherwise makes group DND funky
// - touch: always focus
else if ((this.stacks.groups.length === 1 || !(e instanceof MouseEvent)) && !DOM.isAncestor(((e as GestureEvent).initialTarget || e.target || e.srcElement) as HTMLElement, this.editorActionsToolbar.getContainer())) {
this.editorGroupService.focusGroup(group);
this.group.closeEditor(this.group.activeEditor);
}
}
protected doRefresh(): void {
const group = this.context;
const editor = group && group.activeEditor;
getPreferredHeight(): number {
return EDITOR_TITLE_HEIGHT;
}
openEditor(editor: IEditorInput): void {
this.ifActiveEditorChanged(() => this.redraw());
}
closeEditor(editor: IEditorInput): void {
this.ifActiveEditorChanged(() => this.redraw());
}
closeEditors(editors: IEditorInput[]): void {
this.ifActiveEditorChanged(() => this.redraw());
}
closeAllEditors(): void {
this.redraw();
}
moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void {
this.ifActiveEditorChanged(() => this.redraw());
}
pinEditor(editor: IEditorInput): void {
this.ifEditorIsActive(editor, () => this.redraw());
}
setActive(isActive: boolean): void {
this.redraw();
}
updateEditorLabel(editor: IEditorInput): void {
this.ifEditorIsActive(editor, () => this.redraw());
}
updateEditorDirty(editor: IEditorInput): void {
this.ifEditorIsActive(editor, () => {
if (editor.isDirty()) {
addClass(this.titleContainer, 'dirty');
} else {
removeClass(this.titleContainer, 'dirty');
}
});
}
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
if (oldOptions.labelFormat !== newOptions.labelFormat) {
this.redraw();
}
}
updateStyles(): void {
this.redraw();
}
private ifActiveEditorChanged(fn: () => void): void {
if (
!this.lastRenderedActiveEditor && this.group.activeEditor || // active editor changed from null => editor
this.lastRenderedActiveEditor && !this.group.activeEditor || // active editor changed from editor => null
!this.group.isActive(this.lastRenderedActiveEditor) // active editor changed from editorA => editorB
) {
fn();
}
}
private ifEditorIsActive(editor: IEditorInput, fn: () => void): void {
if (this.group.isActive(editor)) {
fn(); // only run if editor is current active
}
}
private redraw(): void {
const editor = this.group.activeEditor;
this.lastRenderedActiveEditor = editor;
const isEditorPinned = this.group.isPinned(this.group.activeEditor);
const isGroupActive = this.accessor.activeGroup === this.group;
// Update Breadcrumbs
if (this.breadcrumbsControl) {
if (isGroupActive) {
this.breadcrumbsControl.update();
toggleClass(this.breadcrumbsControl.domNode, 'preview', !isEditorPinned);
} else {
this.breadcrumbsControl.hide();
}
}
// Clear if there is no editor
if (!editor) {
removeClass(this.titleContainer, 'dirty');
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);
// Otherwise render it
else {
// Dirty state
this.updateEditorDirty(editor);
// Activity state
if (isActive) {
DOM.addClass(this.titleContainer, 'active');
} else {
DOM.removeClass(this.titleContainer, 'active');
// Editor Label
const resource = toResource(editor, { supportSideBySide: true });
const name = editor.getName() || '';
const { labelFormat } = this.accessor.partOptions;
let description: string;
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
description = ''; // hide description when showing breadcrumbs
} else if (labelFormat === 'default' && !isGroupActive) {
description = ''; // hide description when group is not active and style is 'default'
} else {
description = editor.getDescription(this.getVerbosity(labelFormat)) || '';
}
let title = editor.getTitle(Verbosity.LONG);
if (description === title) {
title = ''; // dont repeat what is already shown
}
this.editorLabel.setLabel({ name, description, resource }, { title, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] });
if (isGroupActive) {
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();
}
// 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 labelFormat = this.editorGroupService.getTabOptions().labelFormat;
let description: string;
if (labelFormat === 'default' && !isActive) {
description = ''; // hide description when group is not active and style is 'default'
} else {
description = editor.getDescription(this.getVerbosity(labelFormat)) || '';
}
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();
}
private getVerbosity(style: string): Verbosity {
@@ -155,4 +230,16 @@ export class NoTabsTitleControl extends TitleControl {
default: return Verbosity.MEDIUM;
}
}
protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[], secondaryEditorActions: IAction[] } {
const isGroupActive = this.accessor.activeGroup === this.group;
// Group active: show all actions
if (isGroupActive) {
return super.prepareEditorActions(editorActions);
}
// Group inactive: only show close action
return { primaryEditorActions: editorActions.primary.filter(action => action.id === CLOSE_EDITOR_COMMAND_ID), secondaryEditorActions: [] };
}
}

View File

@@ -3,10 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IRange } from 'vs/editor/common/core/range';
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
@@ -19,27 +19,29 @@ export interface IRangeHighlightDecoration {
isWholeLine?: boolean;
}
export class RangeHighlightDecorations implements IDisposable {
export class RangeHighlightDecorations extends Disposable {
private rangeHighlightDecorationId: string = null;
private editor: ICodeEditor = null;
private editorDisposables: IDisposable[] = [];
private readonly _onHighlightRemoved: Emitter<void> = new Emitter<void>();
public readonly onHighlghtRemoved: Event<void> = this._onHighlightRemoved.event;
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());
get onHighlghtRemoved(): Event<void> { return this._onHighlightRemoved.event; }
constructor(@IWorkbenchEditorService private editorService: IWorkbenchEditorService) {
constructor(@IEditorService private editorService: IEditorService) {
super();
}
public removeHighlightRange() {
removeHighlightRange() {
if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) {
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
this._onHighlightRemoved.fire();
}
this.rangeHighlightDecorationId = null;
}
public highlightRange(range: IRangeHighlightDecoration, editor?: ICodeEditor) {
highlightRange(range: IRangeHighlightDecoration, editor?: ICodeEditor) {
editor = editor ? editor : this.getEditor(range);
if (editor) {
this.doHighlightRange(editor, range);
@@ -48,20 +50,23 @@ export class RangeHighlightDecorations implements IDisposable {
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
this.removeHighlightRange();
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
});
this.setEditor(editor);
}
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor {
const activeInput = this.editorService.getActiveEditorInput();
const resource = activeInput && activeInput.getResource();
const activeEditor = this.editorService.activeEditor;
const resource = activeEditor && activeEditor.getResource();
if (resource) {
if (resource.toString() === resourceRange.resource.toString()) {
return <ICodeEditor>this.editorService.getActiveEditor().getControl();
return this.editorService.activeTextEditorWidget as ICodeEditor;
}
}
return null;
}
@@ -107,7 +112,9 @@ export class RangeHighlightDecorations implements IDisposable {
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
}
public dispose() {
dispose() {
super.dispose();
if (this.editor && this.editor.getModel()) {
this.removeHighlightRange();
this.disposeEditorListeners();

View File

@@ -3,13 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/resourceviewer';
import * as nls from 'vs/nls';
import * as mimes from 'vs/base/common/mime';
import URI from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { Builder, $ } from 'vs/base/browser/builder';
import * as DOM from 'vs/base/browser/dom';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
@@ -24,79 +21,26 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Registry } from 'vs/platform/registry/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { memoize } from 'vs/base/common/decorators';
import * as platform from 'vs/base/common/platform';
interface MapExtToMediaMimes {
[index: string]: string;
}
// Known media mimes that we can handle
const mapExtToMediaMimes: MapExtToMediaMimes = {
'.bmp': 'image/bmp',
'.gif': 'image/gif',
'.jpg': 'image/jpg',
'.jpeg': 'image/jpg',
'.jpe': 'image/jpg',
'.png': 'image/png',
'.tiff': 'image/tiff',
'.tif': 'image/tiff',
'.ico': 'image/x-icon',
'.tga': 'image/x-tga',
'.psd': 'image/vnd.adobe.photoshop',
'.webp': 'image/webp',
'.mid': 'audio/midi',
'.midi': 'audio/midi',
'.mp4a': 'audio/mp4',
'.mpga': 'audio/mpeg',
'.mp2': 'audio/mpeg',
'.mp2a': 'audio/mpeg',
'.mp3': 'audio/mpeg',
'.m2a': 'audio/mpeg',
'.m3a': 'audio/mpeg',
'.oga': 'audio/ogg',
'.ogg': 'audio/ogg',
'.spx': 'audio/ogg',
'.aac': 'audio/x-aac',
'.wav': 'audio/x-wav',
'.wma': 'audio/x-ms-wma',
'.mp4': 'video/mp4',
'.mp4v': 'video/mp4',
'.mpg4': 'video/mp4',
'.mpeg': 'video/mpeg',
'.mpg': 'video/mpeg',
'.mpe': 'video/mpeg',
'.m1v': 'video/mpeg',
'.m2v': 'video/mpeg',
'.ogv': 'video/ogg',
'.qt': 'video/quicktime',
'.mov': 'video/quicktime',
'.webm': 'video/webm',
'.mkv': 'video/x-matroska',
'.mk3d': 'video/x-matroska',
'.mks': 'video/x-matroska',
'.wmv': 'video/x-ms-wmv',
'.flv': 'video/x-flv',
'.avi': 'video/x-msvideo',
'.movie': 'video/x-sgi-movie'
};
import { IFileService } from 'vs/platform/files/common/files';
export interface IResourceDescriptor {
resource: URI;
name: string;
size: number;
etag: string;
mime: string;
readonly resource: URI;
readonly name: string;
readonly size: number;
readonly etag: string;
readonly mime: string;
}
class BinarySize {
public static readonly KB = 1024;
public static readonly MB = BinarySize.KB * BinarySize.KB;
public static readonly GB = BinarySize.MB * BinarySize.KB;
public static readonly TB = BinarySize.GB * BinarySize.KB;
static readonly KB = 1024;
static readonly MB = BinarySize.KB * BinarySize.KB;
static readonly GB = BinarySize.MB * BinarySize.KB;
static readonly TB = BinarySize.GB * BinarySize.KB;
public static formatSize(size: number): string {
static formatSize(size: number): string {
if (size < BinarySize.KB) {
return nls.localize('sizeB', "{0}B", size);
}
@@ -129,8 +73,9 @@ export class ResourceViewer {
private static readonly MAX_OPEN_INTERNAL_SIZE = BinarySize.MB * 200; // max size until we offer an action to open internally
public static show(
static show(
descriptor: IResourceDescriptor,
fileService: IFileService,
container: HTMLElement,
scrollbar: DomScrollableElement,
openInternalClb: (uri: URI) => void,
@@ -143,7 +88,7 @@ export class ResourceViewer {
// Images
if (ResourceViewer.isImageResource(descriptor)) {
return ImageView.create(container, descriptor, scrollbar, openExternalClb, metadataClb);
return ImageView.create(container, descriptor, fileService, scrollbar, openExternalClb, metadataClb);
}
// Large Files
@@ -160,37 +105,26 @@ export class ResourceViewer {
}
private static isImageResource(descriptor: IResourceDescriptor) {
const mime = ResourceViewer.getMime(descriptor);
const mime = getMime(descriptor);
return mime.indexOf('image/') >= 0;
}
private static getMime(descriptor: IResourceDescriptor): string {
let mime = descriptor.mime;
if (!mime && descriptor.resource.scheme !== Schemas.data) {
const ext = paths.extname(descriptor.resource.toString());
if (ext) {
mime = mapExtToMediaMimes[ext.toLowerCase()];
}
}
return mime || mimes.MIME_BINARY;
}
}
class ImageView {
private static readonly MAX_IMAGE_SIZE = BinarySize.MB; // showing images inline is memory intense, so we have a limit
private static readonly BASE64_MARKER = 'base64,';
public static create(
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
fileService: IFileService,
scrollbar: DomScrollableElement,
openExternalClb: (uri: URI) => void,
metadataClb: (meta: string) => void
): ResourceViewerContext | null {
if (ImageView.shouldShowImageInline(descriptor)) {
return InlineImageView.create(container, descriptor, scrollbar, metadataClb);
return InlineImageView.create(container, descriptor, fileService, scrollbar, metadataClb);
}
LargeImageView.create(container, descriptor, openExternalClb);
@@ -219,7 +153,7 @@ class ImageView {
}
class LargeImageView {
public static create(
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
openExternalClb: (uri: URI) => void
@@ -245,7 +179,7 @@ class LargeImageView {
}
class FileTooLargeFileView {
public static create(
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
scrollbar: DomScrollableElement,
@@ -268,7 +202,7 @@ class FileTooLargeFileView {
}
class FileSeemsBinaryFileView {
public static create(
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
scrollbar: DomScrollableElement,
@@ -302,29 +236,32 @@ class FileSeemsBinaryFileView {
type Scale = number | 'fit';
class ZoomStatusbarItem extends Themable implements IStatusbarItem {
static instance: ZoomStatusbarItem;
showTimeout: number;
public static instance: ZoomStatusbarItem;
private statusBarItem: HTMLElement;
private onSelectScale?: (scale: Scale) => void;
constructor(
@IContextMenuService private contextMenuService: IContextMenuService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IEditorService editorService: IEditorService,
@IThemeService themeService: IThemeService
) {
super(themeService);
ZoomStatusbarItem.instance = this;
this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
this._register(editorService.onDidActiveEditorChange(() => this.onActiveEditorChanged()));
}
private onEditorsChanged(): void {
private onActiveEditorChanged(): void {
this.hide();
this.onSelectScale = void 0;
}
public show(scale: Scale, onSelectScale: (scale: number) => void) {
show(scale: Scale, onSelectScale: (scale: number) => void) {
clearTimeout(this.showTimeout);
this.showTimeout = setTimeout(() => {
this.onSelectScale = onSelectScale;
@@ -333,11 +270,11 @@ class ZoomStatusbarItem extends Themable implements IStatusbarItem {
}, 0);
}
public hide() {
hide() {
this.statusBarItem.style.display = 'none';
}
public render(container: HTMLElement): IDisposable {
render(container: HTMLElement): IDisposable {
if (!this.statusBarItem && container) {
this.statusBarItem = $(container).a()
.addClass('.zoom-statusbar-item')
@@ -419,22 +356,15 @@ class InlineImageView {
*/
private static readonly PIXELATION_THRESHOLD = 3;
/**
* Chrome is caching images very aggressively and so we use the ETag information to find out if
* we need to bypass the cache or not. We could always bypass the cache everytime we show the image
* however that has very bad impact on memory consumption because each time the image gets shown,
* memory grows (see also https://github.com/electron/electron/issues/6275)
*/
private static IMAGE_RESOURCE_ETAG_CACHE = new LRUCache<string, { etag: string, src: string }>(100);
/**
* Store the scale and position of an image so it can be restored when changing editor tabs
*/
private static readonly imageStateCache = new LRUCache<string, ImageState>(100);
public static create(
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
fileService: IFileService,
scrollbar: DomScrollableElement,
metadataClb: (meta: string) => void
) {
@@ -601,7 +531,7 @@ class InlineImageView {
$(container)
.empty()
.addClass('image', 'zoom-in')
.img({ src: InlineImageView.imageSrc(descriptor) })
.img({})
.style('visibility', 'hidden')
.addClass('scale-to-fit')
.on(DOM.EventType.LOAD, (e, i) => {
@@ -619,27 +549,33 @@ class InlineImageView {
}
});
InlineImageView.imageSrc(descriptor, fileService).then(dataUri => {
const imgs = container.getElementsByTagName('img');
if (imgs.length) {
imgs[0].src = dataUri;
}
});
return context;
}
private static imageSrc(descriptor: IResourceDescriptor): string {
private static imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): TPromise<string> {
if (descriptor.resource.scheme === Schemas.data) {
return descriptor.resource.toString(true /* skip encoding */);
return TPromise.as(descriptor.resource.toString(true /* skip encoding */));
}
const src = descriptor.resource.toString();
return fileService.resolveContent(descriptor.resource, { encoding: 'base64' }).then(data => {
const mime = getMime(descriptor);
let cached = InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.get(src);
if (!cached) {
cached = { etag: descriptor.etag, src };
InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.set(src, cached);
}
if (cached.etag !== descriptor.etag) {
cached.etag = descriptor.etag;
cached.src = `${src}?${Date.now()}`; // bypass cache with this trick
}
return cached.src;
return `data:${mime};base64,${data.value}`;
});
}
}
}
function getMime(descriptor: IResourceDescriptor) {
let mime = descriptor.mime;
if (!mime && descriptor.resource.scheme !== Schemas.data) {
mime = mimes.getMediaMime(descriptor.resource.toString());
}
return mime || mimes.MIME_BINARY;
}

View File

@@ -6,29 +6,55 @@
import { TPromise } from 'vs/base/common/winjs.base';
import * as DOM from 'vs/base/browser/dom';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorInput, EditorOptions, SideBySideEditorInput } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditor } from 'vs/workbench/common/editor';
import { BaseEditor } 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';
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Event, Relay, anyEvent, mapEvent, Emitter } from 'vs/base/common/event';
export class SideBySideEditor extends BaseEditor {
public static readonly ID: string = 'workbench.editor.sidebysideEditor';
static readonly ID: string = 'workbench.editor.sidebysideEditor';
private dimension: DOM.Dimension;
get minimumMasterWidth() { return this.masterEditor ? this.masterEditor.minimumWidth : 0; }
get maximumMasterWidth() { return this.masterEditor ? this.masterEditor.maximumWidth : Number.POSITIVE_INFINITY; }
get minimumMasterHeight() { return this.masterEditor ? this.masterEditor.minimumHeight : 0; }
get maximumMasterHeight() { return this.masterEditor ? this.masterEditor.maximumHeight : Number.POSITIVE_INFINITY; }
get minimumDetailsWidth() { return this.detailsEditor ? this.detailsEditor.minimumWidth : 0; }
get maximumDetailsWidth() { return this.detailsEditor ? this.detailsEditor.maximumWidth : Number.POSITIVE_INFINITY; }
get minimumDetailsHeight() { return this.detailsEditor ? this.detailsEditor.minimumHeight : 0; }
get maximumDetailsHeight() { return this.detailsEditor ? this.detailsEditor.maximumHeight : Number.POSITIVE_INFINITY; }
// these setters need to exist because this extends from BaseEditor
set minimumWidth(value: number) { /* noop */ }
set maximumWidth(value: number) { /* noop */ }
set minimumHeight(value: number) { /* noop */ }
set maximumHeight(value: number) { /* noop */ }
get minimumWidth() { return this.minimumMasterWidth + this.minimumDetailsWidth; }
get maximumWidth() { return this.maximumMasterWidth + this.maximumDetailsWidth; }
get minimumHeight() { return this.minimumMasterHeight + this.minimumDetailsHeight; }
get maximumHeight() { return this.maximumMasterHeight + this.maximumDetailsHeight; }
protected masterEditor: BaseEditor;
private masterEditorContainer: HTMLElement;
protected detailsEditor: BaseEditor;
private masterEditorContainer: HTMLElement;
private detailsEditorContainer: HTMLElement;
private sash: VSash;
private splitview: SplitView;
private dimension: DOM.Dimension = new DOM.Dimension(0, 0);
private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; }>());
private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; }>());
readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; }> = anyEvent(this.onDidCreateEditors.event, this._onDidSizeConstraintsChange.event);
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@@ -40,36 +66,54 @@ export class SideBySideEditor extends BaseEditor {
protected createEditor(parent: HTMLElement): void {
DOM.addClass(parent, 'side-by-side-editor');
this.createSash(parent);
this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL }));
this._register(this.splitview.onDidSashReset(() => this.splitview.distributeViewSizes()));
this.detailsEditorContainer = DOM.$('.details-editor-container');
this.splitview.addView({
element: this.detailsEditorContainer,
layout: size => this.detailsEditor && this.detailsEditor.layout(new DOM.Dimension(size, this.dimension.height)),
minimumSize: 220,
maximumSize: Number.POSITIVE_INFINITY,
onDidChange: Event.None
}, Sizing.Distribute);
this.masterEditorContainer = DOM.$('.master-editor-container');
this.splitview.addView({
element: this.masterEditorContainer,
layout: size => this.masterEditor && this.masterEditor.layout(new DOM.Dimension(size, this.dimension.height)),
minimumSize: 220,
maximumSize: Number.POSITIVE_INFINITY,
onDidChange: Event.None
}, Sizing.Distribute);
this.updateStyles();
}
public setInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
setInput(newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
const oldInput = <SideBySideEditorInput>this.input;
return super.setInput(newInput, options)
.then(() => this.updateInput(oldInput, newInput, options));
return super.setInput(newInput, options, token)
.then(() => this.updateInput(oldInput, newInput, options, token));
}
protected setEditorVisible(visible: boolean, position: Position): void {
setOptions(options: EditorOptions): void {
if (this.masterEditor) {
this.masterEditor.setVisible(visible, position);
this.masterEditor.setOptions(options);
}
}
protected setEditorVisible(visible: boolean, group: IEditorGroup): void {
if (this.masterEditor) {
this.masterEditor.setVisible(visible, group);
}
if (this.detailsEditor) {
this.detailsEditor.setVisible(visible, position);
this.detailsEditor.setVisible(visible, group);
}
super.setEditorVisible(visible, position);
super.setEditorVisible(visible, group);
}
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 {
clearInput(): void {
if (this.masterEditor) {
this.masterEditor.clearInput();
}
@@ -80,57 +124,49 @@ export class SideBySideEditor extends BaseEditor {
super.clearInput();
}
public focus(): void {
focus(): void {
if (this.masterEditor) {
this.masterEditor.focus();
}
}
public layout(dimension: DOM.Dimension): void {
layout(dimension: DOM.Dimension): void {
this.dimension = dimension;
this.sash.setDimenesion(this.dimension);
this.splitview.layout(dimension.width);
}
public getControl(): IEditorControl {
getControl(): IEditorControl {
if (this.masterEditor) {
return this.masterEditor.getControl();
}
return null;
}
public getMasterEditor(): IEditor {
getMasterEditor(): IEditor {
return this.masterEditor;
}
public getDetailsEditor(): IEditor {
getDetailsEditor(): IEditor {
return this.detailsEditor;
}
public supportsCenteredLayout(): boolean {
return false;
}
private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options?: EditorOptions): void {
private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable<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;
return this.setNewInput(newInput, options, token);
}
return TPromise.join([this.detailsEditor.setInput(newInput.details, null, token), this.masterEditor.setInput(newInput.master, options, token)]).then(() => void 0);
}
private setNewInput(newInput: SideBySideEditorInput, options?: EditorOptions): void {
private setNewInput(newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
const detailsEditor = this._createEditor(<EditorInput>newInput.details, this.detailsEditorContainer);
const masterEditor = this._createEditor(<EditorInput>newInput.master, this.masterEditorContainer);
this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options);
return this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options, token);
}
private _createEditor(editorInput: EditorInput, container: HTMLElement): BaseEditor {
@@ -138,29 +174,26 @@ export class SideBySideEditor extends BaseEditor {
const editor = descriptor.instantiate(this.instantiationService);
editor.create(container);
editor.setVisible(this.isVisible(), this.position);
editor.setVisible(this.isVisible(), this.group);
return editor;
}
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions): TPromise<void> {
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions, token: CancellationToken): 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());
this._onDidSizeConstraintsChange.input = anyEvent(
mapEvent(details.onDidSizeConstraintsChange, () => undefined),
mapEvent(master.onDidSizeConstraintsChange, () => undefined)
);
this.onDidCreateEditors.fire();
return TPromise.join([this.detailsEditor.setInput(detailsInput, null, token), this.masterEditor.setInput(masterInput, options, token)]).then(() => this.focus());
}
private createEditorContainers(): void {
const parentElement = this.getContainer();
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 {
updateStyles(): void {
super.updateStyles();
if (this.masterEditorContainer) {
@@ -168,52 +201,24 @@ export class SideBySideEditor extends BaseEditor {
}
}
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 DOM.Dimension(detailsEditorWidth, this.dimension.height));
this.masterEditor.layout(new DOM.Dimension(masterEditorWidth, this.dimension.height));
}
private disposeEditors(): void {
const parentContainer = this.getContainer();
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;
}
this.detailsEditorContainer.innerHTML = '';
this.masterEditorContainer.innerHTML = '';
}
public dispose(): void {
dispose(): void {
this.disposeEditors();
super.dispose();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,11 @@ import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import * as objects from 'vs/base/common/objects';
import { Action, IAction } from 'vs/base/common/actions';
import { onUnexpectedError } from 'vs/base/common/errors';
import * as types from 'vs/base/common/types';
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditor, IEditorMemento } 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';
@@ -26,28 +25,28 @@ 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, DelegatingWorkbenchEditorService } 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 { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/common/editorCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import URI from 'vs/base/common/uri';
import { getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
import { once } from 'vs/base/common/event';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor';
/**
* The text editor that leverages the diff text editor for the editing experience.
*/
export class TextDiffEditor extends BaseTextEditor {
export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
public static readonly ID = TEXT_DIFF_EDITOR_ID;
static readonly ID = TEXT_DIFF_EDITOR_ID;
private diffNavigator: DiffNavigator;
private diffNavigatorDisposables: IDisposable[];
private diffNavigatorDisposables: IDisposable[] = [];
private nextDiffAction: NavigateAction;
private previousDiffAction: NavigateAction;
private toggleIgnoreTrimWhitespaceAction: ToggleIgnoreTrimWhitespaceAction;
@@ -58,22 +57,25 @@ export class TextDiffEditor extends BaseTextEditor {
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IConfigurationService private readonly _actualConfigurationService: IConfigurationService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorService editorService: IEditorService,
@IThemeService themeService: IThemeService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@ITextFileService textFileService: ITextFileService
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@ITextFileService textFileService: ITextFileService,
) {
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService);
this.diffNavigatorDisposables = [];
this.toUnbind.push(this._actualConfigurationService.onDidChangeConfiguration((e) => {
this._register(this._actualConfigurationService.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('diffEditor.ignoreTrimWhitespace')) {
this.updateIgnoreTrimWhitespaceAction();
}
}));
}
public getTitle(): string {
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted
}
getTitle(): string {
if (this.input) {
return this.input.getName();
}
@@ -81,7 +83,7 @@ export class TextDiffEditor extends BaseTextEditor {
return nls.localize('textDiffEditor', "Text Diff Editor");
}
public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IDiffEditor {
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
// Actions
this.nextDiffAction = new NavigateAction(this, true);
@@ -89,55 +91,10 @@ export class TextDiffEditor extends BaseTextEditor {
this.toggleIgnoreTrimWhitespaceAction = new ToggleIgnoreTrimWhitespaceAction(this._actualConfigurationService);
this.updateIgnoreTrimWhitespaceAction();
// Support navigation within the diff editor by overriding the editor service within
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
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, configuration);
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
// Return early for same input unless we force to open
const forceOpen = options && options.forceOpen;
if (!forceOpen && input.matches(this.input)) {
// Still apply options if any (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.wrap<void>(null);
}
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
// Dispose previous diff navigator
this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables);
@@ -146,27 +103,28 @@ export class TextDiffEditor extends BaseTextEditor {
this.saveTextDiffEditorViewState(this.input);
// Set input and resolve
return super.setInput(input, options).then(() => {
return input.resolve(true).then(resolvedModel => {
return super.setInput(input, options, token).then(() => {
return input.resolve().then(resolvedModel => {
// Check for cancellation
if (token.isCancellationRequested) {
return void 0;
}
// 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;
return void 0;
}
// Set Editor Model
const diffEditor = <IDiffEditor>this.getControl();
diffEditor.setModel((<TextDiffEditorModel>resolvedModel).textDiffEditorModel);
const diffEditor = this.getControl();
const resolvedDiffEditorModel = <TextDiffEditorModel>resolvedModel;
diffEditor.setModel(resolvedDiffEditorModel.textDiffEditorModel);
// Apply Options from TextOptions
let optionsGotApplied = false;
if (options && types.isFunction((<TextEditorOptions>options).apply)) {
optionsGotApplied = (<TextEditorOptions>options).apply(<IDiffEditor>diffEditor, ScrollType.Immediate);
optionsGotApplied = (<TextEditorOptions>options).apply(diffEditor, ScrollType.Immediate);
}
// Otherwise restore View State
@@ -175,6 +133,7 @@ export class TextDiffEditor extends BaseTextEditor {
hasPreviousViewState = this.restoreTextDiffEditorViewState(input);
}
// Diff navigator
this.diffNavigator = new DiffNavigator(diffEditor, {
alwaysRevealFirst: !optionsGotApplied && !hasPreviousViewState // only reveal first change if we had no options or viewstate
});
@@ -185,7 +144,11 @@ export class TextDiffEditor extends BaseTextEditor {
this.previousDiffAction.updateEnablement();
}));
// Enablement of actions
this.updateIgnoreTrimWhitespaceAction();
// Readonly flag
diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() });
}, error => {
// In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff.
@@ -199,8 +162,11 @@ export class TextDiffEditor extends BaseTextEditor {
});
}
public supportsCenteredLayout(): boolean {
return false;
setOptions(options: EditorOptions): void {
const textOptions = <TextEditorOptions>options;
if (textOptions && types.isFunction(textOptions.apply)) {
textOptions.apply(this.getControl(), ScrollType.Smooth);
}
}
private restoreTextDiffEditorViewState(input: EditorInput): boolean {
@@ -243,7 +209,7 @@ export class TextDiffEditor extends BaseTextEditor {
modifiedInput.setForceOpenAsBinary();
}
this.editorService.openEditor(binaryDiffInput, options, this.position).done(null, onUnexpectedError);
this.editorService.openEditor(binaryDiffInput, options, this.group);
return true;
}
@@ -251,7 +217,7 @@ export class TextDiffEditor extends BaseTextEditor {
return false;
}
protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions {
protected computeConfiguration(configuration: IEditorConfiguration): ICodeEditorOptions {
const editorConfiguration = super.computeConfiguration(configuration);
// Handle diff editor specially by merging in diffEditor configuration
@@ -262,7 +228,7 @@ export class TextDiffEditor extends BaseTextEditor {
return editorConfiguration;
}
protected getConfigurationOverrides(): IEditorOptions {
protected getConfigurationOverrides(): ICodeEditorOptions {
const options: IDiffEditorOptions = super.getConfigurationOverrides();
options.readOnly = this.isReadOnly();
@@ -304,7 +270,7 @@ export class TextDiffEditor extends BaseTextEditor {
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY;
}
public clearInput(): void {
clearInput(): void {
// Dispose previous diff navigator
this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables);
@@ -319,11 +285,11 @@ export class TextDiffEditor extends BaseTextEditor {
super.clearInput();
}
public getDiffNavigator(): DiffNavigator {
getDiffNavigator(): DiffNavigator {
return this.diffNavigator;
}
public getActions(): IAction[] {
getActions(): IAction[] {
return [
this.toggleIgnoreTrimWhitespaceAction,
this.previousDiffAction,
@@ -331,7 +297,7 @@ export class TextDiffEditor extends BaseTextEditor {
];
}
public getControl(): IDiffEditor {
getControl(): IDiffEditor {
return super.getControl() as IDiffEditor;
}
@@ -370,12 +336,8 @@ export class TextDiffEditor extends BaseTextEditor {
}
private retrieveTextDiffEditorViewState(resource: URI): IDiffEditorViewState {
const editor = getCodeOrDiffEditor(this).diffEditor;
if (!editor) {
return null; // not supported for non-diff editors
}
const model = editor.getModel();
const control = this.getControl();
const model = control.getModel();
if (!model || !model.modified || !model.original) {
return null; // view state always needs a model
}
@@ -389,7 +351,7 @@ export class TextDiffEditor extends BaseTextEditor {
return null; // prevent saving view state for a model that is not the expected one
}
return editor.saveViewState();
return control.saveViewState();
}
private toDiffEditorViewStateResource(modelOrInput: IDiffEditorModel | DiffEditorInput): URI {
@@ -412,7 +374,7 @@ export class TextDiffEditor extends BaseTextEditor {
return URI.from({ scheme: 'diff', path: `${btoa(original.toString())}${btoa(modified.toString())}` });
}
public dispose(): void {
dispose(): void {
this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables);
super.dispose();
@@ -437,7 +399,7 @@ class NavigateAction extends Action {
this.enabled = false;
}
public run(): TPromise<any> {
run(): TPromise<any> {
if (this.next) {
this.editor.getDiffNavigator().next();
} else {
@@ -447,7 +409,7 @@ class NavigateAction extends Action {
return null;
}
public updateEnablement(): void {
updateEnablement(): void {
this.enabled = this.editor.getDiffNavigator().canNavigate();
}
}
@@ -464,12 +426,12 @@ class ToggleIgnoreTrimWhitespaceAction extends Action {
this.label = nls.localize('toggleIgnoreTrimWhitespace.label', "Ignore Trim Whitespace");
}
public updateClassName(ignoreTrimWhitespace: boolean): void {
updateClassName(ignoreTrimWhitespace: boolean): void {
this._isChecked = ignoreTrimWhitespace;
this.class = `textdiff-editor-action toggleIgnoreTrimWhitespace${this._isChecked ? ' is-checked' : ''}`;
}
public run(): TPromise<any> {
run(): TPromise<any> {
this._configurationService.updateValue(`diffEditor.ignoreTrimWhitespace`, !this._isChecked);
return null;
}

View File

@@ -6,28 +6,26 @@
'use strict';
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import * as objects from 'vs/base/common/objects';
import * as types from 'vs/base/common/types';
import * as errors from 'vs/base/common/errors';
import * as DOM from 'vs/base/browser/dom';
import { CodeEditor } from 'vs/editor/browser/codeEditor';
import { EditorInput, EditorOptions, EditorViewStateMemento } from 'vs/workbench/common/editor';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorViewState, IEditor } 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, getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { isDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { isDiffEditor, isCodeEditor, ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState';
@@ -40,12 +38,12 @@ export interface IEditorConfiguration {
* 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 {
export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
private editorControl: IEditor;
private _editorContainer: HTMLElement;
private hasPendingConfigurationChange: boolean;
private lastAppliedEditorOptions: IEditorOptions;
private editorViewStateMemento: EditorViewStateMemento<IEditorViewState>;
private editorMemento: IEditorMemento<IEditorViewState>;
constructor(
id: string,
@@ -55,13 +53,14 @@ export abstract class BaseTextEditor extends BaseEditor {
@ITextResourceConfigurationService private readonly _configurationService: ITextResourceConfigurationService,
@IThemeService protected themeService: IThemeService,
@ITextFileService private readonly _textFileService: ITextFileService,
@IEditorGroupService protected editorGroupService: IEditorGroupService
@IEditorService protected editorService: IEditorService,
@IEditorGroupsService protected editorGroupService: IEditorGroupsService,
) {
super(id, telemetryService, themeService);
this.editorViewStateMemento = new EditorViewStateMemento<IEditorViewState>(this.getMemento(storageService, Scope.WORKSPACE), TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this.editorMemento = this.getEditorMemento<IEditorViewState>(storageService, editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
this._register(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
}
protected get instantiationService(): IInstantiationService {
@@ -107,8 +106,10 @@ export abstract class BaseTextEditor extends BaseEditor {
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);
if (ariaLabel) {
if (this.group) {
ariaLabel = nls.localize('editorLabelWithGroup', "{0}, {1}.", ariaLabel, this.group.label);
}
}
return ariaLabel;
@@ -129,25 +130,25 @@ export abstract class BaseTextEditor extends BaseEditor {
// Editor for Text
this._editorContainer = parent;
this.editorControl = this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue<IEditorConfiguration>(this.getResource())));
this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
// Model & Language changes
const codeEditor = getCodeEditor(this);
const codeEditor = getCodeEditor(this.editorControl);
if (codeEditor) {
this.toUnbind.push(codeEditor.onDidChangeModelLanguage(e => this.updateEditorConfiguration()));
this.toUnbind.push(codeEditor.onDidChangeModel(e => this.updateEditorConfiguration()));
this._register(codeEditor.onDidChangeModelLanguage(e => this.updateEditorConfiguration()));
this._register(codeEditor.onDidChangeModel(e => this.updateEditorConfiguration()));
}
// Application & Editor focus change to respect auto save settings
if (isCodeEditor(this.editorControl)) {
this.toUnbind.push(this.editorControl.onDidBlurEditor(() => this.onEditorFocusLost()));
this._register(this.editorControl.onDidBlurEditorWidget(() => this.onEditorFocusLost()));
} else if (isDiffEditor(this.editorControl)) {
this.toUnbind.push(this.editorControl.getOriginalEditor().onDidBlurEditor(() => this.onEditorFocusLost()));
this.toUnbind.push(this.editorControl.getModifiedEditor().onDidBlurEditor(() => this.onEditorFocusLost()));
this._register(this.editorControl.getOriginalEditor().onDidBlurEditorWidget(() => this.onEditorFocusLost()));
this._register(this.editorControl.getModifiedEditor().onDidBlurEditorWidget(() => this.onEditorFocusLost()));
}
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusLost()));
this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost()));
this._register(this.editorService.onDidActiveEditorChange(() => this.onEditorFocusLost()));
this._register(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost()));
}
private onEditorFocusLost(): void {
@@ -182,11 +183,11 @@ export abstract class BaseTextEditor extends BaseEditor {
protected createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IEditor {
// Use a getter for the instantiation service since some subclasses might use scoped instantiation services
return this.instantiationService.createInstance(CodeEditor, parent, configuration);
return this.instantiationService.createInstance(CodeEditorWidget, parent, configuration, {});
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
return super.setInput(input, options).then(() => {
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
return super.setInput(input, options, token).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)
@@ -195,16 +196,7 @@ export abstract class BaseTextEditor extends BaseEditor {
});
}
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 {
protected setEditorVisible(visible: boolean, group: IEditorGroup): void {
// Pass on to Editor
if (visible) {
@@ -214,20 +206,20 @@ export abstract class BaseTextEditor extends BaseEditor {
this.editorControl.onHide();
}
super.setEditorVisible(visible, position);
super.setEditorVisible(visible, group);
}
public focus(): void {
focus(): void {
this.editorControl.focus();
}
public layout(dimension: DOM.Dimension): void {
layout(dimension: DOM.Dimension): void {
// Pass on to Editor
this.editorControl.layout(dimension);
}
public getControl(): IEditor {
getControl(): IEditor {
return this.editorControl;
}
@@ -240,16 +232,12 @@ export abstract class BaseTextEditor extends BaseEditor {
return;
}
this.editorViewStateMemento.saveState(resource, this.position, editorViewState);
this.editorMemento.saveState(this.group, resource, editorViewState);
}
protected retrieveTextEditorViewState(resource: URI): IEditorViewState {
const editor = getCodeOrDiffEditor(this).codeEditor;
if (!editor) {
return null; // not supported for diff editors
}
const model = editor.getModel();
const control = this.getControl() as ICodeEditor;
const model = control.getModel();
if (!model) {
return null; // view state always needs a model
}
@@ -263,7 +251,7 @@ export abstract class BaseTextEditor extends BaseEditor {
return null; // prevent saving view state for a model that is not the expected one
}
return editor.saveViewState();
return control.saveViewState();
}
/**
@@ -271,7 +259,7 @@ export abstract class BaseTextEditor extends BaseEditor {
*/
protected clearTextEditorViewState(resources: URI[]): void {
resources.forEach(resource => {
this.editorViewStateMemento.clearState(resource);
this.editorMemento.clearState(resource);
});
}
@@ -279,7 +267,7 @@ export abstract class BaseTextEditor extends BaseEditor {
* Loads the text editor view state for the given resource and returns it.
*/
protected loadTextEditorViewState(resource: URI): IEditorViewState {
return this.editorViewStateMemento.loadState(resource, this.position);
return this.editorMemento.loadState(this.group, resource);
}
private updateEditorConfiguration(configuration = this.configurationService.getValue<IEditorConfiguration>(this.getResource())): void {
@@ -304,7 +292,7 @@ export abstract class BaseTextEditor extends BaseEditor {
}
protected getResource(): URI {
const codeEditor = getCodeEditor(this);
const codeEditor = getCodeEditor(this.editorControl);
if (codeEditor) {
const model = codeEditor.getModel();
if (model) {
@@ -321,17 +309,8 @@ export abstract class BaseTextEditor extends BaseEditor {
protected abstract getAriaLabel(): string;
protected saveMemento(): void {
// ensure to first save our view state memento
this.editorViewStateMemento.save();
super.saveMemento();
}
public dispose(): void {
dispose(): void {
this.lastAppliedEditorOptions = void 0;
this.editorControl.dispose();
super.dispose();
}

View File

@@ -19,10 +19,12 @@ 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 { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { once } from 'vs/base/common/event';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
/**
* An editor implementation that is capable of showing the contents of resource inputs. Uses
@@ -37,13 +39,14 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IThemeService themeService: IThemeService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@ITextFileService textFileService: ITextFileService
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@ITextFileService textFileService: ITextFileService,
@IEditorService editorService: IEditorService
) {
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService);
}
public getTitle(): string {
getTitle(): string {
if (this.input) {
return this.input.getName();
}
@@ -51,38 +54,25 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
return nls.localize('textEditor', "Text Editor");
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
// Return early for same input unless we force to open
const forceOpen = options && options.forceOpen;
if (!forceOpen && input.matches(this.input)) {
// Still apply options if any (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.wrap<void>(null);
}
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
// Remember view settings if input changes
this.saveTextResourceEditorViewState(this.input);
// Set input and resolve
return super.setInput(input, options).then(() => {
return input.resolve(true).then((resolvedModel: EditorModel) => {
return super.setInput(input, options, token).then(() => {
return input.resolve().then((resolvedModel: EditorModel) => {
// Check for cancellation
if (token.isCancellationRequested) {
return void 0;
}
// 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;
@@ -114,6 +104,13 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
}
}
setOptions(options: EditorOptions): void {
const textOptions = <TextEditorOptions>options;
if (textOptions && types.isFunction(textOptions.apply)) {
textOptions.apply(this.getControl(), ScrollType.Smooth);
}
}
protected getConfigurationOverrides(): IEditorOptions {
const options = super.getConfigurationOverrides();
@@ -139,18 +136,23 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
/**
* Reveals the last line of this editor if it has a model set.
* When smart is true only scroll if the cursor is currently on the last line of the output panel.
* This allows users to click on the output panel to stop scrolling when they see something of interest.
* To resume, they should scroll to the end of the output panel again.
*/
public revealLastLine(): void {
revealLastLine(smart: boolean): 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);
if (!smart || codeEditor.getPosition().lineNumber === lastLine) {
codeEditor.revealPosition({ lineNumber: lastLine, column: model.getLineMaxColumn(lastLine) }, ScrollType.Smooth);
}
}
}
public clearInput(): void {
clearInput(): void {
// Keep editor view state in settings to restore when coming back
this.saveTextResourceEditorViewState(this.input);
@@ -161,7 +163,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
super.clearInput();
}
public shutdown(): void {
shutdown(): void {
// Save View State (only for untitled)
if (this.input instanceof UntitledEditorInput) {
@@ -198,7 +200,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
export class TextResourceEditor extends AbstractTextResourceEditor {
public static readonly ID = 'workbench.editors.textResourceEditor';
static readonly ID = 'workbench.editors.textResourceEditor';
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@@ -206,9 +208,10 @@ export class TextResourceEditor extends AbstractTextResourceEditor {
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IThemeService themeService: IThemeService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@ITextFileService textFileService: ITextFileService
@ITextFileService textFileService: ITextFileService,
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService);
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService);
}
}
}

View File

@@ -5,234 +5,133 @@
'use strict';
import 'vs/css!./media/titlecontrol';
import * as nls from 'vs/nls';
import { prepareActions } from 'vs/workbench/browser/actions';
import { IAction, Action, IRunEvent } from 'vs/base/common/actions';
import * as errors from 'vs/base/common/errors';
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 * as arrays from 'vs/base/common/arrays';
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource, IEditorCommandsContext } from 'vs/workbench/common/editor';
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { applyDragImage } from 'vs/base/browser/dnd';
import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom';
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 { ActionsOrientation, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { Action, IAction, IRunEvent } from 'vs/base/common/actions';
import * as arrays from 'vs/base/common/arrays';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import 'vs/css!./media/titlecontrol';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { localize } from 'vs/nls';
import { createActionItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { ExecuteCommandAction, IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SplitEditorAction, CloseOneEditorAction } 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 { isDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Dimension, findParentWithClass } from 'vs/base/browser/dom';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { prepareActions } from 'vs/workbench/browser/actions';
import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptions } from 'vs/workbench/browser/parts/editor/editor';
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource } from 'vs/workbench/common/editor';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { Themable } from 'vs/workbench/common/theme';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
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;
updateEditorActionsToolbar(): void;
layout(dimension: Dimension): void;
dispose(): void;
}
export abstract class TitleControl extends Themable {
export abstract class TitleControl extends Themable implements ITitleAreaControl {
protected readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
protected readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
protected stacks: IEditorStacksModel;
protected context: IEditorGroup;
protected dragged: boolean;
protected closeOneEditorAction: CloseOneEditorAction;
protected splitEditorAction: SplitEditorAction;
private parent: HTMLElement;
protected breadcrumbsControl: BreadcrumbsControl;
private currentPrimaryEditorActionIds: string[] = [];
private currentSecondaryEditorActionIds: string[] = [];
protected editorActionsToolbar: ToolBar;
private mapActionsToEditors: { [editorId: string]: IToolbarActions; };
private titleAreaUpdateScheduler: RunOnceScheduler;
private titleAreaToolbarUpdateScheduler: RunOnceScheduler;
private refreshScheduled: boolean;
private mapEditorToActions: Map<string, IToolbarActions> = new Map();
private resourceContext: ResourceContextKey;
private disposeOnEditorActions: IDisposable[] = [];
private editorToolBarMenuDisposables: IDisposable[] = [];
private contextMenu: IMenu;
constructor(
@IContextMenuService protected contextMenuService: IContextMenuService,
parent: HTMLElement,
protected accessor: IEditorGroupsAccessor,
protected group: IEditorGroupView,
@IContextMenuService private 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,
@IContextKeyService private contextKeyService: IContextKeyService,
@IKeybindingService private keybindingService: IKeybindingService,
@ITelemetryService private telemetryService: ITelemetryService,
@INotificationService private notificationService: INotificationService,
@IMenuService protected menuService: IMenuService,
@IMenuService private menuService: IMenuService,
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@IThemeService protected themeService: IThemeService
@IThemeService themeService: IThemeService,
@IExtensionService private extensionService: IExtensionService,
@IConfigurationService protected configurationService: IConfigurationService
) {
super(themeService);
this.stacks = editorGroupService.getStacksModel();
this.mapActionsToEditors = Object.create(null);
this.titleAreaUpdateScheduler = new RunOnceScheduler(() => this.onSchedule(), 0);
this.toUnbind.push(this.titleAreaUpdateScheduler);
this.titleAreaToolbarUpdateScheduler = new RunOnceScheduler(() => this.updateEditorActionsToolbar(), 0);
this.toUnbind.push(this.titleAreaToolbarUpdateScheduler);
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
this.contextMenu = this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService);
this.toUnbind.push(this.contextMenu);
this.initActions(this.instantiationService);
this.create(parent);
this.registerListeners();
}
public setDragged(dragged: boolean): void {
this.dragged = dragged;
}
private registerListeners(): void {
this.toUnbind.push(this.stacks.onModelChanged(e => this.onStacksChanged(e)));
this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar()));
}
private onStacksChanged(e: IStacksModelChangeEvent): void {
if (e.structural) {
this.updateSplitActionEnablement();
protected abstract create(parent: HTMLElement): void;
protected createBreadcrumbsControl(container: HTMLElement, options: IBreadcrumbsControlOptions): void {
const config = this._register(BreadcrumbsConfig.IsEnabled.bindTo(this.configurationService));
config.onDidChange(value => {
if (!value && this.breadcrumbsControl) {
this.breadcrumbsControl.dispose();
this.breadcrumbsControl = undefined;
this.group.relayout();
} else if (value && !this.breadcrumbsControl) {
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
this.breadcrumbsControl.update();
this.group.relayout();
}
});
if (config.value) {
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
}
}
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;
this.editorActionsToolbar.context = { groupId: group ? group.id : void 0 } as IEditorCommandsContext;
}
public hasContext(): boolean {
return !!this.context;
}
public update(instant?: boolean): void {
if (instant) {
this.titleAreaUpdateScheduler.cancel();
this.onSchedule();
} else {
this.titleAreaUpdateScheduler.schedule();
}
this.titleAreaToolbarUpdateScheduler.cancel(); // a title area update will always refresh the toolbar too
}
public refresh(instant?: boolean) {
this.refreshScheduled = true;
if (instant) {
this.titleAreaUpdateScheduler.cancel();
this.onSchedule();
} else {
this.titleAreaUpdateScheduler.schedule();
}
this.titleAreaToolbarUpdateScheduler.cancel(); // a title area update will always refresh the toolbar too
}
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(dimension: Dimension): void {
// Subclasses can opt in to react on layout
}
public allowDragging(element: HTMLElement): boolean {
return !findParentWithClass(element, 'monaco-action-bar', 'one-editor-silo');
}
protected initActions(services: IInstantiationService): void {
this.closeOneEditorAction = services.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL);
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),
const context = { groupId: this.group.id } as IEditorCommandsContext;
this.editorActionsToolbar = this._register(new ToolBar(container, this.contextMenuService, {
actionItemProvider: action => this.actionItemProvider(action as Action),
orientation: ActionsOrientation.HORIZONTAL,
ariaLabel: nls.localize('araLabelEditorActions', "Editor actions"),
getKeyBinding: (action) => this.getKeybinding(action)
});
ariaLabel: localize('araLabelEditorActions', "Editor actions"),
getKeyBinding: action => this.getKeybinding(action),
actionRunner: this._register(new EditorCommandsContextActionRunner(context))
}));
// Context
this.editorActionsToolbar.context = context;
// Action Run Handling
this.toUnbind.push(this.editorActionsToolbar.actionRunner.onDidRun((e: IRunEvent) => {
this._register(this.editorActionsToolbar.actionRunner.onDidRun((e: IRunEvent) => {
// Check for Error
if (e.error && !errors.isPromiseCanceledError(e.error)) {
this.notificationService.error(e.error);
}
// Notify for Error
this.notificationService.error(e.error);
// Log in telemetry
if (this.telemetryService) {
@@ -247,20 +146,13 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
}));
}
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;
private actionItemProvider(action: Action): IActionItem {
const activeControl = this.group.activeControl;
// Check Active Editor
if (editor instanceof BaseEditor) {
actionItem = editor.getActionItem(action);
let actionItem: IActionItem;
if (activeControl instanceof BaseEditor) {
actionItem = activeControl.getActionItem(action);
}
// Check extensions
@@ -271,86 +163,13 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
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 = isCodeEditor(widget) && widget || isDiffEditor(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(_ => {
// schedule the update for the title area toolbar only if no other
// update to the title area is scheduled which will always also
// update the toolbar
if (!this.titleAreaUpdateScheduler.isScheduled()) {
this.titleAreaToolbarUpdateScheduler.schedule();
}
}));
fillInActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, this.contextMenuService);
}
return { primary, secondary };
}
public updateEditorActionsToolbar(): void {
const group = this.context;
if (!group) {
return;
}
const editor = group && group.activeEditor;
const isActive = this.stacks.isActive(group);
protected updateEditorActionsToolbar(): void {
// Update Editor Actions Toolbar
let primaryEditorActions: IAction[] = [];
let secondaryEditorActions: IAction[] = [];
const editorActions = this.getEditorActions({ group, editor });
// Primary actions only for the active group
if (isActive) {
primaryEditorActions = prepareActions(editorActions.primary);
if (editor instanceof EditorInput && editor.supportsSplitEditor()) {
this.updateSplitActionEnablement();
primaryEditorActions.push(this.splitEditorAction);
}
}
secondaryEditorActions = prepareActions(editorActions.secondary);
const tabOptions = this.editorGroupService.getTabOptions();
const { primaryEditorActions, secondaryEditorActions } = this.prepareEditorActions(this.getEditorActions());
// Only update if something actually has changed
const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
if (!tabOptions.showTabs) {
primaryEditorActionIds.push(this.closeOneEditorAction.id); // always show "Close" when tabs are disabled
}
const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id);
if (
!arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) ||
@@ -360,15 +179,66 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
) {
this.editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();
if (!tabOptions.showTabs) {
this.editorActionsToolbar.addPrimaryAction(this.closeOneEditorAction)();
}
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
}
}
protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[]; secondaryEditorActions: IAction[]; } {
let primaryEditorActions: IAction[];
let secondaryEditorActions: IAction[];
// Primary actions only for the active group
if (this.accessor.activeGroup === this.group) {
primaryEditorActions = prepareActions(editorActions.primary);
} else {
primaryEditorActions = [];
}
// Secondary actions for all groups
secondaryEditorActions = prepareActions(editorActions.secondary);
return { primaryEditorActions, secondaryEditorActions };
}
private getEditorActions(): IToolbarActions {
const primary: IAction[] = [];
const secondary: IAction[] = [];
// Dispose previous listeners
this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables);
// Update the resource context
this.resourceContext.set(toResource(this.group.activeEditor, { supportSideBySide: true }));
// Editor actions require the editor control to be there, so we retrieve it via service
const activeControl = this.group.activeControl;
if (activeControl instanceof BaseEditor) {
// Editor Control Actions
let editorActions = this.mapEditorToActions.get(activeControl.getId());
if (!editorActions) {
editorActions = { primary: activeControl.getActions(), secondary: activeControl.getSecondaryActions() };
this.mapEditorToActions.set(activeControl.getId(), editorActions);
}
primary.push(...editorActions.primary);
secondary.push(...editorActions.secondary);
// Contributed Actions
const codeEditor = getCodeEditor(activeControl.getControl());
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
this.editorToolBarMenuDisposables.push(titleBarMenu);
this.editorToolBarMenuDisposables.push(titleBarMenu.onDidChange(() => {
this.updateEditorActionsToolbar(); // Update editor toolbar whenever contributed actions change
}));
fillInActionBarActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary });
}
return { primary, secondary };
}
protected clearEditorActionsToolbar(): void {
this.editorActionsToolbar.setActions([], [])();
@@ -376,11 +246,46 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
this.currentSecondaryEditorActionIds = [];
}
protected onContextMenu(identifier: IEditorIdentifier, e: Event, node: HTMLElement): void {
protected enableGroupDragging(element: HTMLElement): void {
// Drag start
this._register(addDisposableListener(element, EventType.DRAG_START, (e: DragEvent) => {
if (e.target !== element) {
return; // only if originating from tabs container
}
// Set editor group as transfer
this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.group.id)], DraggedEditorGroupIdentifier.prototype);
e.dataTransfer.effectAllowed = 'copyMove';
// If tabs are disabled, treat dragging as if an editor tab was dragged
if (!this.accessor.partOptions.showTabs) {
const resource = toResource(this.group.activeEditor, { supportSideBySide: true });
if (resource) {
this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
}
}
// Drag Image
let label = this.group.activeEditor.getName();
if (this.accessor.partOptions.showTabs && this.group.count > 1) {
label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1);
}
applyDragImage(e, label, 'monaco-editor-group-drag-image');
}));
// Drag end
this._register(addDisposableListener(element, EventType.DRAG_END, () => {
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
}));
}
protected onContextMenu(editor: IEditorInput, e: Event, node: HTMLElement): void {
// Update the resource context
const currentContext = this.resourceContext.get();
this.resourceContext.set(toResource(identifier.editor, { supportSideBySide: true }));
this.resourceContext.set(toResource(editor, { supportSideBySide: true }));
// Find target anchor
let anchor: HTMLElement | { x: number, y: number } = node;
@@ -391,24 +296,21 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
// Fill in contributed actions
const actions: IAction[] = [];
fillInActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService);
fillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService);
// Show it
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.as(actions),
getActionsContext: () => ({ groupId: identifier.group.id, editorIndex: identifier.group.indexOf(identifier.editor) } as IEditorCommandsContext),
getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) } as IEditorCommandsContext),
getKeyBinding: (action) => this.getKeybinding(action),
onHide: (cancel) => {
onHide: () => {
// restore previous context
this.resourceContext.set(currentContext);
// restore focus to active editor if any
const editor = this.editorService.getActiveEditor();
if (editor) {
editor.focus();
}
// restore focus to active group
this.accessor.activeGroup.focus();
}
});
}
@@ -423,18 +325,61 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
return keybinding ? keybinding.getLabel() : void 0;
}
public dispose(): void {
super.dispose();
//#region ITitleAreaControl
// Actions
[
this.splitEditorAction,
this.closeOneEditorAction
].forEach((action) => {
action.dispose();
});
abstract openEditor(editor: IEditorInput): void;
// Toolbar
this.editorActionsToolbar.dispose();
abstract closeEditor(editor: IEditorInput): void;
abstract closeEditors(editors: IEditorInput[]): void;
abstract closeAllEditors(): void;
abstract moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void;
abstract pinEditor(editor: IEditorInput): void;
abstract setActive(isActive: boolean): void;
abstract updateEditorLabel(editor: IEditorInput): void;
abstract updateEditorDirty(editor: IEditorInput): void;
abstract updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void;
abstract updateStyles(): void;
layout(dimension: Dimension): void {
// Optionally implemented in subclasses
if (this.breadcrumbsControl) {
this.breadcrumbsControl.layout(undefined);
}
}
getPreferredHeight(): number {
return EDITOR_TITLE_HEIGHT + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0);
}
dispose(): void {
this.breadcrumbsControl = dispose(this.breadcrumbsControl);
this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables);
super.dispose();
}
//#endregion
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
// Drag Feedback
const dragImageBackground = theme.getColor(listActiveSelectionBackground);
const dragImageForeground = theme.getColor(listActiveSelectionForeground);
collector.addRule(`
.monaco-editor-group-drag-image {
background: ${dragImageBackground};
color: ${dragImageForeground};
}
`);
});

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* 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.menubar {
display: flex;
flex-shrink: 1;
box-sizing: border-box;
height: 30px;
-webkit-app-region: no-drag;
overflow: hidden;
flex-wrap: wrap;
}
.monaco-workbench.fullscreen .part.menubar {
margin: 0px;
padding: 0px 5px;
}
.monaco-workbench .part.menubar > .menubar-menu-button {
align-items: center;
box-sizing: border-box;
padding: 0px 8px;
cursor: default;
-webkit-app-region: no-drag;
zoom: 1;
white-space: nowrap;
}
.monaco-workbench .part.menubar .menubar-menu-items-holder {
position: absolute;
left: 0px;
opacity: 1;
z-index: 2000;
}
.monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container {
font-family: "Segoe WPC", "Segoe UI", ".SFNSDisplay-Light", "SFUIText-Light", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback";
outline: 0;
border: none;
}
.monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container :focus {
outline: 0;
}
.hc-black .monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container {
border: 2px solid #6FC3DF;
}

View File

@@ -0,0 +1,402 @@
/*---------------------------------------------------------------------------------------------
* 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 { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { isMacintosh } from 'vs/base/common/platform';
goMenuRegistration();
if (isMacintosh) {
windowMenuRegistration();
}
helpMenuRegistration();
// Menu registration
function goMenuRegistration() {
// Forward/Back
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '1_fwd_back',
command: {
id: 'workbench.action.navigateBack',
title: nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '1_fwd_back',
command: {
id: 'workbench.action.navigateForward',
title: nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward")
},
order: 2
});
// Switch Editor
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
group: '1_any',
command: {
id: 'workbench.action.nextEditor',
title: nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
group: '1_any',
command: {
id: 'workbench.action.previousEditor',
title: nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
group: '2_used',
command: {
id: 'workbench.action.openNextRecentlyUsedEditorInGroup',
title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
group: '2_used',
command: {
id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup',
title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '2_switch',
title: nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor"),
submenu: MenuId.MenubarSwitchEditorMenu,
order: 1
});
// Switch Group
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusFirstEditorGroup',
title: nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusSecondEditorGroup',
title: nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusThirdEditorGroup',
title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusFourthEditorGroup',
title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4")
},
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '1_focus_index',
command: {
id: 'workbench.action.focusFifthEditorGroup',
title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5")
},
order: 5
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '2_next_prev',
command: {
id: 'workbench.action.focusNextGroup',
title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '2_next_prev',
command: {
id: 'workbench.action.focusPreviousGroup',
title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '3_directional',
command: {
id: 'workbench.action.focusLeftGroup',
title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '3_directional',
command: {
id: 'workbench.action.focusRightGroup',
title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '3_directional',
command: {
id: 'workbench.action.focusAboveGroup',
title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
group: '3_directional',
command: {
id: 'workbench.action.focusBelowGroup',
title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below")
},
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '2_switch',
title: nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group"),
submenu: MenuId.MenubarSwitchGroupMenu,
order: 2
});
// Go to
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'workbench.action.quickOpen',
title: nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File...")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'workbench.action.gotoSymbol',
title: nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File...")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'workbench.action.showAllSymbols',
title: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace...")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'editor.action.goToDeclaration',
title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition")
},
order: 4
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'editor.action.goToTypeDefinition',
title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition")
},
order: 5
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'editor.action.goToImplementation',
title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation")
},
order: 6
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'workbench.action.gotoLine',
title: nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line...")
},
order: 7
});
}
function windowMenuRegistration() {
}
function helpMenuRegistration() {
// Welcome
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '1_welcome',
command: {
id: 'workbench.action.showWelcomePage',
title: nls.localize({ key: 'miWelcome', comment: ['&& denotes a mnemonic'] }, "&&Welcome")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '1_welcome',
command: {
id: 'workbench.action.showInteractivePlayground',
title: nls.localize({ key: 'miInteractivePlayground', comment: ['&& denotes a mnemonic'] }, "&&Interactive Playground")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '1_welcome',
command: {
id: 'workbench.action.openDocumentationUrl',
title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation")
},
order: 3
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '1_welcome',
command: {
id: 'update.showCurrentReleaseNotes',
title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes")
},
order: 4
});
// Reference
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '2_reference',
command: {
id: 'workbench.action.keybindingsReference',
title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '2_reference',
command: {
id: 'workbench.action.openIntroductoryVideosUrl',
title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '2_reference',
command: {
id: 'workbench.action.openTipsAndTricksUrl',
title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "&&Tips and Tricks")
},
order: 3
});
// Feedback
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '3_feedback',
command: {
id: 'workbench.action.openTwitterUrl',
title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '3_feedback',
command: {
id: 'workbench.action.openRequestFeatureUrl',
title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests")
},
order: 2
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '3_feedback',
command: {
id: 'workbench.action.openIssueReporter',
title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue")
},
order: 3
});
// Legal
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '4_legal',
command: {
id: 'workbench.action.openLicenseUrl',
title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '4_legal',
command: {
id: 'workbench.action.openPrivacyStatementUrl',
title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")
},
order: 2
});
// Tools
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '5_tools',
command: {
id: 'workbench.action.toggleDevTools',
title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: '5_tools',
command: {
id: 'workbench.action.openProcessExplorer',
title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer")
},
order: 2
});
if (!isMacintosh) {
// About
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
group: 'z_about',
command: {
id: 'workbench.action.showAboutDialog',
title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")
},
order: 1
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -115,7 +115,6 @@
/** Notification: Source */
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-source {
opacity: 0.7;
flex: 1;
font-size: 12px;
overflow: hidden; /* always give away space to buttons container */

View File

@@ -18,8 +18,8 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
export class ClearNotificationAction extends Action {
public static readonly ID = CLEAR_NOTIFICATION;
public static readonly LABEL = localize('clearNotification', "Clear Notification");
static readonly ID = CLEAR_NOTIFICATION;
static readonly LABEL = localize('clearNotification', "Clear Notification");
constructor(
id: string,
@@ -29,7 +29,7 @@ export class ClearNotificationAction extends Action {
super(id, label, 'clear-notification-action');
}
public run(notification: INotificationViewItem): TPromise<any> {
run(notification: INotificationViewItem): TPromise<any> {
this.commandService.executeCommand(CLEAR_NOTIFICATION, notification);
return TPromise.as(void 0);
@@ -38,8 +38,8 @@ export class ClearNotificationAction extends Action {
export class ClearAllNotificationsAction extends Action {
public static readonly ID = CLEAR_ALL_NOTIFICATIONS;
public static readonly LABEL = localize('clearNotifications', "Clear All Notifications");
static readonly ID = CLEAR_ALL_NOTIFICATIONS;
static readonly LABEL = localize('clearNotifications', "Clear All Notifications");
constructor(
id: string,
@@ -49,7 +49,7 @@ export class ClearAllNotificationsAction extends Action {
super(id, label, 'clear-all-notifications-action');
}
public run(notification: INotificationViewItem): TPromise<any> {
run(notification: INotificationViewItem): TPromise<any> {
this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS);
return TPromise.as(void 0);
@@ -58,8 +58,8 @@ export class ClearAllNotificationsAction extends Action {
export class HideNotificationsCenterAction extends Action {
public static readonly ID = HIDE_NOTIFICATIONS_CENTER;
public static readonly LABEL = localize('hideNotificationsCenter', "Hide Notifications");
static readonly ID = HIDE_NOTIFICATIONS_CENTER;
static readonly LABEL = localize('hideNotificationsCenter', "Hide Notifications");
constructor(
id: string,
@@ -69,7 +69,7 @@ export class HideNotificationsCenterAction extends Action {
super(id, label, 'hide-all-notifications-action');
}
public run(notification: INotificationViewItem): TPromise<any> {
run(notification: INotificationViewItem): TPromise<any> {
this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER);
return TPromise.as(void 0);
@@ -78,8 +78,8 @@ export class HideNotificationsCenterAction extends Action {
export class ExpandNotificationAction extends Action {
public static readonly ID = EXPAND_NOTIFICATION;
public static readonly LABEL = localize('expandNotification', "Expand Notification");
static readonly ID = EXPAND_NOTIFICATION;
static readonly LABEL = localize('expandNotification', "Expand Notification");
constructor(
id: string,
@@ -89,7 +89,7 @@ export class ExpandNotificationAction extends Action {
super(id, label, 'expand-notification-action');
}
public run(notification: INotificationViewItem): TPromise<any> {
run(notification: INotificationViewItem): TPromise<any> {
this.commandService.executeCommand(EXPAND_NOTIFICATION, notification);
return TPromise.as(void 0);
@@ -98,8 +98,8 @@ export class ExpandNotificationAction extends Action {
export class CollapseNotificationAction extends Action {
public static readonly ID = COLLAPSE_NOTIFICATION;
public static readonly LABEL = localize('collapseNotification', "Collapse Notification");
static readonly ID = COLLAPSE_NOTIFICATION;
static readonly LABEL = localize('collapseNotification', "Collapse Notification");
constructor(
id: string,
@@ -109,7 +109,7 @@ export class CollapseNotificationAction extends Action {
super(id, label, 'collapse-notification-action');
}
public run(notification: INotificationViewItem): TPromise<any> {
run(notification: INotificationViewItem): TPromise<any> {
this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification);
return TPromise.as(void 0);
@@ -118,8 +118,8 @@ export class CollapseNotificationAction extends Action {
export class ConfigureNotificationAction extends Action {
public static readonly ID = 'workbench.action.configureNotification';
public static readonly LABEL = localize('configureNotification', "Configure Notification");
static readonly ID = 'workbench.action.configureNotification';
static readonly LABEL = localize('configureNotification', "Configure Notification");
constructor(
id: string,
@@ -129,15 +129,15 @@ export class ConfigureNotificationAction extends Action {
super(id, label, 'configure-notification-action');
}
public get configurationActions(): IAction[] {
get configurationActions(): IAction[] {
return this._configurationActions;
}
}
export class CopyNotificationMessageAction extends Action {
public static readonly ID = 'workbench.action.copyNotificationMessage';
public static readonly LABEL = localize('copyNotification', "Copy Text");
static readonly ID = 'workbench.action.copyNotificationMessage';
static readonly LABEL = localize('copyNotification', "Copy Text");
constructor(
id: string,
@@ -147,7 +147,7 @@ export class CopyNotificationMessageAction extends Action {
super(id, label);
}
public run(notification: INotificationViewItem): TPromise<any> {
run(notification: INotificationViewItem): TPromise<any> {
this.clipboardService.writeText(notification.message.raw);
return TPromise.as(void 0);

View File

@@ -8,15 +8,14 @@
import { alert } from 'vs/base/browser/ui/aria/aria';
import { localize } from 'vs/nls';
import { INotificationViewItem, INotificationsModel, NotificationChangeType, INotificationChangeEvent } from 'vs/workbench/common/notifications';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Severity } from 'vs/platform/notification/common/notification';
export class NotificationsAlerts {
private toDispose: IDisposable[];
export class NotificationsAlerts extends Disposable {
constructor(private model: INotificationsModel) {
this.toDispose = [];
super();
// Alert initial notifications if any
model.notifications.forEach(n => this.ariaAlert(n));
@@ -25,7 +24,7 @@ export class NotificationsAlerts {
}
private registerListeners(): void {
this.toDispose.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
}
private onDidNotificationChange(e: INotificationChangeEvent): void {
@@ -36,7 +35,11 @@ export class NotificationsAlerts {
// Always log errors to console with full details
if (e.item.severity === Severity.Error) {
console.error(toErrorMessage(e.item.message.value, true));
if (e.item.message.original instanceof Error) {
console.error(e.item.message.original);
} else {
console.error(toErrorMessage(e.item.message.value, true));
}
}
}
}
@@ -53,8 +56,4 @@ export class NotificationsAlerts {
alert(alertText);
}
public dispose(): void {
this.toDispose = dispose(this.toDispose);
}
}

View File

@@ -18,7 +18,7 @@ import { NotificationsList } from 'vs/workbench/browser/parts/notifications/noti
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { addClass, removeClass, isAncestor, Dimension } from 'vs/base/browser/dom';
import { widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { localize } from 'vs/nls';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions';
@@ -29,13 +29,15 @@ export class NotificationsCenter extends Themable {
private static MAX_DIMENSIONS = new Dimension(450, 400);
private readonly _onDidChangeVisibility: Emitter<void> = this._register(new Emitter<void>());
get onDidChangeVisibility(): Event<void> { return this._onDidChangeVisibility.event; }
private notificationsCenterContainer: HTMLElement;
private notificationsCenterHeader: HTMLElement;
private notificationsCenterTitle: HTMLSpanElement;
private notificationsList: NotificationsList;
private _isVisible: boolean;
private workbenchDimensions: Dimension;
private readonly _onDidChangeVisibility: Emitter<void>;
private notificationsCenterVisibleContextKey: IContextKey<boolean>;
constructor(
@@ -45,32 +47,25 @@ export class NotificationsCenter extends Themable {
@IInstantiationService private instantiationService: IInstantiationService,
@IPartService private partService: IPartService,
@IContextKeyService contextKeyService: IContextKeyService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
@IKeybindingService private keybindingService: IKeybindingService
) {
super(themeService);
this._onDidChangeVisibility = new Emitter<void>();
this.toUnbind.push(this._onDidChangeVisibility);
this.notificationsCenterVisibleContextKey = NotificationsCenterVisibleContext.bindTo(contextKeyService);
this.registerListeners();
}
private registerListeners(): void {
this.toUnbind.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
}
public get onDidChangeVisibility(): Event<void> {
return this._onDidChangeVisibility.event;
}
public get isVisible(): boolean {
get isVisible(): boolean {
return this._isVisible;
}
public show(): void {
show(): void {
if (this._isVisible) {
this.notificationsList.show(true /* focus */);
@@ -138,21 +133,17 @@ export class NotificationsCenter extends Themable {
addClass(toolbarContainer, 'notifications-center-header-toolbar');
this.notificationsCenterHeader.appendChild(toolbarContainer);
const actionRunner = this.instantiationService.createInstance(NotificationActionRunner);
this.toUnbind.push(actionRunner);
const actionRunner = this._register(this.instantiationService.createInstance(NotificationActionRunner));
const notificationsToolBar = new ActionBar(toolbarContainer, {
const notificationsToolBar = this._register(new ActionBar(toolbarContainer, {
ariaLabel: localize('notificationsToolbar', "Notification Center Actions"),
actionRunner
});
this.toUnbind.push(notificationsToolBar);
}));
const hideAllAction = this.instantiationService.createInstance(HideNotificationsCenterAction, HideNotificationsCenterAction.ID, HideNotificationsCenterAction.LABEL);
this.toUnbind.push(hideAllAction);
const hideAllAction = this._register(this.instantiationService.createInstance(HideNotificationsCenterAction, HideNotificationsCenterAction.ID, HideNotificationsCenterAction.LABEL));
notificationsToolBar.push(hideAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(hideAllAction) });
const clearAllAction = this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL);
this.toUnbind.push(clearAllAction);
const clearAllAction = this._register(this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL));
notificationsToolBar.push(clearAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(clearAllAction) });
// Notifications List
@@ -174,7 +165,7 @@ export class NotificationsCenter extends Themable {
return; // only if visible
}
let focusEditor = false;
let focusGroup = false;
// Update notifications list based on event
switch (e.kind) {
@@ -185,7 +176,7 @@ export class NotificationsCenter extends Themable {
this.notificationsList.updateNotificationsList(e.index, 1, [e.item]);
break;
case NotificationChangeType.REMOVE:
focusEditor = isAncestor(document.activeElement, this.notificationsCenterContainer);
focusGroup = isAncestor(document.activeElement, this.notificationsCenterContainer);
this.notificationsList.updateNotificationsList(e.index, 1);
break;
}
@@ -197,26 +188,19 @@ export class NotificationsCenter extends Themable {
if (this.model.notifications.length === 0) {
this.hide();
// Restore focus to editor if we had focus
if (focusEditor) {
this.focusEditor();
// Restore focus to editor group if we had focus
if (focusGroup) {
this.editorGroupService.activeGroup.focus();
}
}
}
private focusEditor(): void {
const editor = this.editorService.getActiveEditor();
if (editor) {
editor.focus();
}
}
public hide(): void {
hide(): void {
if (!this._isVisible || !this.notificationsCenterContainer) {
return; // already hidden
}
const focusEditor = isAncestor(document.activeElement, this.notificationsCenterContainer);
const focusGroup = isAncestor(document.activeElement, this.notificationsCenterContainer);
// Hide
this._isVisible = false;
@@ -229,8 +213,9 @@ export class NotificationsCenter extends Themable {
// Event
this._onDidChangeVisibility.fire();
if (focusEditor) {
this.focusEditor();
// Restore focus to editor group if we had focus
if (focusGroup) {
this.editorGroupService.activeGroup.focus();
}
}
@@ -250,7 +235,7 @@ export class NotificationsCenter extends Themable {
}
}
public layout(dimension: Dimension): void {
layout(dimension: Dimension): void {
this.workbenchDimensions = dimension;
if (this._isVisible && this.notificationsCenterContainer) {
@@ -284,7 +269,7 @@ export class NotificationsCenter extends Themable {
}
}
public clearAll(): void {
clearAll(): void {
// Hide notifications center first
this.hide();

View File

@@ -7,7 +7,7 @@
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { INotificationViewItem, isNotificationViewItem } from 'vs/workbench/common/notifications';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
@@ -88,7 +88,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Hide Notifications Center
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: HIDE_NOTIFICATIONS_CENTER,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
weight: KeybindingWeight.WorkbenchContrib + 50,
when: NotificationsCenterVisibleContext,
primary: KeyCode.Escape,
handler: accessor => center.hide()
@@ -106,7 +106,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Clear Notification
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: CLEAR_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: NotificationFocusedContext,
primary: KeyCode.Delete,
mac: {
@@ -123,7 +123,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Expand Notification
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: EXPAND_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: NotificationFocusedContext,
primary: KeyCode.RightArrow,
handler: (accessor, args?: any) => {
@@ -137,7 +137,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Collapse Notification
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: COLLAPSE_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: NotificationFocusedContext,
primary: KeyCode.LeftArrow,
handler: (accessor, args?: any) => {
@@ -151,7 +151,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Toggle Notification
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: TOGGLE_NOTIFICATION,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: NotificationFocusedContext,
primary: KeyCode.Space,
secondary: [KeyCode.Enter],
@@ -166,7 +166,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Hide Toasts
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: HIDE_NOTIFICATION_TOAST,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
weight: KeybindingWeight.WorkbenchContrib + 50,
when: NotificationsToastsVisibleContext,
primary: KeyCode.Escape,
handler: accessor => toasts.hide()
@@ -178,7 +178,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Focus Next Toast
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: FOCUS_NEXT_NOTIFICATION_TOAST,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
primary: KeyCode.DownArrow,
handler: (accessor) => {
@@ -189,7 +189,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Focus Previous Toast
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: FOCUS_PREVIOUS_NOTIFICATION_TOAST,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
primary: KeyCode.UpArrow,
handler: (accessor) => {
@@ -200,7 +200,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Focus First Toast
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: FOCUS_FIRST_NOTIFICATION_TOAST,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
primary: KeyCode.PageUp,
secondary: [KeyCode.Home],
@@ -212,7 +212,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
// Focus Last Toast
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: FOCUS_LAST_NOTIFICATION_TOAST,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
primary: KeyCode.PageDown,
secondary: [KeyCode.End],

View File

@@ -38,7 +38,7 @@ export class NotificationsList extends Themable {
this.viewModel = [];
}
public show(focus?: boolean): void {
show(focus?: boolean): void {
if (this.isVisible) {
if (focus) {
this.list.domFocus();
@@ -67,47 +67,43 @@ export class NotificationsList extends Themable {
this.listContainer = document.createElement('div');
addClass(this.listContainer, 'notifications-list-container');
const actionRunner = this.instantiationService.createInstance(NotificationActionRunner);
this.toUnbind.push(actionRunner);
const actionRunner = this._register(this.instantiationService.createInstance(NotificationActionRunner));
// Notification Renderer
const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner);
// List
this.list = <WorkbenchList<INotificationViewItem>>this.instantiationService.createInstance(
this.list = this._register(<WorkbenchList<INotificationViewItem>>this.instantiationService.createInstance(
WorkbenchList,
this.listContainer,
new NotificationsListDelegate(this.listContainer),
[renderer],
this.options
);
this.toUnbind.push(this.list);
));
// Context menu to copy message
const copyAction = this.instantiationService.createInstance(CopyNotificationMessageAction, CopyNotificationMessageAction.ID, CopyNotificationMessageAction.LABEL);
this.toUnbind.push(copyAction);
this.toUnbind.push(this.list.onContextMenu(e => {
const copyAction = this._register(this.instantiationService.createInstance(CopyNotificationMessageAction, CopyNotificationMessageAction.ID, CopyNotificationMessageAction.LABEL));
this._register((this.list.onContextMenu(e => {
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => TPromise.as([copyAction]),
getActionsContext: () => e.element,
actionRunner
});
}));
})));
// Toggle on double click
this.toUnbind.push(this.list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle()));
this._register((this.list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle())));
// Clear focus when DOM focus moves out
// Use document.hasFocus() to not clear the focus when the entire window lost focus
// This ensures that when the focus comes back, the notifciation is still focused
const listFocusTracker = trackFocus(this.list.getHTMLElement());
listFocusTracker.onDidBlur(() => {
const listFocusTracker = this._register(trackFocus(this.list.getHTMLElement()));
this._register(listFocusTracker.onDidBlur(() => {
if (document.hasFocus()) {
this.list.setFocus([]);
}
});
this.toUnbind.push(listFocusTracker);
}));
// Context key
NotificationFocusedContext.bindTo(this.list.contextKeyService);
@@ -115,7 +111,7 @@ export class NotificationsList extends Themable {
// Only allow for focus in notifications, as the
// selection is too strong over the contents of
// the notification
this.toUnbind.push(this.list.onSelectionChange(e => {
this._register(this.list.onSelectionChange(e => {
if (e.indexes.length > 0) {
this.list.setSelection([]);
}
@@ -126,7 +122,7 @@ export class NotificationsList extends Themable {
this.updateStyles();
}
public updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) {
updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) {
const listHasDOMFocus = isAncestor(document.activeElement, this.listContainer);
// Remember focus and relative top of that item
@@ -177,7 +173,7 @@ export class NotificationsList extends Themable {
}
}
public hide(): void {
hide(): void {
if (!this.isVisible || !this.list) {
return; // already hidden
}
@@ -192,7 +188,7 @@ export class NotificationsList extends Themable {
this.viewModel = [];
}
public focusFirst(): void {
focusFirst(): void {
if (!this.isVisible || !this.list) {
return; // hidden
}
@@ -201,7 +197,7 @@ export class NotificationsList extends Themable {
this.list.domFocus();
}
public hasFocus(): boolean {
hasFocus(): boolean {
if (!this.isVisible || !this.list) {
return false; // hidden
}
@@ -222,7 +218,7 @@ export class NotificationsList extends Themable {
}
}
public layout(width: number, maxHeight?: number): void {
layout(width: number, maxHeight?: number): void {
if (this.list) {
this.listContainer.style.width = `${width}px`;
@@ -234,7 +230,7 @@ export class NotificationsList extends Themable {
}
}
public dispose(): void {
dispose(): void {
this.hide();
super.dispose();

View File

@@ -7,13 +7,12 @@
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem } from 'vs/workbench/common/notifications';
import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
import { localize } from 'vs/nls';
export class NotificationsStatus {
export class NotificationsStatus extends Disposable {
private statusItem: IDisposable;
private toDispose: IDisposable[];
private isNotificationsCenterVisible: boolean;
private _counter: Set<INotificationViewItem>;
@@ -21,7 +20,8 @@ export class NotificationsStatus {
private model: INotificationsModel,
@IStatusbarService private statusbarService: IStatusbarService
) {
this.toDispose = [];
super();
this._counter = new Set<INotificationViewItem>();
this.updateNotificationsStatusItem();
@@ -33,7 +33,7 @@ export class NotificationsStatus {
return this._counter.size;
}
public update(isCenterVisible: boolean): void {
update(isCenterVisible: boolean): void {
if (this.isNotificationsCenterVisible !== isCenterVisible) {
this.isNotificationsCenterVisible = isCenterVisible;
@@ -44,7 +44,7 @@ export class NotificationsStatus {
}
private registerListeners(): void {
this.toDispose.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
}
private onDidNotificationChange(e: INotificationChangeEvent): void {
@@ -101,8 +101,8 @@ export class NotificationsStatus {
return localize('notifications', "{0} New Notifications", this.count);
}
public dispose() {
this.toDispose = dispose(this.toDispose);
dispose() {
super.dispose();
if (this.statusItem) {
this.statusItem.dispose();

View File

@@ -16,7 +16,7 @@ import { IPartService, Parts } from 'vs/workbench/services/part/common/partServi
import { Themable, NOTIFICATIONS_TOAST_BORDER } from 'vs/workbench/common/theme';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { NotificationsToastsVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { localize } from 'vs/nls';
@@ -64,7 +64,7 @@ export class NotificationsToasts extends Themable {
@IInstantiationService private instantiationService: IInstantiationService,
@IPartService private partService: IPartService,
@IThemeService themeService: IThemeService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
@IContextKeyService contextKeyService: IContextKeyService,
@ILifecycleService private lifecycleService: ILifecycleService
) {
@@ -80,7 +80,7 @@ export class NotificationsToasts extends Themable {
}
private registerListeners(): void {
this.toUnbind.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
}
private onDidNotificationChange(e: INotificationChangeEvent): void {
@@ -185,7 +185,8 @@ export class NotificationsToasts extends Themable {
let timeoutHandle: number;
const hideAfterTimeout = () => {
timeoutHandle = setTimeout(() => {
if (!notificationList.hasFocus() && !item.expanded && !isMouseOverToast) {
const showsProgress = item.hasProgress() && !item.progress.state.done;
if (!notificationList.hasFocus() && !item.expanded && !isMouseOverToast && !showsProgress) {
this.removeToast(item);
} else {
hideAfterTimeout(); // push out disposal if item has focus or is expanded
@@ -218,11 +219,11 @@ export class NotificationsToasts extends Themable {
private removeToast(item: INotificationViewItem): void {
const notificationToast = this.mapNotificationToToast.get(item);
let focusEditor = false;
let focusGroup = false;
if (notificationToast) {
const toastHasDOMFocus = isAncestor(document.activeElement, notificationToast.container);
if (toastHasDOMFocus) {
focusEditor = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor
focusGroup = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor
}
// Listeners
@@ -241,20 +242,13 @@ export class NotificationsToasts extends Themable {
else {
this.doHide();
// Move focus to editor as needed
if (focusEditor) {
this.focusEditor();
// Move focus back to editor group as needed
if (focusGroup) {
this.editorGroupService.activeGroup.focus();
}
}
}
private focusEditor(): void {
const editor = this.editorService.getActiveEditor();
if (editor) {
editor.focus();
}
}
private removeToasts(): void {
this.mapNotificationToToast.forEach(toast => dispose(toast.disposeables));
this.mapNotificationToToast.clear();
@@ -271,17 +265,17 @@ export class NotificationsToasts extends Themable {
this.notificationsToastsVisibleContextKey.set(false);
}
public hide(): void {
const focusEditor = isAncestor(document.activeElement, this.notificationsToastsContainer);
hide(): void {
const focusGroup = isAncestor(document.activeElement, this.notificationsToastsContainer);
this.removeToasts();
if (focusEditor) {
this.focusEditor();
if (focusGroup) {
this.editorGroupService.activeGroup.focus();
}
}
public focus(): boolean {
focus(): boolean {
const toasts = this.getToasts(ToastVisibility.VISIBLE);
if (toasts.length > 0) {
toasts[0].list.focusFirst();
@@ -292,7 +286,7 @@ export class NotificationsToasts extends Themable {
return false;
}
public focusNext(): boolean {
focusNext(): boolean {
const toasts = this.getToasts(ToastVisibility.VISIBLE);
for (let i = 0; i < toasts.length; i++) {
const toast = toasts[i];
@@ -311,7 +305,7 @@ export class NotificationsToasts extends Themable {
return false;
}
public focusPrevious(): boolean {
focusPrevious(): boolean {
const toasts = this.getToasts(ToastVisibility.VISIBLE);
for (let i = 0; i < toasts.length; i++) {
const toast = toasts[i];
@@ -330,7 +324,7 @@ export class NotificationsToasts extends Themable {
return false;
}
public focusFirst(): boolean {
focusFirst(): boolean {
const toast = this.getToasts(ToastVisibility.VISIBLE)[0];
if (toast) {
toast.list.focusFirst();
@@ -341,7 +335,7 @@ export class NotificationsToasts extends Themable {
return false;
}
public focusLast(): boolean {
focusLast(): boolean {
const toasts = this.getToasts(ToastVisibility.VISIBLE);
if (toasts.length > 0) {
toasts[toasts.length - 1].list.focusFirst();
@@ -352,7 +346,7 @@ export class NotificationsToasts extends Themable {
return false;
}
public update(isCenterVisible: boolean): void {
update(isCenterVisible: boolean): void {
if (this.isNotificationsCenterVisible !== isCenterVisible) {
this.isNotificationsCenterVisible = isCenterVisible;
@@ -397,7 +391,7 @@ export class NotificationsToasts extends Themable {
return notificationToasts.reverse(); // from newest to oldest
}
public layout(dimension: Dimension): void {
layout(dimension: Dimension): void {
this.workbenchDimensions = dimension;
const maxDimensions = this.computeMaxDimensions();

View File

@@ -5,8 +5,8 @@
'use strict';
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import { clearNode, addClass, removeClass, toggleClass, addDisposableListener } from 'vs/base/browser/dom';
import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import { clearNode, addClass, removeClass, toggleClass, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import URI from 'vs/base/common/uri';
import { onUnexpectedError } from 'vs/base/common/errors';
@@ -26,7 +26,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { Severity } from 'vs/platform/notification/common/notification';
export class NotificationsListDelegate implements IDelegate<INotificationViewItem> {
export class NotificationsListDelegate implements IVirtualDelegate<INotificationViewItem> {
private static readonly ROW_HEIGHT = 42;
private static readonly LINE_HEIGHT = 22;
@@ -46,7 +46,7 @@ export class NotificationsListDelegate implements IDelegate<INotificationViewIte
return offsetHelper;
}
public getHeight(notification: INotificationViewItem): number {
getHeight(notification: INotificationViewItem): number {
// First row: message and actions
let expandedHeight = NotificationsListDelegate.ROW_HEIGHT;
@@ -102,7 +102,7 @@ export class NotificationsListDelegate implements IDelegate<INotificationViewIte
return preferredHeight;
}
public getTemplateId(element: INotificationViewItem): string {
getTemplateId(element: INotificationViewItem): string {
if (element instanceof NotificationViewItem) {
return NotificationRenderer.TEMPLATE_ID;
}
@@ -135,7 +135,7 @@ interface IMessageActionHandler {
class NotificationMessageRenderer {
public static render(message: INotificationMessage, actionHandler?: IMessageActionHandler): HTMLElement {
static render(message: INotificationMessage, actionHandler?: IMessageActionHandler): HTMLElement {
const messageContainer = document.createElement('span');
// Message has no links
@@ -181,7 +181,7 @@ class NotificationMessageRenderer {
export class NotificationRenderer implements IRenderer<INotificationViewItem, INotificationTemplateData> {
public static readonly TEMPLATE_ID = 'notification';
static readonly TEMPLATE_ID = 'notification';
constructor(
private actionRunner: IActionRunner,
@@ -191,11 +191,11 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN
) {
}
public get templateId() {
get templateId() {
return NotificationRenderer.TEMPLATE_ID;
}
public renderTemplate(container: HTMLElement): INotificationTemplateData {
renderTemplate(container: HTMLElement): INotificationTemplateData {
const data: INotificationTemplateData = Object.create(null);
data.toDispose = [];
@@ -274,11 +274,15 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN
return data;
}
public renderElement(notification: INotificationViewItem, index: number, data: INotificationTemplateData): void {
renderElement(notification: INotificationViewItem, index: number, data: INotificationTemplateData): void {
data.renderer.setInput(notification);
}
public disposeTemplate(templateData: INotificationTemplateData): void {
disposeElement(): void {
// noop
}
disposeTemplate(templateData: INotificationTemplateData): void {
templateData.toDispose = dispose(templateData.toDispose);
}
}
@@ -291,7 +295,7 @@ export class NotificationTemplateRenderer {
private static readonly SEVERITIES: ('info' | 'warning' | 'error')[] = ['info', 'warning', 'error'];
private inputDisposeables: IDisposable[];
private inputDisposeables: IDisposable[] = [];
constructor(
private template: INotificationTemplateData,
@@ -301,8 +305,6 @@ export class NotificationTemplateRenderer {
@IThemeService private themeService: IThemeService,
@IKeybindingService private keybindingService: IKeybindingService
) {
this.inputDisposeables = [];
if (!NotificationTemplateRenderer.closeNotificationAction) {
NotificationTemplateRenderer.closeNotificationAction = instantiationService.createInstance(ClearNotificationAction, ClearNotificationAction.ID, ClearNotificationAction.LABEL);
NotificationTemplateRenderer.expandNotificationAction = instantiationService.createInstance(ExpandNotificationAction, ExpandNotificationAction.ID, ExpandNotificationAction.LABEL);
@@ -310,7 +312,7 @@ export class NotificationTemplateRenderer {
}
}
public setInput(notification: INotificationViewItem): void {
setInput(notification: INotificationViewItem): void {
this.inputDisposeables = dispose(this.inputDisposeables);
this.render(notification);
@@ -320,6 +322,13 @@ export class NotificationTemplateRenderer {
// Container
toggleClass(this.template.container, 'expanded', notification.expanded);
this.inputDisposeables.push(addDisposableListener(this.template.container, EventType.MOUSE_UP, e => {
if (e.button === 1 /* Middle Button */) {
EventHelper.stop(e);
notification.close();
}
}));
// Severity Icon
this.renderSeverity(notification);
@@ -435,8 +444,7 @@ export class NotificationTemplateRenderer {
button.label = action.label;
this.inputDisposeables.push(button.onDidClick(e => {
e.preventDefault();
e.stopPropagation();
EventHelper.stop(e, true);
// Run action
this.actionRunner.run(action, notification);
@@ -474,7 +482,7 @@ export class NotificationTemplateRenderer {
}
if (typeof state.worked === 'number') {
this.template.progress.worked(state.worked).show();
this.template.progress.setWorked(state.worked).show();
}
}
@@ -501,7 +509,7 @@ export class NotificationTemplateRenderer {
return keybinding ? keybinding.getLabel() : void 0;
}
public dispose(): void {
dispose(): void {
this.inputDisposeables = dispose(this.inputDisposeables);
}
}
}

View File

@@ -1 +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>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#C5C5C5"/></svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 253 B

View File

@@ -1 +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>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#424242"/></svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 253 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#2d2d30;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>CollapseChevronDown_md_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M15.444,6.061,8,13.5.556,6.061,3.03,3.586,8,8.556l4.97-4.97Z" style="display: none;"/><path class="icon-vs-bg" d="M14.03,6.061,8,12.091,1.97,6.061,3.03,5,8,9.97,12.97,5Z"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15 5.232l-7 7-7-7L2.23 4 8 9.77 13.77 4 15 5.232z" fill="#C5C5C5"/></svg>

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 178 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>CollapseChevronDown_md_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M15.444,6.061,8,13.5.556,6.061,3.03,3.586,8,8.556l4.97-4.97Z" style="display: none;"/><path class="icon-vs-bg" d="M14.03,6.061,8,12.091,1.97,6.061,3.03,5,8,9.97,12.97,5Z"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15 5.232l-7 7-7-7L2.23 4 8 9.77 13.77 4 15 5.232z" fill="#424242"/></svg>

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 178 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#262626"><circle cx="3.5" cy="7.5" r="2.5"/><circle cx="8.5" cy="7.5" r="2.5"/><circle cx="13.5" cy="7.5" r="2.5"/></g><g fill="#C5C5C5"><circle cx="3.5" cy="7.5" r="1.5"/><circle cx="8.5" cy="7.5" r="1.5"/><circle cx="13.5" cy="7.5" r="1.5"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>Ellipsis_bold_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M6,7.5A2.5,2.5,0,1,1,3.5,5,2.5,2.5,0,0,1,6,7.5ZM8.5,5A2.5,2.5,0,1,0,11,7.5,2.5,2.5,0,0,0,8.5,5Zm5,0A2.5,2.5,0,1,0,16,7.5,2.5,2.5,0,0,0,13.5,5Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M5,7.5A1.5,1.5,0,1,1,3.5,6,1.5,1.5,0,0,1,5,7.5ZM8.5,6A1.5,1.5,0,1,0,10,7.5,1.5,1.5,0,0,0,8.5,6Zm5,0A1.5,1.5,0,1,0,15,7.5,1.5,1.5,0,0,0,13.5,6Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 748 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#F6F6F6"><circle cx="3.5" cy="7.5" r="2.5"/><circle cx="8.5" cy="7.5" r="2.5"/><circle cx="13.5" cy="7.5" r="2.5"/></g><g fill="#424242"><circle cx="3.5" cy="7.5" r="1.5"/><circle cx="8.5" cy="7.5" r="1.5"/><circle cx="13.5" cy="7.5" r="1.5"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>Ellipsis_bold_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M6,7.5A2.5,2.5,0,1,1,3.5,5,2.5,2.5,0,0,1,6,7.5ZM8.5,5A2.5,2.5,0,1,0,11,7.5,2.5,2.5,0,0,0,8.5,5Zm5,0A2.5,2.5,0,1,0,16,7.5,2.5,2.5,0,0,0,13.5,5Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M5,7.5A1.5,1.5,0,1,1,3.5,6,1.5,1.5,0,0,1,5,7.5ZM8.5,6A1.5,1.5,0,1,0,10,7.5,1.5,1.5,0,0,0,8.5,6Zm5,0A1.5,1.5,0,1,0,15,7.5,1.5,1.5,0,0,0,13.5,6Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 748 B

View File

@@ -37,48 +37,63 @@
/** Panel Switcher */
.monaco-workbench > .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more {
background: url('ellipsis.svg') center center no-repeat;
background-image: url('ellipsis.svg');
display: block;
height: 31px;
height: 28px;
min-width: 28px;
margin-left: 0px;
margin-right: 0px;
background-size: 16px;
background-repeat: no-repeat;
background-position: center center;
}
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar {
line-height: 30px;
line-height: 27px; /* matches panel titles in settings */
height: 35px;
}
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:first-child {
margin-left: 12px;
padding-left: 12px;
}
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item {
text-transform: uppercase;
padding-left: 16px;
padding-right: 16px;
padding-left: 10px;
padding-right: 10px;
font-size: 11px;
padding-bottom: 3px; /* puts the bottom border down */
padding-top: 2px;
padding-top: 4px;
display: flex;
}
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label{
margin-right: 0;
}
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:last-child {
padding-right: 6px;
padding-right: 10px;
}
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label {
border-bottom: 1px solid;
margin-right: 0;
}
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .badge {
margin-left: 8px;
}
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .badge .badge-content {
padding: 0.2em 0.5em;
padding: 0.3em 0.5em;
border-radius: 1em;
font-weight: normal;
text-align: center;
display: inline;
}
display: inline-block;
min-width: 1.6em;
line-height: 1em;
box-sizing: border-box;
}
/** Actions */
@@ -89,6 +104,7 @@
.monaco-workbench .panel .monaco-action-bar .action-item .monaco-select-box {
cursor: pointer;
min-width: 110px;
margin-right: 10px;
}
.monaco-workbench .hide-panel-action {
@@ -156,5 +172,5 @@
.vs-dark .monaco-workbench > .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more,
.hc-black .monaco-workbench > .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more {
background: url('ellipsis-inverse.svg') center center no-repeat;
background-image: url('ellipsis-inverse.svg');
}

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#2d2d30;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>CollapseChevronUp_md_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M15.444,9.939,12.97,12.414,8,7.444l-4.97,4.97L.556,9.939,8,2.5Z" style="display: none;"/><path class="icon-vs-bg" d="M14.03,9.939,12.97,11,8,6.03,3.03,11,1.97,9.939,8,3.909Z"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15 11l-1.23 1.232L8 6.462l-5.77 5.77L1 11l7-7 7 7z" fill="#C5C5C5"/></svg>

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 179 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>CollapseChevronUp_md_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M15.444,9.939,12.97,12.414,8,7.444l-4.97,4.97L.556,9.939,8,2.5Z" style="display: none;"/><path class="icon-vs-bg" d="M14.03,9.939,12.97,11,8,6.03,3.03,11,1.97,9.939,8,3.909Z"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15 11l-1.23 1.232L8 6.462l-5.77 5.77L1 11l7-7 7 7z" fill="#424242"/></svg>

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 179 B

View File

@@ -10,7 +10,7 @@ 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 { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService';
@@ -18,6 +18,7 @@ import { ActivityAction } from 'vs/workbench/browser/parts/compositebar/composit
import { IActivity } from 'vs/workbench/common/activity';
export class ClosePanelAction extends Action {
static readonly ID = 'workbench.action.closePanel';
static LABEL = nls.localize('closePanel', "Close Panel");
@@ -29,12 +30,13 @@ export class ClosePanelAction extends Action {
super(id, name, 'hide-panel-action');
}
public run(): TPromise<any> {
run(): TPromise<any> {
return this.partService.setPanelHidden(true);
}
}
export class TogglePanelAction extends Action {
static readonly ID = 'workbench.action.togglePanel';
static LABEL = nls.localize('togglePanel', "Toggle Panel");
@@ -46,15 +48,15 @@ export class TogglePanelAction extends Action {
super(id, name, partService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel');
}
public run(): TPromise<any> {
run(): TPromise<any> {
return this.partService.setPanelHidden(this.partService.isVisible(Parts.PANEL_PART));
}
}
class FocusPanelAction extends Action {
public static readonly ID = 'workbench.action.focusPanel';
public static readonly LABEL = nls.localize('focusPanel', "Focus into Panel");
static readonly ID = 'workbench.action.focusPanel';
static readonly LABEL = nls.localize('focusPanel', "Focus into Panel");
constructor(
id: string,
@@ -65,7 +67,7 @@ class FocusPanelAction extends Action {
super(id, label);
}
public run(): TPromise<any> {
run(): TPromise<any> {
// Show panel
if (!this.partService.isVisible(Parts.PANEL_PART)) {
@@ -83,10 +85,12 @@ class FocusPanelAction extends Action {
export class TogglePanelPositionAction extends Action {
public static readonly ID = 'workbench.action.togglePanelPosition';
public static readonly LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position");
static readonly ID = 'workbench.action.togglePanelPosition';
static readonly LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position");
private static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move to Right");
private static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move to Bottom");
private toDispose: IDisposable[];
constructor(
@@ -106,12 +110,12 @@ export class TogglePanelPositionAction extends Action {
setClassAndLabel();
}
public run(): TPromise<any> {
run(): TPromise<any> {
const position = this.partService.getPanelPosition();
return this.partService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM);
}
public dispose(): void {
dispose(): void {
super.dispose();
this.toDispose = dispose(this.toDispose);
}
@@ -119,10 +123,12 @@ export class TogglePanelPositionAction extends Action {
export class ToggleMaximizedPanelAction extends Action {
public static readonly ID = 'workbench.action.toggleMaximizedPanel';
public static readonly LABEL = nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel");
static readonly ID = 'workbench.action.toggleMaximizedPanel';
static readonly LABEL = nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel");
private static readonly MAXIMIZE_LABEL = nls.localize('maximizePanel', "Maximize Panel Size");
private static readonly RESTORE_LABEL = nls.localize('minimizePanel', "Restore Panel Size");
private toDispose: IDisposable[];
constructor(
@@ -139,13 +145,12 @@ export class ToggleMaximizedPanelAction extends Action {
}));
}
public run(): TPromise<any> {
// Show panel
run(): TPromise<any> {
return (!this.partService.isVisible(Parts.PANEL_PART) ? this.partService.setPanelHidden(false) : TPromise.as(null))
.then(() => this.partService.toggleMaximizedPanel());
}
public dispose(): void {
dispose(): void {
super.dispose();
this.toDispose = dispose(this.toDispose);
}
@@ -160,7 +165,7 @@ export class PanelActivityAction extends ActivityAction {
super(activity);
}
public run(event: any): TPromise<any> {
run(event: any): TPromise<any> {
return this.panelService.openPanel(this.activity.id, true).then(() => this.activate());
}
}
@@ -172,3 +177,12 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedP
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View"));
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View"));
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: TogglePanelAction.ID,
title: nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel")
},
order: 5
});

View File

@@ -5,7 +5,7 @@
import 'vs/css!./media/panelpart';
import { TPromise } from 'vs/base/common/winjs.base';
import { IAction, Action } from 'vs/base/common/actions';
import { IAction } from 'vs/base/common/actions';
import { Event } from 'vs/base/common/event';
import { $ } from 'vs/base/browser/builder';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -20,28 +20,35 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
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 { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, ToggleMaximizedPanelAction } from 'vs/workbench/browser/parts/panel/panelActions';
import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction } 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, PANEL_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Dimension } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IDisposable } from 'vs/base/common/lifecycle';
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
const ActivePanleContextId = 'activePanel';
export const ActivePanelContext = new RawContextKey<string>(ActivePanleContextId, '');
export class PanelPart extends CompositePart<Panel> implements IPanelService {
public static readonly activePanelSettingsKey = 'workbench.panelpart.activepanelid';
static readonly activePanelSettingsKey = 'workbench.panelpart.activepanelid';
private static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels';
private static readonly MIN_COMPOSITE_BAR_WIDTH = 50;
public _serviceBrand: any;
_serviceBrand: any;
private activePanelContextKey: IContextKey<string>;
private blockOpeningPanel: boolean;
private compositeBar: CompositeBar;
private compositeActions: { [compositeId: string]: { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null);
private dimension: Dimension;
constructor(
@@ -53,7 +60,8 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
@IPartService partService: IPartService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
super(
notificationService,
@@ -74,17 +82,18 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
{ hasTitle: true }
);
this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, {
icon: false,
storageId: PanelPart.PINNED_PANELS,
orientation: ActionsOrientation.HORIZONTAL,
composites: this.getPanels(),
openComposite: (compositeId: string) => this.openPanel(compositeId, true),
getActivityAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
getCompositePinnedAction: (compositeId: string) => new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar),
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
getContextMenuActions: () => [this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))],
getDefaultCompositeId: () => Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
hidePart: () => this.partService.setPanelHidden(true),
compositeSize: 0,
overflowActionSize: 44,
colors: {
backgroundColor: PANEL_BACKGROUND,
@@ -92,37 +101,54 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
badgeForeground,
dragAndDropBackground: PANEL_DRAG_AND_DROP_BACKGROUND
}
});
this.toUnbind.push(this.compositeBar);
}));
for (const panel of this.getPanels()) {
this.compositeBar.addComposite(panel);
}
this.activePanelContextKey = ActivePanelContext.bindTo(contextKeyService);
this.registerListeners();
}
private registerListeners(): void {
this._register(this.onDidPanelOpen(this._onDidPanelOpen, this));
this._register(this.onDidPanelClose(this._onDidPanelClose, this));
this.toUnbind.push(this.registry.onDidRegister(panelDescriptor => this.compositeBar.addComposite(panelDescriptor, false)));
this._register(this.registry.onDidRegister(panelDescriptor => this.compositeBar.addComposite(panelDescriptor)));
// Activate panel action on opening of a panel
this.toUnbind.push(this.onDidPanelOpen(panel => {
this._register(this.onDidPanelOpen(panel => {
this.compositeBar.activateComposite(panel.getId());
// Need to relayout composite bar since different panels have different action bar width
this.layoutCompositeBar();
this.layoutCompositeBar(); // Need to relayout composite bar since different panels have different action bar width
}));
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
// Deactivate panel action on close
this.toUnbind.push(this.onDidPanelClose(panel => this.compositeBar.deactivateComposite(panel.getId())));
this._register(this.onDidPanelClose(panel => this.compositeBar.deactivateComposite(panel.getId())));
}
public get onDidPanelOpen(): Event<IPanel> {
private _onDidPanelOpen(panel: IPanel): void {
this.activePanelContextKey.set(panel.getId());
}
private _onDidPanelClose(panel: IPanel): void {
const id = panel.getId();
if (this.activePanelContextKey.get() === id) {
this.activePanelContextKey.reset();
}
}
get onDidPanelOpen(): Event<IPanel> {
return this._onDidCompositeOpen.event;
}
public get onDidPanelClose(): Event<IPanel> {
get onDidPanelClose(): Event<IPanel> {
return this._onDidCompositeClose.event;
}
public updateStyles(): void {
updateStyles(): void {
super.updateStyles();
const container = $(this.getContainer());
@@ -133,7 +159,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
title.style('border-top-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder));
}
public openPanel(id: string, focus?: boolean): TPromise<Panel> {
openPanel(id: string, focus?: boolean): TPromise<Panel> {
if (this.blockOpeningPanel) {
return TPromise.as(null); // Workaround against a potential race condition
}
@@ -152,7 +178,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
return promise.then(() => this.openComposite(id, focus));
}
public showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable {
showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable {
return this.compositeBar.showActivity(panelId, badge, clazz);
}
@@ -160,31 +186,20 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
return this.getPanels().filter(p => p.id === panelId).pop();
}
private showContextMenu(e: MouseEvent): void {
const event = new StandardMouseEvent(e);
const actions: Action[] = this.getPanels().map(panel => this.instantiationService.createInstance(ToggleCompositePinnedAction, panel, this.compositeBar));
this.contextMenuService.showContextMenu({
getAnchor: () => { return { x: event.posx, y: event.posy }; },
getActions: () => TPromise.as(actions),
onHide: () => dispose(actions)
});
}
public getPanels(): PanelDescriptor[] {
getPanels(): PanelDescriptor[] {
return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels()
.filter(p => p.enabled)
.sort((v1, v2) => v1.order - v2.order);
}
public setPanelEnablement(id: string, enabled: boolean): void {
setPanelEnablement(id: string, enabled: boolean): void {
const descriptor = Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels().filter(p => p.id === id).pop();
if (descriptor && descriptor.enabled !== enabled) {
descriptor.enabled = enabled;
if (enabled) {
this.compositeBar.addComposite(descriptor, true);
this.compositeBar.addComposite(descriptor);
} else {
this.compositeBar.removeComposite(id);
this.removeComposite(id);
}
}
}
@@ -197,15 +212,15 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
];
}
public getActivePanel(): IPanel {
getActivePanel(): IPanel {
return this.getActiveComposite();
}
public getLastActivePanelId(): string {
getLastActivePanelId(): string {
return this.getLastActiveCompositetId();
}
public hideActivePanel(): TPromise<void> {
hideActivePanel(): TPromise<void> {
return this.hideActiveComposite().then(composite => void 0);
}
@@ -226,7 +241,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
};
}
public layout(dimension: Dimension): Dimension[] {
layout(dimension: Dimension): Dimension[] {
if (!this.partService.isVisible(Parts.PANEL_PART)) {
return [dimension];
}
@@ -243,11 +258,6 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
return sizes;
}
public shutdown(): void {
this.compositeBar.shutdown();
super.shutdown();
}
private layoutCompositeBar(): void {
if (this.dimension) {
let availableWidth = this.dimension.width - 40; // take padding into account
@@ -259,6 +269,28 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
}
private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } {
let compositeActions = this.compositeActions[compositeId];
if (!compositeActions) {
compositeActions = {
activityAction: this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
pinnedAction: new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar)
};
this.compositeActions[compositeId] = compositeActions;
}
return compositeActions;
}
private removeComposite(compositeId: string): void {
this.compositeBar.removeComposite(compositeId);
const compositeActions = this.compositeActions[compositeId];
if (compositeActions) {
compositeActions.activityAction.dispose();
compositeActions.pinnedAction.dispose();
delete this.compositeActions[compositeId];
}
}
private getToolbarWidth(): number {
const activePanel = this.getActivePanel();
if (!activePanel) {

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="10px" height="16px" viewBox="0 0 10 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 40.3 (33839) - http://www.bohemiancoding.com/sketch -->
<title>arrow-left</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Octicons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="arrow-left" fill="#c5c5c5">
<polygon id="Shape" points="6 3 0 8 6 13 6 10 10 10 10 6 6 6"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 594 B

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="10px" height="16px" viewBox="0 0 10 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 40.3 (33839) - http://www.bohemiancoding.com/sketch -->
<title>arrow-left</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Octicons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="arrow-left" fill="#424242">
<polygon id="Shape" points="6 3 0 8 6 13 6 10 10 10 10 6 6 6"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 594 B

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* 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 { QuickPickManyToggle, BackAction } from 'vs/workbench/browser/parts/quickinput/quickInput';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen';
KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle);
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(BackAction, BackAction.ID, BackAction.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 } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Back');

View File

@@ -12,6 +12,39 @@
margin-left: -300px;
}
.quick-input-titlebar {
display: flex;
}
.quick-input-left-action-bar {
display: flex;
margin-left: 4px;
flex: 1;
}
.quick-input-left-action-bar.monaco-action-bar .actions-container {
justify-content: flex-start;
}
.quick-input-title {
padding: 3px 0px;
text-align: center;
}
.quick-input-right-action-bar {
display: flex;
margin-right: 4px;
flex: 1;
}
.quick-input-titlebar .monaco-action-bar .action-label.icon {
margin: 0;
width: 19px;
height: 100%;
background-position: center;
background-repeat: no-repeat;
}
.quick-input-header {
display: flex;
padding: 6px 6px 4px 6px;
@@ -32,10 +65,15 @@
flex-grow: 1;
}
.quick-input-widget[data-type=selectMany] .quick-input-box {
.quick-input-widget.show-checkboxes .quick-input-box {
margin-left: 5px;
}
.quick-input-visible-count {
position: absolute;
left: -10000px;
}
.quick-input-count {
align-self: center;
position: absolute;
@@ -65,56 +103,69 @@
height: 2px;
}
.quick-input-checkbox-list {
.quick-input-list {
line-height: 22px;
}
.quick-input-checkbox-list .monaco-list {
.quick-input-list .monaco-list {
overflow: hidden;
max-height: calc(20 * 22px);
}
.quick-input-checkbox-list .quick-input-checkbox-list-entry {
.quick-input-list .quick-input-list-entry {
overflow: hidden;
display: flex;
height: 100%;
padding: 0 6px;
}
.quick-input-checkbox-list .quick-input-checkbox-list-label {
.quick-input-list .quick-input-list-label {
overflow: hidden;
display: flex;
height: 100%;
flex: 1;
}
.quick-input-checkbox-list .quick-input-checkbox-list-checkbox {
.quick-input-list .quick-input-list-checkbox {
align-self: center;
margin: 0;
/* TODO */
/* margin-top: 5px; */
}
.quick-input-checkbox-list .quick-input-checkbox-list-rows {
.quick-input-list .quick-input-list-rows {
overflow: hidden;
text-overflow: ellipsis;
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
margin-left: 5px;
}
.quick-input-widget.show-checkboxes .quick-input-list .quick-input-list-rows {
margin-left: 10px;
}
.quick-input-checkbox-list .quick-input-checkbox-list-rows > .quick-input-checkbox-list-row {
.quick-input-widget .quick-input-list .quick-input-list-checkbox {
display: none;
}
.quick-input-widget.show-checkboxes .quick-input-list .quick-input-list-checkbox {
display: inline;
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row {
display: flex;
align-items: center;
}
.quick-input-checkbox-list .quick-input-checkbox-list-rows .monaco-highlighted-label span {
.quick-input-list .quick-input-list-rows .monaco-highlighted-label span {
opacity: 1;
}
.quick-input-checkbox-list .quick-input-checkbox-list-label-meta {
.quick-input-list .quick-input-list-label-meta {
opacity: 0.7;
line-height: normal;
}
.quick-input-list .monaco-highlighted-label .highlight {
font-weight: bold;
}

File diff suppressed because it is too large Load Diff

View File

@@ -67,8 +67,24 @@ export class QuickInputBox {
this.inputBox.setPlaceHolder(placeholder);
}
setPassword(isPassword: boolean): void {
this.inputBox.inputElement.type = isPassword ? 'password' : 'text';
get placeholder() {
return this.inputBox.inputElement.getAttribute('placeholder');
}
set placeholder(placeholder: string) {
this.inputBox.setPlaceHolder(placeholder);
}
get password() {
return this.inputBox.inputElement.type === 'password';
}
set password(password: boolean) {
this.inputBox.inputElement.type = password ? 'password' : 'text';
}
set enabled(enabled: boolean) {
this.inputBox.setEnabled(enabled);
}
showDecoration(decoration: Severity): void {

View File

@@ -6,12 +6,12 @@
'use strict';
import 'vs/css!./quickInput';
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import * as dom from 'vs/base/browser/dom';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IMatch } from 'vs/base/common/filters';
import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon';
import { compareAnything } from 'vs/base/common/comparers';
@@ -24,23 +24,25 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte
import { memoize } from 'vs/base/common/decorators';
import { range } from 'vs/base/common/arrays';
import * as platform from 'vs/base/common/platform';
import { listFocusBackground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
const $ = dom.$;
interface ICheckableElement {
interface IListElement {
index: number;
item: IPickOpenEntry;
item: IQuickPickItem;
checked: boolean;
}
class CheckableElement implements ICheckableElement {
class ListElement implements IListElement {
index: number;
item: IPickOpenEntry;
item: IQuickPickItem;
shouldAlwaysShow = false;
hidden = false;
private _onChecked = new Emitter<boolean>();
onChecked = this._onChecked.event;
_checked: boolean;
_checked?: boolean;
get checked() {
return this._checked;
}
@@ -54,58 +56,59 @@ class CheckableElement implements ICheckableElement {
descriptionHighlights?: IMatch[];
detailHighlights?: IMatch[];
constructor(init: ICheckableElement) {
constructor(init: IListElement) {
assign(this, init);
}
}
interface ICheckableElementTemplateData {
interface IListElementTemplateData {
checkbox: HTMLInputElement;
label: IconLabel;
detail: HighlightedLabel;
element: CheckableElement;
element: ListElement;
toDisposeElement: IDisposable[];
toDisposeTemplate: IDisposable[];
}
class CheckableElementRenderer implements IRenderer<CheckableElement, ICheckableElementTemplateData> {
class ListElementRenderer implements IRenderer<ListElement, IListElementTemplateData> {
static readonly ID = 'checkableelement';
static readonly ID = 'listelement';
get templateId() {
return CheckableElementRenderer.ID;
return ListElementRenderer.ID;
}
renderTemplate(container: HTMLElement): ICheckableElementTemplateData {
const data: ICheckableElementTemplateData = Object.create(null);
const entry = dom.append(container, $('.quick-input-checkbox-list-entry'));
const label = dom.append(entry, $('label.quick-input-checkbox-list-label'));
// Entry
data.checkbox = <HTMLInputElement>dom.append(label, $('input.quick-input-checkbox-list-checkbox'));
data.checkbox.type = 'checkbox';
renderTemplate(container: HTMLElement): IListElementTemplateData {
const data: IListElementTemplateData = Object.create(null);
data.toDisposeElement = [];
data.toDisposeTemplate = [];
const entry = dom.append(container, $('.quick-input-list-entry'));
// Checkbox
const label = dom.append(entry, $('label.quick-input-list-label'));
data.checkbox = <HTMLInputElement>dom.append(label, $('input.quick-input-list-checkbox'));
data.checkbox.type = 'checkbox';
data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => {
data.element.checked = data.checkbox.checked;
}));
const rows = dom.append(label, $('.quick-input-checkbox-list-rows'));
const row1 = dom.append(rows, $('.quick-input-checkbox-list-row'));
const row2 = dom.append(rows, $('.quick-input-checkbox-list-row'));
// Rows
const rows = dom.append(label, $('.quick-input-list-rows'));
const row1 = dom.append(rows, $('.quick-input-list-row'));
const row2 = dom.append(rows, $('.quick-input-list-row'));
// Label
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true });
// Detail
const detailContainer = dom.append(row2, $('.quick-input-checkbox-list-label-meta'));
const detailContainer = dom.append(row2, $('.quick-input-list-label-meta'));
data.detail = new HighlightedLabel(detailContainer);
return data;
}
renderElement(element: CheckableElement, index: number, data: ICheckableElementTemplateData): void {
renderElement(element: ListElement, index: number, data: IListElementTemplateData): void {
data.toDisposeElement = dispose(data.toDisposeElement);
data.element = element;
data.checkbox.checked = element.checked;
@@ -124,34 +127,43 @@ class CheckableElementRenderer implements IRenderer<CheckableElement, ICheckable
data.detail.set(element.item.detail, detailHighlights);
}
disposeTemplate(data: ICheckableElementTemplateData): void {
disposeElement(): void {
// noop
}
disposeTemplate(data: IListElementTemplateData): void {
data.toDisposeElement = dispose(data.toDisposeElement);
data.toDisposeTemplate = dispose(data.toDisposeTemplate);
}
}
class CheckableElementDelegate implements IDelegate<CheckableElement> {
class ListElementDelegate implements IVirtualDelegate<ListElement> {
getHeight(element: CheckableElement): number {
getHeight(element: ListElement): number {
return element.item.detail ? 44 : 22;
}
getTemplateId(element: CheckableElement): string {
return CheckableElementRenderer.ID;
getTemplateId(element: ListElement): string {
return ListElementRenderer.ID;
}
}
export class QuickInputCheckboxList {
export class QuickInputList {
private container: HTMLElement;
private list: WorkbenchList<CheckableElement>;
private elements: CheckableElement[] = [];
private list: WorkbenchList<ListElement>;
private elements: ListElement[] = [];
private elementsToIndexes = new Map<IQuickPickItem, number>();
matchOnDescription = false;
matchOnDetail = false;
private _onAllVisibleCheckedChanged = new Emitter<boolean>(); // TODO: Debounce
onAllVisibleCheckedChanged: Event<boolean> = this._onAllVisibleCheckedChanged.event;
private _onCheckedCountChanged = new Emitter<number>(); // TODO: Debounce
onCheckedCountChanged: Event<number> = this._onCheckedCountChanged.event;
private _onChangedAllVisibleChecked = new Emitter<boolean>();
onChangedAllVisibleChecked: Event<boolean> = this._onChangedAllVisibleChecked.event;
private _onChangedCheckedCount = new Emitter<number>();
onChangedCheckedCount: Event<number> = this._onChangedCheckedCount.event;
private _onChangedVisibleCount = new Emitter<number>();
onChangedVisibleCount: Event<number> = this._onChangedVisibleCount.event;
private _onChangedCheckedElements = new Emitter<IQuickPickItem[]>();
onChangedCheckedElements: Event<IQuickPickItem[]> = this._onChangedCheckedElements.event;
private _onLeave = new Emitter<void>();
onLeave: Event<void> = this._onLeave.event;
private _fireCheckedEvents = true;
@@ -162,12 +174,12 @@ export class QuickInputCheckboxList {
private parent: HTMLElement,
@IInstantiationService private instantiationService: IInstantiationService
) {
this.container = dom.append(this.parent, $('.quick-input-checkbox-list'));
const delegate = new CheckableElementDelegate();
this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new CheckableElementRenderer()], {
this.container = dom.append(this.parent, $('.quick-input-list'));
const delegate = new ListElementDelegate();
this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new ListElementRenderer()], {
identityProvider: element => element.label,
multipleSelectionSupport: false
}) as WorkbenchList<CheckableElement>;
}) as WorkbenchList<ListElement>;
this.disposables.push(this.list);
this.disposables.push(this.list.onKeyDown(e => {
const event = new StandardKeyboardEvent(e);
@@ -181,12 +193,14 @@ export class QuickInputCheckboxList {
}
break;
case KeyCode.UpArrow:
case KeyCode.PageUp:
const focus1 = this.list.getFocus();
if (focus1.length === 1 && focus1[0] === 0) {
this._onLeave.fire();
}
break;
case KeyCode.DownArrow:
case KeyCode.PageDown:
const focus2 = this.list.getFocus();
if (focus2.length === 1 && focus2[0] === this.list.length - 1) {
this._onLeave.fire();
@@ -207,15 +221,20 @@ export class QuickInputCheckboxList {
}
@memoize
get onFocusChange() {
get onDidChangeFocus() {
return mapEvent(this.list.onFocusChange, e => e.elements.map(e => e.item));
}
@memoize
get onDidChangeSelection() {
return mapEvent(this.list.onSelectionChange, e => e.elements.map(e => e.item));
}
getAllVisibleChecked() {
return this.allVisibleChecked(this.elements, false);
}
private allVisibleChecked(elements: CheckableElement[], whenNoneVisible = true) {
private allVisibleChecked(elements: ListElement[], whenNoneVisible = true) {
for (let i = 0, n = elements.length; i < n; i++) {
const element = elements[i];
if (!element.hidden) {
@@ -240,6 +259,17 @@ export class QuickInputCheckboxList {
return count;
}
getVisibleCount() {
let count = 0;
const elements = this.elements;
for (let i = 0, n = elements.length; i < n; i++) {
if (!elements[i].hidden) {
count++;
}
}
return count;
}
setAllVisibleChecked(checked: boolean) {
try {
this._fireCheckedEvents = false;
@@ -254,16 +284,44 @@ export class QuickInputCheckboxList {
}
}
setElements(elements: IPickOpenEntry[]): void {
setElements(elements: IQuickPickItem[]): void {
this.elementDisposables = dispose(this.elementDisposables);
this.elements = elements.map((item, index) => new CheckableElement({
this.elements = elements.map((item, index) => new ListElement({
index,
item,
checked: !!item.picked
checked: false
}));
this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents())));
this.elementsToIndexes = this.elements.reduce((map, element, index) => {
map.set(element.item, index);
return map;
}, new Map<IQuickPickItem, number>());
this.list.splice(0, this.list.length, this.elements);
this.list.setFocus([]);
this._onChangedVisibleCount.fire(this.elements.length);
}
getFocusedElements() {
return this.list.getFocusedElements()
.map(e => e.item);
}
setFocusedElements(items: IQuickPickItem[]) {
this.list.setFocus(items
.filter(item => this.elementsToIndexes.has(item))
.map(item => this.elementsToIndexes.get(item)));
}
getSelectedElements() {
return this.list.getSelectedElements()
.map(e => e.item);
}
setSelectedElements(items: IQuickPickItem[]) {
this.list.setSelection(items
.filter(item => this.elementsToIndexes.has(item))
.map(item => this.elementsToIndexes.get(item)));
}
getCheckedElements() {
@@ -271,7 +329,38 @@ export class QuickInputCheckboxList {
.map(e => e.item);
}
setCheckedElements(items: IQuickPickItem[]) {
try {
this._fireCheckedEvents = false;
const checked = new Set();
for (const item of items) {
checked.add(item);
}
for (const element of this.elements) {
element.checked = checked.has(element.item);
}
} finally {
this._fireCheckedEvents = true;
this.fireCheckedEvents();
}
}
set enabled(value: boolean) {
this.list.getHTMLElement().style.pointerEvents = value ? null : 'none';
}
focus(what: 'First' | 'Last' | 'Next' | 'Previous' | 'NextPage' | 'PreviousPage'): void {
if (!this.list.length) {
return;
}
if ((what === 'Next' || what === 'NextPage') && this.list.getFocus()[0] === this.list.length - 1) {
what = 'First';
}
if ((what === 'Previous' || what === 'PreviousPage') && this.list.getFocus()[0] === 0) {
what = 'Last';
}
this.list['focus' + what]();
this.list.reveal(this.list.getFocus()[0]);
}
@@ -322,20 +411,27 @@ export class QuickInputCheckboxList {
});
}
const shownElements = this.elements.filter(element => !element.hidden);
// Sort by value
const normalizedSearchValue = query.toLowerCase();
this.elements.sort((a, b) => {
shownElements.sort((a, b) => {
if (!query) {
return a.index - b.index; // restore natural order
}
return compareEntries(a, b, normalizedSearchValue);
});
this.list.splice(0, this.list.length, this.elements.filter(element => !element.hidden));
this.elementsToIndexes = shownElements.reduce((map, element, index) => {
map.set(element.item, index);
return map;
}, new Map<IQuickPickItem, number>());
this.list.splice(0, this.list.length, shownElements);
this.list.setFocus([]);
this.list.layout();
this._onAllVisibleCheckedChanged.fire(this.getAllVisibleChecked());
this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked());
this._onChangedVisibleCount.fire(shownElements.length);
}
toggleCheckbox() {
@@ -353,7 +449,7 @@ export class QuickInputCheckboxList {
}
display(display: boolean) {
this.container.style.display = display ? null : 'none';
this.container.style.display = display ? '' : 'none';
}
isDisplayed() {
@@ -367,13 +463,14 @@ export class QuickInputCheckboxList {
private fireCheckedEvents() {
if (this._fireCheckedEvents) {
this._onAllVisibleCheckedChanged.fire(this.getAllVisibleChecked());
this._onCheckedCountChanged.fire(this.getCheckedCount());
this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked());
this._onChangedCheckedCount.fire(this.getCheckedCount());
this._onChangedCheckedElements.fire(this.getCheckedElements());
}
}
}
function compareEntries(elementA: CheckableElement, elementB: CheckableElement, lookFor: string): number {
function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number {
const labelHighlightsA = elementA.labelHighlights || [];
const labelHighlightsB = elementB.labelHighlights || [];
@@ -386,4 +483,13 @@ function compareEntries(elementA: CheckableElement, elementB: CheckableElement,
}
return compareAnything(elementA.item.label, elementB.item.label, lookFor);
}
}
registerThemingParticipant((theme, collector) => {
// Override inactive focus background with active focus background for single-pick case.
const listInactiveFocusBackground = theme.getColor(listFocusBackground);
if (listInactiveFocusBackground) {
collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { background-color: ${listInactiveFocusBackground}; }`);
collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused:hover { background-color: ${listInactiveFocusBackground}; }`);
}
});

View File

@@ -21,24 +21,21 @@ import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel
import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, compareEntries, QuickOpenItemAccessorClass } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget';
import { ContributableActionProvider } from 'vs/workbench/browser/actions';
import * as labels from 'vs/base/common/labels';
import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles';
import { Registry } from 'vs/platform/registry/common/platform';
import { IResourceInput, IEditorInput } from 'vs/platform/editor/common/editor';
import { IResourceInput } from 'vs/platform/editor/common/editor';
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/workbench/browser/labels';
import { IModelService } from 'vs/editor/common/services/modelService';
import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { EditorInput, IWorkbenchEditorConfiguration, IEditorInput } from 'vs/workbench/common/editor';
import { Component } from 'vs/workbench/common/component';
import { Event, Emitter } from 'vs/base/common/event';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { QuickOpenHandler, QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions, EditorQuickOpenEntry, CLOSE_ON_FOCUS_LOST_CONFIG } from 'vs/workbench/browser/quickopen';
import * as errors from 'vs/base/common/errors';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPickOpenEntry, IFilePickOpenEntry, IInputOptions, IQuickOpenService, IPickOptions, IShowOptions, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen';
import { IPickOpenEntry, IFilePickOpenEntry, IQuickOpenService, IShowOptions, IPickOpenItem, IStringPickOptions, ITypedPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -56,6 +53,9 @@ import { Schemas } from 'vs/base/common/network';
import Severity from 'vs/base/common/severity';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Dimension, addClass } from 'vs/base/browser/dom';
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
const HELP_PREFIX = '?';
@@ -72,37 +72,38 @@ interface IInternalPickOptions {
ignoreFocusLost?: boolean;
quickNavigateConfiguration?: IQuickNavigateConfiguration;
onDidType?: (value: string) => any;
onDidFocus?: (item: any) => void;
}
export class QuickOpenController extends Component implements IQuickOpenService {
private static readonly MAX_SHORT_RESPONSE_TIME = 500;
public _serviceBrand: any;
private static readonly ID = 'workbench.component.quickopen';
private readonly _onShow: Emitter<void>;
private readonly _onHide: Emitter<void>;
_serviceBrand: any;
private readonly _onShow: Emitter<void> = this._register(new Emitter<void>());
get onShow(): Event<void> { return this._onShow.event; }
private readonly _onHide: Emitter<void> = this._register(new Emitter<void>());
get onHide(): Event<void> { return this._onHide.event; }
private quickOpenWidget: QuickOpenWidget;
private pickOpenWidget: QuickOpenWidget;
private layoutDimensions: Dimension;
private mapResolvedHandlersToPrefix: { [prefix: string]: TPromise<QuickOpenHandler>; };
private mapContextKeyToContext: { [id: string]: IContextKey<boolean>; };
private handlerOnOpenCalled: { [prefix: string]: boolean; };
private mapResolvedHandlersToPrefix: { [prefix: string]: TPromise<QuickOpenHandler>; } = Object.create(null);
private mapContextKeyToContext: { [id: string]: IContextKey<boolean>; } = Object.create(null);
private handlerOnOpenCalled: { [prefix: string]: boolean; } = Object.create(null);
private currentResultToken: string;
private currentPickerToken: string;
private inQuickOpenMode: IContextKey<boolean>;
private promisesToCompleteOnHide: ValueCallback[];
private promisesToCompleteOnHide: ValueCallback[] = [];
private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor;
private actionProvider = new ContributableActionProvider();
private visibilityChangeTimeoutHandle: number;
private closeOnFocusLost: boolean;
private editorHistoryHandler: EditorHistoryHandler;
constructor(
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
@INotificationService private notificationService: INotificationService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IConfigurationService private configurationService: IConfigurationService,
@@ -113,28 +114,17 @@ export class QuickOpenController extends Component implements IQuickOpenService
) {
super(QuickOpenController.ID, themeService);
this.mapResolvedHandlersToPrefix = {};
this.handlerOnOpenCalled = {};
this.mapContextKeyToContext = {};
this.promisesToCompleteOnHide = [];
this.editorHistoryHandler = this.instantiationService.createInstance(EditorHistoryHandler);
this.inQuickOpenMode = new RawContextKey<boolean>('inQuickOpen', false).bindTo(contextKeyService);
this._onShow = new Emitter<void>();
this._onHide = new Emitter<void>();
this.updateConfiguration();
this.registerListeners();
}
private registerListeners(): void {
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()));
this.toUnbind.push(this.partService.onTitleBarVisibilityChange(() => this.positionQuickOpenWidget()));
this.toUnbind.push(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget()));
this._register(this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()));
this._register(this.partService.onTitleBarVisibilityChange(() => this.positionQuickOpenWidget()));
this._register(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget()));
}
private updateConfiguration(): void {
@@ -145,15 +135,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
}
}
public get onShow(): Event<void> {
return this._onShow.event;
}
public get onHide(): Event<void> {
return this._onHide.event;
}
public navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void {
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void {
if (this.quickOpenWidget) {
this.quickOpenWidget.navigate(next, quickNavigate);
}
@@ -163,91 +145,11 @@ export class QuickOpenController extends Component implements IQuickOpenService
}
}
public input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): TPromise<string> {
if (this.pickOpenWidget && this.pickOpenWidget.isVisible()) {
this.pickOpenWidget.hide(HideReason.CANCELED);
}
const defaultMessage = options.prompt
? nls.localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", options.prompt)
: nls.localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
let currentPick = defaultMessage;
let currentValidation: TPromise<boolean>;
let currentDecoration: Severity;
let lastValue: string;
const init = (resolve: (value: IPickOpenEntry | TPromise<IPickOpenEntry>) => any, reject: (value: any) => any) => {
// open quick pick with just one choice. we will recurse whenever
// the validation/success message changes
this.doPick(TPromise.as([{ label: currentPick, tooltip: currentPick /* make sure message/validation can be read through the hover */ }]), {
ignoreFocusLost: options.ignoreFocusLost,
autoFocus: { autoFocusFirstEntry: true },
password: options.password,
placeHolder: options.placeHolder,
value: lastValue === void 0 ? options.value : lastValue,
valueSelection: options.valueSelection,
inputDecoration: currentDecoration,
onDidType: (value) => {
if (lastValue !== value) {
lastValue = value;
if (options.validateInput) {
if (currentValidation) {
currentValidation.cancel();
}
currentValidation = TPromise.timeout(100).then(() => {
return options.validateInput(value).then(message => {
currentDecoration = !!message ? Severity.Error : void 0;
const newPick = message || defaultMessage;
if (newPick !== currentPick) {
options.valueSelection = null;
currentPick = newPick;
resolve(new TPromise<any>(init));
}
return !message;
});
}, err => {
// ignore
return null;
});
}
}
}
}, token).then(resolve, reject);
};
return new TPromise(init).then(item => {
if (!currentValidation) {
if (options.validateInput) {
currentValidation = options
.validateInput(lastValue === void 0 ? options.value : lastValue)
.then(message => !message);
} else {
currentValidation = TPromise.as(true);
}
}
return currentValidation.then(valid => {
if (valid && item) {
return lastValue === void 0 ? (options.value || '') : lastValue;
}
return void 0;
});
});
}
public pick(picks: TPromise<string[]>, options?: IPickOptions, token?: CancellationToken): TPromise<string>;
public pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions, token?: CancellationToken): TPromise<string>;
public pick(picks: string[], options?: IPickOptions, token?: CancellationToken): TPromise<string>;
public pick<T extends IPickOpenEntry>(picks: T[], options?: IPickOptions, token?: CancellationToken): TPromise<T>;
public pick(arg1: string[] | TPromise<string[]> | IPickOpenEntry[] | TPromise<IPickOpenEntry[]>, options?: IPickOptions, token?: CancellationToken): TPromise<string | IPickOpenEntry> {
pick(picks: TPromise<string[]>, options?: IStringPickOptions, token?: CancellationToken): TPromise<string>;
pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: ITypedPickOptions<T>, token?: CancellationToken): TPromise<string>;
pick(picks: string[], options?: IStringPickOptions, token?: CancellationToken): TPromise<string>;
pick<T extends IPickOpenEntry>(picks: T[], options?: ITypedPickOptions<T>, token?: CancellationToken): TPromise<T>;
pick(arg1: string[] | TPromise<string[]> | IPickOpenEntry[] | TPromise<IPickOpenEntry[]>, options?: IStringPickOptions | ITypedPickOptions<IPickOpenEntry>, token?: CancellationToken): TPromise<string | IPickOpenEntry> {
if (!options) {
options = Object.create(null);
}
@@ -278,13 +180,13 @@ export class QuickOpenController extends Component implements IQuickOpenService
this.pickOpenWidget.hide(HideReason.CANCELED);
}
return new TPromise<string | IPickOpenEntry>((resolve, reject, progress) => {
return new TPromise<string | IPickOpenEntry>((resolve, reject) => {
function onItem(item: IPickOpenEntry): string | IPickOpenEntry {
return item && isAboutStrings ? item.label : item;
}
this.doPick(entryPromise, options, token).then(item => resolve(onItem(item)), err => reject(err), item => progress(onItem(item)));
this.doPick(entryPromise, options, token).then(item => resolve(onItem(item)), err => reject(err));
});
}
@@ -300,7 +202,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
// Create upon first open
if (!this.pickOpenWidget) {
this.pickOpenWidget = new QuickOpenWidget(
this.pickOpenWidget = this._register(new QuickOpenWidget(
document.getElementById(this.partService.getWorkbenchElementId()),
{
onOk: () => { /* ignore, handle later */ },
@@ -313,8 +215,8 @@ export class QuickOpenController extends Component implements IQuickOpenService
keyboardSupport: false,
treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts)
}
);
this.toUnbind.push(attachQuickOpenStyler(this.pickOpenWidget, this.themeService, { background: SIDE_BAR_BACKGROUND, foreground: SIDE_BAR_FOREGROUND }));
));
this._register(attachQuickOpenStyler(this.pickOpenWidget, this.themeService, { background: SIDE_BAR_BACKGROUND, foreground: SIDE_BAR_FOREGROUND }));
const pickOpenContainer = this.pickOpenWidget.create();
addClass(pickOpenContainer, 'show-file-icons');
@@ -346,7 +248,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
this.pickOpenWidget.layout(this.layoutDimensions);
}
return new TPromise<IPickOpenEntry>((complete, error, progress) => {
return new TPromise<IPickOpenEntry>((complete, error) => {
// Detect cancellation while pick promise is loading
this.pickOpenWidget.setCallbacks({
@@ -377,7 +279,14 @@ export class QuickOpenController extends Component implements IQuickOpenService
// Model
const model = new QuickOpenModel([], new PickOpenActionProvider());
const entries = picks.map((e, index) => this.instantiationService.createInstance(PickOpenEntry, e, index, () => progress(e), () => this.pickOpenWidget.refresh()));
const entries = picks.map((e, index) => {
const onPreview = () => {
if (options.onDidFocus) {
options.onDidFocus(e);
}
};
return this.instantiationService.createInstance(PickOpenEntry, e, index, onPreview, () => this.pickOpenWidget.refresh());
});
if (picks.length === 0) {
entries.push(this.instantiationService.createInstance(PickOpenEntry, { label: nls.localize('emptyPicks', "There are no entries to pick from") }, 0, null, null));
}
@@ -497,7 +406,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
});
}
public accept(): void {
accept(): void {
[this.quickOpenWidget, this.pickOpenWidget].forEach(w => {
if (w && w.isVisible()) {
w.accept();
@@ -505,7 +414,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
});
}
public focus(): void {
focus(): void {
[this.quickOpenWidget, this.pickOpenWidget].forEach(w => {
if (w && w.isVisible()) {
w.focus();
@@ -513,7 +422,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
});
}
public close(): void {
close(): void {
[this.quickOpenWidget, this.pickOpenWidget].forEach(w => {
if (w && w.isVisible()) {
w.hide(HideReason.CANCELED);
@@ -522,22 +431,14 @@ export class QuickOpenController extends Component implements IQuickOpenService
}
private emitQuickOpenVisibilityChange(isVisible: boolean): void {
if (this.visibilityChangeTimeoutHandle) {
window.clearTimeout(this.visibilityChangeTimeoutHandle);
if (isVisible) {
this._onShow.fire();
} else {
this._onHide.fire();
}
this.visibilityChangeTimeoutHandle = setTimeout(() => {
if (isVisible) {
this._onShow.fire();
} else {
this._onHide.fire();
}
this.visibilityChangeTimeoutHandle = void 0;
}, 100 /* to prevent flashing, we accumulate visibility changes over a timeout of 100ms */);
}
public show(prefix?: string, options?: IShowOptions): TPromise<void> {
show(prefix?: string, options?: IShowOptions): TPromise<void> {
let quickNavigateConfiguration = options ? options.quickNavigateConfiguration : void 0;
let inputSelection = options ? options.inputSelection : void 0;
let autoFocus = options ? options.autoFocus : void 0;
@@ -555,7 +456,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
// Create upon first open
if (!this.quickOpenWidget) {
this.quickOpenWidget = new QuickOpenWidget(
this.quickOpenWidget = this._register(new QuickOpenWidget(
document.getElementById(this.partService.getWorkbenchElementId()),
{
onOk: () => { /* ignore */ },
@@ -569,8 +470,8 @@ export class QuickOpenController extends Component implements IQuickOpenService
keyboardSupport: false,
treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts)
}
);
this.toUnbind.push(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: SIDE_BAR_BACKGROUND, foreground: SIDE_BAR_FOREGROUND }));
));
this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: SIDE_BAR_BACKGROUND, foreground: SIDE_BAR_FOREGROUND }));
const quickOpenContainer = this.quickOpenWidget.create();
addClass(quickOpenContainer, 'show-file-icons');
@@ -597,8 +498,8 @@ export class QuickOpenController extends Component implements IQuickOpenService
if (!quickNavigateConfiguration) {
autoFocus = { autoFocusFirstEntry: true };
} else {
const visibleEditorCount = this.editorService.getVisibleEditors().length;
autoFocus = { autoFocusFirstEntry: visibleEditorCount === 0, autoFocusSecondEntry: visibleEditorCount !== 0 };
const autoFocusFirstEntry = this.editorGroupService.activeGroup.count === 0;
autoFocus = { autoFocusFirstEntry, autoFocusSecondEntry: !autoFocusFirstEntry };
}
}
@@ -637,7 +538,6 @@ export class QuickOpenController extends Component implements IQuickOpenService
this.pickOpenWidget.hide(HideReason.FOCUS_LOST);
}
this.inQuickOpenMode.set(true);
this.emitQuickOpenVisibilityChange(true);
}
@@ -649,14 +549,12 @@ export class QuickOpenController extends Component implements IQuickOpenService
// Pass to handlers
for (let prefix in this.mapResolvedHandlersToPrefix) {
if (this.mapResolvedHandlersToPrefix.hasOwnProperty(prefix)) {
const promise = this.mapResolvedHandlersToPrefix[prefix];
promise.then(handler => {
this.handlerOnOpenCalled[prefix] = false;
const promise = this.mapResolvedHandlersToPrefix[prefix];
promise.then(handler => {
this.handlerOnOpenCalled[prefix] = false;
handler.onClose(reason === HideReason.CANCELED); // Don't check if onOpen was called to preserve old behaviour for now
});
}
handler.onClose(reason === HideReason.CANCELED); // Don't check if onOpen was called to preserve old behaviour for now
});
}
// Complete promises that are waiting
@@ -666,11 +564,10 @@ export class QuickOpenController extends Component implements IQuickOpenService
}
if (reason !== HideReason.FOCUS_LOST) {
this.restoreFocus(); // focus back to editor unless user clicked somewhere else
this.editorGroupService.activeGroup.focus(); // focus back to editor group unless user clicked somewhere else
}
// Reset context keys
this.inQuickOpenMode.reset();
this.resetQuickOpenContextKeys();
// Events
@@ -717,15 +614,6 @@ export class QuickOpenController extends Component implements IQuickOpenService
return new QuickOpenModel(entries, this.actionProvider);
}
private restoreFocus(): void {
// Try to focus active editor
const editor = this.editorService.getActiveEditor();
if (editor) {
editor.focus();
}
}
private onType(value: string): void {
// look for a handler
@@ -981,7 +869,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
return this.mapResolvedHandlersToPrefix[id] = TPromise.as(handler.instantiate(this.instantiationService));
}
public layout(dimension: Dimension): void {
layout(dimension: Dimension): void {
this.layoutDimensions = dimension;
if (this.quickOpenWidget) {
this.quickOpenWidget.layout(this.layoutDimensions);
@@ -991,18 +879,6 @@ export class QuickOpenController extends Component implements IQuickOpenService
this.pickOpenWidget.layout(this.layoutDimensions);
}
}
public dispose(): void {
if (this.quickOpenWidget) {
this.quickOpenWidget.dispose();
}
if (this.pickOpenWidget) {
this.pickOpenWidget.dispose();
}
super.dispose();
}
}
class PlaceholderQuickOpenEntry extends QuickOpenEntryGroup {
@@ -1014,7 +890,7 @@ class PlaceholderQuickOpenEntry extends QuickOpenEntryGroup {
this.placeHolderLabel = placeHolderLabel;
}
public getLabel(): string {
getLabel(): string {
return this.placeHolderLabel;
}
}
@@ -1063,7 +939,7 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
this.fileKind = fileItem.fileKind;
}
public matchesFuzzy(query: string, options: IInternalPickOptions): { labelHighlights: IMatch[], descriptionHighlights: IMatch[], detailHighlights: IMatch[] } {
matchesFuzzy(query: string, options: IInternalPickOptions): { labelHighlights: IMatch[], descriptionHighlights: IMatch[], detailHighlights: IMatch[] } {
if (!this.labelOcticons) {
this.labelOcticons = parseOcticons(this.getLabel()); // parse on demand
}
@@ -1080,72 +956,72 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
};
}
public getPayload(): any {
getPayload(): any {
return this.payload;
}
public remove(): void {
remove(): void {
super.setHidden(true);
this.removed = true;
this.onRemove();
}
public isHidden(): boolean {
isHidden(): boolean {
return this.removed || super.isHidden();
}
public get action(): IAction {
get action(): IAction {
return this._action;
}
public get index(): number {
get index(): number {
return this._index;
}
public getLabelOptions(): IIconLabelValueOptions {
getLabelOptions(): IIconLabelValueOptions {
return {
extraClasses: this.resource ? getIconClasses(this.modelService, this.modeService, this.resource, this.fileKind) : []
};
}
public get shouldRunWithContext(): IEntryRunContext {
get shouldRunWithContext(): IEntryRunContext {
return this._shouldRunWithContext;
}
public getDescription(): string {
getDescription(): string {
return this.description;
}
public getDetail(): string {
getDetail(): string {
return this.detail;
}
public getTooltip(): string {
getTooltip(): string {
return this.tooltip;
}
public getDescriptionTooltip(): string {
getDescriptionTooltip(): string {
return this.descriptionTooltip;
}
public showBorder(): boolean {
showBorder(): boolean {
return this.hasSeparator;
}
public getGroupLabel(): string {
getGroupLabel(): string {
return this.separatorLabel;
}
public shouldAlwaysShow(): boolean {
shouldAlwaysShow(): boolean {
return this.alwaysShow;
}
public getResource(): URI {
getResource(): URI {
return this.resource;
}
public run(mode: Mode, context: IEntryRunContext): boolean {
run(mode: Mode, context: IEntryRunContext): boolean {
if (mode === Mode.OPEN) {
this._shouldRunWithContext = context;
@@ -1161,23 +1037,23 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
}
class PickOpenActionProvider implements IActionProvider {
public hasActions(tree: ITree, element: PickOpenEntry): boolean {
hasActions(tree: ITree, element: PickOpenEntry): boolean {
return !!element.action;
}
public getActions(tree: ITree, element: PickOpenEntry): TPromise<IAction[]> {
getActions(tree: ITree, element: PickOpenEntry): TPromise<IAction[]> {
return TPromise.as(element.action ? [element.action] : []);
}
public hasSecondaryActions(tree: ITree, element: PickOpenEntry): boolean {
hasSecondaryActions(tree: ITree, element: PickOpenEntry): boolean {
return false;
}
public getSecondaryActions(tree: ITree, element: PickOpenEntry): TPromise<IAction[]> {
getSecondaryActions(tree: ITree, element: PickOpenEntry): TPromise<IAction[]> {
return TPromise.as([]);
}
public getActionItem(tree: ITree, element: PickOpenEntry, action: Action): BaseActionItem {
getActionItem(tree: ITree, element: PickOpenEntry, action: Action): BaseActionItem {
return null;
}
}
@@ -1193,7 +1069,7 @@ class EditorHistoryHandler {
this.scorerCache = Object.create(null);
}
public getResults(searchValue?: string): QuickOpenEntry[] {
getResults(searchValue?: string): QuickOpenEntry[] {
// Massage search for scoring
const query = prepareQuery(searchValue);
@@ -1248,7 +1124,7 @@ class EditorHistoryItemAccessorClass extends QuickOpenItemAccessorClass {
super();
}
public getItemDescription(entry: QuickOpenEntry): string {
getItemDescription(entry: QuickOpenEntry): string {
return this.allowMatchOnDescription ? entry.getDescription() : void 0;
}
}
@@ -1269,13 +1145,12 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
constructor(
input: IEditorInput | IResourceInput,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IEditorService editorService: IEditorService,
@IModeService private modeService: IModeService,
@IModelService private modelService: IModelService,
@ITextFileService private textFileService: ITextFileService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IConfigurationService private configurationService: IConfigurationService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUriDisplayService uriDisplayService: IUriDisplayService,
@IFileService fileService: IFileService
) {
super(editorService);
@@ -1290,8 +1165,8 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
} else {
const resourceInput = input as IResourceInput;
this.resource = resourceInput.resource;
this.label = labels.getBaseLabel(resourceInput.resource);
this.description = labels.getPathLabel(resources.dirname(this.resource), contextService, environmentService);
this.label = resources.basenameOrAuthority(resourceInput.resource);
this.description = uriDisplayService.getLabel(resources.dirname(this.resource), true);
this.dirty = this.resource && this.textFileService.isDirty(this.resource);
if (this.dirty && this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
@@ -1300,45 +1175,45 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
}
}
public getIcon(): string {
getIcon(): string {
return this.dirty ? 'dirty' : '';
}
public getLabel(): string {
getLabel(): string {
return this.label;
}
public getLabelOptions(): IIconLabelValueOptions {
getLabelOptions(): IIconLabelValueOptions {
return {
extraClasses: getIconClasses(this.modelService, this.modeService, this.resource)
};
}
public getAriaLabel(): string {
getAriaLabel(): string {
return nls.localize('entryAriaLabel', "{0}, recently opened", this.getLabel());
}
public getDescription(): string {
getDescription(): string {
return this.description;
}
public getResource(): URI {
getResource(): URI {
return this.resource;
}
public getInput(): IEditorInput | IResourceInput {
getInput(): IEditorInput | IResourceInput {
return this.input;
}
public run(mode: Mode, context: IEntryRunContext): boolean {
run(mode: Mode, context: IEntryRunContext): boolean {
if (mode === Mode.OPEN) {
const sideBySide = !context.quickNavigateConfiguration && (context.keymods.alt || context.keymods.ctrlCmd);
const pinned = !this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor.enablePreviewFromQuickOpen || context.keymods.alt;
if (this.input instanceof EditorInput) {
this.editorService.openEditor(this.input, { pinned }, sideBySide).done(null, errors.onUnexpectedError);
this.editorService.openEditor(this.input, { pinned }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
} else {
this.editorService.openEditor({ resource: (this.input as IResourceInput).resource, options: { pinned } }, sideBySide);
this.editorService.openEditor({ resource: (this.input as IResourceInput).resource, options: { pinned } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}
return true;
@@ -1362,8 +1237,8 @@ function resourceForEditorHistory(input: EditorInput, fileService: IFileService)
export class RemoveFromEditorHistoryAction extends Action {
public static readonly ID = 'workbench.action.removeFromEditorHistory';
public static readonly LABEL = nls.localize('removeFromEditorHistory', "Remove From History");
static readonly ID = 'workbench.action.removeFromEditorHistory';
static readonly LABEL = nls.localize('removeFromEditorHistory', "Remove From History");
constructor(
id: string,
@@ -1375,7 +1250,7 @@ export class RemoveFromEditorHistoryAction extends Action {
super(id, label);
}
public run(): TPromise<any> {
run(): TPromise<any> {
interface IHistoryPickEntry extends IFilePickOpenEntry {
input: IEditorInput | IResourceInput;
}

View File

@@ -9,14 +9,14 @@ 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/actions';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeybindingsRegistry, KeybindingWeight } 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';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.closeQuickOpen',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickOpenContext,
primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape],
handler: accessor => {
@@ -29,7 +29,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.acceptSelectedQuickOpenItem',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickOpenContext,
primary: null,
handler: accessor => {
@@ -42,7 +42,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.focusQuickOpen',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickOpenContext,
primary: null,
handler: accessor => {
@@ -59,7 +59,7 @@ const globalQuickOpenKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, sec
KeybindingsRegistry.registerKeybindingRule({
id: QUICKOPEN_ACTION_ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: globalQuickOpenKeybinding.primary,
secondary: globalQuickOpenKeybinding.secondary,
@@ -70,8 +70,8 @@ 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(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: null, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.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, KeybindingWeight.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');
@@ -79,7 +79,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveFromEditorHistor
const quickOpenNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickOpenNavigateNextInFilePickerId,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickOpenNavigateNextInFilePickerId, true),
when: defaultQuickOpenContext,
primary: globalQuickOpenKeybinding.primary,
@@ -90,7 +90,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const quickOpenNavigatePreviousInFilePickerId = 'workbench.action.quickOpenNavigatePreviousInFilePicker';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: quickOpenNavigatePreviousInFilePickerId,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInFilePickerId, false),
when: defaultQuickOpenContext,
primary: globalQuickOpenKeybinding.primary | KeyMod.Shift,

View File

@@ -9,6 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
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';
@@ -46,16 +47,18 @@ export class BaseQuickOpenNavigateAction extends Action {
private next: boolean,
private quickNavigate: boolean,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IQuickInputService private quickInputService: IQuickInputService,
@IKeybindingService private keybindingService: IKeybindingService
) {
super(id, label);
}
public run(event?: any): TPromise<any> {
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);
this.quickInputService.navigate(this.next, quickNavigate);
return TPromise.as(true);
}
@@ -65,70 +68,76 @@ export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHan
return accessor => {
const keybindingService = accessor.get(IKeybindingService);
const quickOpenService = accessor.get(IQuickOpenService);
const quickInputService = accessor.get(IQuickInputService);
const keys = keybindingService.lookupKeybindings(id);
const quickNavigate = { keybindings: keys };
quickOpenService.navigate(next, quickNavigate);
quickInputService.navigate(next, quickNavigate);
};
}
export class QuickOpenNavigateNextAction extends BaseQuickOpenNavigateAction {
public static readonly ID = 'workbench.action.quickOpenNavigateNext';
public static readonly LABEL = nls.localize('quickNavigateNext', "Navigate Next in Quick Open");
static readonly ID = 'workbench.action.quickOpenNavigateNext';
static readonly LABEL = nls.localize('quickNavigateNext', "Navigate Next in Quick Open");
constructor(
id: string,
label: string,
@IQuickOpenService quickOpenService: IQuickOpenService,
@IQuickInputService quickInputService: IQuickInputService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(id, label, true, true, quickOpenService, keybindingService);
super(id, label, true, true, quickOpenService, quickInputService, keybindingService);
}
}
export class QuickOpenNavigatePreviousAction extends BaseQuickOpenNavigateAction {
public static readonly ID = 'workbench.action.quickOpenNavigatePrevious';
public static readonly LABEL = nls.localize('quickNavigatePrevious', "Navigate Previous in Quick Open");
static readonly ID = 'workbench.action.quickOpenNavigatePrevious';
static readonly LABEL = nls.localize('quickNavigatePrevious', "Navigate Previous in Quick Open");
constructor(
id: string,
label: string,
@IQuickOpenService quickOpenService: IQuickOpenService,
@IQuickInputService quickInputService: IQuickInputService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(id, label, false, true, quickOpenService, keybindingService);
super(id, label, false, true, quickOpenService, quickInputService, keybindingService);
}
}
export class QuickOpenSelectNextAction extends BaseQuickOpenNavigateAction {
public static readonly ID = 'workbench.action.quickOpenSelectNext';
public static readonly LABEL = nls.localize('quickSelectNext', "Select Next in Quick Open");
static readonly ID = 'workbench.action.quickOpenSelectNext';
static readonly LABEL = nls.localize('quickSelectNext', "Select Next in Quick Open");
constructor(
id: string,
label: string,
@IQuickOpenService quickOpenService: IQuickOpenService,
@IQuickInputService quickInputService: IQuickInputService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(id, label, true, false, quickOpenService, keybindingService);
super(id, label, true, false, quickOpenService, quickInputService, keybindingService);
}
}
export class QuickOpenSelectPreviousAction extends BaseQuickOpenNavigateAction {
public static readonly ID = 'workbench.action.quickOpenSelectPrevious';
public static readonly LABEL = nls.localize('quickSelectPrevious', "Select Previous in Quick Open");
static readonly ID = 'workbench.action.quickOpenSelectPrevious';
static readonly LABEL = nls.localize('quickSelectPrevious', "Select Previous in Quick Open");
constructor(
id: string,
label: string,
@IQuickOpenService quickOpenService: IQuickOpenService,
@IQuickInputService quickInputService: IQuickInputService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(id, label, false, false, quickOpenService, keybindingService);
super(id, label, false, false, quickOpenService, quickInputService, keybindingService);
}
}

View File

@@ -12,7 +12,7 @@
visibility: hidden !important;
}
.monaco-workbench > .sidebar > .title > .title-label span {
.monaco-workbench > .sidebar > .title > .title-label h2 {
text-transform: uppercase;
}

View File

@@ -32,9 +32,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
export class SidebarPart extends CompositePart<Viewlet> {
public static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid';
public _serviceBrand: any;
static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid';
private blockOpeningViewlet: boolean;
@@ -69,22 +67,22 @@ export class SidebarPart extends CompositePart<Viewlet> {
);
}
public get onDidViewletOpen(): Event<IViewlet> {
get onDidViewletOpen(): Event<IViewlet> {
return this._onDidCompositeOpen.event as Event<IViewlet>;
}
public get onDidViewletClose(): Event<IViewlet> {
get onDidViewletClose(): Event<IViewlet> {
return this._onDidCompositeClose.event as Event<IViewlet>;
}
public createTitleArea(parent: HTMLElement): HTMLElement {
createTitleArea(parent: HTMLElement): HTMLElement {
const titleArea = super.createTitleArea(parent);
$(titleArea).on(EventType.CONTEXT_MENU, (e: MouseEvent) => this.onTitleAreaContextMenu(new StandardMouseEvent(e)));
$(titleArea).on(EventType.CONTEXT_MENU, (e: MouseEvent) => this.onTitleAreaContextMenu(new StandardMouseEvent(e)), this.toDispose);
return titleArea;
}
public updateStyles(): void {
updateStyles(): void {
super.updateStyles();
// Part container
@@ -103,7 +101,7 @@ export class SidebarPart extends CompositePart<Viewlet> {
container.style('border-left-color', !isPositionLeft ? borderColor : null);
}
public openViewlet(id: string, focus?: boolean): TPromise<Viewlet> {
openViewlet(id: string, focus?: boolean): TPromise<Viewlet> {
if (this.blockOpeningViewlet) {
return TPromise.as(null); // Workaround against a potential race condition
}
@@ -122,19 +120,19 @@ export class SidebarPart extends CompositePart<Viewlet> {
return promise.then(() => this.openComposite(id, focus)) as TPromise<Viewlet>;
}
public getActiveViewlet(): IViewlet {
getActiveViewlet(): IViewlet {
return <IViewlet>this.getActiveComposite();
}
public getLastActiveViewletId(): string {
getLastActiveViewletId(): string {
return this.getLastActiveCompositetId();
}
public hideActiveViewlet(): TPromise<void> {
hideActiveViewlet(): TPromise<void> {
return this.hideActiveComposite().then(composite => void 0);
}
public layout(dimension: Dimension): Dimension[] {
layout(dimension: Dimension): Dimension[] {
if (!this.partService.isVisible(Parts.SIDEBAR_PART)) {
return [dimension];
}
@@ -162,8 +160,8 @@ export class SidebarPart extends CompositePart<Viewlet> {
class FocusSideBarAction extends Action {
public static readonly ID = 'workbench.action.focusSideBar';
public static readonly LABEL = nls.localize('focusSideBar', "Focus into Side Bar");
static readonly ID = 'workbench.action.focusSideBar';
static readonly LABEL = nls.localize('focusSideBar', "Focus into Side Bar");
constructor(
id: string,
@@ -174,7 +172,7 @@ class FocusSideBarAction extends Action {
super(id, label);
}
public run(): TPromise<any> {
run(): TPromise<any> {
// Show side bar
if (!this.partService.isVisible(Parts.SIDEBAR_PART)) {

View File

@@ -5,6 +5,7 @@
.monaco-workbench > .part.statusbar {
box-sizing: border-box;
cursor: default;
width: 100%;
height: 22px;
font-size: 12px;
@@ -52,7 +53,6 @@
}
.monaco-workbench > .part.statusbar > .statusbar-entry > span {
cursor: default;
height: 100%;
}

Some files were not shown because too many files have changed in this diff Show More