Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TextEditorOptions } from 'vs/workbench/common/editor';
|
||||
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
export class CodeEditorService extends CodeEditorServiceImpl {
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(themeService);
|
||||
}
|
||||
|
||||
getActiveCodeEditor(): ICodeEditor | null {
|
||||
const activeTextEditorControl = this.editorService.activeTextEditorControl;
|
||||
if (isCodeEditor(activeTextEditorControl)) {
|
||||
return activeTextEditorControl;
|
||||
}
|
||||
|
||||
if (isDiffEditor(activeTextEditorControl)) {
|
||||
return activeTextEditorControl.getModifiedEditor();
|
||||
}
|
||||
|
||||
const activeControl = this.editorService.activeEditorPane?.getControl();
|
||||
if (isCompositeEditor(activeControl) && isCodeEditor(activeControl.activeCodeEditor)) {
|
||||
return activeControl.activeCodeEditor;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
|
||||
// Special case: If the active editor is a diff editor and the request to open originates and
|
||||
// targets the modified side of it, we just apply the request there to prevent opening the modified
|
||||
// side as separate editor.
|
||||
const activeTextEditorControl = this.editorService.activeTextEditorControl;
|
||||
if (
|
||||
!sideBySide && // we need the current active group to be the taret
|
||||
isDiffEditor(activeTextEditorControl) && // we only support this for active text diff editors
|
||||
input.options && // we need options to apply
|
||||
input.resource && // we need a request resource to compare with
|
||||
activeTextEditorControl.getModel() && // we need a target model to compare with
|
||||
source === activeTextEditorControl.getModifiedEditor() && // we need the source of this request to be the modified side of the diff editor
|
||||
isEqual(input.resource, activeTextEditorControl.getModel()!.modified.uri) // we need the input resources to match with modified side
|
||||
) {
|
||||
const targetEditor = activeTextEditorControl.getModifiedEditor();
|
||||
|
||||
const textOptions = TextEditorOptions.create(input.options);
|
||||
textOptions.apply(targetEditor, ScrollType.Smooth);
|
||||
|
||||
return targetEditor;
|
||||
}
|
||||
|
||||
// Open using our normal editor service
|
||||
return this.doOpenCodeEditor(input, source, sideBySide);
|
||||
}
|
||||
|
||||
private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
const control = await this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
|
||||
if (control) {
|
||||
const widget = control.getControl();
|
||||
if (isCodeEditor(widget)) {
|
||||
return widget;
|
||||
}
|
||||
if (isCompositeEditor(widget) && isCodeEditor(widget.activeCodeEditor)) {
|
||||
return widget.activeCodeEditor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ICodeEditorService, CodeEditorService, true);
|
||||
@@ -0,0 +1,20 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IEditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget';
|
||||
|
||||
export const IEditorDropService = createDecorator<IEditorDropService>('editorDropService');
|
||||
|
||||
export interface IEditorDropService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Allows to register a drag and drop target for editors.
|
||||
*/
|
||||
createEditorDropTarget(container: HTMLElement, delegate: IEditorDropTargetDelegate): IDisposable;
|
||||
}
|
||||
1353
lib/vscode/src/vs/workbench/services/editor/browser/editorService.ts
Normal file
1353
lib/vscode/src/vs/workbench/services/editor/browser/editorService.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,597 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDimension } from 'vs/editor/common/editorCommon';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export const IEditorGroupsService = createDecorator<IEditorGroupsService>('editorGroupsService');
|
||||
|
||||
export const enum GroupDirection {
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
export function preferredSideBySideGroupDirection(configurationService: IConfigurationService): GroupDirection.DOWN | GroupDirection.RIGHT {
|
||||
const openSideBySideDirection = configurationService.getValue<'right' | 'down'>('workbench.editor.openSideBySideDirection');
|
||||
|
||||
if (openSideBySideDirection === 'down') {
|
||||
return GroupDirection.DOWN;
|
||||
}
|
||||
|
||||
return GroupDirection.RIGHT;
|
||||
}
|
||||
|
||||
export const enum GroupOrientation {
|
||||
HORIZONTAL,
|
||||
VERTICAL
|
||||
}
|
||||
|
||||
export const enum GroupLocation {
|
||||
FIRST,
|
||||
LAST,
|
||||
NEXT,
|
||||
PREVIOUS
|
||||
}
|
||||
|
||||
export interface IFindGroupScope {
|
||||
direction?: GroupDirection;
|
||||
location?: GroupLocation;
|
||||
}
|
||||
|
||||
export const enum GroupsArrangement {
|
||||
|
||||
/**
|
||||
* Make the current active group consume the maximum
|
||||
* amount of space possible.
|
||||
*/
|
||||
MINIMIZE_OTHERS,
|
||||
|
||||
/**
|
||||
* Size all groups evenly.
|
||||
*/
|
||||
EVEN,
|
||||
|
||||
/**
|
||||
* Will behave like MINIMIZE_OTHERS if the active
|
||||
* group is not already maximized and EVEN otherwise
|
||||
*/
|
||||
TOGGLE
|
||||
}
|
||||
|
||||
export interface GroupLayoutArgument {
|
||||
size?: number;
|
||||
groups?: GroupLayoutArgument[];
|
||||
}
|
||||
|
||||
export interface EditorGroupLayout {
|
||||
orientation: GroupOrientation;
|
||||
groups: GroupLayoutArgument[];
|
||||
}
|
||||
|
||||
export interface IMoveEditorOptions {
|
||||
index?: number;
|
||||
inactive?: boolean;
|
||||
preserveFocus?: boolean;
|
||||
}
|
||||
|
||||
export interface ICopyEditorOptions extends IMoveEditorOptions { }
|
||||
|
||||
export interface IAddGroupOptions {
|
||||
activate?: boolean;
|
||||
}
|
||||
|
||||
export const enum MergeGroupMode {
|
||||
COPY_EDITORS,
|
||||
MOVE_EDITORS
|
||||
}
|
||||
|
||||
export interface IMergeGroupOptions {
|
||||
mode?: MergeGroupMode;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface ICloseEditorOptions {
|
||||
preserveFocus?: boolean;
|
||||
}
|
||||
|
||||
export type ICloseEditorsFilter = {
|
||||
except?: IEditorInput,
|
||||
direction?: CloseDirection,
|
||||
savedOnly?: boolean,
|
||||
excludeSticky?: boolean
|
||||
};
|
||||
|
||||
export interface ICloseAllEditorsOptions {
|
||||
excludeSticky?: boolean;
|
||||
}
|
||||
|
||||
export interface IEditorReplacement {
|
||||
editor: IEditorInput;
|
||||
replacement: IEditorInput;
|
||||
options?: IEditorOptions | ITextEditorOptions;
|
||||
}
|
||||
|
||||
export const enum GroupsOrder {
|
||||
|
||||
/**
|
||||
* Groups sorted by creation order (oldest one first)
|
||||
*/
|
||||
CREATION_TIME,
|
||||
|
||||
/**
|
||||
* Groups sorted by most recent activity (most recent active first)
|
||||
*/
|
||||
MOST_RECENTLY_ACTIVE,
|
||||
|
||||
/**
|
||||
* Groups sorted by grid widget order
|
||||
*/
|
||||
GRID_APPEARANCE
|
||||
}
|
||||
|
||||
export interface IEditorGroupsService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* An event for when the active editor group changes. The active editor
|
||||
* group is the default location for new editors to open.
|
||||
*/
|
||||
readonly onDidActiveGroupChange: Event<IEditorGroup>;
|
||||
|
||||
/**
|
||||
* An event for when a new group was added.
|
||||
*/
|
||||
readonly onDidAddGroup: Event<IEditorGroup>;
|
||||
|
||||
/**
|
||||
* An event for when a group was removed.
|
||||
*/
|
||||
readonly onDidRemoveGroup: Event<IEditorGroup>;
|
||||
|
||||
/**
|
||||
* An event for when a group was moved.
|
||||
*/
|
||||
readonly onDidMoveGroup: Event<IEditorGroup>;
|
||||
|
||||
/**
|
||||
* An event for when a group gets activated.
|
||||
*/
|
||||
readonly onDidActivateGroup: Event<IEditorGroup>;
|
||||
|
||||
/**
|
||||
* An event for when the group container is layed out.
|
||||
*/
|
||||
readonly onDidLayout: Event<IDimension>;
|
||||
|
||||
/**
|
||||
* An event for when the index of a group changes.
|
||||
*/
|
||||
readonly onDidGroupIndexChange: Event<IEditorGroup>;
|
||||
|
||||
/**
|
||||
* The size of the editor groups area.
|
||||
*/
|
||||
readonly contentDimension: IDimension;
|
||||
|
||||
/**
|
||||
* An active group is the default location for new editors to open.
|
||||
*/
|
||||
readonly activeGroup: IEditorGroup;
|
||||
|
||||
/**
|
||||
* All groups that are currently visible in the editor area in the
|
||||
* order of their creation (oldest first).
|
||||
*/
|
||||
readonly groups: ReadonlyArray<IEditorGroup>;
|
||||
|
||||
/**
|
||||
* The number of editor groups that are currently opened.
|
||||
*/
|
||||
readonly count: number;
|
||||
|
||||
/**
|
||||
* The current layout orientation of the root group.
|
||||
*/
|
||||
readonly orientation: GroupOrientation;
|
||||
|
||||
/**
|
||||
* A promise that resolves when groups have been restored.
|
||||
*/
|
||||
readonly whenRestored: Promise<void>;
|
||||
|
||||
/**
|
||||
* Find out if the editor group service has editors to restore from a previous session.
|
||||
*/
|
||||
readonly willRestoreEditors: boolean;
|
||||
|
||||
/**
|
||||
* Get all groups that are currently visible in the editor area.
|
||||
*
|
||||
* @param order the order of the editors to use
|
||||
*/
|
||||
getGroups(order: GroupsOrder): ReadonlyArray<IEditorGroup>;
|
||||
|
||||
/**
|
||||
* Allows to convert a group identifier to a group.
|
||||
*/
|
||||
getGroup(identifier: GroupIdentifier): IEditorGroup | undefined;
|
||||
|
||||
/**
|
||||
* Set a group as active. An active group is the default location for new editors to open.
|
||||
*/
|
||||
activateGroup(group: IEditorGroup | GroupIdentifier): IEditorGroup;
|
||||
|
||||
/**
|
||||
* Returns the size of a group.
|
||||
*/
|
||||
getSize(group: IEditorGroup | GroupIdentifier): { width: number, height: number };
|
||||
|
||||
/**
|
||||
* Sets the size of a group.
|
||||
*/
|
||||
setSize(group: IEditorGroup | GroupIdentifier, size: { width: number, height: number }): void;
|
||||
|
||||
/**
|
||||
* Arrange all groups according to the provided arrangement.
|
||||
*/
|
||||
arrangeGroups(arrangement: GroupsArrangement): void;
|
||||
|
||||
/**
|
||||
* Applies the provided layout by either moving existing groups or creating new groups.
|
||||
*/
|
||||
applyLayout(layout: EditorGroupLayout): void;
|
||||
|
||||
/**
|
||||
* Enable or disable centered editor layout.
|
||||
*/
|
||||
centerLayout(active: boolean): void;
|
||||
|
||||
/**
|
||||
* Find out if the editor layout is currently centered.
|
||||
*/
|
||||
isLayoutCentered(): boolean;
|
||||
|
||||
/**
|
||||
* Sets the orientation of the root group to be either vertical or horizontal.
|
||||
*/
|
||||
setGroupOrientation(orientation: GroupOrientation): void;
|
||||
|
||||
/**
|
||||
* Find a groupd in a specific scope:
|
||||
* * `GroupLocation.FIRST`: the first group
|
||||
* * `GroupLocation.LAST`: the last group
|
||||
* * `GroupLocation.NEXT`: the next group from either the active one or `source`
|
||||
* * `GroupLocation.PREVIOUS`: the previous group from either the active one or `source`
|
||||
* * `GroupDirection.UP`: the next group above the active one or `source`
|
||||
* * `GroupDirection.DOWN`: the next group below the active one or `source`
|
||||
* * `GroupDirection.LEFT`: the next group to the left of the active one or `source`
|
||||
* * `GroupDirection.RIGHT`: the next group to the right of the active one or `source`
|
||||
*
|
||||
* @param scope the scope of the group to search in
|
||||
* @param source optional source to search from
|
||||
* @param wrap optionally wrap around if reaching the edge of groups
|
||||
*/
|
||||
findGroup(scope: IFindGroupScope, source?: IEditorGroup | GroupIdentifier, wrap?: boolean): IEditorGroup;
|
||||
|
||||
/**
|
||||
* Add a new group to the editor area. A new group is added by splitting a provided one in
|
||||
* one of the four directions.
|
||||
*
|
||||
* @param location the group from which to split to add a new group
|
||||
* @param direction the direction of where to split to
|
||||
* @param options configure the newly group with options
|
||||
*/
|
||||
addGroup(location: IEditorGroup | GroupIdentifier, direction: GroupDirection, options?: IAddGroupOptions): IEditorGroup;
|
||||
|
||||
/**
|
||||
* Remove a group from the editor area.
|
||||
*/
|
||||
removeGroup(group: IEditorGroup | GroupIdentifier): void;
|
||||
|
||||
/**
|
||||
* Move a group to a new group in the editor area.
|
||||
*
|
||||
* @param group the group to move
|
||||
* @param location the group from which to split to add the moved group
|
||||
* @param direction the direction of where to split to
|
||||
*/
|
||||
moveGroup(group: IEditorGroup | GroupIdentifier, location: IEditorGroup | GroupIdentifier, direction: GroupDirection): IEditorGroup;
|
||||
|
||||
/**
|
||||
* Merge the editors of a group into a target group. By default, all editors will
|
||||
* move and the source group will close. This behaviour can be configured via the
|
||||
* `IMergeGroupOptions` options.
|
||||
*
|
||||
* @param group the group to merge
|
||||
* @param target the target group to merge into
|
||||
* @param options controls how the merge should be performed. by default all editors
|
||||
* will be moved over to the target and the source group will close. Configure to
|
||||
* `MOVE_EDITORS_KEEP_GROUP` to prevent the source group from closing. Set to
|
||||
* `COPY_EDITORS` to copy the editors into the target instead of moding them.
|
||||
*/
|
||||
mergeGroup(group: IEditorGroup | GroupIdentifier, target: IEditorGroup | GroupIdentifier, options?: IMergeGroupOptions): IEditorGroup;
|
||||
|
||||
/**
|
||||
* Copy a group to a new group in the editor area.
|
||||
*
|
||||
* @param group the group to copy
|
||||
* @param location the group from which to split to add the copied group
|
||||
* @param direction the direction of where to split to
|
||||
*/
|
||||
copyGroup(group: IEditorGroup | GroupIdentifier, location: IEditorGroup | GroupIdentifier, direction: GroupDirection): IEditorGroup;
|
||||
|
||||
/**
|
||||
* Access the options of the editor part.
|
||||
*/
|
||||
readonly partOptions: IEditorPartOptions;
|
||||
|
||||
/**
|
||||
* An event that notifies when editor part options change.
|
||||
*/
|
||||
readonly onDidEditorPartOptionsChange: Event<IEditorPartOptionsChangeEvent>;
|
||||
|
||||
/**
|
||||
* Enforce editor part options temporarily.
|
||||
*/
|
||||
enforcePartOptions(options: IEditorPartOptions): IDisposable;
|
||||
}
|
||||
|
||||
export const enum GroupChangeKind {
|
||||
|
||||
/* Group Changes */
|
||||
GROUP_ACTIVE,
|
||||
GROUP_INDEX,
|
||||
|
||||
/* Editor Changes */
|
||||
EDITOR_OPEN,
|
||||
EDITOR_CLOSE,
|
||||
EDITOR_MOVE,
|
||||
EDITOR_ACTIVE,
|
||||
EDITOR_LABEL,
|
||||
EDITOR_PIN,
|
||||
EDITOR_STICKY,
|
||||
EDITOR_DIRTY
|
||||
}
|
||||
|
||||
export interface IGroupChangeEvent {
|
||||
kind: GroupChangeKind;
|
||||
editor?: IEditorInput;
|
||||
editorIndex?: number;
|
||||
}
|
||||
|
||||
export const enum OpenEditorContext {
|
||||
NEW_EDITOR = 1,
|
||||
MOVE_EDITOR = 2,
|
||||
COPY_EDITOR = 3
|
||||
}
|
||||
|
||||
export interface IEditorGroup {
|
||||
|
||||
/**
|
||||
* An aggregated event for when the group changes in any way.
|
||||
*/
|
||||
readonly onDidGroupChange: Event<IGroupChangeEvent>;
|
||||
|
||||
/**
|
||||
* An event that is fired when the group gets disposed.
|
||||
*/
|
||||
readonly onWillDispose: Event<void>;
|
||||
|
||||
/**
|
||||
* An event that is fired when an editor is about to close.
|
||||
*/
|
||||
readonly onWillCloseEditor: Event<IEditorCloseEvent>;
|
||||
|
||||
/**
|
||||
* A unique identifier of this group that remains identical even if the
|
||||
* group is moved to different locations.
|
||||
*/
|
||||
readonly id: GroupIdentifier;
|
||||
|
||||
/**
|
||||
* A number that indicates the position of this group in the visual
|
||||
* order of groups from left to right and top to bottom. The lowest
|
||||
* index will likely be top-left while the largest index in most
|
||||
* cases should be bottom-right, but that depends on the grid.
|
||||
*/
|
||||
readonly index: number;
|
||||
|
||||
/**
|
||||
* A human readable label for the group. This label can change depending
|
||||
* on the layout of all editor groups. Clients should listen on the
|
||||
* `onDidGroupChange` event to react to that.
|
||||
*/
|
||||
readonly label: string;
|
||||
|
||||
/**
|
||||
* A human readable label for the group to be used by screen readers.
|
||||
*/
|
||||
readonly ariaLabel: string;
|
||||
|
||||
/**
|
||||
* The active editor pane is the currently visible editor pane of the group.
|
||||
*/
|
||||
readonly activeEditorPane: IVisibleEditorPane | undefined;
|
||||
|
||||
/**
|
||||
* The active editor is the currently visible editor of the group
|
||||
* within the current active editor pane.
|
||||
*/
|
||||
readonly activeEditor: IEditorInput | null;
|
||||
|
||||
/**
|
||||
* The editor in the group that is in preview mode if any. There can
|
||||
* only ever be one editor in preview mode.
|
||||
*/
|
||||
readonly previewEditor: IEditorInput | null;
|
||||
|
||||
/**
|
||||
* The number of opened editors in this group.
|
||||
*/
|
||||
readonly count: number;
|
||||
|
||||
/**
|
||||
* The number of sticky editors in this group.
|
||||
*/
|
||||
readonly stickyCount: number;
|
||||
|
||||
/**
|
||||
* All opened editors in the group in sequential order of their appearance.
|
||||
*/
|
||||
readonly editors: ReadonlyArray<IEditorInput>;
|
||||
|
||||
/**
|
||||
* The scoped context key service for this group.
|
||||
*/
|
||||
readonly scopedContextKeyService: IContextKeyService;
|
||||
|
||||
/**
|
||||
* Get all editors that are currently opened in the group.
|
||||
*
|
||||
* @param order the order of the editors to use
|
||||
* @param options options to select only specific editors as instructed
|
||||
*/
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorInput>;
|
||||
|
||||
/**
|
||||
* Returns the editor at a specific index of the group.
|
||||
*/
|
||||
getEditorByIndex(index: number): IEditorInput | undefined;
|
||||
|
||||
/**
|
||||
* Returns the index of the editor in the group or -1 if not opened.
|
||||
*/
|
||||
getIndexOfEditor(editor: IEditorInput): number;
|
||||
|
||||
/**
|
||||
* Open an editor in this group.
|
||||
*
|
||||
* @returns a promise that resolves around an IEditor instance unless
|
||||
* the call failed, or the editor was not opened as active editor.
|
||||
*/
|
||||
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, context?: OpenEditorContext): Promise<IEditorPane | null>;
|
||||
|
||||
/**
|
||||
* Opens editors in this group.
|
||||
*
|
||||
* @returns a promise that resolves around an IEditor instance unless
|
||||
* the call failed, or the editor was not opened as active editor. Since
|
||||
* a group can only ever have one active editor, even if many editors are
|
||||
* opened, the result will only be one editor.
|
||||
*/
|
||||
openEditors(editors: IEditorInputWithOptions[]): Promise<IEditorPane | null>;
|
||||
|
||||
/**
|
||||
* Find out if the provided editor is opened in the group.
|
||||
*
|
||||
* Note: An editor can be opened but not actively visible.
|
||||
*/
|
||||
isOpened(editor: IEditorInput): boolean;
|
||||
|
||||
/**
|
||||
* Find out if the provided editor is pinned in the group.
|
||||
*/
|
||||
isPinned(editor: IEditorInput): boolean;
|
||||
|
||||
/**
|
||||
* Find out if the provided editor or index of editor is sticky in the group.
|
||||
*/
|
||||
isSticky(editorOrIndex: IEditorInput | number): boolean;
|
||||
|
||||
/**
|
||||
* Find out if the provided editor is active in the group.
|
||||
*/
|
||||
isActive(editor: IEditorInput): boolean;
|
||||
|
||||
/**
|
||||
* Move an editor from this group either within this group or to another group.
|
||||
*/
|
||||
moveEditor(editor: IEditorInput, target: IEditorGroup, options?: IMoveEditorOptions): void;
|
||||
|
||||
/**
|
||||
* Copy an editor from this group to another group.
|
||||
*
|
||||
* Note: It is currently not supported to show the same editor more than once in the same group.
|
||||
*/
|
||||
copyEditor(editor: IEditorInput, target: IEditorGroup, options?: ICopyEditorOptions): void;
|
||||
|
||||
/**
|
||||
* Close an editor from the group. This may trigger a confirmation dialog if
|
||||
* the editor is dirty and thus returns a promise as value.
|
||||
*
|
||||
* @param editor the editor to close, or the currently active editor
|
||||
* if unspecified.
|
||||
*
|
||||
* @returns a promise when the editor is closed.
|
||||
*/
|
||||
closeEditor(editor?: IEditorInput, options?: ICloseEditorOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Closes specific editors in this group. This may trigger a confirmation dialog if
|
||||
* there are dirty editors and thus returns a promise as value.
|
||||
*
|
||||
* @returns a promise when all editors are closed.
|
||||
*/
|
||||
closeEditors(editors: IEditorInput[] | ICloseEditorsFilter, options?: ICloseEditorOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Closes all editors from the group. This may trigger a confirmation dialog if
|
||||
* there are dirty editors and thus returns a promise as value.
|
||||
*
|
||||
* @returns a promise when all editors are closed.
|
||||
*/
|
||||
closeAllEditors(options?: ICloseAllEditorsOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Replaces editors in this group with the provided replacement.
|
||||
*
|
||||
* @param editors the editors to replace
|
||||
*
|
||||
* @returns a promise that is resolved when the replaced active
|
||||
* editor (if any) has finished loading.
|
||||
*/
|
||||
replaceEditors(editors: IEditorReplacement[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Set an editor to be pinned. A pinned editor is not replaced
|
||||
* when another editor opens at the same location.
|
||||
*
|
||||
* @param editor the editor to pin, or the currently active editor
|
||||
* if unspecified.
|
||||
*/
|
||||
pinEditor(editor?: IEditorInput): void;
|
||||
|
||||
/**
|
||||
* Set an editor to be sticky. A sticky editor is showing in the beginning
|
||||
* of the tab stripe and will not be impacted by close operations.
|
||||
*
|
||||
* @param editor the editor to make sticky, or the currently active editor
|
||||
* if unspecified.
|
||||
*/
|
||||
stickEditor(editor?: IEditorInput): void;
|
||||
|
||||
/**
|
||||
* Set an editor to be non-sticky and thus moves back to a location after
|
||||
* sticky editors and can be closed normally.
|
||||
*
|
||||
* @param editor the editor to make unsticky, or the currently active editor
|
||||
* if unspecified.
|
||||
*/
|
||||
unstickEditor(editor?: IEditorInput): void;
|
||||
|
||||
/**
|
||||
* Move keyboard focus into the group.
|
||||
*/
|
||||
focus(): void;
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ICustomEditorInfo, IEditorService, IOpenEditorOverrideHandler, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorInput, IEditorPane, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorResourceAccessor } from 'vs/workbench/common/editor';
|
||||
import { ITextEditorOptions, IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorGroup, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { extname, basename, isEqual } from 'vs/base/common/resources';
|
||||
|
||||
/**
|
||||
* Id of the default editor for open with.
|
||||
*/
|
||||
export const DEFAULT_EDITOR_ID = 'default';
|
||||
|
||||
/**
|
||||
* Try to open an resource with a given editor.
|
||||
*
|
||||
* @param input Resource to open.
|
||||
* @param id Id of the editor to use. If not provided, the user is prompted for which editor to use.
|
||||
*/
|
||||
export async function openEditorWith(
|
||||
input: IEditorInput,
|
||||
id: string | undefined,
|
||||
options: IEditorOptions | ITextEditorOptions | undefined,
|
||||
group: IEditorGroup,
|
||||
editorService: IEditorService,
|
||||
configurationService: IConfigurationService,
|
||||
quickInputService: IQuickInputService,
|
||||
): Promise<IEditorPane | undefined> {
|
||||
const resource = input.resource;
|
||||
if (!resource) {
|
||||
return;
|
||||
}
|
||||
|
||||
const overrideOptions = { ...options, override: id };
|
||||
|
||||
const allEditorOverrides = getAllAvailableEditors(resource, id, overrideOptions, group, editorService);
|
||||
if (!allEditorOverrides.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let overrideToUse;
|
||||
if (typeof id === 'string') {
|
||||
overrideToUse = allEditorOverrides.find(([_, entry]) => entry.id === id);
|
||||
} else if (allEditorOverrides.length === 1) {
|
||||
overrideToUse = allEditorOverrides[0];
|
||||
}
|
||||
if (overrideToUse) {
|
||||
return overrideToUse[0].open(input, overrideOptions, group, OpenEditorContext.NEW_EDITOR)?.override;
|
||||
}
|
||||
|
||||
// Prompt
|
||||
const originalResource = EditorResourceAccessor.getOriginalUri(input) || resource;
|
||||
const resourceExt = extname(originalResource);
|
||||
|
||||
const items: (IQuickPickItem & { handler: IOpenEditorOverrideHandler })[] = allEditorOverrides.map(([handler, entry]) => {
|
||||
return {
|
||||
handler: handler,
|
||||
id: entry.id,
|
||||
label: entry.label,
|
||||
description: entry.active ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined,
|
||||
detail: entry.detail,
|
||||
buttons: resourceExt ? [{
|
||||
iconClass: 'codicon-settings-gear',
|
||||
tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt)
|
||||
}] : undefined
|
||||
};
|
||||
});
|
||||
|
||||
const picker = quickInputService.createQuickPick<(IQuickPickItem & { handler: IOpenEditorOverrideHandler })>();
|
||||
picker.items = items;
|
||||
if (items.length) {
|
||||
picker.selectedItems = [items[0]];
|
||||
}
|
||||
picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(originalResource));
|
||||
|
||||
const pickedItem = await new Promise<(IQuickPickItem & { handler: IOpenEditorOverrideHandler }) | undefined>(resolve => {
|
||||
picker.onDidAccept(() => {
|
||||
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined);
|
||||
picker.dispose();
|
||||
});
|
||||
|
||||
picker.onDidTriggerItemButton(e => {
|
||||
const pick = e.item;
|
||||
const id = pick.id;
|
||||
resolve(pick); // open the view
|
||||
picker.dispose();
|
||||
|
||||
// And persist the setting
|
||||
if (pick && id) {
|
||||
const newAssociation: CustomEditorAssociation = { viewType: id, filenamePattern: '*' + resourceExt };
|
||||
const currentAssociations = [...configurationService.getValue<CustomEditorsAssociations>(customEditorsAssociationsSettingId)];
|
||||
|
||||
// First try updating existing association
|
||||
for (let i = 0; i < currentAssociations.length; ++i) {
|
||||
const existing = currentAssociations[i];
|
||||
if (existing.filenamePattern === newAssociation.filenamePattern) {
|
||||
currentAssociations.splice(i, 1, newAssociation);
|
||||
configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, create a new one
|
||||
currentAssociations.unshift(newAssociation);
|
||||
configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations);
|
||||
}
|
||||
});
|
||||
|
||||
picker.show();
|
||||
});
|
||||
|
||||
return pickedItem?.handler.open(input, { ...options, override: pickedItem.id }, group, OpenEditorContext.NEW_EDITOR)?.override;
|
||||
}
|
||||
|
||||
const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in");
|
||||
|
||||
export const defaultEditorOverrideEntry = Object.freeze({
|
||||
id: DEFAULT_EDITOR_ID,
|
||||
label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"),
|
||||
detail: builtinProviderDisplayName
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a list of all available editors, including the default text editor.
|
||||
*/
|
||||
export function getAllAvailableEditors(
|
||||
resource: URI,
|
||||
id: string | undefined,
|
||||
options: IEditorOptions | ITextEditorOptions | undefined,
|
||||
group: IEditorGroup,
|
||||
editorService: IEditorService
|
||||
): Array<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]> {
|
||||
const fileEditorInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileEditorInputFactory();
|
||||
const overrides = editorService.getEditorOverrides(resource, options, group);
|
||||
if (!overrides.some(([_, entry]) => entry.id === DEFAULT_EDITOR_ID)) {
|
||||
overrides.unshift([
|
||||
{
|
||||
open: (input: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => {
|
||||
const resource = EditorResourceAccessor.getOriginalUri(input);
|
||||
if (!resource) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileEditorInput = editorService.createEditorInput({ resource, forceFile: true });
|
||||
const textOptions: IEditorOptions | ITextEditorOptions = options ? { ...options, override: false } : { override: false };
|
||||
return { override: editorService.openEditor(fileEditorInput, textOptions, group) };
|
||||
}
|
||||
},
|
||||
{
|
||||
...defaultEditorOverrideEntry,
|
||||
active: fileEditorInputFactory.isFileEditorInput(editorService.activeEditor) && isEqual(editorService.activeEditor.resource, resource),
|
||||
}]);
|
||||
}
|
||||
|
||||
return overrides;
|
||||
}
|
||||
|
||||
export const customEditorsAssociationsSettingId = 'workbench.editorAssociations';
|
||||
|
||||
export const viewTypeSchamaAddition: IJSONSchema = {
|
||||
type: 'string',
|
||||
enum: []
|
||||
};
|
||||
|
||||
export type CustomEditorAssociation = {
|
||||
readonly viewType: string;
|
||||
readonly filenamePattern?: string;
|
||||
};
|
||||
|
||||
export type CustomEditorsAssociations = readonly CustomEditorAssociation[];
|
||||
|
||||
export const editorAssociationsConfigurationNode: IConfigurationNode = {
|
||||
...workbenchConfigurationNodeBase,
|
||||
properties: {
|
||||
[customEditorsAssociationsSettingId]: {
|
||||
type: 'array',
|
||||
markdownDescription: nls.localize('editor.editorAssociations', "Configure which editor to use for specific file types."),
|
||||
items: {
|
||||
type: 'object',
|
||||
defaultSnippets: [{
|
||||
body: {
|
||||
'viewType': '$1',
|
||||
'filenamePattern': '$2'
|
||||
}
|
||||
}],
|
||||
properties: {
|
||||
'viewType': {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
description: nls.localize('editor.editorAssociations.viewType', "The unique id of the editor to use."),
|
||||
},
|
||||
viewTypeSchamaAddition
|
||||
]
|
||||
},
|
||||
'filenamePattern': {
|
||||
type: 'string',
|
||||
description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const DEFAULT_CUSTOM_EDITOR: ICustomEditorInfo = {
|
||||
id: 'default',
|
||||
displayName: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"),
|
||||
providerDisplayName: builtinProviderDisplayName
|
||||
};
|
||||
|
||||
export function updateViewTypeSchema(enumValues: string[], enumDescriptions: string[]): void {
|
||||
viewTypeSchamaAddition.enum = enumValues;
|
||||
viewTypeSchamaAddition.enumDescriptions = enumDescriptions;
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
|
||||
.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode);
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IResourceEditorInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextEditorPane, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorGroup, IEditorReplacement, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const IEditorService = createDecorator<IEditorService>('editorService');
|
||||
|
||||
export type IResourceEditorInputType = IResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput;
|
||||
|
||||
export interface IResourceEditorReplacement {
|
||||
readonly editor: IResourceEditorInputType;
|
||||
readonly replacement: IResourceEditorInputType;
|
||||
}
|
||||
|
||||
export const ACTIVE_GROUP = -1;
|
||||
export type ACTIVE_GROUP_TYPE = typeof ACTIVE_GROUP;
|
||||
|
||||
export const SIDE_GROUP = -2;
|
||||
export type SIDE_GROUP_TYPE = typeof SIDE_GROUP;
|
||||
|
||||
export interface IOpenEditorOverrideEntry {
|
||||
id: string;
|
||||
label: string;
|
||||
active: boolean;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export interface IOpenEditorOverrideHandler {
|
||||
open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, context: OpenEditorContext): IOpenEditorOverride | undefined;
|
||||
getEditorOverrides?(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[];
|
||||
}
|
||||
|
||||
export interface IOpenEditorOverride {
|
||||
|
||||
/**
|
||||
* If defined, will prevent the opening of an editor and replace the resulting
|
||||
* promise with the provided promise for the openEditor() call.
|
||||
*/
|
||||
override?: Promise<IEditorPane | undefined>;
|
||||
}
|
||||
|
||||
export interface ISaveEditorsOptions extends ISaveOptions {
|
||||
|
||||
/**
|
||||
* If true, will ask for a location of the editor to save to.
|
||||
*/
|
||||
readonly saveAs?: boolean;
|
||||
}
|
||||
|
||||
export interface IBaseSaveRevertAllEditorOptions {
|
||||
|
||||
/**
|
||||
* Whether to include untitled editors as well.
|
||||
*/
|
||||
readonly includeUntitled?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to exclude sticky editors.
|
||||
*/
|
||||
readonly excludeSticky?: boolean;
|
||||
}
|
||||
|
||||
export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRevertAllEditorOptions { }
|
||||
|
||||
export interface IRevertAllEditorsOptions extends IRevertOptions, IBaseSaveRevertAllEditorOptions { }
|
||||
|
||||
export interface ICustomEditorInfo {
|
||||
readonly id: string;
|
||||
readonly displayName: string;
|
||||
readonly providerDisplayName: string;
|
||||
}
|
||||
|
||||
export interface ICustomEditorViewTypesHandler {
|
||||
readonly onDidChangeViewTypes: Event<void>;
|
||||
|
||||
getViewTypes(): ICustomEditorInfo[];
|
||||
}
|
||||
|
||||
export interface IEditorService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Emitted when the currently active editor changes.
|
||||
*
|
||||
* @see `IEditorService.activeEditorPane`
|
||||
*/
|
||||
readonly onDidActiveEditorChange: Event<void>;
|
||||
|
||||
/**
|
||||
* Emitted when any of the current visible editors changes.
|
||||
*
|
||||
* @see `IEditorService.visibleEditorPanes`
|
||||
*/
|
||||
readonly onDidVisibleEditorsChange: Event<void>;
|
||||
|
||||
/**
|
||||
* Emitted when an editor is closed.
|
||||
*/
|
||||
readonly onDidCloseEditor: Event<IEditorCloseEvent>;
|
||||
|
||||
/**
|
||||
* The currently active editor pane or `undefined` if none. The editor pane is
|
||||
* the workbench container for editors of any kind.
|
||||
*
|
||||
* @see `IEditorService.activeEditor` for access to the active editor input
|
||||
*/
|
||||
readonly activeEditorPane: IVisibleEditorPane | undefined;
|
||||
|
||||
/**
|
||||
* The currently active editor or `undefined` if none. An editor is active when it is
|
||||
* located in the currently active editor group. It will be `undefined` if the active
|
||||
* editor group has no editors open.
|
||||
*/
|
||||
readonly activeEditor: IEditorInput | undefined;
|
||||
|
||||
/**
|
||||
* The currently active text editor control or `undefined` if there is currently no active
|
||||
* editor or the active editor widget is neither a text nor a diff editor.
|
||||
*
|
||||
* @see `IEditorService.activeEditor`
|
||||
*/
|
||||
readonly activeTextEditorControl: IEditor | IDiffEditor | undefined;
|
||||
|
||||
/**
|
||||
* The currently active text editor mode or `undefined` if there is currently no active
|
||||
* editor or the active editor control is neither a text nor a diff editor. If the active
|
||||
* editor is a diff editor, the modified side's mode will be taken.
|
||||
*/
|
||||
readonly activeTextEditorMode: string | undefined;
|
||||
|
||||
/**
|
||||
* All editor panes that are currently visible across all editor groups.
|
||||
*
|
||||
* @see `IEditorService.visibleEditors` for access to the visible editor inputs
|
||||
*/
|
||||
readonly visibleEditorPanes: ReadonlyArray<IVisibleEditorPane>;
|
||||
|
||||
/**
|
||||
* All editors that are currently visible. An editor is visible when it is opened in an
|
||||
* editor group and active in that group. Multiple editor groups can be opened at the same time.
|
||||
*/
|
||||
readonly visibleEditors: ReadonlyArray<IEditorInput>;
|
||||
|
||||
/**
|
||||
* All text editor widgets that are currently visible across all editor groups. A text editor
|
||||
* widget is either a text or a diff editor.
|
||||
*/
|
||||
readonly visibleTextEditorControls: ReadonlyArray<IEditor | IDiffEditor>;
|
||||
|
||||
/**
|
||||
* All editors that are opened across all editor groups in sequential order
|
||||
* of appearance.
|
||||
*
|
||||
* This includes active as well as inactive editors in each editor group.
|
||||
*/
|
||||
readonly editors: ReadonlyArray<IEditorInput>;
|
||||
|
||||
/**
|
||||
* The total number of editors that are opened either inactive or active.
|
||||
*/
|
||||
readonly count: number;
|
||||
|
||||
/**
|
||||
* All editors that are opened across all editor groups with their group
|
||||
* identifier.
|
||||
*
|
||||
* @param order the order of the editors to use
|
||||
* @param options wether to exclude sticky editors or not
|
||||
*/
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray<IEditorIdentifier>;
|
||||
|
||||
/**
|
||||
* Open an editor in an editor group.
|
||||
*
|
||||
* @param editor the editor to open
|
||||
* @param options the options to use for the editor
|
||||
* @param group the target group. If unspecified, the editor will open in the currently
|
||||
* active group. Use `SIDE_GROUP_TYPE` to open the editor in a new editor group to the side
|
||||
* of the currently active group.
|
||||
*
|
||||
* @returns the editor that opened or `undefined` if the operation failed or the editor was not
|
||||
* opened to be active.
|
||||
*/
|
||||
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditorPane | undefined>;
|
||||
openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextEditorPane | undefined>;
|
||||
openEditor(editor: IResourceDiffEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextDiffEditorPane | undefined>;
|
||||
|
||||
/**
|
||||
* Open editors in an editor group.
|
||||
*
|
||||
* @param editors the editors to open with associated options
|
||||
* @param group the target group. If unspecified, the editor will open in the currently
|
||||
* active group. Use `SIDE_GROUP_TYPE` to open the editor in a new editor group to the side
|
||||
* of the currently active group.
|
||||
*
|
||||
* @returns the editors that opened. The array can be empty or have less elements for editors
|
||||
* that failed to open or were instructed to open as inactive.
|
||||
*/
|
||||
openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ReadonlyArray<IEditorPane>>;
|
||||
openEditors(editors: IResourceEditorInputType[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ReadonlyArray<IEditorPane>>;
|
||||
|
||||
/**
|
||||
* Replaces editors in an editor group with the provided replacement.
|
||||
*
|
||||
* @param editors the editors to replace
|
||||
*
|
||||
* @returns a promise that is resolved when the replaced active
|
||||
* editor (if any) has finished loading.
|
||||
*/
|
||||
replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
|
||||
replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
|
||||
|
||||
/**
|
||||
* Find out if the provided editor is opened in any editor group.
|
||||
*
|
||||
* Note: An editor can be opened but not actively visible.
|
||||
*
|
||||
* @param editor the editor to check for being opened. If a
|
||||
* `IResourceEditorInput` is passed in, the resource is checked on
|
||||
* all opened editors. In case of a side by side editor, the
|
||||
* right hand side resource is considered only.
|
||||
*/
|
||||
isOpen(editor: IResourceEditorInput): boolean;
|
||||
isOpen(editor: IEditorInput): boolean;
|
||||
|
||||
/**
|
||||
* Get all available editor overrides for the editor input.
|
||||
*/
|
||||
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][];
|
||||
|
||||
/**
|
||||
* Allows to override the opening of editors by installing a handler that will
|
||||
* be called each time an editor is about to open allowing to override the
|
||||
* operation to open a different editor.
|
||||
*/
|
||||
overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable;
|
||||
|
||||
/**
|
||||
* Register handlers for custom editor view types.
|
||||
* The handler will provide all available custom editors registered
|
||||
* and also notify the editor service when a custom editor view type is registered/unregistered.
|
||||
*/
|
||||
registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable;
|
||||
|
||||
/**
|
||||
* Converts a lightweight input to a workbench editor input.
|
||||
*/
|
||||
createEditorInput(input: IResourceEditorInputType): IEditorInput;
|
||||
|
||||
/**
|
||||
* Save the provided list of editors.
|
||||
*
|
||||
* @returns `true` if all editors saved and `false` otherwise.
|
||||
*/
|
||||
save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Save all editors.
|
||||
*
|
||||
* @returns `true` if all editors saved and `false` otherwise.
|
||||
*/
|
||||
saveAll(options?: ISaveAllEditorsOptions): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Reverts the provided list of editors.
|
||||
*
|
||||
* @returns `true` if all editors reverted and `false` otherwise.
|
||||
*/
|
||||
revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Reverts all editors.
|
||||
*
|
||||
* @returns `true` if all editors reverted and `false` otherwise.
|
||||
*/
|
||||
revertAll(options?: IRevertAllEditorsOptions): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Track the provided editors until all have been closed.
|
||||
*
|
||||
* @param options use `waitForSaved: true` to wait for the resources
|
||||
* being saved. If auto-save is enabled, it may be possible to close
|
||||
* an editor while the save continues in the background.
|
||||
*/
|
||||
whenClosed(editors: IResourceEditorInput[], options?: { waitForSaved: boolean }): Promise<void>;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,572 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { EditorOptions, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, TestEditorPart } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { EditorActivation } from 'vs/platform/editor/common/editor';
|
||||
import { WillSaveStateReason } from 'vs/platform/storage/common/storage';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
|
||||
const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver';
|
||||
const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver';
|
||||
const TEST_SERIALIZABLE_EDITOR_INPUT_ID = 'testSerializableEditorInputForEditorsObserver';
|
||||
|
||||
suite('EditorsObserver', function () {
|
||||
|
||||
let disposables: IDisposable[] = [];
|
||||
|
||||
setup(() => {
|
||||
disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_SERIALIZABLE_EDITOR_INPUT_ID));
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
dispose(disposables);
|
||||
disposables = [];
|
||||
});
|
||||
|
||||
async function createPart(): Promise<TestEditorPart> {
|
||||
const instantiationService = workbenchInstantiationService();
|
||||
instantiationService.invokeFunction(accessor => Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).start(accessor));
|
||||
|
||||
const part = instantiationService.createInstance(TestEditorPart);
|
||||
part.create(document.createElement('div'));
|
||||
part.layout(400, 300);
|
||||
|
||||
await part.whenRestored;
|
||||
|
||||
return part;
|
||||
}
|
||||
|
||||
async function createEditorObserver(): Promise<[EditorPart, EditorsObserver]> {
|
||||
const part = await createPart();
|
||||
|
||||
const observer = new EditorsObserver(part, new TestStorageService());
|
||||
|
||||
return [part, observer];
|
||||
}
|
||||
|
||||
test('basics (single group)', async () => {
|
||||
const [part, observer] = await createEditorObserver();
|
||||
|
||||
let onDidMostRecentlyActiveEditorsChangeCalled = false;
|
||||
const listener = observer.onDidMostRecentlyActiveEditorsChange(() => {
|
||||
onDidMostRecentlyActiveEditorsChangeCalled = true;
|
||||
});
|
||||
|
||||
let currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 0);
|
||||
assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, false);
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
|
||||
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 1);
|
||||
assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input1);
|
||||
assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, true);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
|
||||
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
|
||||
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 3);
|
||||
assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input3);
|
||||
assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input2);
|
||||
assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input1);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 3);
|
||||
assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input2);
|
||||
assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input3);
|
||||
assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
onDidMostRecentlyActiveEditorsChangeCalled = false;
|
||||
await part.activeGroup.closeEditor(input1);
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 2);
|
||||
assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input2);
|
||||
assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input3);
|
||||
assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, true);
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
await part.activeGroup.closeAllEditors();
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 0);
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), false);
|
||||
assert.equal(observer.hasEditor(input3.resource), false);
|
||||
|
||||
part.dispose();
|
||||
listener.dispose();
|
||||
});
|
||||
|
||||
test('basics (multi group)', async () => {
|
||||
const [part, observer] = await createEditorObserver();
|
||||
|
||||
const rootGroup = part.activeGroup;
|
||||
|
||||
let currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 0);
|
||||
|
||||
const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE }));
|
||||
await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE }));
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 2);
|
||||
assert.equal(currentEditorsMRU[0].groupId, sideGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input1);
|
||||
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE }));
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 2);
|
||||
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input1);
|
||||
assert.equal(currentEditorsMRU[1].groupId, sideGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
|
||||
// Opening an editor inactive should not change
|
||||
// the most recent editor, but rather put it behind
|
||||
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true }));
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 3);
|
||||
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input1);
|
||||
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input2);
|
||||
assert.equal(currentEditorsMRU[2].groupId, sideGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
|
||||
await rootGroup.closeAllEditors();
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 1);
|
||||
assert.equal(currentEditorsMRU[0].groupId, sideGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), false);
|
||||
|
||||
await sideGroup.closeAllEditors();
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 0);
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), false);
|
||||
|
||||
part.dispose();
|
||||
});
|
||||
|
||||
test('copy group', async function () {
|
||||
if (isWeb) {
|
||||
this.skip();
|
||||
}
|
||||
const [part, observer] = await createEditorObserver();
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
|
||||
const rootGroup = part.activeGroup;
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
|
||||
let currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 3);
|
||||
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input3);
|
||||
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input2);
|
||||
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT);
|
||||
copiedGroup.setActive(true);
|
||||
|
||||
currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 6);
|
||||
assert.equal(currentEditorsMRU[0].groupId, copiedGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input3);
|
||||
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input3);
|
||||
assert.equal(currentEditorsMRU[2].groupId, copiedGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input2);
|
||||
assert.equal(currentEditorsMRU[3].groupId, copiedGroup.id);
|
||||
assert.equal(currentEditorsMRU[3].editor, input1);
|
||||
assert.equal(currentEditorsMRU[4].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[4].editor, input2);
|
||||
assert.equal(currentEditorsMRU[5].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[5].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
await rootGroup.closeAllEditors();
|
||||
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
await copiedGroup.closeAllEditors();
|
||||
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), false);
|
||||
assert.equal(observer.hasEditor(input3.resource), false);
|
||||
|
||||
part.dispose();
|
||||
});
|
||||
|
||||
test('initial editors are part of observer and state is persisted & restored (single group)', async () => {
|
||||
const part = await createPart();
|
||||
|
||||
const rootGroup = part.activeGroup;
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
|
||||
const storage = new TestStorageService();
|
||||
const observer = new EditorsObserver(part, storage);
|
||||
await part.whenRestored;
|
||||
|
||||
let currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 3);
|
||||
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input3);
|
||||
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input2);
|
||||
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN });
|
||||
|
||||
const restoredObserver = new EditorsObserver(part, storage);
|
||||
await part.whenRestored;
|
||||
|
||||
currentEditorsMRU = restoredObserver.editors;
|
||||
assert.equal(currentEditorsMRU.length, 3);
|
||||
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input3);
|
||||
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input2);
|
||||
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
part.clearState();
|
||||
part.dispose();
|
||||
});
|
||||
|
||||
test('initial editors are part of observer (multi group)', async () => {
|
||||
const part = await createPart();
|
||||
|
||||
const rootGroup = part.activeGroup;
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
|
||||
const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
|
||||
await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
|
||||
const storage = new TestStorageService();
|
||||
const observer = new EditorsObserver(part, storage);
|
||||
await part.whenRestored;
|
||||
|
||||
let currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 3);
|
||||
assert.equal(currentEditorsMRU[0].groupId, sideGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input3);
|
||||
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input2);
|
||||
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
|
||||
storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN });
|
||||
|
||||
const restoredObserver = new EditorsObserver(part, storage);
|
||||
await part.whenRestored;
|
||||
|
||||
currentEditorsMRU = restoredObserver.editors;
|
||||
assert.equal(currentEditorsMRU.length, 3);
|
||||
assert.equal(currentEditorsMRU[0].groupId, sideGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input3);
|
||||
assert.equal(currentEditorsMRU[1].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[1].editor, input2);
|
||||
assert.equal(currentEditorsMRU[2].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[2].editor, input1);
|
||||
assert.equal(restoredObserver.hasEditor(input1.resource), true);
|
||||
assert.equal(restoredObserver.hasEditor(input2.resource), true);
|
||||
assert.equal(restoredObserver.hasEditor(input3.resource), true);
|
||||
|
||||
part.clearState();
|
||||
part.dispose();
|
||||
});
|
||||
|
||||
test('observer does not restore editors that cannot be serialized', async () => {
|
||||
const part = await createPart();
|
||||
|
||||
const rootGroup = part.activeGroup;
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
|
||||
const storage = new TestStorageService();
|
||||
const observer = new EditorsObserver(part, storage);
|
||||
await part.whenRestored;
|
||||
|
||||
let currentEditorsMRU = observer.editors;
|
||||
assert.equal(currentEditorsMRU.length, 1);
|
||||
assert.equal(currentEditorsMRU[0].groupId, rootGroup.id);
|
||||
assert.equal(currentEditorsMRU[0].editor, input1);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
|
||||
storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN });
|
||||
|
||||
const restoredObserver = new EditorsObserver(part, storage);
|
||||
await part.whenRestored;
|
||||
|
||||
currentEditorsMRU = restoredObserver.editors;
|
||||
assert.equal(currentEditorsMRU.length, 0);
|
||||
assert.equal(restoredObserver.hasEditor(input1.resource), false);
|
||||
|
||||
part.clearState();
|
||||
part.dispose();
|
||||
});
|
||||
|
||||
test('observer closes editors when limit reached (across all groups)', async () => {
|
||||
const part = await createPart();
|
||||
part.enforcePartOptions({ limit: { enabled: true, value: 3 } });
|
||||
|
||||
const storage = new TestStorageService();
|
||||
const observer = new EditorsObserver(part, storage);
|
||||
|
||||
const rootGroup = part.activeGroup;
|
||||
const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
|
||||
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID);
|
||||
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID);
|
||||
const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
|
||||
|
||||
assert.equal(rootGroup.count, 3);
|
||||
assert.equal(rootGroup.isOpened(input1), false);
|
||||
assert.equal(rootGroup.isOpened(input2), true);
|
||||
assert.equal(rootGroup.isOpened(input3), true);
|
||||
assert.equal(rootGroup.isOpened(input4), true);
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
assert.equal(observer.hasEditor(input4.resource), true);
|
||||
|
||||
input2.setDirty();
|
||||
part.enforcePartOptions({ limit: { enabled: true, value: 1 } });
|
||||
|
||||
await timeout(0);
|
||||
|
||||
assert.equal(rootGroup.count, 2);
|
||||
assert.equal(rootGroup.isOpened(input1), false);
|
||||
assert.equal(rootGroup.isOpened(input2), true); // dirty
|
||||
assert.equal(rootGroup.isOpened(input3), false);
|
||||
assert.equal(rootGroup.isOpened(input4), true);
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), false);
|
||||
assert.equal(observer.hasEditor(input4.resource), true);
|
||||
|
||||
const input5 = new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID);
|
||||
await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true }));
|
||||
|
||||
assert.equal(rootGroup.count, 1);
|
||||
assert.equal(rootGroup.isOpened(input1), false);
|
||||
assert.equal(rootGroup.isOpened(input2), true); // dirty
|
||||
assert.equal(rootGroup.isOpened(input3), false);
|
||||
assert.equal(rootGroup.isOpened(input4), false);
|
||||
assert.equal(sideGroup.isOpened(input5), true);
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), false);
|
||||
assert.equal(observer.hasEditor(input4.resource), false);
|
||||
assert.equal(observer.hasEditor(input5.resource), true);
|
||||
|
||||
observer.dispose();
|
||||
part.dispose();
|
||||
});
|
||||
|
||||
test('observer closes editors when limit reached (in group)', async () => {
|
||||
const part = await createPart();
|
||||
part.enforcePartOptions({ limit: { enabled: true, value: 3, perEditorGroup: true } });
|
||||
|
||||
const storage = new TestStorageService();
|
||||
const observer = new EditorsObserver(part, storage);
|
||||
|
||||
const rootGroup = part.activeGroup;
|
||||
const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
|
||||
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID);
|
||||
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID);
|
||||
const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
|
||||
|
||||
assert.equal(rootGroup.count, 3); // 1 editor got closed due to our limit!
|
||||
assert.equal(rootGroup.isOpened(input1), false);
|
||||
assert.equal(rootGroup.isOpened(input2), true);
|
||||
assert.equal(rootGroup.isOpened(input3), true);
|
||||
assert.equal(rootGroup.isOpened(input4), true);
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
assert.equal(observer.hasEditor(input4.resource), true);
|
||||
|
||||
await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
await sideGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
|
||||
|
||||
assert.equal(sideGroup.count, 3);
|
||||
assert.equal(sideGroup.isOpened(input1), false);
|
||||
assert.equal(sideGroup.isOpened(input2), true);
|
||||
assert.equal(sideGroup.isOpened(input3), true);
|
||||
assert.equal(sideGroup.isOpened(input4), true);
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), true);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
assert.equal(observer.hasEditor(input4.resource), true);
|
||||
|
||||
part.enforcePartOptions({ limit: { enabled: true, value: 1, perEditorGroup: true } });
|
||||
|
||||
await timeout(10);
|
||||
|
||||
assert.equal(rootGroup.count, 1);
|
||||
assert.equal(rootGroup.isOpened(input1), false);
|
||||
assert.equal(rootGroup.isOpened(input2), false);
|
||||
assert.equal(rootGroup.isOpened(input3), false);
|
||||
assert.equal(rootGroup.isOpened(input4), true);
|
||||
|
||||
assert.equal(sideGroup.count, 1);
|
||||
assert.equal(sideGroup.isOpened(input1), false);
|
||||
assert.equal(sideGroup.isOpened(input2), false);
|
||||
assert.equal(sideGroup.isOpened(input3), false);
|
||||
assert.equal(sideGroup.isOpened(input4), true);
|
||||
|
||||
assert.equal(observer.hasEditor(input1.resource), false);
|
||||
assert.equal(observer.hasEditor(input2.resource), false);
|
||||
assert.equal(observer.hasEditor(input3.resource), false);
|
||||
assert.equal(observer.hasEditor(input4.resource), true);
|
||||
|
||||
observer.dispose();
|
||||
part.dispose();
|
||||
});
|
||||
|
||||
test('observer does not close sticky', async () => {
|
||||
const part = await createPart();
|
||||
part.enforcePartOptions({ limit: { enabled: true, value: 3 } });
|
||||
|
||||
const storage = new TestStorageService();
|
||||
const observer = new EditorsObserver(part, storage);
|
||||
|
||||
const rootGroup = part.activeGroup;
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
|
||||
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID);
|
||||
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID);
|
||||
const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, sticky: true }));
|
||||
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
|
||||
|
||||
assert.equal(rootGroup.count, 3);
|
||||
assert.equal(rootGroup.isOpened(input1), true);
|
||||
assert.equal(rootGroup.isOpened(input2), false);
|
||||
assert.equal(rootGroup.isOpened(input3), true);
|
||||
assert.equal(rootGroup.isOpened(input4), true);
|
||||
assert.equal(observer.hasEditor(input1.resource), true);
|
||||
assert.equal(observer.hasEditor(input2.resource), false);
|
||||
assert.equal(observer.hasEditor(input3.resource), true);
|
||||
assert.equal(observer.hasEditor(input4.resource), true);
|
||||
|
||||
observer.dispose();
|
||||
part.dispose();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user