mirror of
https://github.com/coder/code-server.git
synced 2026-05-14 08:17:27 +02:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
132
lib/vscode/src/vs/workbench/common/actions.ts
Normal file
132
lib/vscode/src/vs/workbench/common/actions.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId, ICommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export const Extensions = {
|
||||
WorkbenchActions: 'workbench.contributions.actions'
|
||||
};
|
||||
|
||||
export interface IWorkbenchActionRegistry {
|
||||
|
||||
/**
|
||||
* Registers a workbench action to the platform. Workbench actions are not
|
||||
* visible by default and can only be invoked through a keybinding if provided.
|
||||
* @deprecated Register directly with KeybindingsRegistry and MenuRegistry or use registerAction2 instead.
|
||||
*/
|
||||
registerWorkbenchAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable;
|
||||
}
|
||||
|
||||
Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionRegistry {
|
||||
|
||||
registerWorkbenchAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpression): IDisposable {
|
||||
return this.registerWorkbenchCommandFromAction(descriptor, alias, category, when);
|
||||
}
|
||||
|
||||
private registerWorkbenchCommandFromAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpression): IDisposable {
|
||||
const registrations = new DisposableStore();
|
||||
|
||||
// command
|
||||
registrations.add(CommandsRegistry.registerCommand(descriptor.id, this.createCommandHandler(descriptor)));
|
||||
|
||||
// keybinding
|
||||
const weight = (typeof descriptor.keybindingWeight === 'undefined' ? KeybindingWeight.WorkbenchContrib : descriptor.keybindingWeight);
|
||||
const keybindings = descriptor.keybindings;
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: descriptor.id,
|
||||
weight: weight,
|
||||
when:
|
||||
descriptor.keybindingContext && when
|
||||
? ContextKeyExpr.and(descriptor.keybindingContext, when)
|
||||
: descriptor.keybindingContext || when || null,
|
||||
primary: keybindings ? keybindings.primary : 0,
|
||||
secondary: keybindings?.secondary,
|
||||
win: keybindings?.win,
|
||||
mac: keybindings?.mac,
|
||||
linux: keybindings?.linux
|
||||
});
|
||||
|
||||
// menu item
|
||||
// TODO@Rob slightly weird if-check required because of
|
||||
// https://github.com/microsoft/vscode/blob/master/src/vs/workbench/contrib/search/electron-browser/search.contribution.ts#L266
|
||||
if (descriptor.label) {
|
||||
|
||||
let idx = alias.indexOf(': ');
|
||||
let categoryOriginal = '';
|
||||
if (idx > 0) {
|
||||
categoryOriginal = alias.substr(0, idx);
|
||||
alias = alias.substr(idx + 2);
|
||||
}
|
||||
|
||||
const command: ICommandAction = {
|
||||
id: descriptor.id,
|
||||
title: { value: descriptor.label, original: alias },
|
||||
category: category ? { value: category, original: categoryOriginal } : undefined
|
||||
};
|
||||
|
||||
MenuRegistry.addCommand(command);
|
||||
|
||||
registrations.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command, when }));
|
||||
}
|
||||
|
||||
// TODO@alex,joh
|
||||
// support removal of keybinding rule
|
||||
// support removal of command-ui
|
||||
return registrations;
|
||||
}
|
||||
|
||||
private createCommandHandler(descriptor: SyncActionDescriptor): ICommandHandler {
|
||||
return async (accessor, args) => {
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const lifecycleService = accessor.get(ILifecycleService);
|
||||
|
||||
try {
|
||||
await this.triggerAndDisposeAction(instantiationService, lifecycleService, descriptor, args);
|
||||
} catch (error) {
|
||||
notificationService.error(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async triggerAndDisposeAction(instantiationService: IInstantiationService, lifecycleService: ILifecycleService, descriptor: SyncActionDescriptor, args: unknown): Promise<void> {
|
||||
|
||||
// run action when workbench is created
|
||||
await lifecycleService.when(LifecyclePhase.Ready);
|
||||
|
||||
const actionInstance = instantiationService.createInstance(descriptor.syncDescriptor);
|
||||
actionInstance.label = descriptor.label || actionInstance.label;
|
||||
|
||||
// don't run the action when not enabled
|
||||
if (!actionInstance.enabled) {
|
||||
actionInstance.dispose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise run and dispose
|
||||
try {
|
||||
const from = (args as any)?.from || 'keybinding';
|
||||
await actionInstance.run(undefined, { from });
|
||||
} finally {
|
||||
actionInstance.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const CATEGORIES = {
|
||||
View: { value: localize('view', "View"), original: 'View' },
|
||||
Help: { value: localize('help', "Help"), original: 'Help' },
|
||||
Developer: { value: localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"), original: 'Developer' }
|
||||
};
|
||||
17
lib/vscode/src/vs/workbench/common/activity.ts
Normal file
17
lib/vscode/src/vs/workbench/common/activity.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export interface IActivity {
|
||||
id: string;
|
||||
name: string;
|
||||
keybindingId?: string;
|
||||
cssClass?: string;
|
||||
iconUrl?: URI;
|
||||
}
|
||||
|
||||
export const GLOBAL_ACTIVITY_ID = 'workbench.action.globalActivity';
|
||||
export const ACCOUNTS_ACTIVITY_ID = 'workbench.action.accountsActivity';
|
||||
45
lib/vscode/src/vs/workbench/common/component.ts
Normal file
45
lib/vscode/src/vs/workbench/common/component.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Memento, MementoObject } from 'vs/workbench/common/memento';
|
||||
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class Component extends Themable {
|
||||
|
||||
private readonly memento: Memento;
|
||||
|
||||
constructor(
|
||||
private readonly id: string,
|
||||
themeService: IThemeService,
|
||||
storageService: IStorageService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.id = id;
|
||||
this.memento = new Memento(this.id, storageService);
|
||||
|
||||
this._register(storageService.onWillSaveState(() => {
|
||||
|
||||
// Ask the component to persist state into the memento
|
||||
this.saveState();
|
||||
|
||||
// Then save the memento into storage
|
||||
this.memento.saveMemento();
|
||||
}));
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
protected getMemento(scope: StorageScope): MementoObject {
|
||||
return this.memento.getMemento(scope);
|
||||
}
|
||||
|
||||
protected saveState(): void {
|
||||
// Subclasses to implement for storing state
|
||||
}
|
||||
}
|
||||
65
lib/vscode/src/vs/workbench/common/composite.ts
Normal file
65
lib/vscode/src/vs/workbench/common/composite.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IComposite {
|
||||
|
||||
/**
|
||||
* An event when the composite gained focus.
|
||||
*/
|
||||
readonly onDidFocus: Event<void>;
|
||||
|
||||
/**
|
||||
* An event when the composite lost focus.
|
||||
*/
|
||||
readonly onDidBlur: Event<void>;
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of this composite.
|
||||
*/
|
||||
getId(): string;
|
||||
|
||||
/**
|
||||
* Returns the name of this composite to show in the title area.
|
||||
*/
|
||||
getTitle(): string | undefined;
|
||||
|
||||
/**
|
||||
* Returns the primary actions of the composite.
|
||||
*/
|
||||
getActions(): ReadonlyArray<IAction>;
|
||||
|
||||
/**
|
||||
* Returns the secondary actions of the composite.
|
||||
*/
|
||||
getSecondaryActions(): ReadonlyArray<IAction>;
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the context menu of the composite
|
||||
*/
|
||||
getContextMenuActions(): ReadonlyArray<IAction>;
|
||||
|
||||
/**
|
||||
* Returns the action item for a specific action.
|
||||
*/
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined;
|
||||
|
||||
/**
|
||||
* Returns the underlying control of this composite.
|
||||
*/
|
||||
getControl(): ICompositeControl | undefined;
|
||||
|
||||
/**
|
||||
* Asks the underlying control to focus.
|
||||
*/
|
||||
focus(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker interface for the composite control
|
||||
*/
|
||||
export interface ICompositeControl { }
|
||||
14
lib/vscode/src/vs/workbench/common/configuration.ts
Normal file
14
lib/vscode/src/vs/workbench/common/configuration.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
|
||||
export const workbenchConfigurationNodeBase = Object.freeze<IConfigurationNode>({
|
||||
'id': 'workbench',
|
||||
'order': 7,
|
||||
'title': localize('workbenchConfigurationTitle', "Workbench"),
|
||||
'type': 'object',
|
||||
});
|
||||
128
lib/vscode/src/vs/workbench/common/contributions.ts
Normal file
128
lib/vscode/src/vs/workbench/common/contributions.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { runWhenIdle, IdleDeadline } from 'vs/base/common/async';
|
||||
|
||||
/**
|
||||
* A workbench contribution that will be loaded when the workbench starts and disposed when the workbench shuts down.
|
||||
*/
|
||||
export interface IWorkbenchContribution {
|
||||
// Marker Interface
|
||||
}
|
||||
|
||||
export namespace Extensions {
|
||||
export const Workbench = 'workbench.contributions.kind';
|
||||
}
|
||||
|
||||
type IWorkbenchContributionSignature<Service extends BrandedService[]> = new (...services: Service) => IWorkbenchContribution;
|
||||
|
||||
export interface IWorkbenchContributionsRegistry {
|
||||
|
||||
/**
|
||||
* Registers a workbench contribution to the platform that will be loaded when the workbench starts and disposed when
|
||||
* the workbench shuts down.
|
||||
*
|
||||
* @param phase the lifecycle phase when to instantiate the contribution.
|
||||
*/
|
||||
registerWorkbenchContribution<Services extends BrandedService[]>(contribution: IWorkbenchContributionSignature<Services>, phase: LifecyclePhase): void;
|
||||
|
||||
/**
|
||||
* Starts the registry by providing the required services.
|
||||
*/
|
||||
start(accessor: ServicesAccessor): void;
|
||||
}
|
||||
|
||||
class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry {
|
||||
|
||||
private instantiationService: IInstantiationService | undefined;
|
||||
private lifecycleService: ILifecycleService | undefined;
|
||||
|
||||
private readonly toBeInstantiated = new Map<LifecyclePhase, IConstructorSignature0<IWorkbenchContribution>[]>();
|
||||
|
||||
registerWorkbenchContribution(ctor: IConstructorSignature0<IWorkbenchContribution>, phase: LifecyclePhase = LifecyclePhase.Starting): void {
|
||||
|
||||
// Instantiate directly if we are already matching the provided phase
|
||||
if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) {
|
||||
this.instantiationService.createInstance(ctor);
|
||||
}
|
||||
|
||||
// Otherwise keep contributions by lifecycle phase
|
||||
else {
|
||||
let toBeInstantiated = this.toBeInstantiated.get(phase);
|
||||
if (!toBeInstantiated) {
|
||||
toBeInstantiated = [];
|
||||
this.toBeInstantiated.set(phase, toBeInstantiated);
|
||||
}
|
||||
|
||||
toBeInstantiated.push(ctor as IConstructorSignature0<IWorkbenchContribution>);
|
||||
}
|
||||
}
|
||||
|
||||
start(accessor: ServicesAccessor): void {
|
||||
const instantiationService = this.instantiationService = accessor.get(IInstantiationService);
|
||||
const lifecycleService = this.lifecycleService = accessor.get(ILifecycleService);
|
||||
|
||||
[LifecyclePhase.Starting, LifecyclePhase.Ready, LifecyclePhase.Restored, LifecyclePhase.Eventually].forEach(phase => {
|
||||
this.instantiateByPhase(instantiationService, lifecycleService, phase);
|
||||
});
|
||||
}
|
||||
|
||||
private instantiateByPhase(instantiationService: IInstantiationService, lifecycleService: ILifecycleService, phase: LifecyclePhase): void {
|
||||
|
||||
// Instantiate contributions directly when phase is already reached
|
||||
if (lifecycleService.phase >= phase) {
|
||||
this.doInstantiateByPhase(instantiationService, phase);
|
||||
}
|
||||
|
||||
// Otherwise wait for phase to be reached
|
||||
else {
|
||||
lifecycleService.when(phase).then(() => this.doInstantiateByPhase(instantiationService, phase));
|
||||
}
|
||||
}
|
||||
|
||||
private doInstantiateByPhase(instantiationService: IInstantiationService, phase: LifecyclePhase): void {
|
||||
const toBeInstantiated = this.toBeInstantiated.get(phase);
|
||||
if (toBeInstantiated) {
|
||||
this.toBeInstantiated.delete(phase);
|
||||
if (phase !== LifecyclePhase.Eventually) {
|
||||
// instantiate everything synchronously and blocking
|
||||
for (const ctor of toBeInstantiated) {
|
||||
this.safeCreateInstance(instantiationService, ctor); // catch error so that other contributions are still considered
|
||||
}
|
||||
} else {
|
||||
// for the Eventually-phase we instantiate contributions
|
||||
// only when idle. this might take a few idle-busy-cycles
|
||||
// but will finish within the timeouts
|
||||
let forcedTimeout = 3000;
|
||||
let i = 0;
|
||||
let instantiateSome = (idle: IdleDeadline) => {
|
||||
while (i < toBeInstantiated.length) {
|
||||
const ctor = toBeInstantiated[i++];
|
||||
this.safeCreateInstance(instantiationService, ctor); // catch error so that other contributions are still considered
|
||||
if (idle.timeRemaining() < 1) {
|
||||
// time is up -> reschedule
|
||||
runWhenIdle(instantiateSome, forcedTimeout);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
runWhenIdle(instantiateSome, forcedTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private safeCreateInstance(instantiationService: IInstantiationService, ctor: IConstructorSignature0<IWorkbenchContribution>): void {
|
||||
try {
|
||||
instantiationService.createInstance(ctor);
|
||||
} catch (error) {
|
||||
console.error(`Unable to instantiate workbench contribution ${ctor.name}.`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Workbench, new WorkbenchContributionsRegistry());
|
||||
1571
lib/vscode/src/vs/workbench/common/editor.ts
Normal file
1571
lib/vscode/src/vs/workbench/common/editor.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorModel } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { MIME_BINARY } from 'vs/base/common/mime';
|
||||
|
||||
/**
|
||||
* An editor model that just represents a resource that can be loaded.
|
||||
*/
|
||||
export class BinaryEditorModel extends EditorModel {
|
||||
private size: number | undefined;
|
||||
private etag: string | undefined;
|
||||
private readonly mime: string;
|
||||
|
||||
constructor(
|
||||
public readonly resource: URI,
|
||||
private readonly name: string,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.resource = resource;
|
||||
this.name = name;
|
||||
this.mime = MIME_BINARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the binary resource.
|
||||
*/
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The size of the binary resource if known.
|
||||
*/
|
||||
getSize(): number | undefined {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* The mime of the binary resource if known.
|
||||
*/
|
||||
getMime(): string {
|
||||
return this.mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* The etag of the binary resource if known.
|
||||
*/
|
||||
getETag(): string | undefined {
|
||||
return this.etag;
|
||||
}
|
||||
|
||||
async load(): Promise<BinaryEditorModel> {
|
||||
|
||||
// Make sure to resolve up to date stat for file resources
|
||||
if (this.fileService.canHandleResource(this.resource)) {
|
||||
const stat = await this.fileService.resolve(this.resource, { resolveMetadata: true });
|
||||
this.etag = stat.etag;
|
||||
if (typeof stat.size === 'number') {
|
||||
this.size = stat.size;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
104
lib/vscode/src/vs/workbench/common/editor/diffEditorInput.ts
Normal file
104
lib/vscode/src/vs/workbench/common/editor/diffEditorInput.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorModel, EditorInput, SideBySideEditorInput, TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
|
||||
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
||||
import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel';
|
||||
import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
/**
|
||||
* The base editor input for the diff editor. It is made up of two editor inputs, the original version
|
||||
* and the modified version.
|
||||
*/
|
||||
export class DiffEditorInput extends SideBySideEditorInput {
|
||||
|
||||
static readonly ID = 'workbench.editors.diffEditorInput';
|
||||
|
||||
private cachedModel: DiffEditorModel | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
protected name: string | undefined,
|
||||
description: string | undefined,
|
||||
public readonly originalInput: EditorInput,
|
||||
public readonly modifiedInput: EditorInput,
|
||||
private readonly forceOpenAsBinary?: boolean
|
||||
) {
|
||||
super(name, description, originalInput, modifiedInput);
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return DiffEditorInput.ID;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
if (!this.name) {
|
||||
return localize('sideBySideLabels', "{0} ↔ {1}", this.originalInput.getName(), this.modifiedInput.getName());
|
||||
}
|
||||
|
||||
return this.name;
|
||||
}
|
||||
|
||||
async resolve(): Promise<EditorModel> {
|
||||
|
||||
// Create Model - we never reuse our cached model if refresh is true because we cannot
|
||||
// decide for the inputs within if the cached model can be reused or not. There may be
|
||||
// inputs that need to be loaded again and thus we always recreate the model and dispose
|
||||
// the previous one - if any.
|
||||
const resolvedModel = await this.createModel();
|
||||
if (this.cachedModel) {
|
||||
this.cachedModel.dispose();
|
||||
}
|
||||
|
||||
this.cachedModel = resolvedModel;
|
||||
|
||||
return this.cachedModel;
|
||||
}
|
||||
|
||||
getPreferredEditorId(candidates: string[]): string {
|
||||
return this.forceOpenAsBinary ? BINARY_DIFF_EDITOR_ID : TEXT_DIFF_EDITOR_ID;
|
||||
}
|
||||
|
||||
private async createModel(): Promise<DiffEditorModel> {
|
||||
|
||||
// Join resolve call over two inputs and build diff editor model
|
||||
const models = await Promise.all([
|
||||
this.originalInput.resolve(),
|
||||
this.modifiedInput.resolve()
|
||||
]);
|
||||
|
||||
const originalEditorModel = models[0];
|
||||
const modifiedEditorModel = models[1];
|
||||
|
||||
// If both are text models, return textdiffeditor model
|
||||
if (modifiedEditorModel instanceof BaseTextEditorModel && originalEditorModel instanceof BaseTextEditorModel) {
|
||||
return new TextDiffEditorModel(originalEditorModel, modifiedEditorModel);
|
||||
}
|
||||
|
||||
// Otherwise return normal diff model
|
||||
return new DiffEditorModel(originalEditorModel, modifiedEditorModel);
|
||||
}
|
||||
|
||||
matches(otherInput: unknown): boolean {
|
||||
if (!super.matches(otherInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return otherInput instanceof DiffEditorInput && otherInput.forceOpenAsBinary === this.forceOpenAsBinary;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
// Free the diff editor model but do not propagate the dispose() call to the two inputs
|
||||
// We never created the two inputs (original and modified) so we can not dispose
|
||||
// them without sideeffects.
|
||||
if (this.cachedModel) {
|
||||
this.cachedModel.dispose();
|
||||
this.cachedModel = undefined;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
49
lib/vscode/src/vs/workbench/common/editor/diffEditorModel.ts
Normal file
49
lib/vscode/src/vs/workbench/common/editor/diffEditorModel.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorModel } from 'vs/workbench/common/editor';
|
||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||
|
||||
/**
|
||||
* The base editor model for the diff editor. It is made up of two editor models, the original version
|
||||
* and the modified version.
|
||||
*/
|
||||
export class DiffEditorModel extends EditorModel {
|
||||
|
||||
protected readonly _originalModel: IEditorModel | null;
|
||||
get originalModel(): IEditorModel | null { return this._originalModel; }
|
||||
|
||||
protected readonly _modifiedModel: IEditorModel | null;
|
||||
get modifiedModel(): IEditorModel | null { return this._modifiedModel; }
|
||||
|
||||
constructor(originalModel: IEditorModel | null, modifiedModel: IEditorModel | null) {
|
||||
super();
|
||||
|
||||
this._originalModel = originalModel;
|
||||
this._modifiedModel = modifiedModel;
|
||||
}
|
||||
|
||||
async load(): Promise<EditorModel> {
|
||||
await Promise.all([
|
||||
this._originalModel?.load(),
|
||||
this._modifiedModel?.load(),
|
||||
]);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
isResolved(): boolean {
|
||||
return this.originalModel instanceof EditorModel && this.originalModel.isResolved() && this.modifiedModel instanceof EditorModel && this.modifiedModel.isResolved();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
// Do not propagate the dispose() call to the two models inside. We never created the two models
|
||||
// (original and modified) so we can not dispose them without sideeffects. Rather rely on the
|
||||
// models getting disposed when their related inputs get disposed from the diffEditorInput.
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
833
lib/vscode/src/vs/workbench/common/editor/editorGroup.ts
Normal file
833
lib/vscode/src/vs/workbench/common/editor/editorGroup.ts
Normal file
@@ -0,0 +1,833 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
|
||||
const EditorOpenPositioning = {
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right',
|
||||
FIRST: 'first',
|
||||
LAST: 'last'
|
||||
};
|
||||
|
||||
export interface EditorCloseEvent extends IEditorCloseEvent {
|
||||
readonly editor: EditorInput;
|
||||
}
|
||||
|
||||
export interface EditorIdentifier extends IEditorIdentifier {
|
||||
readonly groupId: GroupIdentifier;
|
||||
readonly editor: EditorInput;
|
||||
}
|
||||
|
||||
export interface IEditorOpenOptions {
|
||||
readonly pinned?: boolean;
|
||||
sticky?: boolean;
|
||||
active?: boolean;
|
||||
readonly index?: number;
|
||||
}
|
||||
|
||||
export interface IEditorOpenResult {
|
||||
readonly editor: EditorInput;
|
||||
readonly isNew: boolean;
|
||||
}
|
||||
|
||||
export interface ISerializedEditorInput {
|
||||
readonly id: string;
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
export interface ISerializedEditorGroup {
|
||||
readonly id: number;
|
||||
readonly editors: ISerializedEditorInput[];
|
||||
readonly mru: number[];
|
||||
readonly preview?: number;
|
||||
sticky?: number;
|
||||
}
|
||||
|
||||
export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup {
|
||||
const group = obj as ISerializedEditorGroup;
|
||||
|
||||
return !!(obj && typeof obj === 'object' && Array.isArray(group.editors) && Array.isArray(group.mru));
|
||||
}
|
||||
|
||||
export class EditorGroup extends Disposable {
|
||||
|
||||
private static IDS = 0;
|
||||
|
||||
//#region events
|
||||
|
||||
private readonly _onDidActivateEditor = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidActivateEditor = this._onDidActivateEditor.event;
|
||||
|
||||
private readonly _onDidOpenEditor = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidOpenEditor = this._onDidOpenEditor.event;
|
||||
|
||||
private readonly _onDidCloseEditor = this._register(new Emitter<EditorCloseEvent>());
|
||||
readonly onDidCloseEditor = this._onDidCloseEditor.event;
|
||||
|
||||
private readonly _onDidDisposeEditor = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidDisposeEditor = this._onDidDisposeEditor.event;
|
||||
|
||||
private readonly _onDidChangeEditorDirty = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidChangeEditorDirty = this._onDidChangeEditorDirty.event;
|
||||
|
||||
private readonly _onDidChangeEditorLabel = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidEditorLabelChange = this._onDidChangeEditorLabel.event;
|
||||
|
||||
private readonly _onDidMoveEditor = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidMoveEditor = this._onDidMoveEditor.event;
|
||||
|
||||
private readonly _onDidChangeEditorPinned = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidChangeEditorPinned = this._onDidChangeEditorPinned.event;
|
||||
|
||||
private readonly _onDidChangeEditorSticky = this._register(new Emitter<EditorInput>());
|
||||
readonly onDidChangeEditorSticky = this._onDidChangeEditorSticky.event;
|
||||
|
||||
//#endregion
|
||||
|
||||
private _id: GroupIdentifier;
|
||||
get id(): GroupIdentifier { return this._id; }
|
||||
|
||||
private editors: EditorInput[] = [];
|
||||
private mru: EditorInput[] = [];
|
||||
|
||||
private preview: EditorInput | null = null; // editor in preview state
|
||||
private active: EditorInput | null = null; // editor in active state
|
||||
private sticky: number = -1; // index of first editor in sticky state
|
||||
|
||||
private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined;
|
||||
private focusRecentEditorAfterClose: boolean | undefined;
|
||||
|
||||
constructor(
|
||||
labelOrSerializedGroup: ISerializedEditorGroup | undefined,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
if (isSerializedEditorGroup(labelOrSerializedGroup)) {
|
||||
this._id = this.deserialize(labelOrSerializedGroup);
|
||||
} else {
|
||||
this._id = EditorGroup.IDS++;
|
||||
}
|
||||
|
||||
this.onConfigurationUpdated();
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated()));
|
||||
}
|
||||
|
||||
private onConfigurationUpdated(): void {
|
||||
this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning');
|
||||
this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose');
|
||||
}
|
||||
|
||||
get count(): number {
|
||||
return this.editors.length;
|
||||
}
|
||||
|
||||
get stickyCount(): number {
|
||||
return this.sticky + 1;
|
||||
}
|
||||
|
||||
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
|
||||
const editors = order === EditorsOrder.MOST_RECENTLY_ACTIVE ? this.mru.slice(0) : this.editors.slice(0);
|
||||
|
||||
if (options?.excludeSticky) {
|
||||
|
||||
// MRU: need to check for index on each
|
||||
if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
|
||||
return editors.filter(editor => !this.isSticky(editor));
|
||||
}
|
||||
|
||||
// Sequential: simply start after sticky index
|
||||
return editors.slice(this.sticky + 1);
|
||||
}
|
||||
|
||||
return editors;
|
||||
}
|
||||
|
||||
getEditorByIndex(index: number): EditorInput | undefined {
|
||||
return this.editors[index];
|
||||
}
|
||||
|
||||
get activeEditor(): EditorInput | null {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
isActive(editor: EditorInput): boolean {
|
||||
return this.matches(this.active, editor);
|
||||
}
|
||||
|
||||
get previewEditor(): EditorInput | null {
|
||||
return this.preview;
|
||||
}
|
||||
|
||||
openEditor(candidate: EditorInput, options?: IEditorOpenOptions): IEditorOpenResult {
|
||||
const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index));
|
||||
const makePinned = options?.pinned || options?.sticky;
|
||||
const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
|
||||
|
||||
const existingEditorAndIndex = this.findEditor(candidate);
|
||||
|
||||
// New editor
|
||||
if (!existingEditorAndIndex) {
|
||||
const newEditor = candidate;
|
||||
const indexOfActive = this.indexOf(this.active);
|
||||
|
||||
// Insert into specific position
|
||||
let targetIndex: number;
|
||||
if (options && typeof options.index === 'number') {
|
||||
targetIndex = options.index;
|
||||
}
|
||||
|
||||
// Insert to the BEGINNING
|
||||
else if (this.editorOpenPositioning === EditorOpenPositioning.FIRST) {
|
||||
targetIndex = 0;
|
||||
|
||||
// Always make sure targetIndex is after sticky editors
|
||||
// unless we are explicitly told to make the editor sticky
|
||||
if (!makeSticky && this.isSticky(targetIndex)) {
|
||||
targetIndex = this.sticky + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert to the END
|
||||
else if (this.editorOpenPositioning === EditorOpenPositioning.LAST) {
|
||||
targetIndex = this.editors.length;
|
||||
}
|
||||
|
||||
// Insert to LEFT or RIGHT of active editor
|
||||
else {
|
||||
|
||||
// Insert to the LEFT of active editor
|
||||
if (this.editorOpenPositioning === EditorOpenPositioning.LEFT) {
|
||||
if (indexOfActive === 0 || !this.editors.length) {
|
||||
targetIndex = 0; // to the left becoming first editor in list
|
||||
} else {
|
||||
targetIndex = indexOfActive; // to the left of active editor
|
||||
}
|
||||
}
|
||||
|
||||
// Insert to the RIGHT of active editor
|
||||
else {
|
||||
targetIndex = indexOfActive + 1;
|
||||
}
|
||||
|
||||
// Always make sure targetIndex is after sticky editors
|
||||
// unless we are explicitly told to make the editor sticky
|
||||
if (!makeSticky && this.isSticky(targetIndex)) {
|
||||
targetIndex = this.sticky + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If the editor becomes sticky, increment the sticky index and adjust
|
||||
// the targetIndex to be at the end of sticky editors unless already.
|
||||
if (makeSticky) {
|
||||
this.sticky++;
|
||||
|
||||
if (!this.isSticky(targetIndex)) {
|
||||
targetIndex = this.sticky;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert into our list of editors if pinned or we have no preview editor
|
||||
if (makePinned || !this.preview) {
|
||||
this.splice(targetIndex, false, newEditor);
|
||||
}
|
||||
|
||||
// Handle preview
|
||||
if (!makePinned) {
|
||||
|
||||
// Replace existing preview with this editor if we have a preview
|
||||
if (this.preview) {
|
||||
const indexOfPreview = this.indexOf(this.preview);
|
||||
if (targetIndex > indexOfPreview) {
|
||||
targetIndex--; // accomodate for the fact that the preview editor closes
|
||||
}
|
||||
|
||||
this.replaceEditor(this.preview, newEditor, targetIndex, !makeActive);
|
||||
}
|
||||
|
||||
this.preview = newEditor;
|
||||
}
|
||||
|
||||
// Listeners
|
||||
this.registerEditorListeners(newEditor);
|
||||
|
||||
// Event
|
||||
this._onDidOpenEditor.fire(newEditor);
|
||||
|
||||
// Handle active
|
||||
if (makeActive) {
|
||||
this.doSetActive(newEditor);
|
||||
}
|
||||
|
||||
return {
|
||||
editor: newEditor,
|
||||
isNew: true
|
||||
};
|
||||
}
|
||||
|
||||
// Existing editor
|
||||
else {
|
||||
const [existingEditor] = existingEditorAndIndex;
|
||||
|
||||
// Pin it
|
||||
if (makePinned) {
|
||||
this.doPin(existingEditor);
|
||||
}
|
||||
|
||||
// Activate it
|
||||
if (makeActive) {
|
||||
this.doSetActive(existingEditor);
|
||||
}
|
||||
|
||||
// Respect index
|
||||
if (options && typeof options.index === 'number') {
|
||||
this.moveEditor(existingEditor, options.index);
|
||||
}
|
||||
|
||||
// Stick it (intentionally after the moveEditor call in case
|
||||
// the editor was already moved into the sticky range)
|
||||
if (makeSticky) {
|
||||
this.doStick(existingEditor, this.indexOf(existingEditor));
|
||||
}
|
||||
|
||||
return {
|
||||
editor: existingEditor,
|
||||
isNew: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private registerEditorListeners(editor: EditorInput): void {
|
||||
const listeners = new DisposableStore();
|
||||
|
||||
// Re-emit disposal of editor input as our own event
|
||||
listeners.add(Event.once(editor.onDispose)(() => {
|
||||
if (this.indexOf(editor) >= 0) {
|
||||
this._onDidDisposeEditor.fire(editor);
|
||||
}
|
||||
}));
|
||||
|
||||
// Re-Emit dirty state changes
|
||||
listeners.add(editor.onDidChangeDirty(() => {
|
||||
this._onDidChangeEditorDirty.fire(editor);
|
||||
}));
|
||||
|
||||
// Re-Emit label changes
|
||||
listeners.add(editor.onDidChangeLabel(() => {
|
||||
this._onDidChangeEditorLabel.fire(editor);
|
||||
}));
|
||||
|
||||
// Clean up dispose listeners once the editor gets closed
|
||||
listeners.add(this.onDidCloseEditor(event => {
|
||||
if (event.editor.matches(editor)) {
|
||||
dispose(listeners);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private replaceEditor(toReplace: EditorInput, replaceWith: EditorInput, replaceIndex: number, openNext = true): void {
|
||||
const event = this.doCloseEditor(toReplace, openNext, true); // optimization to prevent multiple setActive() in one call
|
||||
|
||||
// We want to first add the new editor into our model before emitting the close event because
|
||||
// firing the close event can trigger a dispose on the same editor that is now being added.
|
||||
// This can lead into opening a disposed editor which is not what we want.
|
||||
this.splice(replaceIndex, false, replaceWith);
|
||||
|
||||
if (event) {
|
||||
this._onDidCloseEditor.fire(event);
|
||||
}
|
||||
}
|
||||
|
||||
closeEditor(candidate: EditorInput, openNext = true): EditorInput | undefined {
|
||||
const event = this.doCloseEditor(candidate, openNext, false);
|
||||
|
||||
if (event) {
|
||||
this._onDidCloseEditor.fire(event);
|
||||
|
||||
return event.editor;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private doCloseEditor(candidate: EditorInput, openNext: boolean, replaced: boolean): EditorCloseEvent | undefined {
|
||||
const index = this.indexOf(candidate);
|
||||
if (index === -1) {
|
||||
return undefined; // not found
|
||||
}
|
||||
|
||||
const editor = this.editors[index];
|
||||
const sticky = this.isSticky(index);
|
||||
|
||||
// Active Editor closed
|
||||
if (openNext && this.matches(this.active, editor)) {
|
||||
|
||||
// More than one editor
|
||||
if (this.mru.length > 1) {
|
||||
let newActive: EditorInput;
|
||||
if (this.focusRecentEditorAfterClose) {
|
||||
newActive = this.mru[1]; // active editor is always first in MRU, so pick second editor after as new active
|
||||
} else {
|
||||
if (index === this.editors.length - 1) {
|
||||
newActive = this.editors[index - 1]; // last editor is closed, pick previous as new active
|
||||
} else {
|
||||
newActive = this.editors[index + 1]; // pick next editor as new active
|
||||
}
|
||||
}
|
||||
|
||||
this.doSetActive(newActive);
|
||||
}
|
||||
|
||||
// One Editor
|
||||
else {
|
||||
this.active = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Preview Editor closed
|
||||
if (this.matches(this.preview, editor)) {
|
||||
this.preview = null;
|
||||
}
|
||||
|
||||
// Remove from arrays
|
||||
this.splice(index, true);
|
||||
|
||||
// Event
|
||||
return { editor, replaced, sticky, index, groupId: this.id };
|
||||
}
|
||||
|
||||
moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined {
|
||||
|
||||
// Ensure toIndex is in bounds of our model
|
||||
if (toIndex >= this.editors.length) {
|
||||
toIndex = this.editors.length - 1;
|
||||
} else if (toIndex < 0) {
|
||||
toIndex = 0;
|
||||
}
|
||||
|
||||
const index = this.indexOf(candidate);
|
||||
if (index < 0 || toIndex === index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = this.editors[index];
|
||||
|
||||
// Adjust sticky index: editor moved out of sticky state into unsticky state
|
||||
if (this.isSticky(index) && toIndex > this.sticky) {
|
||||
this.sticky--;
|
||||
}
|
||||
|
||||
// ...or editor moved into sticky state from unsticky state
|
||||
else if (!this.isSticky(index) && toIndex <= this.sticky) {
|
||||
this.sticky++;
|
||||
}
|
||||
|
||||
// Move
|
||||
this.editors.splice(index, 1);
|
||||
this.editors.splice(toIndex, 0, editor);
|
||||
|
||||
// Event
|
||||
this._onDidMoveEditor.fire(editor);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
setActive(candidate: EditorInput): EditorInput | undefined {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return; // not found
|
||||
}
|
||||
|
||||
const [editor] = res;
|
||||
|
||||
this.doSetActive(editor);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doSetActive(editor: EditorInput): void {
|
||||
if (this.matches(this.active, editor)) {
|
||||
return; // already active
|
||||
}
|
||||
|
||||
this.active = editor;
|
||||
|
||||
// Bring to front in MRU list
|
||||
const mruIndex = this.indexOf(editor, this.mru);
|
||||
this.mru.splice(mruIndex, 1);
|
||||
this.mru.unshift(editor);
|
||||
|
||||
// Event
|
||||
this._onDidActivateEditor.fire(editor);
|
||||
}
|
||||
|
||||
pin(candidate: EditorInput): EditorInput | undefined {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return; // not found
|
||||
}
|
||||
|
||||
const [editor] = res;
|
||||
|
||||
this.doPin(editor);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doPin(editor: EditorInput): void {
|
||||
if (this.isPinned(editor)) {
|
||||
return; // can only pin a preview editor
|
||||
}
|
||||
|
||||
// Convert the preview editor to be a pinned editor
|
||||
this.preview = null;
|
||||
|
||||
// Event
|
||||
this._onDidChangeEditorPinned.fire(editor);
|
||||
}
|
||||
|
||||
unpin(candidate: EditorInput): EditorInput | undefined {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return; // not found
|
||||
}
|
||||
|
||||
const [editor] = res;
|
||||
|
||||
this.doUnpin(editor);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doUnpin(editor: EditorInput): void {
|
||||
if (!this.isPinned(editor)) {
|
||||
return; // can only unpin a pinned editor
|
||||
}
|
||||
|
||||
// Set new
|
||||
const oldPreview = this.preview;
|
||||
this.preview = editor;
|
||||
|
||||
// Event
|
||||
this._onDidChangeEditorPinned.fire(editor);
|
||||
|
||||
// Close old preview editor if any
|
||||
if (oldPreview) {
|
||||
this.closeEditor(oldPreview);
|
||||
}
|
||||
}
|
||||
|
||||
isPinned(editorOrIndex: EditorInput | number): boolean {
|
||||
let editor: EditorInput;
|
||||
if (typeof editorOrIndex === 'number') {
|
||||
editor = this.editors[editorOrIndex];
|
||||
} else {
|
||||
editor = editorOrIndex;
|
||||
}
|
||||
|
||||
return !this.matches(this.preview, editor);
|
||||
}
|
||||
|
||||
stick(candidate: EditorInput): EditorInput | undefined {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return; // not found
|
||||
}
|
||||
|
||||
const [editor, index] = res;
|
||||
|
||||
this.doStick(editor, index);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doStick(editor: EditorInput, index: number): void {
|
||||
if (this.isSticky(index)) {
|
||||
return; // can only stick a non-sticky editor
|
||||
}
|
||||
|
||||
// Pin editor
|
||||
this.pin(editor);
|
||||
|
||||
// Move editor to be the last sticky editor
|
||||
this.moveEditor(editor, this.sticky + 1);
|
||||
|
||||
// Adjust sticky index
|
||||
this.sticky++;
|
||||
|
||||
// Event
|
||||
this._onDidChangeEditorSticky.fire(editor);
|
||||
}
|
||||
|
||||
unstick(candidate: EditorInput): EditorInput | undefined {
|
||||
const res = this.findEditor(candidate);
|
||||
if (!res) {
|
||||
return; // not found
|
||||
}
|
||||
|
||||
const [editor, index] = res;
|
||||
|
||||
this.doUnstick(editor, index);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doUnstick(editor: EditorInput, index: number): void {
|
||||
if (!this.isSticky(index)) {
|
||||
return; // can only unstick a sticky editor
|
||||
}
|
||||
|
||||
// Move editor to be the first non-sticky editor
|
||||
this.moveEditor(editor, this.sticky);
|
||||
|
||||
// Adjust sticky index
|
||||
this.sticky--;
|
||||
|
||||
// Event
|
||||
this._onDidChangeEditorSticky.fire(editor);
|
||||
}
|
||||
|
||||
isSticky(candidateOrIndex: EditorInput | number): boolean {
|
||||
if (this.sticky < 0) {
|
||||
return false; // no sticky editor
|
||||
}
|
||||
|
||||
let index: number;
|
||||
if (typeof candidateOrIndex === 'number') {
|
||||
index = candidateOrIndex;
|
||||
} else {
|
||||
index = this.indexOf(candidateOrIndex);
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return index <= this.sticky;
|
||||
}
|
||||
|
||||
private splice(index: number, del: boolean, editor?: EditorInput): void {
|
||||
const editorToDeleteOrReplace = this.editors[index];
|
||||
|
||||
// Perform on sticky index
|
||||
if (del && this.isSticky(index)) {
|
||||
this.sticky--;
|
||||
}
|
||||
|
||||
// Perform on editors array
|
||||
if (editor) {
|
||||
this.editors.splice(index, del ? 1 : 0, editor);
|
||||
} else {
|
||||
this.editors.splice(index, del ? 1 : 0);
|
||||
}
|
||||
|
||||
// Perform on MRU
|
||||
{
|
||||
// Add
|
||||
if (!del && editor) {
|
||||
if (this.mru.length === 0) {
|
||||
// the list of most recent editors is empty
|
||||
// so this editor can only be the most recent
|
||||
this.mru.push(editor);
|
||||
} else {
|
||||
// we have most recent editors. as such we
|
||||
// put this newly opened editor right after
|
||||
// the current most recent one because it cannot
|
||||
// be the most recently active one unless
|
||||
// it becomes active. but it is still more
|
||||
// active then any other editor in the list.
|
||||
this.mru.splice(1, 0, editor);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove / Replace
|
||||
else {
|
||||
const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
|
||||
|
||||
// Remove
|
||||
if (del && !editor) {
|
||||
this.mru.splice(indexInMRU, 1); // remove from MRU
|
||||
}
|
||||
|
||||
// Replace
|
||||
else if (del && editor) {
|
||||
this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indexOf(candidate: IEditorInput | null, editors = this.editors): number {
|
||||
if (!candidate) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (let i = 0; i < editors.length; i++) {
|
||||
if (this.matches(editors[i], candidate)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private findEditor(candidate: EditorInput | null): [EditorInput, number /* index */] | undefined {
|
||||
const index = this.indexOf(candidate, this.editors);
|
||||
if (index === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [this.editors[index], index];
|
||||
}
|
||||
|
||||
contains(candidate: EditorInput, options?: { supportSideBySide?: boolean, strictEquals?: boolean }): boolean {
|
||||
for (const editor of this.editors) {
|
||||
if (this.matches(editor, candidate, options?.strictEquals)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) {
|
||||
if (this.matches(editor.primary, candidate, options?.strictEquals) || this.matches(editor.secondary, candidate, options?.strictEquals)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private matches(editor: IEditorInput | null, candidate: IEditorInput | null, strictEquals?: boolean): boolean {
|
||||
if (!editor || !candidate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strictEquals) {
|
||||
return editor === candidate;
|
||||
}
|
||||
|
||||
return editor.matches(candidate);
|
||||
}
|
||||
|
||||
clone(): EditorGroup {
|
||||
const group = this.instantiationService.createInstance(EditorGroup, undefined);
|
||||
group.editors = this.editors.slice(0);
|
||||
group.mru = this.mru.slice(0);
|
||||
group.preview = this.preview;
|
||||
group.active = this.active;
|
||||
group.sticky = this.sticky;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
serialize(): ISerializedEditorGroup {
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
|
||||
|
||||
// Serialize all editor inputs so that we can store them.
|
||||
// Editors that cannot be serialized need to be ignored
|
||||
// from mru, active, preview and sticky if any.
|
||||
let serializableEditors: EditorInput[] = [];
|
||||
let serializedEditors: ISerializedEditorInput[] = [];
|
||||
let serializablePreviewIndex: number | undefined;
|
||||
let serializableSticky = this.sticky;
|
||||
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
let canSerializeEditor = false;
|
||||
|
||||
const factory = registry.getEditorInputFactory(editor.getTypeId());
|
||||
if (factory) {
|
||||
const value = factory.serialize(editor);
|
||||
|
||||
// Editor can be serialized
|
||||
if (typeof value === 'string') {
|
||||
canSerializeEditor = true;
|
||||
|
||||
serializedEditors.push({ id: editor.getTypeId(), value });
|
||||
serializableEditors.push(editor);
|
||||
|
||||
if (this.preview === editor) {
|
||||
serializablePreviewIndex = serializableEditors.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Editor cannot be serialized
|
||||
else {
|
||||
canSerializeEditor = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust index of sticky editors if the editor cannot be serialized and is pinned
|
||||
if (!canSerializeEditor && this.isSticky(i)) {
|
||||
serializableSticky--;
|
||||
}
|
||||
}
|
||||
|
||||
const serializableMru = this.mru.map(editor => this.indexOf(editor, serializableEditors)).filter(i => i >= 0);
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
editors: serializedEditors,
|
||||
mru: serializableMru,
|
||||
preview: serializablePreviewIndex,
|
||||
sticky: serializableSticky >= 0 ? serializableSticky : undefined
|
||||
};
|
||||
}
|
||||
|
||||
private deserialize(data: ISerializedEditorGroup): number {
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
|
||||
|
||||
if (typeof data.id === 'number') {
|
||||
this._id = data.id;
|
||||
|
||||
EditorGroup.IDS = Math.max(data.id + 1, EditorGroup.IDS); // make sure our ID generator is always larger
|
||||
} else {
|
||||
this._id = EditorGroup.IDS++; // backwards compatibility
|
||||
}
|
||||
|
||||
this.editors = coalesce(data.editors.map((e, index) => {
|
||||
let editor: EditorInput | undefined = undefined;
|
||||
|
||||
const factory = registry.getEditorInputFactory(e.id);
|
||||
if (factory) {
|
||||
editor = factory.deserialize(this.instantiationService, e.value);
|
||||
if (editor) {
|
||||
this.registerEditorListeners(editor);
|
||||
}
|
||||
}
|
||||
|
||||
if (!editor && typeof data.sticky === 'number' && index <= data.sticky) {
|
||||
data.sticky--; // if editor cannot be deserialized but was sticky, we need to decrease sticky index
|
||||
}
|
||||
|
||||
return editor;
|
||||
}));
|
||||
|
||||
this.mru = coalesce(data.mru.map(i => this.editors[i]));
|
||||
|
||||
this.active = this.mru[0];
|
||||
|
||||
if (typeof data.preview === 'number') {
|
||||
this.preview = this.editors[data.preview];
|
||||
}
|
||||
|
||||
if (typeof data.sticky === 'number') {
|
||||
this.sticky = data.sticky;
|
||||
}
|
||||
|
||||
return this._id;
|
||||
}
|
||||
}
|
||||
134
lib/vscode/src/vs/workbench/common/editor/resourceEditorInput.ts
Normal file
134
lib/vscode/src/vs/workbench/common/editor/resourceEditorInput.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITextEditorModel, IModeSupport } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IReference } from 'vs/base/common/lifecycle';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
/**
|
||||
* A read-only text editor input whos contents are made of the provided resource that points to an existing
|
||||
* code editor model.
|
||||
*/
|
||||
export class ResourceEditorInput extends AbstractTextResourceEditorInput implements IModeSupport {
|
||||
|
||||
static readonly ID: string = 'workbench.editors.resourceEditorInput';
|
||||
|
||||
private cachedModel: ResourceEditorModel | undefined = undefined;
|
||||
private modelReference: Promise<IReference<ITextEditorModel>> | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
resource: URI,
|
||||
private name: string | undefined,
|
||||
private description: string | undefined,
|
||||
private preferredMode: string | undefined,
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IFileService fileService: IFileService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService
|
||||
) {
|
||||
super(resource, undefined, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService);
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return ResourceEditorInput.ID;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.name || super.getName();
|
||||
}
|
||||
|
||||
setName(name: string): void {
|
||||
if (this.name !== name) {
|
||||
this.name = name;
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
}
|
||||
|
||||
getDescription(): string | undefined {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
setDescription(description: string): void {
|
||||
if (this.description !== description) {
|
||||
this.description = description;
|
||||
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
}
|
||||
|
||||
setMode(mode: string): void {
|
||||
this.setPreferredMode(mode);
|
||||
|
||||
if (this.cachedModel) {
|
||||
this.cachedModel.setMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
setPreferredMode(mode: string): void {
|
||||
this.preferredMode = mode;
|
||||
}
|
||||
|
||||
async resolve(): Promise<ITextEditorModel> {
|
||||
if (!this.modelReference) {
|
||||
this.modelReference = this.textModelResolverService.createModelReference(this.resource);
|
||||
}
|
||||
|
||||
const ref = await this.modelReference;
|
||||
|
||||
// Ensure the resolved model is of expected type
|
||||
const model = ref.object;
|
||||
if (!(model instanceof ResourceEditorModel)) {
|
||||
ref.dispose();
|
||||
this.modelReference = undefined;
|
||||
|
||||
throw new Error(`Unexpected model for ResourcEditorInput: ${this.resource}`);
|
||||
}
|
||||
|
||||
this.cachedModel = model;
|
||||
|
||||
// Set mode if we have a preferred mode configured
|
||||
if (this.preferredMode) {
|
||||
model.setMode(this.preferredMode);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
matches(otherInput: unknown): boolean {
|
||||
if (otherInput === this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (otherInput instanceof ResourceEditorInput) {
|
||||
return isEqual(otherInput.resource, this.resource);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.modelReference) {
|
||||
this.modelReference.then(ref => ref.dispose());
|
||||
this.modelReference = undefined;
|
||||
}
|
||||
|
||||
this.cachedModel = undefined;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
|
||||
/**
|
||||
* An editor model for in-memory, readonly content that is backed by an existing editor model.
|
||||
*/
|
||||
export class ResourceEditorModel extends BaseTextEditorModel {
|
||||
|
||||
constructor(
|
||||
resource: URI,
|
||||
@IModeService modeService: IModeService,
|
||||
@IModelService modelService: IModelService
|
||||
) {
|
||||
super(modelService, modeService, resource);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
// TODO@Joao: force this class to dispose the underlying model
|
||||
if (this.textEditorModelHandle) {
|
||||
this.modelService.destroyModel(this.textEditorModelHandle);
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDiffEditorModel } from 'vs/editor/common/editorCommon';
|
||||
import { EditorModel } from 'vs/workbench/common/editor';
|
||||
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
||||
import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel';
|
||||
|
||||
/**
|
||||
* The base text editor model for the diff editor. It is made up of two text editor models, the original version
|
||||
* and the modified version.
|
||||
*/
|
||||
export class TextDiffEditorModel extends DiffEditorModel {
|
||||
|
||||
protected readonly _originalModel: BaseTextEditorModel | null;
|
||||
get originalModel(): BaseTextEditorModel | null { return this._originalModel; }
|
||||
|
||||
protected readonly _modifiedModel: BaseTextEditorModel | null;
|
||||
get modifiedModel(): BaseTextEditorModel | null { return this._modifiedModel; }
|
||||
|
||||
private _textDiffEditorModel: IDiffEditorModel | null = null;
|
||||
get textDiffEditorModel(): IDiffEditorModel | null { return this._textDiffEditorModel; }
|
||||
|
||||
constructor(originalModel: BaseTextEditorModel, modifiedModel: BaseTextEditorModel) {
|
||||
super(originalModel, modifiedModel);
|
||||
|
||||
this._originalModel = originalModel;
|
||||
this._modifiedModel = modifiedModel;
|
||||
|
||||
this.updateTextDiffEditorModel();
|
||||
}
|
||||
|
||||
async load(): Promise<EditorModel> {
|
||||
await super.load();
|
||||
|
||||
this.updateTextDiffEditorModel();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private updateTextDiffEditorModel(): void {
|
||||
if (this.originalModel?.isResolved() && this.modifiedModel?.isResolved()) {
|
||||
|
||||
// Create new
|
||||
if (!this._textDiffEditorModel) {
|
||||
this._textDiffEditorModel = {
|
||||
original: this.originalModel.textEditorModel,
|
||||
modified: this.modifiedModel.textEditorModel
|
||||
};
|
||||
}
|
||||
|
||||
// Update existing
|
||||
else {
|
||||
this._textDiffEditorModel.original = this.originalModel.textEditorModel;
|
||||
this._textDiffEditorModel.modified = this.modifiedModel.textEditorModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isResolved(): boolean {
|
||||
return !!this._textDiffEditorModel;
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return !!this.modifiedModel && this.modifiedModel.isReadonly();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
// Free the diff editor model but do not propagate the dispose() call to the two models
|
||||
// inside. We never created the two models (original and modified) so we can not dispose
|
||||
// them without sideeffects. Rather rely on the models getting disposed when their related
|
||||
// inputs get disposed from the diffEditorInput.
|
||||
this._textDiffEditorModel = null;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
186
lib/vscode/src/vs/workbench/common/editor/textEditorModel.ts
Normal file
186
lib/vscode/src/vs/workbench/common/editor/textEditorModel.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITextModel, ITextBufferFactory, ITextSnapshot, ModelConstants } from 'vs/editor/common/model';
|
||||
import { EditorModel, IModeSupport } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
|
||||
/**
|
||||
* The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated.
|
||||
*/
|
||||
export class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport {
|
||||
|
||||
protected textEditorModelHandle: URI | null = null;
|
||||
|
||||
private createdEditorModel: boolean | undefined;
|
||||
|
||||
private readonly modelDisposeListener = this._register(new MutableDisposable());
|
||||
|
||||
constructor(
|
||||
@IModelService protected modelService: IModelService,
|
||||
@IModeService protected modeService: IModeService,
|
||||
textEditorModelHandle?: URI
|
||||
) {
|
||||
super();
|
||||
|
||||
if (textEditorModelHandle) {
|
||||
this.handleExistingModel(textEditorModelHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private handleExistingModel(textEditorModelHandle: URI): void {
|
||||
|
||||
// We need the resource to point to an existing model
|
||||
const model = this.modelService.getModel(textEditorModelHandle);
|
||||
if (!model) {
|
||||
throw new Error(`Document with resource ${textEditorModelHandle.toString(true)} does not exist`);
|
||||
}
|
||||
|
||||
this.textEditorModelHandle = textEditorModelHandle;
|
||||
|
||||
// Make sure we clean up when this model gets disposed
|
||||
this.registerModelDisposeListener(model);
|
||||
}
|
||||
|
||||
private registerModelDisposeListener(model: ITextModel): void {
|
||||
this.modelDisposeListener.value = model.onWillDispose(() => {
|
||||
this.textEditorModelHandle = null; // make sure we do not dispose code editor model again
|
||||
this.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
get textEditorModel(): ITextModel | null {
|
||||
return this.textEditorModelHandle ? this.modelService.getModel(this.textEditorModelHandle) : null;
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
setMode(mode: string): void {
|
||||
if (!this.isResolved()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mode || mode === this.textEditorModel.getModeId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modelService.setMode(this.textEditorModel, this.modeService.create(mode));
|
||||
}
|
||||
|
||||
getMode(): string | undefined {
|
||||
return this.textEditorModel?.getModeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the text editor model with the provided value, optional preferred mode
|
||||
* (can be comma separated for multiple values) and optional resource URL.
|
||||
*/
|
||||
protected createTextEditorModel(value: ITextBufferFactory, resource: URI | undefined, preferredMode?: string): ITextModel {
|
||||
const firstLineText = this.getFirstLineText(value);
|
||||
const languageSelection = this.getOrCreateMode(resource, this.modeService, preferredMode, firstLineText);
|
||||
|
||||
return this.doCreateTextEditorModel(value, languageSelection, resource);
|
||||
}
|
||||
|
||||
private doCreateTextEditorModel(value: ITextBufferFactory, languageSelection: ILanguageSelection, resource: URI | undefined): ITextModel {
|
||||
let model = resource && this.modelService.getModel(resource);
|
||||
if (!model) {
|
||||
model = this.modelService.createModel(value, languageSelection, resource);
|
||||
this.createdEditorModel = true;
|
||||
|
||||
// Make sure we clean up when this model gets disposed
|
||||
this.registerModelDisposeListener(model);
|
||||
} else {
|
||||
this.updateTextEditorModel(value, languageSelection.languageIdentifier.language);
|
||||
}
|
||||
|
||||
this.textEditorModelHandle = model.uri;
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
protected getFirstLineText(value: ITextBufferFactory | ITextModel): string {
|
||||
|
||||
// text buffer factory
|
||||
const textBufferFactory = value as ITextBufferFactory;
|
||||
if (typeof textBufferFactory.getFirstLineText === 'function') {
|
||||
return textBufferFactory.getFirstLineText(ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT);
|
||||
}
|
||||
|
||||
// text model
|
||||
const textSnapshot = value as ITextModel;
|
||||
return textSnapshot.getLineContent(1).substr(0, ModelConstants.FIRST_LINE_DETECTION_LENGTH_LIMIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mode for the given identifier. Subclasses can override to provide their own implementation of this lookup.
|
||||
*
|
||||
* @param firstLineText optional first line of the text buffer to set the mode on. This can be used to guess a mode from content.
|
||||
*/
|
||||
protected getOrCreateMode(resource: URI | undefined, modeService: IModeService, preferredMode: string | undefined, firstLineText?: string): ILanguageSelection {
|
||||
|
||||
// lookup mode via resource path if the provided mode is unspecific
|
||||
if (!preferredMode || preferredMode === PLAINTEXT_MODE_ID) {
|
||||
return modeService.createByFilepathOrFirstLine(withUndefinedAsNull(resource), firstLineText);
|
||||
}
|
||||
|
||||
// otherwise take the preferred mode for granted
|
||||
return modeService.create(preferredMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the text editor model with the provided value. If the value is the same as the model has, this is a no-op.
|
||||
*/
|
||||
updateTextEditorModel(newValue?: ITextBufferFactory, preferredMode?: string): void {
|
||||
if (!this.isResolved()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// contents
|
||||
if (newValue) {
|
||||
this.modelService.updateModel(this.textEditorModel, newValue);
|
||||
}
|
||||
|
||||
// mode (only if specific and changed)
|
||||
if (preferredMode && preferredMode !== PLAINTEXT_MODE_ID && this.textEditorModel.getModeId() !== preferredMode) {
|
||||
this.modelService.setMode(this.textEditorModel, this.modeService.create(preferredMode));
|
||||
}
|
||||
}
|
||||
|
||||
createSnapshot(this: IResolvedTextEditorModel): ITextSnapshot;
|
||||
createSnapshot(this: ITextEditorModel): ITextSnapshot | null;
|
||||
createSnapshot(): ITextSnapshot | null {
|
||||
if (!this.textEditorModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.textEditorModel.createSnapshot(true /* preserve BOM */);
|
||||
}
|
||||
|
||||
isResolved(): this is IResolvedTextEditorModel {
|
||||
return !!this.textEditorModelHandle;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.modelDisposeListener.dispose(); // dispose this first because it will trigger another dispose() otherwise
|
||||
|
||||
if (this.textEditorModelHandle && this.createdEditorModel) {
|
||||
this.modelService.destroyModel(this.textEditorModelHandle);
|
||||
}
|
||||
|
||||
this.textEditorModelHandle = null;
|
||||
this.createdEditorModel = false;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, Verbosity, GroupIdentifier, IEditorInput, ISaveOptions, IRevertOptions, IEditorInputWithPreferredResource } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { dirname, isEqual } from 'vs/base/common/resources';
|
||||
|
||||
/**
|
||||
* The base class for all editor inputs that open in text editors.
|
||||
*/
|
||||
export abstract class AbstractTextResourceEditorInput extends EditorInput implements IEditorInputWithPreferredResource {
|
||||
|
||||
private _preferredResource: URI;
|
||||
get preferredResource(): URI { return this._preferredResource; }
|
||||
|
||||
constructor(
|
||||
public readonly resource: URI,
|
||||
preferredResource: URI | undefined,
|
||||
@IEditorService protected readonly editorService: IEditorService,
|
||||
@IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService,
|
||||
@ITextFileService protected readonly textFileService: ITextFileService,
|
||||
@ILabelService protected readonly labelService: ILabelService,
|
||||
@IFileService protected readonly fileService: IFileService,
|
||||
@IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._preferredResource = preferredResource || resource;
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
protected registerListeners(): void {
|
||||
|
||||
// Clear label memoizer on certain events that have impact
|
||||
this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme)));
|
||||
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme)));
|
||||
this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme)));
|
||||
}
|
||||
|
||||
private onLabelEvent(scheme: string): void {
|
||||
if (scheme === this._preferredResource.scheme) {
|
||||
this.updateLabel();
|
||||
}
|
||||
}
|
||||
|
||||
private updateLabel(): void {
|
||||
|
||||
// Clear any cached labels from before
|
||||
this._basename = undefined;
|
||||
this._shortDescription = undefined;
|
||||
this._mediumDescription = undefined;
|
||||
this._longDescription = undefined;
|
||||
this._shortTitle = undefined;
|
||||
this._mediumTitle = undefined;
|
||||
this._longTitle = undefined;
|
||||
|
||||
// Trigger recompute of label
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
|
||||
setPreferredResource(preferredResource: URI): void {
|
||||
if (!isEqual(preferredResource, this._preferredResource)) {
|
||||
this._preferredResource = preferredResource;
|
||||
|
||||
this.updateLabel();
|
||||
}
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.basename;
|
||||
}
|
||||
|
||||
private _basename: string | undefined;
|
||||
private get basename(): string {
|
||||
if (!this._basename) {
|
||||
this._basename = this.labelService.getUriBasenameLabel(this._preferredResource);
|
||||
}
|
||||
|
||||
return this._basename;
|
||||
}
|
||||
|
||||
getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined {
|
||||
switch (verbosity) {
|
||||
case Verbosity.SHORT:
|
||||
return this.shortDescription;
|
||||
case Verbosity.LONG:
|
||||
return this.longDescription;
|
||||
case Verbosity.MEDIUM:
|
||||
default:
|
||||
return this.mediumDescription;
|
||||
}
|
||||
}
|
||||
|
||||
private _shortDescription: string | undefined = undefined;
|
||||
private get shortDescription(): string {
|
||||
if (!this._shortDescription) {
|
||||
this._shortDescription = this.labelService.getUriBasenameLabel(dirname(this._preferredResource));
|
||||
}
|
||||
return this._shortDescription;
|
||||
}
|
||||
|
||||
private _mediumDescription: string | undefined = undefined;
|
||||
private get mediumDescription(): string {
|
||||
if (!this._mediumDescription) {
|
||||
this._mediumDescription = this.labelService.getUriLabel(dirname(this._preferredResource), { relative: true });
|
||||
}
|
||||
return this._mediumDescription;
|
||||
}
|
||||
|
||||
private _longDescription: string | undefined = undefined;
|
||||
private get longDescription(): string {
|
||||
if (!this._longDescription) {
|
||||
this._longDescription = this.labelService.getUriLabel(dirname(this._preferredResource));
|
||||
}
|
||||
return this._longDescription;
|
||||
}
|
||||
|
||||
private _shortTitle: string | undefined = undefined;
|
||||
private get shortTitle(): string {
|
||||
if (!this._shortTitle) {
|
||||
this._shortTitle = this.getName();
|
||||
}
|
||||
return this._shortTitle;
|
||||
}
|
||||
|
||||
private _mediumTitle: string | undefined = undefined;
|
||||
private get mediumTitle(): string {
|
||||
if (!this._mediumTitle) {
|
||||
this._mediumTitle = this.labelService.getUriLabel(this._preferredResource, { relative: true });
|
||||
}
|
||||
return this._mediumTitle;
|
||||
}
|
||||
|
||||
private _longTitle: string | undefined = undefined;
|
||||
private get longTitle(): string {
|
||||
if (!this._longTitle) {
|
||||
this._longTitle = this.labelService.getUriLabel(this._preferredResource);
|
||||
}
|
||||
return this._longTitle;
|
||||
}
|
||||
|
||||
getTitle(verbosity: Verbosity): string {
|
||||
switch (verbosity) {
|
||||
case Verbosity.SHORT:
|
||||
return this.shortTitle;
|
||||
case Verbosity.LONG:
|
||||
return this.longTitle;
|
||||
default:
|
||||
case Verbosity.MEDIUM:
|
||||
return this.mediumTitle;
|
||||
}
|
||||
}
|
||||
|
||||
isUntitled(): boolean {
|
||||
// anyFile: is never untitled as it can be saved
|
||||
// untitled: is untitled by definition
|
||||
// anyOther: is untitled because it cannot be saved, as such we expect a "Save As" dialog
|
||||
return !this.fileService.canHandleResource(this.resource);
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
if (this.isUntitled()) {
|
||||
return false; // untitled is never readonly
|
||||
}
|
||||
|
||||
return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly);
|
||||
}
|
||||
|
||||
isSaving(): boolean {
|
||||
if (this.isUntitled()) {
|
||||
return false; // untitled is never saving automatically
|
||||
}
|
||||
|
||||
if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
return true; // a short auto save is configured, treat this as being saved
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
|
||||
|
||||
// If this is neither an `untitled` resource, nor a resource
|
||||
// we can handle with the file service, we can only "Save As..."
|
||||
if (this.resource.scheme !== Schemas.untitled && !this.fileService.canHandleResource(this.resource)) {
|
||||
return this.saveAs(group, options);
|
||||
}
|
||||
|
||||
// Normal save
|
||||
return this.doSave(group, options, false);
|
||||
}
|
||||
|
||||
saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
|
||||
return this.doSave(group, options, true);
|
||||
}
|
||||
|
||||
private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise<IEditorInput | undefined> {
|
||||
|
||||
// Save / Save As
|
||||
let target: URI | undefined;
|
||||
if (saveAs) {
|
||||
target = await this.textFileService.saveAs(this.resource, undefined, { ...options, suggestedTarget: this.preferredResource });
|
||||
} else {
|
||||
target = await this.textFileService.save(this.resource, options);
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
return undefined; // save cancelled
|
||||
}
|
||||
|
||||
// If the target is a different resource, return with a new editor input
|
||||
if (!isEqual(target, this.preferredResource)) {
|
||||
return this.editorService.createEditorInput({ resource: target });
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
|
||||
await this.textFileService.revert(this.resource, options);
|
||||
}
|
||||
}
|
||||
100
lib/vscode/src/vs/workbench/common/memento.ts
Normal file
100
lib/vscode/src/vs/workbench/common/memento.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
export type MementoObject = { [key: string]: any };
|
||||
|
||||
export class Memento {
|
||||
|
||||
private static readonly globalMementos = new Map<string, ScopedMemento>();
|
||||
private static readonly workspaceMementos = new Map<string, ScopedMemento>();
|
||||
|
||||
private static readonly COMMON_PREFIX = 'memento/';
|
||||
|
||||
private readonly id: string;
|
||||
|
||||
constructor(id: string, private storageService: IStorageService) {
|
||||
this.id = Memento.COMMON_PREFIX + id;
|
||||
}
|
||||
|
||||
getMemento(scope: StorageScope): MementoObject {
|
||||
|
||||
// Scope by Workspace
|
||||
if (scope === StorageScope.WORKSPACE) {
|
||||
let workspaceMemento = Memento.workspaceMementos.get(this.id);
|
||||
if (!workspaceMemento) {
|
||||
workspaceMemento = new ScopedMemento(this.id, scope, this.storageService);
|
||||
Memento.workspaceMementos.set(this.id, workspaceMemento);
|
||||
}
|
||||
|
||||
return workspaceMemento.getMemento();
|
||||
}
|
||||
|
||||
// Scope Global
|
||||
let globalMemento = Memento.globalMementos.get(this.id);
|
||||
if (!globalMemento) {
|
||||
globalMemento = new ScopedMemento(this.id, scope, this.storageService);
|
||||
Memento.globalMementos.set(this.id, globalMemento);
|
||||
}
|
||||
|
||||
return globalMemento.getMemento();
|
||||
}
|
||||
|
||||
saveMemento(): void {
|
||||
|
||||
// Workspace
|
||||
const workspaceMemento = Memento.workspaceMementos.get(this.id);
|
||||
if (workspaceMemento) {
|
||||
workspaceMemento.save();
|
||||
}
|
||||
|
||||
// Global
|
||||
const globalMemento = Memento.globalMementos.get(this.id);
|
||||
if (globalMemento) {
|
||||
globalMemento.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScopedMemento {
|
||||
|
||||
private readonly mementoObj: MementoObject;
|
||||
|
||||
constructor(private id: string, private scope: StorageScope, private storageService: IStorageService) {
|
||||
this.mementoObj = this.load();
|
||||
}
|
||||
|
||||
getMemento(): MementoObject {
|
||||
return this.mementoObj;
|
||||
}
|
||||
|
||||
private load(): MementoObject {
|
||||
const memento = this.storageService.get(this.id, this.scope);
|
||||
if (memento) {
|
||||
try {
|
||||
return JSON.parse(memento);
|
||||
} catch (error) {
|
||||
// Seeing reports from users unable to open editors
|
||||
// from memento parsing exceptions. Log the contents
|
||||
// to diagnose further
|
||||
// https://github.com/microsoft/vscode/issues/102251
|
||||
onUnexpectedError(`[memento]: failed to parse contents: ${error} (id: ${this.id}, scope: ${this.scope}, contents: ${memento})`);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
save(): void {
|
||||
if (!isEmptyObject(this.mementoObj)) {
|
||||
this.storageService.store(this.id, JSON.stringify(this.mementoObj), this.scope);
|
||||
} else {
|
||||
this.storageService.remove(this.id, this.scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
737
lib/vscode/src/vs/workbench/common/notifications.ts
Normal file
737
lib/vscode/src/vs/workbench/common/notifications.ts
Normal file
@@ -0,0 +1,737 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter, INotificationProgressProperties } from 'vs/platform/notification/common/notification';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { isErrorWithActions } from 'vs/base/common/errorsWithActions';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { parseLinkedText, LinkedText } from 'vs/base/common/linkedText';
|
||||
|
||||
export interface INotificationsModel {
|
||||
|
||||
//#region Notifications as Toasts/Center
|
||||
|
||||
readonly notifications: INotificationViewItem[];
|
||||
|
||||
readonly onDidChangeNotification: Event<INotificationChangeEvent>;
|
||||
readonly onDidChangeFilter: Event<NotificationsFilter>;
|
||||
|
||||
addNotification(notification: INotification): INotificationHandle;
|
||||
|
||||
setFilter(filter: NotificationsFilter): void;
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Notifications as Status
|
||||
|
||||
readonly statusMessage: IStatusMessageViewItem | undefined;
|
||||
|
||||
readonly onDidChangeStatusMessage: Event<IStatusMessageChangeEvent>;
|
||||
|
||||
showStatusMessage(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable;
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export const enum NotificationChangeType {
|
||||
|
||||
/**
|
||||
* A notification was added.
|
||||
*/
|
||||
ADD,
|
||||
|
||||
/**
|
||||
* A notification changed. Check `detail` property
|
||||
* on the event for additional information.
|
||||
*/
|
||||
CHANGE,
|
||||
|
||||
/**
|
||||
* A notification expanded or collapsed.
|
||||
*/
|
||||
EXPAND_COLLAPSE,
|
||||
|
||||
/**
|
||||
* A notification was removed.
|
||||
*/
|
||||
REMOVE
|
||||
}
|
||||
|
||||
export interface INotificationChangeEvent {
|
||||
|
||||
/**
|
||||
* The index this notification has in the list of notifications.
|
||||
*/
|
||||
index: number;
|
||||
|
||||
/**
|
||||
* The notification this change is about.
|
||||
*/
|
||||
item: INotificationViewItem;
|
||||
|
||||
/**
|
||||
* The kind of notification change.
|
||||
*/
|
||||
kind: NotificationChangeType;
|
||||
|
||||
/**
|
||||
* Additional detail about the item change. Only applies to
|
||||
* `NotificationChangeType.CHANGE`.
|
||||
*/
|
||||
detail?: NotificationViewItemContentChangeKind
|
||||
}
|
||||
|
||||
export const enum StatusMessageChangeType {
|
||||
ADD,
|
||||
REMOVE
|
||||
}
|
||||
|
||||
export interface IStatusMessageViewItem {
|
||||
message: string;
|
||||
options?: IStatusMessageOptions;
|
||||
}
|
||||
|
||||
export interface IStatusMessageChangeEvent {
|
||||
|
||||
/**
|
||||
* The status message item this change is about.
|
||||
*/
|
||||
item: IStatusMessageViewItem;
|
||||
|
||||
/**
|
||||
* The kind of status message change.
|
||||
*/
|
||||
kind: StatusMessageChangeType;
|
||||
}
|
||||
|
||||
export class NotificationHandle extends Disposable implements INotificationHandle {
|
||||
|
||||
private readonly _onDidClose = this._register(new Emitter<void>());
|
||||
readonly onDidClose = this._onDidClose.event;
|
||||
|
||||
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
constructor(private readonly item: INotificationViewItem, private readonly onClose: (item: INotificationViewItem) => void) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Visibility
|
||||
this._register(this.item.onDidChangeVisibility(visible => this._onDidChangeVisibility.fire(visible)));
|
||||
|
||||
// Closing
|
||||
Event.once(this.item.onDidClose)(() => {
|
||||
this._onDidClose.fire();
|
||||
|
||||
this.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
get progress(): INotificationProgress {
|
||||
return this.item.progress;
|
||||
}
|
||||
|
||||
updateSeverity(severity: Severity): void {
|
||||
this.item.updateSeverity(severity);
|
||||
}
|
||||
|
||||
updateMessage(message: NotificationMessage): void {
|
||||
this.item.updateMessage(message);
|
||||
}
|
||||
|
||||
updateActions(actions?: INotificationActions): void {
|
||||
this.item.updateActions(actions);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.onClose(this.item);
|
||||
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class NotificationsModel extends Disposable implements INotificationsModel {
|
||||
|
||||
private static readonly NO_OP_NOTIFICATION = new NoOpNotification();
|
||||
|
||||
private readonly _onDidChangeNotification = this._register(new Emitter<INotificationChangeEvent>());
|
||||
readonly onDidChangeNotification = this._onDidChangeNotification.event;
|
||||
|
||||
private readonly _onDidChangeStatusMessage = this._register(new Emitter<IStatusMessageChangeEvent>());
|
||||
readonly onDidChangeStatusMessage = this._onDidChangeStatusMessage.event;
|
||||
|
||||
private readonly _onDidChangeFilter = this._register(new Emitter<NotificationsFilter>());
|
||||
readonly onDidChangeFilter = this._onDidChangeFilter.event;
|
||||
|
||||
private readonly _notifications: INotificationViewItem[] = [];
|
||||
get notifications(): INotificationViewItem[] { return this._notifications; }
|
||||
|
||||
private _statusMessage: IStatusMessageViewItem | undefined;
|
||||
get statusMessage(): IStatusMessageViewItem | undefined { return this._statusMessage; }
|
||||
|
||||
private filter = NotificationsFilter.OFF;
|
||||
|
||||
setFilter(filter: NotificationsFilter): void {
|
||||
this.filter = filter;
|
||||
|
||||
this._onDidChangeFilter.fire(filter);
|
||||
}
|
||||
|
||||
addNotification(notification: INotification): INotificationHandle {
|
||||
const item = this.createViewItem(notification);
|
||||
if (!item) {
|
||||
return NotificationsModel.NO_OP_NOTIFICATION; // return early if this is a no-op
|
||||
}
|
||||
|
||||
// Deduplicate
|
||||
const duplicate = this.findNotification(item);
|
||||
if (duplicate) {
|
||||
duplicate.close();
|
||||
}
|
||||
|
||||
// Add to list as first entry
|
||||
this._notifications.splice(0, 0, item);
|
||||
|
||||
// Events
|
||||
this._onDidChangeNotification.fire({ item, index: 0, kind: NotificationChangeType.ADD });
|
||||
|
||||
// Wrap into handle
|
||||
return new NotificationHandle(item, item => this.onClose(item));
|
||||
}
|
||||
|
||||
private onClose(item: INotificationViewItem): void {
|
||||
const liveItem = this.findNotification(item);
|
||||
if (liveItem && liveItem !== item) {
|
||||
liveItem.close(); // item could have been replaced with another one, make sure to close the live item
|
||||
} else {
|
||||
item.close(); // otherwise just close the item that was passed in
|
||||
}
|
||||
}
|
||||
|
||||
private findNotification(item: INotificationViewItem): INotificationViewItem | undefined {
|
||||
return this._notifications.find(notification => notification.equals(item));
|
||||
}
|
||||
|
||||
private createViewItem(notification: INotification): INotificationViewItem | undefined {
|
||||
const item = NotificationViewItem.create(notification, this.filter);
|
||||
if (!item) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Item Events
|
||||
const fireNotificationChangeEvent = (kind: NotificationChangeType, detail?: NotificationViewItemContentChangeKind) => {
|
||||
const index = this._notifications.indexOf(item);
|
||||
if (index >= 0) {
|
||||
this._onDidChangeNotification.fire({ item, index, kind, detail });
|
||||
}
|
||||
};
|
||||
|
||||
const itemExpansionChangeListener = item.onDidChangeExpansion(() => fireNotificationChangeEvent(NotificationChangeType.EXPAND_COLLAPSE));
|
||||
const itemContentChangeListener = item.onDidChangeContent(e => fireNotificationChangeEvent(NotificationChangeType.CHANGE, e.kind));
|
||||
|
||||
Event.once(item.onDidClose)(() => {
|
||||
itemExpansionChangeListener.dispose();
|
||||
itemContentChangeListener.dispose();
|
||||
|
||||
const index = this._notifications.indexOf(item);
|
||||
if (index >= 0) {
|
||||
this._notifications.splice(index, 1);
|
||||
this._onDidChangeNotification.fire({ item, index, kind: NotificationChangeType.REMOVE });
|
||||
}
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
showStatusMessage(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable {
|
||||
const item = StatusMessageViewItem.create(message, options);
|
||||
if (!item) {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
// Remember as current status message and fire events
|
||||
this._statusMessage = item;
|
||||
this._onDidChangeStatusMessage.fire({ kind: StatusMessageChangeType.ADD, item });
|
||||
|
||||
return toDisposable(() => {
|
||||
|
||||
// Only reset status message if the item is still the one we had remembered
|
||||
if (this._statusMessage === item) {
|
||||
this._statusMessage = undefined;
|
||||
this._onDidChangeStatusMessage.fire({ kind: StatusMessageChangeType.REMOVE, item });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface INotificationViewItem {
|
||||
readonly severity: Severity;
|
||||
readonly sticky: boolean;
|
||||
readonly silent: boolean;
|
||||
readonly message: INotificationMessage;
|
||||
readonly source: string | undefined;
|
||||
readonly actions: INotificationActions | undefined;
|
||||
readonly progress: INotificationViewItemProgress;
|
||||
|
||||
readonly expanded: boolean;
|
||||
readonly canCollapse: boolean;
|
||||
readonly hasProgress: boolean;
|
||||
|
||||
readonly onDidChangeExpansion: Event<void>;
|
||||
readonly onDidChangeVisibility: Event<boolean>;
|
||||
readonly onDidChangeContent: Event<INotificationViewItemContentChangeEvent>;
|
||||
readonly onDidClose: Event<void>;
|
||||
|
||||
expand(): void;
|
||||
collapse(skipEvents?: boolean): void;
|
||||
toggle(): void;
|
||||
|
||||
updateSeverity(severity: Severity): void;
|
||||
updateMessage(message: NotificationMessage): void;
|
||||
updateActions(actions?: INotificationActions): void;
|
||||
|
||||
updateVisibility(visible: boolean): void;
|
||||
|
||||
close(): void;
|
||||
|
||||
equals(item: INotificationViewItem): boolean;
|
||||
}
|
||||
|
||||
export function isNotificationViewItem(obj: unknown): obj is INotificationViewItem {
|
||||
return obj instanceof NotificationViewItem;
|
||||
}
|
||||
|
||||
export const enum NotificationViewItemContentChangeKind {
|
||||
SEVERITY,
|
||||
MESSAGE,
|
||||
ACTIONS,
|
||||
PROGRESS
|
||||
}
|
||||
|
||||
export interface INotificationViewItemContentChangeEvent {
|
||||
kind: NotificationViewItemContentChangeKind;
|
||||
}
|
||||
|
||||
export interface INotificationViewItemProgressState {
|
||||
infinite?: boolean;
|
||||
total?: number;
|
||||
worked?: number;
|
||||
done?: boolean;
|
||||
}
|
||||
|
||||
export interface INotificationViewItemProgress extends INotificationProgress {
|
||||
readonly state: INotificationViewItemProgressState;
|
||||
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class NotificationViewItemProgress extends Disposable implements INotificationViewItemProgress {
|
||||
private readonly _state: INotificationViewItemProgressState;
|
||||
|
||||
private readonly _onDidChange = this._register(new Emitter<void>());
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._state = Object.create(null);
|
||||
}
|
||||
|
||||
get state(): INotificationViewItemProgressState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
infinite(): void {
|
||||
if (this._state.infinite) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state.infinite = true;
|
||||
|
||||
this._state.total = undefined;
|
||||
this._state.worked = undefined;
|
||||
this._state.done = undefined;
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
done(): void {
|
||||
if (this._state.done) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state.done = true;
|
||||
|
||||
this._state.infinite = undefined;
|
||||
this._state.total = undefined;
|
||||
this._state.worked = undefined;
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
total(value: number): void {
|
||||
if (this._state.total === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state.total = value;
|
||||
|
||||
this._state.infinite = undefined;
|
||||
this._state.done = undefined;
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
worked(value: number): void {
|
||||
if (typeof this._state.worked === 'number') {
|
||||
this._state.worked += value;
|
||||
} else {
|
||||
this._state.worked = value;
|
||||
}
|
||||
|
||||
this._state.infinite = undefined;
|
||||
this._state.done = undefined;
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMessageLink {
|
||||
href: string;
|
||||
name: string;
|
||||
title: string;
|
||||
offset: number;
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface INotificationMessage {
|
||||
raw: string;
|
||||
original: NotificationMessage;
|
||||
linkedText: LinkedText;
|
||||
}
|
||||
|
||||
export class NotificationViewItem extends Disposable implements INotificationViewItem {
|
||||
|
||||
private static readonly MAX_MESSAGE_LENGTH = 1000;
|
||||
|
||||
private _expanded: boolean | undefined;
|
||||
private _visible: boolean = false;
|
||||
|
||||
private _actions: INotificationActions | undefined;
|
||||
private _progress: NotificationViewItemProgress | undefined;
|
||||
|
||||
private readonly _onDidChangeExpansion = this._register(new Emitter<void>());
|
||||
readonly onDidChangeExpansion = this._onDidChangeExpansion.event;
|
||||
|
||||
private readonly _onDidClose = this._register(new Emitter<void>());
|
||||
readonly onDidClose = this._onDidClose.event;
|
||||
|
||||
private readonly _onDidChangeContent = this._register(new Emitter<INotificationViewItemContentChangeEvent>());
|
||||
readonly onDidChangeContent = this._onDidChangeContent.event;
|
||||
|
||||
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
static create(notification: INotification, filter: NotificationsFilter = NotificationsFilter.OFF): INotificationViewItem | undefined {
|
||||
if (!notification || !notification.message || isPromiseCanceledError(notification.message)) {
|
||||
return undefined; // we need a message to show
|
||||
}
|
||||
|
||||
let severity: Severity;
|
||||
if (typeof notification.severity === 'number') {
|
||||
severity = notification.severity;
|
||||
} else {
|
||||
severity = Severity.Info;
|
||||
}
|
||||
|
||||
const message = NotificationViewItem.parseNotificationMessage(notification.message);
|
||||
if (!message) {
|
||||
return undefined; // we need a message to show
|
||||
}
|
||||
|
||||
let actions: INotificationActions | undefined;
|
||||
if (notification.actions) {
|
||||
actions = notification.actions;
|
||||
} else if (isErrorWithActions(notification.message)) {
|
||||
actions = { primary: notification.message.actions };
|
||||
}
|
||||
|
||||
return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, notification.progress, actions);
|
||||
}
|
||||
|
||||
private static parseNotificationMessage(input: NotificationMessage): INotificationMessage | undefined {
|
||||
let message: string | undefined;
|
||||
if (input instanceof Error) {
|
||||
message = toErrorMessage(input, false);
|
||||
} else if (typeof input === 'string') {
|
||||
message = input;
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return undefined; // we need a message to show
|
||||
}
|
||||
|
||||
const raw = message;
|
||||
|
||||
// Make sure message is in the limits
|
||||
if (message.length > NotificationViewItem.MAX_MESSAGE_LENGTH) {
|
||||
message = `${message.substr(0, NotificationViewItem.MAX_MESSAGE_LENGTH)}...`;
|
||||
}
|
||||
|
||||
// Remove newlines from messages as we do not support that and it makes link parsing hard
|
||||
message = message.replace(/(\r\n|\n|\r)/gm, ' ').trim();
|
||||
|
||||
// Parse Links
|
||||
const linkedText = parseLinkedText(message);
|
||||
|
||||
return { raw, linkedText, original: input };
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private _severity: Severity,
|
||||
private _sticky: boolean | undefined,
|
||||
private _silent: boolean | undefined,
|
||||
private _message: INotificationMessage,
|
||||
private _source: string | undefined,
|
||||
progress: INotificationProgressProperties | undefined,
|
||||
actions?: INotificationActions
|
||||
) {
|
||||
super();
|
||||
|
||||
if (progress) {
|
||||
this.setProgress(progress);
|
||||
}
|
||||
|
||||
this.setActions(actions);
|
||||
}
|
||||
|
||||
private setProgress(progress: INotificationProgressProperties): void {
|
||||
if (progress.infinite) {
|
||||
this.progress.infinite();
|
||||
} else if (progress.total) {
|
||||
this.progress.total(progress.total);
|
||||
|
||||
if (progress.worked) {
|
||||
this.progress.worked(progress.worked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setActions(actions: INotificationActions = { primary: [], secondary: [] }): void {
|
||||
this._actions = {
|
||||
primary: Array.isArray(actions.primary) ? actions.primary : [],
|
||||
secondary: Array.isArray(actions.secondary) ? actions.secondary : []
|
||||
};
|
||||
|
||||
this._expanded = actions.primary && actions.primary.length > 0;
|
||||
}
|
||||
|
||||
get canCollapse(): boolean {
|
||||
return !this.hasActions;
|
||||
}
|
||||
|
||||
get expanded(): boolean {
|
||||
return !!this._expanded;
|
||||
}
|
||||
|
||||
get severity(): Severity {
|
||||
return this._severity;
|
||||
}
|
||||
|
||||
get sticky(): boolean {
|
||||
if (this._sticky) {
|
||||
return true; // explicitly sticky
|
||||
}
|
||||
|
||||
const hasActions = this.hasActions;
|
||||
if (
|
||||
(hasActions && this._severity === Severity.Error) || // notification errors with actions are sticky
|
||||
(!hasActions && this._expanded) || // notifications that got expanded are sticky
|
||||
(this._progress && !this._progress.state.done) // notifications with running progress are sticky
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // not sticky
|
||||
}
|
||||
|
||||
get silent(): boolean {
|
||||
return !!this._silent;
|
||||
}
|
||||
|
||||
private get hasActions(): boolean {
|
||||
if (!this._actions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._actions.primary) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._actions.primary.length > 0;
|
||||
}
|
||||
|
||||
get hasProgress(): boolean {
|
||||
return !!this._progress;
|
||||
}
|
||||
|
||||
get progress(): INotificationViewItemProgress {
|
||||
if (!this._progress) {
|
||||
this._progress = this._register(new NotificationViewItemProgress());
|
||||
this._register(this._progress.onDidChange(() => this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.PROGRESS })));
|
||||
}
|
||||
|
||||
return this._progress;
|
||||
}
|
||||
|
||||
get message(): INotificationMessage {
|
||||
return this._message;
|
||||
}
|
||||
|
||||
get source(): string | undefined {
|
||||
return this._source;
|
||||
}
|
||||
|
||||
get actions(): INotificationActions | undefined {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
updateSeverity(severity: Severity): void {
|
||||
this._severity = severity;
|
||||
this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.SEVERITY });
|
||||
}
|
||||
|
||||
updateMessage(input: NotificationMessage): void {
|
||||
const message = NotificationViewItem.parseNotificationMessage(input);
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._message = message;
|
||||
this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.MESSAGE });
|
||||
}
|
||||
|
||||
updateActions(actions?: INotificationActions): void {
|
||||
this.setActions(actions);
|
||||
this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.ACTIONS });
|
||||
}
|
||||
|
||||
updateVisibility(visible: boolean): void {
|
||||
if (this._visible !== visible) {
|
||||
this._visible = visible;
|
||||
|
||||
this._onDidChangeVisibility.fire(visible);
|
||||
}
|
||||
}
|
||||
|
||||
expand(): void {
|
||||
if (this._expanded || !this.canCollapse) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._expanded = true;
|
||||
this._onDidChangeExpansion.fire();
|
||||
}
|
||||
|
||||
collapse(skipEvents?: boolean): void {
|
||||
if (!this._expanded || !this.canCollapse) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._expanded = false;
|
||||
|
||||
if (!skipEvents) {
|
||||
this._onDidChangeExpansion.fire();
|
||||
}
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
if (this._expanded) {
|
||||
this.collapse();
|
||||
} else {
|
||||
this.expand();
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this._onDidClose.fire();
|
||||
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
equals(other: INotificationViewItem): boolean {
|
||||
if (this.hasProgress || other.hasProgress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._source !== other.source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._message.raw !== other.message.raw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const primaryActions = (this._actions && this._actions.primary) || [];
|
||||
const otherPrimaryActions = (other.actions && other.actions.primary) || [];
|
||||
return equals(primaryActions, otherPrimaryActions, (a, b) => (a.id + a.label) === (b.id + b.label));
|
||||
}
|
||||
}
|
||||
|
||||
export class ChoiceAction extends Action {
|
||||
|
||||
private readonly _onDidRun = this._register(new Emitter<void>());
|
||||
readonly onDidRun = this._onDidRun.event;
|
||||
|
||||
private readonly _keepOpen: boolean;
|
||||
|
||||
constructor(id: string, choice: IPromptChoice) {
|
||||
super(id, choice.label, undefined, true, async () => {
|
||||
|
||||
// Pass to runner
|
||||
choice.run();
|
||||
|
||||
// Emit Event
|
||||
this._onDidRun.fire();
|
||||
});
|
||||
|
||||
this._keepOpen = !!choice.keepOpen;
|
||||
}
|
||||
|
||||
get keepOpen(): boolean {
|
||||
return this._keepOpen;
|
||||
}
|
||||
}
|
||||
|
||||
class StatusMessageViewItem {
|
||||
|
||||
static create(notification: NotificationMessage, options?: IStatusMessageOptions): IStatusMessageViewItem | undefined {
|
||||
if (!notification || isPromiseCanceledError(notification)) {
|
||||
return undefined; // we need a message to show
|
||||
}
|
||||
|
||||
let message: string | undefined;
|
||||
if (notification instanceof Error) {
|
||||
message = toErrorMessage(notification, false);
|
||||
} else if (typeof notification === 'string') {
|
||||
message = notification;
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return undefined; // we need a message to show
|
||||
}
|
||||
|
||||
return { message, options };
|
||||
}
|
||||
}
|
||||
13
lib/vscode/src/vs/workbench/common/panecomposite.ts
Normal file
13
lib/vscode/src/vs/workbench/common/panecomposite.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IView, IViewPaneContainer } from 'vs/workbench/common/views';
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
|
||||
export interface IPaneComposite extends IComposite {
|
||||
openView<T extends IView>(id: string, focus?: boolean): T | undefined;
|
||||
getViewPaneContainer(): IViewPaneContainer;
|
||||
saveState(): void;
|
||||
}
|
||||
13
lib/vscode/src/vs/workbench/common/panel.ts
Normal file
13
lib/vscode/src/vs/workbench/common/panel.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export const ActivePanelContext = new RawContextKey<string>('activePanel', '');
|
||||
export const PanelFocusContext = new RawContextKey<boolean>('panelFocus', false);
|
||||
export const PanelPositionContext = new RawContextKey<string>('panelPosition', 'bottom');
|
||||
|
||||
export interface IPanel extends IComposite { }
|
||||
221
lib/vscode/src/vs/workbench/common/resources.ts
Normal file
221
lib/vscode/src/vs/workbench/common/resources.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { basename, dirname, extname, relativePath } from 'vs/base/common/resources';
|
||||
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
|
||||
export class ResourceContextKey extends Disposable implements IContextKey<URI> {
|
||||
|
||||
// NOTE: DO NOT CHANGE THE DEFAULT VALUE TO ANYTHING BUT
|
||||
// UNDEFINED! IT IS IMPORTANT THAT DEFAULTS ARE INHERITED
|
||||
// FROM THE PARENT CONTEXT AND ONLY UNDEFINED DOES THIS
|
||||
|
||||
static readonly Scheme = new RawContextKey<string>('resourceScheme', undefined);
|
||||
static readonly Filename = new RawContextKey<string>('resourceFilename', undefined);
|
||||
static readonly Dirname = new RawContextKey<string>('resourceDirname', undefined);
|
||||
static readonly Path = new RawContextKey<string>('resourcePath', undefined);
|
||||
static readonly LangId = new RawContextKey<string>('resourceLangId', undefined);
|
||||
static readonly Resource = new RawContextKey<URI>('resource', undefined);
|
||||
static readonly Extension = new RawContextKey<string>('resourceExtname', undefined);
|
||||
static readonly HasResource = new RawContextKey<boolean>('resourceSet', undefined);
|
||||
static readonly IsFileSystemResource = new RawContextKey<boolean>('isFileSystemResource', undefined);
|
||||
|
||||
private readonly _resourceKey: IContextKey<URI | null>;
|
||||
private readonly _schemeKey: IContextKey<string | null>;
|
||||
private readonly _filenameKey: IContextKey<string | null>;
|
||||
private readonly _dirnameKey: IContextKey<string | null>;
|
||||
private readonly _pathKey: IContextKey<string | null>;
|
||||
private readonly _langIdKey: IContextKey<string | null>;
|
||||
private readonly _extensionKey: IContextKey<string | null>;
|
||||
private readonly _hasResource: IContextKey<boolean>;
|
||||
private readonly _isFileSystemResource: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IModeService private readonly _modeService: IModeService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._schemeKey = ResourceContextKey.Scheme.bindTo(this._contextKeyService);
|
||||
this._filenameKey = ResourceContextKey.Filename.bindTo(this._contextKeyService);
|
||||
this._dirnameKey = ResourceContextKey.Dirname.bindTo(this._contextKeyService);
|
||||
this._pathKey = ResourceContextKey.Path.bindTo(this._contextKeyService);
|
||||
this._langIdKey = ResourceContextKey.LangId.bindTo(this._contextKeyService);
|
||||
this._resourceKey = ResourceContextKey.Resource.bindTo(this._contextKeyService);
|
||||
this._extensionKey = ResourceContextKey.Extension.bindTo(this._contextKeyService);
|
||||
this._hasResource = ResourceContextKey.HasResource.bindTo(this._contextKeyService);
|
||||
this._isFileSystemResource = ResourceContextKey.IsFileSystemResource.bindTo(this._contextKeyService);
|
||||
|
||||
this._register(_fileService.onDidChangeFileSystemProviderRegistrations(() => {
|
||||
const resource = this._resourceKey.get();
|
||||
this._isFileSystemResource.set(Boolean(resource && _fileService.canHandleResource(resource)));
|
||||
}));
|
||||
|
||||
this._register(_modeService.onDidCreateMode(() => {
|
||||
const value = this._resourceKey.get();
|
||||
this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
|
||||
}));
|
||||
}
|
||||
|
||||
set(value: URI | null) {
|
||||
if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
|
||||
this._contextKeyService.bufferChangeEvents(() => {
|
||||
this._resourceKey.set(value);
|
||||
this._schemeKey.set(value ? value.scheme : null);
|
||||
this._filenameKey.set(value ? basename(value) : null);
|
||||
this._dirnameKey.set(value ? dirname(value).fsPath : null);
|
||||
this._pathKey.set(value ? value.fsPath : null);
|
||||
this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
|
||||
this._extensionKey.set(value ? extname(value) : null);
|
||||
this._hasResource.set(!!value);
|
||||
this._isFileSystemResource.set(value ? this._fileService.canHandleResource(value) : false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this._contextKeyService.bufferChangeEvents(() => {
|
||||
this._resourceKey.reset();
|
||||
this._schemeKey.reset();
|
||||
this._filenameKey.reset();
|
||||
this._dirnameKey.reset();
|
||||
this._pathKey.reset();
|
||||
this._langIdKey.reset();
|
||||
this._extensionKey.reset();
|
||||
this._hasResource.reset();
|
||||
this._isFileSystemResource.reset();
|
||||
});
|
||||
}
|
||||
|
||||
get(): URI | undefined {
|
||||
return withNullAsUndefined(this._resourceKey.get());
|
||||
}
|
||||
|
||||
private static _uriEquals(a: URI | undefined | null, b: URI | undefined | null): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
return a.scheme === b.scheme // checks for not equals (fail fast)
|
||||
&& a.authority === b.authority
|
||||
&& a.path === b.path
|
||||
&& a.query === b.query
|
||||
&& a.fragment === b.fragment
|
||||
&& a.toString() === b.toString(); // for equal we use the normalized toString-form
|
||||
}
|
||||
}
|
||||
|
||||
export class ResourceGlobMatcher extends Disposable {
|
||||
|
||||
private static readonly NO_ROOT: string | null = null;
|
||||
|
||||
private readonly _onExpressionChange = this._register(new Emitter<void>());
|
||||
readonly onExpressionChange = this._onExpressionChange.event;
|
||||
|
||||
private readonly mapRootToParsedExpression = new Map<string | null, ParsedExpression>();
|
||||
private readonly mapRootToExpressionConfig = new Map<string | null, IExpression>();
|
||||
|
||||
constructor(
|
||||
private globFn: (root?: URI) => IExpression,
|
||||
private shouldUpdate: (event: IConfigurationChangeEvent) => boolean,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.updateExcludes(false);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (this.shouldUpdate(e)) {
|
||||
this.updateExcludes(true);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateExcludes(true)));
|
||||
}
|
||||
|
||||
private updateExcludes(fromEvent: boolean): void {
|
||||
let changed = false;
|
||||
|
||||
// Add excludes per workspaces that got added
|
||||
this.contextService.getWorkspace().folders.forEach(folder => {
|
||||
const rootExcludes = this.globFn(folder.uri);
|
||||
if (!this.mapRootToExpressionConfig.has(folder.uri.toString()) || !objects.equals(this.mapRootToExpressionConfig.get(folder.uri.toString()), rootExcludes)) {
|
||||
changed = true;
|
||||
|
||||
this.mapRootToParsedExpression.set(folder.uri.toString(), parse(rootExcludes));
|
||||
this.mapRootToExpressionConfig.set(folder.uri.toString(), objects.deepClone(rootExcludes));
|
||||
}
|
||||
});
|
||||
|
||||
// Remove excludes per workspace no longer present
|
||||
this.mapRootToExpressionConfig.forEach((value, root) => {
|
||||
if (root === ResourceGlobMatcher.NO_ROOT) {
|
||||
return; // always keep this one
|
||||
}
|
||||
|
||||
if (root && !this.contextService.getWorkspaceFolder(URI.parse(root))) {
|
||||
this.mapRootToParsedExpression.delete(root);
|
||||
this.mapRootToExpressionConfig.delete(root);
|
||||
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Always set for resources outside root as well
|
||||
const globalExcludes = this.globFn();
|
||||
if (!this.mapRootToExpressionConfig.has(ResourceGlobMatcher.NO_ROOT) || !objects.equals(this.mapRootToExpressionConfig.get(ResourceGlobMatcher.NO_ROOT), globalExcludes)) {
|
||||
changed = true;
|
||||
|
||||
this.mapRootToParsedExpression.set(ResourceGlobMatcher.NO_ROOT, parse(globalExcludes));
|
||||
this.mapRootToExpressionConfig.set(ResourceGlobMatcher.NO_ROOT, objects.deepClone(globalExcludes));
|
||||
}
|
||||
|
||||
if (fromEvent && changed) {
|
||||
this._onExpressionChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
matches(resource: URI): boolean {
|
||||
const folder = this.contextService.getWorkspaceFolder(resource);
|
||||
|
||||
let expressionForRoot: ParsedExpression | undefined;
|
||||
if (folder && this.mapRootToParsedExpression.has(folder.uri.toString())) {
|
||||
expressionForRoot = this.mapRootToParsedExpression.get(folder.uri.toString());
|
||||
} else {
|
||||
expressionForRoot = this.mapRootToParsedExpression.get(ResourceGlobMatcher.NO_ROOT);
|
||||
}
|
||||
|
||||
// If the resource if from a workspace, convert its absolute path to a relative
|
||||
// path so that glob patterns have a higher probability to match. For example
|
||||
// a glob pattern of "src/**" will not match on an absolute path "/folder/src/file.txt"
|
||||
// but can match on "src/file.txt"
|
||||
let resourcePathToMatch: string | undefined;
|
||||
if (folder) {
|
||||
resourcePathToMatch = relativePath(folder.uri, resource); // always uses forward slashes
|
||||
} else {
|
||||
resourcePathToMatch = resource.fsPath; // TODO@isidor: support non-file URIs
|
||||
}
|
||||
|
||||
return !!expressionForRoot && typeof resourcePathToMatch === 'string' && !!expressionForRoot(resourcePathToMatch);
|
||||
}
|
||||
}
|
||||
675
lib/vscode/src/vs/workbench/common/theme.ts
Normal file
675
lib/vscode/src/vs/workbench/common/theme.ts
Normal file
@@ -0,0 +1,675 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground, treeIndentGuidesStroke } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
// < --- Workbench (not customizable) --- >
|
||||
|
||||
export function WORKBENCH_BACKGROUND(theme: IColorTheme): Color {
|
||||
switch (theme.type) {
|
||||
case 'dark':
|
||||
return Color.fromHex('#252526');
|
||||
case 'light':
|
||||
return Color.fromHex('#F3F3F3');
|
||||
default:
|
||||
return Color.fromHex('#000000');
|
||||
}
|
||||
}
|
||||
|
||||
// < --- Tabs --- >
|
||||
|
||||
//#region Tab Background
|
||||
|
||||
export const TAB_ACTIVE_BACKGROUND = registerColor('tab.activeBackground', {
|
||||
dark: editorBackground,
|
||||
light: editorBackground,
|
||||
hc: editorBackground
|
||||
}, nls.localize('tabActiveBackground', "Active tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_ACTIVE_BACKGROUND = registerColor('tab.unfocusedActiveBackground', {
|
||||
dark: TAB_ACTIVE_BACKGROUND,
|
||||
light: TAB_ACTIVE_BACKGROUND,
|
||||
hc: TAB_ACTIVE_BACKGROUND
|
||||
}, nls.localize('tabUnfocusedActiveBackground', "Active tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_INACTIVE_BACKGROUND = registerColor('tab.inactiveBackground', {
|
||||
dark: '#2D2D2D',
|
||||
light: '#ECECEC',
|
||||
hc: null
|
||||
}, nls.localize('tabInactiveBackground', "Inactive tab background color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_INACTIVE_BACKGROUND = registerColor('tab.unfocusedInactiveBackground', {
|
||||
dark: TAB_INACTIVE_BACKGROUND,
|
||||
light: TAB_INACTIVE_BACKGROUND,
|
||||
hc: TAB_INACTIVE_BACKGROUND
|
||||
}, nls.localize('tabUnfocusedInactiveBackground', "Inactive tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Tab Foreground
|
||||
|
||||
export const TAB_ACTIVE_FOREGROUND = registerColor('tab.activeForeground', {
|
||||
dark: Color.white,
|
||||
light: '#333333',
|
||||
hc: Color.white
|
||||
}, nls.localize('tabActiveForeground', "Active tab foreground color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_INACTIVE_FOREGROUND = registerColor('tab.inactiveForeground', {
|
||||
dark: transparent(TAB_ACTIVE_FOREGROUND, 0.5),
|
||||
light: transparent(TAB_ACTIVE_FOREGROUND, 0.7),
|
||||
hc: Color.white
|
||||
}, nls.localize('tabInactiveForeground', "Inactive tab foreground color in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_ACTIVE_FOREGROUND = registerColor('tab.unfocusedActiveForeground', {
|
||||
dark: transparent(TAB_ACTIVE_FOREGROUND, 0.5),
|
||||
light: transparent(TAB_ACTIVE_FOREGROUND, 0.7),
|
||||
hc: Color.white
|
||||
}, nls.localize('tabUnfocusedActiveForeground', "Active tab foreground color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_INACTIVE_FOREGROUND = registerColor('tab.unfocusedInactiveForeground', {
|
||||
dark: transparent(TAB_INACTIVE_FOREGROUND, 0.5),
|
||||
light: transparent(TAB_INACTIVE_FOREGROUND, 0.5),
|
||||
hc: Color.white
|
||||
}, nls.localize('tabUnfocusedInactiveForeground', "Inactive tab foreground color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Tab Hover Foreground/Background
|
||||
|
||||
export const TAB_HOVER_BACKGROUND = registerColor('tab.hoverBackground', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('tabHoverBackground', "Tab background color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_HOVER_BACKGROUND = registerColor('tab.unfocusedHoverBackground', {
|
||||
dark: transparent(TAB_HOVER_BACKGROUND, 0.5),
|
||||
light: transparent(TAB_HOVER_BACKGROUND, 0.7),
|
||||
hc: null
|
||||
}, nls.localize('tabUnfocusedHoverBackground', "Tab background color in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_HOVER_FOREGROUND = registerColor('tab.hoverForeground', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('tabHoverForeground', "Tab foreground color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_HOVER_FOREGROUND = registerColor('tab.unfocusedHoverForeground', {
|
||||
dark: transparent(TAB_HOVER_FOREGROUND, 0.5),
|
||||
light: transparent(TAB_HOVER_FOREGROUND, 0.5),
|
||||
hc: null
|
||||
}, nls.localize('tabUnfocusedHoverForeground', "Tab foreground color in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Tab Borders
|
||||
|
||||
export const TAB_BORDER = registerColor('tab.border', {
|
||||
dark: '#252526',
|
||||
light: '#F3F3F3',
|
||||
hc: contrastBorder
|
||||
}, nls.localize('tabBorder', "Border to separate tabs from each other. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_LAST_PINNED_BORDER = registerColor('tab.lastPinnedBorder', {
|
||||
dark: treeIndentGuidesStroke,
|
||||
light: treeIndentGuidesStroke,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('lastPinnedTabBorder', "Border to separate pinned tabs from other tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_ACTIVE_BORDER = registerColor('tab.activeBorder', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('tabActiveBorder', "Border on the bottom of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_ACTIVE_BORDER = registerColor('tab.unfocusedActiveBorder', {
|
||||
dark: transparent(TAB_ACTIVE_BORDER, 0.5),
|
||||
light: transparent(TAB_ACTIVE_BORDER, 0.7),
|
||||
hc: null
|
||||
}, nls.localize('tabActiveUnfocusedBorder', "Border on the bottom of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_ACTIVE_BORDER_TOP = registerColor('tab.activeBorderTop', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('tabActiveBorderTop', "Border to the top of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_ACTIVE_BORDER_TOP = registerColor('tab.unfocusedActiveBorderTop', {
|
||||
dark: transparent(TAB_ACTIVE_BORDER_TOP, 0.5),
|
||||
light: transparent(TAB_ACTIVE_BORDER_TOP, 0.7),
|
||||
hc: null
|
||||
}, nls.localize('tabActiveUnfocusedBorderTop', "Border to the top of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_HOVER_BORDER = registerColor('tab.hoverBorder', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('tabHoverBorder', "Border to highlight tabs when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_HOVER_BORDER = registerColor('tab.unfocusedHoverBorder', {
|
||||
dark: transparent(TAB_HOVER_BORDER, 0.5),
|
||||
light: transparent(TAB_HOVER_BORDER, 0.7),
|
||||
hc: null
|
||||
}, nls.localize('tabUnfocusedHoverBorder', "Border to highlight tabs in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Tab Modified Border
|
||||
|
||||
export const TAB_ACTIVE_MODIFIED_BORDER = registerColor('tab.activeModifiedBorder', {
|
||||
dark: '#3399CC',
|
||||
light: '#33AAEE',
|
||||
hc: null
|
||||
}, nls.localize('tabActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_INACTIVE_MODIFIED_BORDER = registerColor('tab.inactiveModifiedBorder', {
|
||||
dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5),
|
||||
light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5),
|
||||
hc: Color.white
|
||||
}, nls.localize('tabInactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedActiveModifiedBorder', {
|
||||
dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5),
|
||||
light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.7),
|
||||
hc: Color.white
|
||||
}, nls.localize('unfocusedActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
export const TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedInactiveModifiedBorder', {
|
||||
dark: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5),
|
||||
light: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5),
|
||||
hc: Color.white
|
||||
}, nls.localize('unfocusedINactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
|
||||
|
||||
//#endregion
|
||||
|
||||
// < --- Editors --- >
|
||||
|
||||
export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', {
|
||||
dark: editorBackground,
|
||||
light: editorBackground,
|
||||
hc: editorBackground
|
||||
}, nls.localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout."));
|
||||
|
||||
registerColor('editorGroup.background', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('editorGroupBackground', "Deprecated background color of an editor group."), false, nls.localize('deprecatedEditorGroupBackground', "Deprecated: Background color of an editor group is no longer being supported with the introduction of the grid editor layout. You can use editorGroup.emptyBackground to set the background color of empty editor groups."));
|
||||
|
||||
export const EDITOR_GROUP_EMPTY_BACKGROUND = registerColor('editorGroup.emptyBackground', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('editorGroupEmptyBackground', "Background color of an empty editor group. Editor groups are the containers of editors."));
|
||||
|
||||
export const EDITOR_GROUP_FOCUSED_EMPTY_BORDER = registerColor('editorGroup.focusedEmptyBorder', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: focusBorder
|
||||
}, nls.localize('editorGroupFocusedEmptyBorder', "Border color of an empty editor group that is focused. Editor groups are the containers of editors."));
|
||||
|
||||
export const EDITOR_GROUP_HEADER_TABS_BACKGROUND = registerColor('editorGroupHeader.tabsBackground', {
|
||||
dark: '#252526',
|
||||
light: '#F3F3F3',
|
||||
hc: null
|
||||
}, nls.localize('tabsContainerBackground', "Background color of the editor group title header when tabs are enabled. Editor groups are the containers of editors."));
|
||||
|
||||
export const EDITOR_GROUP_HEADER_TABS_BORDER = registerColor('editorGroupHeader.tabsBorder', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('tabsContainerBorder', "Border color of the editor group title header when tabs are enabled. Editor groups are the containers of editors."));
|
||||
|
||||
export const EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND = registerColor('editorGroupHeader.noTabsBackground', {
|
||||
dark: editorBackground,
|
||||
light: editorBackground,
|
||||
hc: editorBackground
|
||||
}, nls.localize('editorGroupHeaderBackground', "Background color of the editor group title header when tabs are disabled (`\"workbench.editor.showTabs\": false`). Editor groups are the containers of editors."));
|
||||
|
||||
export const EDITOR_GROUP_HEADER_BORDER = registerColor('editorGroupHeader.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('editorTitleContainerBorder', "Border color of the editor group title header. Editor groups are the containers of editors."));
|
||||
|
||||
export const EDITOR_GROUP_BORDER = registerColor('editorGroup.border', {
|
||||
dark: '#444444',
|
||||
light: '#E7E7E7',
|
||||
hc: contrastBorder
|
||||
}, nls.localize('editorGroupBorder', "Color to separate multiple editor groups from each other. Editor groups are the containers of editors."));
|
||||
|
||||
export const EDITOR_DRAG_AND_DROP_BACKGROUND = registerColor('editorGroup.dropBackground', {
|
||||
dark: Color.fromHex('#53595D').transparent(0.5),
|
||||
light: Color.fromHex('#2677CB').transparent(0.18),
|
||||
hc: null
|
||||
}, nls.localize('editorDragAndDropBackground', "Background color when dragging editors around. The color should have transparency so that the editor contents can still shine through."));
|
||||
|
||||
// < --- Resource Viewer --- >
|
||||
|
||||
export const IMAGE_PREVIEW_BORDER = registerColor('imagePreview.border', {
|
||||
dark: Color.fromHex('#808080').transparent(0.35),
|
||||
light: Color.fromHex('#808080').transparent(0.35),
|
||||
hc: contrastBorder
|
||||
}, nls.localize('imagePreviewBorder', "Border color for image in image preview."));
|
||||
|
||||
// < --- Panels --- >
|
||||
|
||||
export const PANEL_BACKGROUND = registerColor('panel.background', {
|
||||
dark: editorBackground,
|
||||
light: editorBackground,
|
||||
hc: editorBackground
|
||||
}, nls.localize('panelBackground', "Panel background color. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_BORDER = registerColor('panel.border', {
|
||||
dark: Color.fromHex('#808080').transparent(0.35),
|
||||
light: Color.fromHex('#808080').transparent(0.35),
|
||||
hc: contrastBorder
|
||||
}, nls.localize('panelBorder', "Panel border color to separate the panel from the editor. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_ACTIVE_TITLE_FOREGROUND = registerColor('panelTitle.activeForeground', {
|
||||
dark: '#E7E7E7',
|
||||
light: '#424242',
|
||||
hc: Color.white
|
||||
}, nls.localize('panelActiveTitleForeground', "Title color for the active panel. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_INACTIVE_TITLE_FOREGROUND = registerColor('panelTitle.inactiveForeground', {
|
||||
dark: transparent(PANEL_ACTIVE_TITLE_FOREGROUND, 0.6),
|
||||
light: transparent(PANEL_ACTIVE_TITLE_FOREGROUND, 0.75),
|
||||
hc: Color.white
|
||||
}, nls.localize('panelInactiveTitleForeground', "Title color for the inactive panel. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_ACTIVE_TITLE_BORDER = registerColor('panelTitle.activeBorder', {
|
||||
dark: PANEL_ACTIVE_TITLE_FOREGROUND,
|
||||
light: PANEL_ACTIVE_TITLE_FOREGROUND,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('panelActiveTitleBorder', "Border color for the active panel title. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
export const PANEL_INPUT_BORDER = registerColor('panelInput.border', {
|
||||
dark: null,
|
||||
light: Color.fromHex('#ddd'),
|
||||
hc: null
|
||||
}, nls.localize('panelInputBorder', "Input box border for inputs in the panel."));
|
||||
|
||||
export const PANEL_DRAG_AND_DROP_BORDER = registerColor('panel.dropBorder', {
|
||||
dark: PANEL_ACTIVE_TITLE_FOREGROUND,
|
||||
light: PANEL_ACTIVE_TITLE_FOREGROUND,
|
||||
hc: PANEL_ACTIVE_TITLE_FOREGROUND,
|
||||
}, nls.localize('panelDragAndDropBorder', "Drag and drop feedback color for the panel titles. Panels are shown below the editor area and contain views like output and integrated terminal."));
|
||||
|
||||
|
||||
export const PANEL_SECTION_DRAG_AND_DROP_BACKGROUND = registerColor('panelSection.dropBackground', {
|
||||
dark: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
light: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
hc: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
}, nls.localize('panelSectionDragAndDropBackground', "Drag and drop feedback color for the panel sections. The color should have transparency so that the panel sections can still shine through. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels."));
|
||||
|
||||
export const PANEL_SECTION_HEADER_BACKGROUND = registerColor('panelSectionHeader.background', {
|
||||
dark: Color.fromHex('#808080').transparent(0.2),
|
||||
light: Color.fromHex('#808080').transparent(0.2),
|
||||
hc: null
|
||||
}, nls.localize('panelSectionHeaderBackground', "Panel section header background color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels."));
|
||||
|
||||
export const PANEL_SECTION_HEADER_FOREGROUND = registerColor('panelSectionHeader.foreground', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('panelSectionHeaderForeground', "Panel section header foreground color. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels."));
|
||||
|
||||
export const PANEL_SECTION_HEADER_BORDER = registerColor('panelSectionHeader.border', {
|
||||
dark: contrastBorder,
|
||||
light: contrastBorder,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('panelSectionHeaderBorder', "Panel section header border color used when multiple views are stacked vertically in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels."));
|
||||
|
||||
export const PANEL_SECTION_BORDER = registerColor('panelSection.border', {
|
||||
dark: PANEL_BORDER,
|
||||
light: PANEL_BORDER,
|
||||
hc: PANEL_BORDER
|
||||
}, nls.localize('panelSectionBorder', "Panel section border color used when multiple views are stacked horizontally in the panel. Panels are shown below the editor area and contain views like output and integrated terminal. Panel sections are views nested within the panels."));
|
||||
|
||||
|
||||
// < --- Status --- >
|
||||
|
||||
export const STATUS_BAR_FOREGROUND = registerColor('statusBar.foreground', {
|
||||
dark: '#FFFFFF',
|
||||
light: '#FFFFFF',
|
||||
hc: '#FFFFFF'
|
||||
}, nls.localize('statusBarForeground', "Status bar foreground color when a workspace is opened. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_NO_FOLDER_FOREGROUND = registerColor('statusBar.noFolderForeground', {
|
||||
dark: STATUS_BAR_FOREGROUND,
|
||||
light: STATUS_BAR_FOREGROUND,
|
||||
hc: STATUS_BAR_FOREGROUND
|
||||
}, nls.localize('statusBarNoFolderForeground', "Status bar foreground color when no folder is opened. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_BACKGROUND = registerColor('statusBar.background', {
|
||||
dark: '#007ACC',
|
||||
light: '#007ACC',
|
||||
hc: null
|
||||
}, nls.localize('statusBarBackground', "Status bar background color when a workspace is opened. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_NO_FOLDER_BACKGROUND = registerColor('statusBar.noFolderBackground', {
|
||||
dark: '#68217A',
|
||||
light: '#68217A',
|
||||
hc: null
|
||||
}, nls.localize('statusBarNoFolderBackground', "Status bar background color when no folder is opened. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_BORDER = registerColor('statusBar.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('statusBarBorder', "Status bar border color separating to the sidebar and editor. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_NO_FOLDER_BORDER = registerColor('statusBar.noFolderBorder', {
|
||||
dark: STATUS_BAR_BORDER,
|
||||
light: STATUS_BAR_BORDER,
|
||||
hc: STATUS_BAR_BORDER
|
||||
}, nls.localize('statusBarNoFolderBorder', "Status bar border color separating to the sidebar and editor when no folder is opened. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_ITEM_ACTIVE_BACKGROUND = registerColor('statusBarItem.activeBackground', {
|
||||
dark: Color.white.transparent(0.18),
|
||||
light: Color.white.transparent(0.18),
|
||||
hc: Color.white.transparent(0.18)
|
||||
}, nls.localize('statusBarItemActiveBackground', "Status bar item background color when clicking. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.hoverBackground', {
|
||||
dark: Color.white.transparent(0.12),
|
||||
light: Color.white.transparent(0.12),
|
||||
hc: Color.white.transparent(0.12)
|
||||
}, nls.localize('statusBarItemHoverBackground', "Status bar item background color when hovering. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_PROMINENT_ITEM_FOREGROUND = registerColor('statusBarItem.prominentForeground', {
|
||||
dark: STATUS_BAR_FOREGROUND,
|
||||
light: STATUS_BAR_FOREGROUND,
|
||||
hc: STATUS_BAR_FOREGROUND
|
||||
}, nls.localize('statusBarProminentItemForeground', "Status bar prominent items foreground color. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_PROMINENT_ITEM_BACKGROUND = registerColor('statusBarItem.prominentBackground', {
|
||||
dark: Color.black.transparent(0.5),
|
||||
light: Color.black.transparent(0.5),
|
||||
hc: Color.black.transparent(0.5),
|
||||
}, nls.localize('statusBarProminentItemBackground', "Status bar prominent items background color. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window."));
|
||||
|
||||
export const STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.prominentHoverBackground', {
|
||||
dark: Color.black.transparent(0.3),
|
||||
light: Color.black.transparent(0.3),
|
||||
hc: Color.black.transparent(0.3),
|
||||
}, nls.localize('statusBarProminentItemHoverBackground', "Status bar prominent items background color when hovering. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window."));
|
||||
|
||||
// < --- Activity Bar --- >
|
||||
|
||||
export const ACTIVITY_BAR_BACKGROUND = registerColor('activityBar.background', {
|
||||
dark: '#333333',
|
||||
light: '#2C2C2C',
|
||||
hc: '#000000'
|
||||
}, nls.localize('activityBarBackground', "Activity bar background color. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_FOREGROUND = registerColor('activityBar.foreground', {
|
||||
dark: Color.white,
|
||||
light: Color.white,
|
||||
hc: Color.white
|
||||
}, nls.localize('activityBarForeground', "Activity bar item foreground color when it is active. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_INACTIVE_FOREGROUND = registerColor('activityBar.inactiveForeground', {
|
||||
dark: transparent(ACTIVITY_BAR_FOREGROUND, 0.4),
|
||||
light: transparent(ACTIVITY_BAR_FOREGROUND, 0.4),
|
||||
hc: Color.white
|
||||
}, nls.localize('activityBarInActiveForeground', "Activity bar item foreground color when it is inactive. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_BORDER = registerColor('activityBar.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('activityBarBorder', "Activity bar border color separating to the side bar. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_ACTIVE_BORDER = registerColor('activityBar.activeBorder', {
|
||||
dark: ACTIVITY_BAR_FOREGROUND,
|
||||
light: ACTIVITY_BAR_FOREGROUND,
|
||||
hc: null
|
||||
}, nls.localize('activityBarActiveBorder', "Activity bar border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_ACTIVE_FOCUS_BORDER = registerColor('activityBar.activeFocusBorder', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('activityBarActiveFocusBorder', "Activity bar focus border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_ACTIVE_BACKGROUND = registerColor('activityBar.activeBackground', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('activityBarActiveBackground', "Activity bar background color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_DRAG_AND_DROP_BORDER = registerColor('activityBar.dropBorder', {
|
||||
dark: ACTIVITY_BAR_FOREGROUND,
|
||||
light: ACTIVITY_BAR_FOREGROUND,
|
||||
hc: ACTIVITY_BAR_FOREGROUND,
|
||||
}, nls.localize('activityBarDragAndDropBorder', "Drag and drop feedback color for the activity bar items. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_BADGE_BACKGROUND = registerColor('activityBarBadge.background', {
|
||||
dark: '#007ACC',
|
||||
light: '#007ACC',
|
||||
hc: '#000000'
|
||||
}, nls.localize('activityBarBadgeBackground', "Activity notification badge background color. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
export const ACTIVITY_BAR_BADGE_FOREGROUND = registerColor('activityBarBadge.foreground', {
|
||||
dark: Color.white,
|
||||
light: Color.white,
|
||||
hc: Color.white
|
||||
}, nls.localize('activityBarBadgeForeground', "Activity notification badge foreground color. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
|
||||
|
||||
|
||||
// < --- Remote --- >
|
||||
|
||||
export const STATUS_BAR_HOST_NAME_BACKGROUND = registerColor('statusBarItem.remoteBackground', {
|
||||
dark: ACTIVITY_BAR_BADGE_BACKGROUND,
|
||||
light: ACTIVITY_BAR_BADGE_BACKGROUND,
|
||||
hc: ACTIVITY_BAR_BADGE_BACKGROUND
|
||||
}, nls.localize('statusBarItemHostBackground', "Background color for the remote indicator on the status bar."));
|
||||
|
||||
export const STATUS_BAR_HOST_NAME_FOREGROUND = registerColor('statusBarItem.remoteForeground', {
|
||||
dark: ACTIVITY_BAR_BADGE_FOREGROUND,
|
||||
light: ACTIVITY_BAR_BADGE_FOREGROUND,
|
||||
hc: ACTIVITY_BAR_BADGE_FOREGROUND
|
||||
}, nls.localize('statusBarItemHostForeground', "Foreground color for the remote indicator on the status bar."));
|
||||
|
||||
export const EXTENSION_BADGE_REMOTE_BACKGROUND = registerColor('extensionBadge.remoteBackground', {
|
||||
dark: ACTIVITY_BAR_BADGE_BACKGROUND,
|
||||
light: ACTIVITY_BAR_BADGE_BACKGROUND,
|
||||
hc: ACTIVITY_BAR_BADGE_BACKGROUND
|
||||
}, nls.localize('extensionBadge.remoteBackground', "Background color for the remote badge in the extensions view."));
|
||||
|
||||
export const EXTENSION_BADGE_REMOTE_FOREGROUND = registerColor('extensionBadge.remoteForeground', {
|
||||
dark: ACTIVITY_BAR_BADGE_FOREGROUND,
|
||||
light: ACTIVITY_BAR_BADGE_FOREGROUND,
|
||||
hc: ACTIVITY_BAR_BADGE_FOREGROUND
|
||||
}, nls.localize('extensionBadge.remoteForeground', "Foreground color for the remote badge in the extensions view."));
|
||||
|
||||
|
||||
// < --- Side Bar --- >
|
||||
|
||||
export const SIDE_BAR_BACKGROUND = registerColor('sideBar.background', {
|
||||
dark: '#252526',
|
||||
light: '#F3F3F3',
|
||||
hc: '#000000'
|
||||
}, nls.localize('sideBarBackground', "Side bar background color. The side bar is the container for views like explorer and search."));
|
||||
|
||||
export const SIDE_BAR_FOREGROUND = registerColor('sideBar.foreground', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('sideBarForeground', "Side bar foreground color. The side bar is the container for views like explorer and search."));
|
||||
|
||||
export const SIDE_BAR_BORDER = registerColor('sideBar.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('sideBarBorder', "Side bar border color on the side separating to the editor. The side bar is the container for views like explorer and search."));
|
||||
|
||||
export const SIDE_BAR_TITLE_FOREGROUND = registerColor('sideBarTitle.foreground', {
|
||||
dark: SIDE_BAR_FOREGROUND,
|
||||
light: SIDE_BAR_FOREGROUND,
|
||||
hc: SIDE_BAR_FOREGROUND
|
||||
}, nls.localize('sideBarTitleForeground', "Side bar title foreground color. The side bar is the container for views like explorer and search."));
|
||||
|
||||
export const SIDE_BAR_DRAG_AND_DROP_BACKGROUND = registerColor('sideBar.dropBackground', {
|
||||
dark: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
light: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
hc: EDITOR_DRAG_AND_DROP_BACKGROUND,
|
||||
}, nls.localize('sideBarDragAndDropBackground', "Drag and drop feedback color for the side bar sections. The color should have transparency so that the side bar sections can still shine through. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar."));
|
||||
|
||||
export const SIDE_BAR_SECTION_HEADER_BACKGROUND = registerColor('sideBarSectionHeader.background', {
|
||||
dark: Color.fromHex('#808080').transparent(0.2),
|
||||
light: Color.fromHex('#808080').transparent(0.2),
|
||||
hc: null
|
||||
}, nls.localize('sideBarSectionHeaderBackground', "Side bar section header background color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar."));
|
||||
|
||||
export const SIDE_BAR_SECTION_HEADER_FOREGROUND = registerColor('sideBarSectionHeader.foreground', {
|
||||
dark: SIDE_BAR_FOREGROUND,
|
||||
light: SIDE_BAR_FOREGROUND,
|
||||
hc: SIDE_BAR_FOREGROUND
|
||||
}, nls.localize('sideBarSectionHeaderForeground', "Side bar section header foreground color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar."));
|
||||
|
||||
export const SIDE_BAR_SECTION_HEADER_BORDER = registerColor('sideBarSectionHeader.border', {
|
||||
dark: contrastBorder,
|
||||
light: contrastBorder,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search. Side bar sections are views nested within the side bar."));
|
||||
|
||||
|
||||
// < --- Title Bar --- >
|
||||
|
||||
export const TITLE_BAR_ACTIVE_FOREGROUND = registerColor('titleBar.activeForeground', {
|
||||
dark: '#CCCCCC',
|
||||
light: '#333333',
|
||||
hc: '#FFFFFF'
|
||||
}, nls.localize('titleBarActiveForeground', "Title bar foreground when the window is active."));
|
||||
|
||||
export const TITLE_BAR_INACTIVE_FOREGROUND = registerColor('titleBar.inactiveForeground', {
|
||||
dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, 0.6),
|
||||
light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, 0.6),
|
||||
hc: null
|
||||
}, nls.localize('titleBarInactiveForeground', "Title bar foreground when the window is inactive."));
|
||||
|
||||
export const TITLE_BAR_ACTIVE_BACKGROUND = registerColor('titleBar.activeBackground', {
|
||||
dark: '#3C3C3C',
|
||||
light: '#DDDDDD',
|
||||
hc: '#000000'
|
||||
}, nls.localize('titleBarActiveBackground', "Title bar background when the window is active."));
|
||||
|
||||
export const TITLE_BAR_INACTIVE_BACKGROUND = registerColor('titleBar.inactiveBackground', {
|
||||
dark: transparent(TITLE_BAR_ACTIVE_BACKGROUND, 0.6),
|
||||
light: transparent(TITLE_BAR_ACTIVE_BACKGROUND, 0.6),
|
||||
hc: null
|
||||
}, nls.localize('titleBarInactiveBackground', "Title bar background when the window is inactive."));
|
||||
|
||||
export const TITLE_BAR_BORDER = registerColor('titleBar.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('titleBarBorder', "Title bar border color."));
|
||||
|
||||
// < --- Menubar --- >
|
||||
|
||||
export const MENUBAR_SELECTION_FOREGROUND = registerColor('menubar.selectionForeground', {
|
||||
dark: TITLE_BAR_ACTIVE_FOREGROUND,
|
||||
light: TITLE_BAR_ACTIVE_FOREGROUND,
|
||||
hc: TITLE_BAR_ACTIVE_FOREGROUND
|
||||
}, nls.localize('menubarSelectionForeground', "Foreground color of the selected menu item in the menubar."));
|
||||
|
||||
export const MENUBAR_SELECTION_BACKGROUND = registerColor('menubar.selectionBackground', {
|
||||
dark: transparent(Color.white, 0.1),
|
||||
light: transparent(Color.black, 0.1),
|
||||
hc: null
|
||||
}, nls.localize('menubarSelectionBackground', "Background color of the selected menu item in the menubar."));
|
||||
|
||||
export const MENUBAR_SELECTION_BORDER = registerColor('menubar.selectionBorder', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: activeContrastBorder
|
||||
}, nls.localize('menubarSelectionBorder', "Border color of the selected menu item in the menubar."));
|
||||
|
||||
// < --- Notifications --- >
|
||||
|
||||
export const NOTIFICATIONS_CENTER_BORDER = registerColor('notificationCenter.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('notificationCenterBorder', "Notifications center border color. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_TOAST_BORDER = registerColor('notificationToast.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('notificationToastBorder', "Notification toast border color. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_FOREGROUND = registerColor('notifications.foreground', {
|
||||
dark: editorWidgetForeground,
|
||||
light: editorWidgetForeground,
|
||||
hc: editorWidgetForeground
|
||||
}, nls.localize('notificationsForeground', "Notifications foreground color. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_BACKGROUND = registerColor('notifications.background', {
|
||||
dark: editorWidgetBackground,
|
||||
light: editorWidgetBackground,
|
||||
hc: editorWidgetBackground
|
||||
}, nls.localize('notificationsBackground', "Notifications background color. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_LINKS = registerColor('notificationLink.foreground', {
|
||||
dark: textLinkForeground,
|
||||
light: textLinkForeground,
|
||||
hc: textLinkForeground
|
||||
}, nls.localize('notificationsLink', "Notification links foreground color. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_CENTER_HEADER_FOREGROUND = registerColor('notificationCenterHeader.foreground', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, nls.localize('notificationCenterHeaderForeground', "Notifications center header foreground color. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_CENTER_HEADER_BACKGROUND = registerColor('notificationCenterHeader.background', {
|
||||
dark: lighten(NOTIFICATIONS_BACKGROUND, 0.3),
|
||||
light: darken(NOTIFICATIONS_BACKGROUND, 0.05),
|
||||
hc: NOTIFICATIONS_BACKGROUND
|
||||
}, nls.localize('notificationCenterHeaderBackground', "Notifications center header background color. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_BORDER = registerColor('notifications.border', {
|
||||
dark: NOTIFICATIONS_CENTER_HEADER_BACKGROUND,
|
||||
light: NOTIFICATIONS_CENTER_HEADER_BACKGROUND,
|
||||
hc: NOTIFICATIONS_CENTER_HEADER_BACKGROUND
|
||||
}, nls.localize('notificationsBorder', "Notifications border color separating from other notifications in the notifications center. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_ERROR_ICON_FOREGROUND = registerColor('notificationsErrorIcon.foreground', {
|
||||
dark: editorErrorForeground,
|
||||
light: editorErrorForeground,
|
||||
hc: editorErrorForeground
|
||||
}, nls.localize('notificationsErrorIconForeground', "The color used for the icon of error notifications. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_WARNING_ICON_FOREGROUND = registerColor('notificationsWarningIcon.foreground', {
|
||||
dark: editorWarningForeground,
|
||||
light: editorWarningForeground,
|
||||
hc: editorWarningForeground
|
||||
}, nls.localize('notificationsWarningIconForeground', "The color used for the icon of warning notifications. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const NOTIFICATIONS_INFO_ICON_FOREGROUND = registerColor('notificationsInfoIcon.foreground', {
|
||||
dark: editorInfoForeground,
|
||||
light: editorInfoForeground,
|
||||
hc: editorInfoForeground
|
||||
}, nls.localize('notificationsInfoIconForeground', "The color used for the icon of info notifications. Notifications slide in from the bottom right of the window."));
|
||||
|
||||
export const WINDOW_ACTIVE_BORDER = registerColor('window.activeBorder', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('windowActiveBorder', "The color used for the border of the window when it is active. Only supported in the desktop client when using the custom title bar."));
|
||||
|
||||
export const WINDOW_INACTIVE_BORDER = registerColor('window.inactiveBorder', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: contrastBorder
|
||||
}, nls.localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client when using the custom title bar."));
|
||||
19
lib/vscode/src/vs/workbench/common/viewlet.ts
Normal file
19
lib/vscode/src/vs/workbench/common/viewlet.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
|
||||
export const SideBarVisibleContext = new RawContextKey<boolean>('sideBarVisible', false);
|
||||
export const SidebarFocusContext = new RawContextKey<boolean>('sideBarFocus', false);
|
||||
export const ActiveViewletContext = new RawContextKey<string>('activeViewlet', '');
|
||||
|
||||
export interface IViewlet extends IPaneComposite {
|
||||
|
||||
/**
|
||||
* Returns the minimal width needed to avoid any content horizontal truncation
|
||||
*/
|
||||
getOptimalWidth(): number | undefined;
|
||||
}
|
||||
738
lib/vscode/src/vs/workbench/common/views.ts
Normal file
738
lib/vscode/src/vs/workbench/common/views.ts
Normal file
@@ -0,0 +1,738 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
import { UriComponents, URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { RawContextKey, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { localize } from 'vs/nls';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { getOrSet } from 'vs/base/common/map';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { flatten, mergeSort } from 'vs/base/common/arrays';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { SetMap } from 'vs/base/common/collections';
|
||||
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
|
||||
|
||||
export namespace Extensions {
|
||||
export const ViewContainersRegistry = 'workbench.registry.view.containers';
|
||||
export const ViewsRegistry = 'workbench.registry.view';
|
||||
}
|
||||
|
||||
export enum ViewContainerLocation {
|
||||
Sidebar,
|
||||
Panel
|
||||
}
|
||||
|
||||
export interface IViewContainerDescriptor {
|
||||
|
||||
readonly id: string;
|
||||
|
||||
readonly name: string;
|
||||
|
||||
readonly ctorDescriptor: SyncDescriptor<IViewPaneContainer>;
|
||||
|
||||
readonly storageId?: string;
|
||||
|
||||
readonly icon?: string | URI;
|
||||
|
||||
readonly alwaysUseContainerInfo?: boolean;
|
||||
|
||||
readonly focusCommand?: { id: string, keybindings?: IKeybindings };
|
||||
|
||||
readonly viewOrderDelegate?: ViewOrderDelegate;
|
||||
|
||||
readonly hideIfEmpty?: boolean;
|
||||
|
||||
readonly extensionId?: ExtensionIdentifier;
|
||||
|
||||
readonly rejectAddedViews?: boolean;
|
||||
|
||||
readonly order?: number;
|
||||
|
||||
requestedIndex?: number;
|
||||
}
|
||||
|
||||
export interface IViewContainersRegistry {
|
||||
/**
|
||||
* An event that is triggered when a view container is registered.
|
||||
*/
|
||||
readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
|
||||
|
||||
/**
|
||||
* An event that is triggered when a view container is deregistered.
|
||||
*/
|
||||
readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
|
||||
|
||||
/**
|
||||
* All registered view containers
|
||||
*/
|
||||
readonly all: ViewContainer[];
|
||||
|
||||
/**
|
||||
* Registers a view container to given location.
|
||||
* No op if a view container is already registered.
|
||||
*
|
||||
* @param viewContainerDescriptor descriptor of view container
|
||||
* @param location location of the view container
|
||||
*
|
||||
* @returns the registered ViewContainer.
|
||||
*/
|
||||
registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation, isDefault?: boolean): ViewContainer;
|
||||
|
||||
/**
|
||||
* Deregisters the given view container
|
||||
* No op if the view container is not registered
|
||||
*/
|
||||
deregisterViewContainer(viewContainer: ViewContainer): void;
|
||||
|
||||
/**
|
||||
* Returns the view container with given id.
|
||||
*
|
||||
* @returns the view container with given id.
|
||||
*/
|
||||
get(id: string): ViewContainer | undefined;
|
||||
|
||||
/**
|
||||
* Returns all view containers in the given location
|
||||
*/
|
||||
getViewContainers(location: ViewContainerLocation): ViewContainer[];
|
||||
|
||||
/**
|
||||
* Returns the view container location
|
||||
*/
|
||||
getViewContainerLocation(container: ViewContainer): ViewContainerLocation;
|
||||
|
||||
/**
|
||||
* Return the default view container from the given location
|
||||
*/
|
||||
getDefaultViewContainer(location: ViewContainerLocation): ViewContainer | undefined;
|
||||
}
|
||||
|
||||
interface ViewOrderDelegate {
|
||||
getOrder(group?: string): number | undefined;
|
||||
}
|
||||
|
||||
export interface ViewContainer extends IViewContainerDescriptor { }
|
||||
|
||||
class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry {
|
||||
|
||||
private readonly _onDidRegister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>());
|
||||
readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidRegister.event;
|
||||
|
||||
private readonly _onDidDeregister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>());
|
||||
readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidDeregister.event;
|
||||
|
||||
private readonly viewContainers: Map<ViewContainerLocation, ViewContainer[]> = new Map<ViewContainerLocation, ViewContainer[]>();
|
||||
private readonly defaultViewContainers: ViewContainer[] = [];
|
||||
|
||||
get all(): ViewContainer[] {
|
||||
return flatten([...this.viewContainers.values()]);
|
||||
}
|
||||
|
||||
registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation, isDefault?: boolean): ViewContainer {
|
||||
const existing = this.get(viewContainerDescriptor.id);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const viewContainer: ViewContainer = viewContainerDescriptor;
|
||||
const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []);
|
||||
viewContainers.push(viewContainer);
|
||||
if (isDefault) {
|
||||
this.defaultViewContainers.push(viewContainer);
|
||||
}
|
||||
this._onDidRegister.fire({ viewContainer, viewContainerLocation });
|
||||
return viewContainer;
|
||||
}
|
||||
|
||||
deregisterViewContainer(viewContainer: ViewContainer): void {
|
||||
for (const viewContainerLocation of this.viewContainers.keys()) {
|
||||
const viewContainers = this.viewContainers.get(viewContainerLocation)!;
|
||||
const index = viewContainers?.indexOf(viewContainer);
|
||||
if (index !== -1) {
|
||||
viewContainers?.splice(index, 1);
|
||||
if (viewContainers.length === 0) {
|
||||
this.viewContainers.delete(viewContainerLocation);
|
||||
}
|
||||
this._onDidDeregister.fire({ viewContainer, viewContainerLocation });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get(id: string): ViewContainer | undefined {
|
||||
return this.all.filter(viewContainer => viewContainer.id === id)[0];
|
||||
}
|
||||
|
||||
getViewContainers(location: ViewContainerLocation): ViewContainer[] {
|
||||
return [...(this.viewContainers.get(location) || [])];
|
||||
}
|
||||
|
||||
getViewContainerLocation(container: ViewContainer): ViewContainerLocation {
|
||||
return [...this.viewContainers.keys()].filter(location => this.getViewContainers(location).filter(viewContainer => viewContainer?.id === container.id).length > 0)[0];
|
||||
}
|
||||
|
||||
getDefaultViewContainer(location: ViewContainerLocation): ViewContainer | undefined {
|
||||
return this.defaultViewContainers.find(viewContainer => this.getViewContainerLocation(viewContainer) === location);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.ViewContainersRegistry, new ViewContainersRegistryImpl());
|
||||
|
||||
export interface IViewDescriptor {
|
||||
|
||||
readonly type?: string;
|
||||
|
||||
readonly id: string;
|
||||
|
||||
readonly name: string;
|
||||
|
||||
readonly ctorDescriptor: SyncDescriptor<IView>;
|
||||
|
||||
readonly when?: ContextKeyExpression;
|
||||
|
||||
readonly order?: number;
|
||||
|
||||
readonly weight?: number;
|
||||
|
||||
readonly collapsed?: boolean;
|
||||
|
||||
readonly canToggleVisibility?: boolean;
|
||||
|
||||
readonly canMoveView?: boolean;
|
||||
|
||||
readonly containerIcon?: string | URI;
|
||||
|
||||
readonly containerTitle?: string;
|
||||
|
||||
// Applies only to newly created views
|
||||
readonly hideByDefault?: boolean;
|
||||
|
||||
readonly workspace?: boolean;
|
||||
|
||||
readonly focusCommand?: { id: string, keybindings?: IKeybindings };
|
||||
|
||||
// For contributed remote explorer views
|
||||
readonly group?: string;
|
||||
|
||||
readonly remoteAuthority?: string | string[];
|
||||
}
|
||||
|
||||
export interface IViewDescriptorRef {
|
||||
viewDescriptor: IViewDescriptor;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface IAddedViewDescriptorRef extends IViewDescriptorRef {
|
||||
collapsed: boolean;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface IAddedViewDescriptorState {
|
||||
viewDescriptor: IViewDescriptor,
|
||||
collapsed?: boolean;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface IViewContainerModel {
|
||||
|
||||
readonly title: string;
|
||||
readonly icon: string | URI | undefined;
|
||||
readonly onDidChangeContainerInfo: Event<{ title?: boolean, icon?: boolean }>;
|
||||
|
||||
readonly allViewDescriptors: ReadonlyArray<IViewDescriptor>;
|
||||
readonly onDidChangeAllViewDescriptors: Event<{ added: ReadonlyArray<IViewDescriptor>, removed: ReadonlyArray<IViewDescriptor> }>;
|
||||
|
||||
readonly activeViewDescriptors: ReadonlyArray<IViewDescriptor>;
|
||||
readonly onDidChangeActiveViewDescriptors: Event<{ added: ReadonlyArray<IViewDescriptor>, removed: ReadonlyArray<IViewDescriptor> }>;
|
||||
|
||||
readonly visibleViewDescriptors: ReadonlyArray<IViewDescriptor>;
|
||||
readonly onDidAddVisibleViewDescriptors: Event<IAddedViewDescriptorRef[]>;
|
||||
readonly onDidRemoveVisibleViewDescriptors: Event<IViewDescriptorRef[]>
|
||||
readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>
|
||||
|
||||
isVisible(id: string): boolean;
|
||||
setVisible(id: string, visible: boolean, size?: number): void;
|
||||
|
||||
isCollapsed(id: string): boolean;
|
||||
setCollapsed(id: string, collapsed: boolean): void;
|
||||
|
||||
getSize(id: string): number | undefined;
|
||||
setSize(id: string, size: number): void
|
||||
|
||||
move(from: string, to: string): void;
|
||||
}
|
||||
|
||||
export enum ViewContentGroups {
|
||||
Open = '2_open',
|
||||
Debug = '4_debug',
|
||||
SCM = '5_scm',
|
||||
More = '9_more'
|
||||
}
|
||||
|
||||
export interface IViewContentDescriptor {
|
||||
readonly content: string;
|
||||
readonly when?: ContextKeyExpression | 'default';
|
||||
readonly group?: string;
|
||||
readonly order?: number;
|
||||
|
||||
/**
|
||||
* ordered preconditions for each button in the content
|
||||
*/
|
||||
readonly preconditions?: (ContextKeyExpression | undefined)[];
|
||||
}
|
||||
|
||||
export interface IViewsRegistry {
|
||||
|
||||
readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }[]>;
|
||||
|
||||
readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>;
|
||||
|
||||
readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>;
|
||||
|
||||
registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
|
||||
|
||||
registerViews2(views: { views: IViewDescriptor[], viewContainer: ViewContainer }[]): void;
|
||||
|
||||
deregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
|
||||
|
||||
moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
|
||||
|
||||
getViews(viewContainer: ViewContainer): IViewDescriptor[];
|
||||
|
||||
getView(id: string): IViewDescriptor | null;
|
||||
|
||||
getViewContainer(id: string): ViewContainer | null;
|
||||
|
||||
readonly onDidChangeViewWelcomeContent: Event<string>;
|
||||
registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable;
|
||||
getViewWelcomeContent(id: string): IViewContentDescriptor[];
|
||||
}
|
||||
|
||||
function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewContentDescriptor): number {
|
||||
const aGroup = a.group ?? ViewContentGroups.More;
|
||||
const bGroup = b.group ?? ViewContentGroups.More;
|
||||
if (aGroup !== bGroup) {
|
||||
return aGroup.localeCompare(bGroup);
|
||||
}
|
||||
return (a.order ?? 5) - (b.order ?? 5);
|
||||
}
|
||||
|
||||
class ViewsRegistry extends Disposable implements IViewsRegistry {
|
||||
|
||||
private readonly _onViewsRegistered = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }[]>());
|
||||
readonly onViewsRegistered = this._onViewsRegistered.event;
|
||||
|
||||
private readonly _onViewsDeregistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>());
|
||||
readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsDeregistered.event;
|
||||
|
||||
private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>());
|
||||
readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event;
|
||||
|
||||
private readonly _onDidChangeViewWelcomeContent: Emitter<string> = this._register(new Emitter<string>());
|
||||
readonly onDidChangeViewWelcomeContent: Event<string> = this._onDidChangeViewWelcomeContent.event;
|
||||
|
||||
private _viewContainers: ViewContainer[] = [];
|
||||
private _views: Map<ViewContainer, IViewDescriptor[]> = new Map<ViewContainer, IViewDescriptor[]>();
|
||||
private _viewWelcomeContents = new SetMap<string, IViewContentDescriptor>();
|
||||
|
||||
registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void {
|
||||
this.registerViews2([{ views, viewContainer }]);
|
||||
}
|
||||
|
||||
registerViews2(views: { views: IViewDescriptor[], viewContainer: ViewContainer }[]): void {
|
||||
views.forEach(({ views, viewContainer }) => this.addViews(views, viewContainer));
|
||||
this._onViewsRegistered.fire(views);
|
||||
}
|
||||
|
||||
deregisterViews(viewDescriptors: IViewDescriptor[], viewContainer: ViewContainer): void {
|
||||
const views = this.removeViews(viewDescriptors, viewContainer);
|
||||
if (views.length) {
|
||||
this._onViewsDeregistered.fire({ views, viewContainer });
|
||||
}
|
||||
}
|
||||
|
||||
moveViews(viewsToMove: IViewDescriptor[], viewContainer: ViewContainer): void {
|
||||
for (const container of this._views.keys()) {
|
||||
if (container !== viewContainer) {
|
||||
const views = this.removeViews(viewsToMove, container);
|
||||
if (views.length) {
|
||||
this.addViews(views, viewContainer);
|
||||
this._onDidChangeContainer.fire({ views, from: container, to: viewContainer });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getViews(loc: ViewContainer): IViewDescriptor[] {
|
||||
return this._views.get(loc) || [];
|
||||
}
|
||||
|
||||
getView(id: string): IViewDescriptor | null {
|
||||
for (const viewContainer of this._viewContainers) {
|
||||
const viewDescriptor = (this._views.get(viewContainer) || []).filter(v => v.id === id)[0];
|
||||
if (viewDescriptor) {
|
||||
return viewDescriptor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getViewContainer(viewId: string): ViewContainer | null {
|
||||
for (const viewContainer of this._viewContainers) {
|
||||
const viewDescriptor = (this._views.get(viewContainer) || []).filter(v => v.id === viewId)[0];
|
||||
if (viewDescriptor) {
|
||||
return viewContainer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable {
|
||||
this._viewWelcomeContents.add(id, viewContent);
|
||||
this._onDidChangeViewWelcomeContent.fire(id);
|
||||
|
||||
return toDisposable(() => {
|
||||
this._viewWelcomeContents.delete(id, viewContent);
|
||||
this._onDidChangeViewWelcomeContent.fire(id);
|
||||
});
|
||||
}
|
||||
|
||||
getViewWelcomeContent(id: string): IViewContentDescriptor[] {
|
||||
const result: IViewContentDescriptor[] = [];
|
||||
this._viewWelcomeContents.forEach(id, descriptor => result.push(descriptor));
|
||||
mergeSort(result, compareViewContentDescriptors);
|
||||
return result;
|
||||
}
|
||||
|
||||
private addViews(viewDescriptors: IViewDescriptor[], viewContainer: ViewContainer): void {
|
||||
let views = this._views.get(viewContainer);
|
||||
if (!views) {
|
||||
views = [];
|
||||
this._views.set(viewContainer, views);
|
||||
this._viewContainers.push(viewContainer);
|
||||
}
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
if (this.getView(viewDescriptor.id) !== null) {
|
||||
throw new Error(localize('duplicateId', "A view with id '{0}' is already registered", viewDescriptor.id));
|
||||
}
|
||||
views.push(viewDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private removeViews(viewDescriptors: IViewDescriptor[], viewContainer: ViewContainer): IViewDescriptor[] {
|
||||
const views = this._views.get(viewContainer);
|
||||
if (!views) {
|
||||
return [];
|
||||
}
|
||||
const viewsToDeregister: IViewDescriptor[] = [];
|
||||
const remaningViews: IViewDescriptor[] = [];
|
||||
for (const view of views) {
|
||||
if (!viewDescriptors.includes(view)) {
|
||||
remaningViews.push(view);
|
||||
} else {
|
||||
viewsToDeregister.push(view);
|
||||
}
|
||||
}
|
||||
if (viewsToDeregister.length) {
|
||||
if (remaningViews.length) {
|
||||
this._views.set(viewContainer, remaningViews);
|
||||
} else {
|
||||
this._views.delete(viewContainer);
|
||||
this._viewContainers.splice(this._viewContainers.indexOf(viewContainer), 1);
|
||||
}
|
||||
}
|
||||
return viewsToDeregister;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.ViewsRegistry, new ViewsRegistry());
|
||||
|
||||
export interface IView {
|
||||
|
||||
readonly id: string;
|
||||
|
||||
focus(): void;
|
||||
|
||||
isVisible(): boolean;
|
||||
|
||||
isBodyVisible(): boolean;
|
||||
|
||||
setExpanded(expanded: boolean): boolean;
|
||||
|
||||
getProgressIndicator(): IProgressIndicator | undefined;
|
||||
}
|
||||
|
||||
export const IViewsService = createDecorator<IViewsService>('viewsService');
|
||||
export interface IViewsService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
// View Container APIs
|
||||
readonly onDidChangeViewContainerVisibility: Event<{ id: string, visible: boolean, location: ViewContainerLocation }>;
|
||||
isViewContainerVisible(id: string): boolean;
|
||||
openViewContainer(id: string, focus?: boolean): Promise<IPaneComposite | null>;
|
||||
closeViewContainer(id: string): void;
|
||||
getVisibleViewContainer(location: ViewContainerLocation): ViewContainer | null;
|
||||
getActiveViewPaneContainerWithId(viewContainerId: string): IViewPaneContainer | null;
|
||||
|
||||
// View APIs
|
||||
readonly onDidChangeViewVisibility: Event<{ id: string, visible: boolean }>;
|
||||
isViewVisible(id: string): boolean;
|
||||
openView<T extends IView>(id: string, focus?: boolean): Promise<T | null>;
|
||||
closeView(id: string): void;
|
||||
getActiveViewWithId<T extends IView>(id: string): T | null;
|
||||
getViewProgressIndicator(id: string): IProgressIndicator | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* View Contexts
|
||||
*/
|
||||
export const FocusedViewContext = new RawContextKey<string>('focusedView', '');
|
||||
export function getVisbileViewContextKey(viewId: string): string { return `${viewId}.visible`; }
|
||||
|
||||
export const IViewDescriptorService = createDecorator<IViewDescriptorService>('viewDescriptorService');
|
||||
|
||||
export enum ViewVisibilityState {
|
||||
Default = 0,
|
||||
Expand = 1
|
||||
}
|
||||
|
||||
export interface IViewDescriptorService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
// ViewContainers
|
||||
readonly viewContainers: ReadonlyArray<ViewContainer>;
|
||||
readonly onDidChangeViewContainers: Event<{ added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }> }>;
|
||||
|
||||
getDefaultViewContainer(location: ViewContainerLocation): ViewContainer | undefined;
|
||||
getViewContainerById(id: string): ViewContainer | null;
|
||||
isViewContainerRemovedPermanently(id: string): boolean;
|
||||
getDefaultViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null;
|
||||
getViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null;
|
||||
getViewContainersByLocation(location: ViewContainerLocation): ViewContainer[];
|
||||
getViewContainerModel(viewContainer: ViewContainer): IViewContainerModel;
|
||||
|
||||
readonly onDidChangeContainerLocation: Event<{ viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation }>;
|
||||
moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation, requestedIndex?: number): void;
|
||||
|
||||
// Views
|
||||
getViewDescriptorById(id: string): IViewDescriptor | null;
|
||||
getViewContainerByViewId(id: string): ViewContainer | null;
|
||||
getDefaultContainerById(id: string): ViewContainer | null;
|
||||
getViewLocationById(id: string): ViewContainerLocation | null;
|
||||
|
||||
readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>;
|
||||
moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer, visibilityState?: ViewVisibilityState): void;
|
||||
|
||||
readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>;
|
||||
moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void;
|
||||
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
// Custom views
|
||||
|
||||
export interface ITreeView extends IDisposable {
|
||||
|
||||
dataProvider: ITreeViewDataProvider | undefined;
|
||||
|
||||
showCollapseAllAction: boolean;
|
||||
|
||||
canSelectMany: boolean;
|
||||
|
||||
message?: string;
|
||||
|
||||
title: string;
|
||||
|
||||
description: string | undefined;
|
||||
|
||||
readonly visible: boolean;
|
||||
|
||||
readonly onDidExpandItem: Event<ITreeItem>;
|
||||
|
||||
readonly onDidCollapseItem: Event<ITreeItem>;
|
||||
|
||||
readonly onDidChangeSelection: Event<ITreeItem[]>;
|
||||
|
||||
readonly onDidChangeVisibility: Event<boolean>;
|
||||
|
||||
readonly onDidChangeActions: Event<void>;
|
||||
|
||||
readonly onDidChangeTitle: Event<string>;
|
||||
|
||||
readonly onDidChangeDescription: Event<string | undefined>;
|
||||
|
||||
readonly onDidChangeWelcomeState: Event<void>;
|
||||
|
||||
refresh(treeItems?: ITreeItem[]): Promise<void>;
|
||||
|
||||
setVisibility(visible: boolean): void;
|
||||
|
||||
focus(): void;
|
||||
|
||||
layout(height: number, width: number): void;
|
||||
|
||||
getOptimalWidth(): number;
|
||||
|
||||
reveal(item: ITreeItem): Promise<void>;
|
||||
|
||||
expand(itemOrItems: ITreeItem | ITreeItem[]): Promise<void>;
|
||||
|
||||
setSelection(items: ITreeItem[]): void;
|
||||
|
||||
setFocus(item: ITreeItem): void;
|
||||
|
||||
show(container: any): void;
|
||||
}
|
||||
|
||||
export interface IRevealOptions {
|
||||
|
||||
select?: boolean;
|
||||
|
||||
focus?: boolean;
|
||||
|
||||
expand?: boolean | number;
|
||||
|
||||
}
|
||||
|
||||
export interface ITreeViewDescriptor extends IViewDescriptor {
|
||||
treeView: ITreeView;
|
||||
}
|
||||
|
||||
export type TreeViewItemHandleArg = {
|
||||
$treeViewId: string,
|
||||
$treeItemHandle: string
|
||||
};
|
||||
|
||||
export enum TreeItemCollapsibleState {
|
||||
None = 0,
|
||||
Collapsed = 1,
|
||||
Expanded = 2
|
||||
}
|
||||
|
||||
export interface ITreeItemLabel {
|
||||
|
||||
label: string;
|
||||
|
||||
highlights?: [number, number][];
|
||||
|
||||
strikethrough?: boolean;
|
||||
|
||||
}
|
||||
|
||||
export interface ITreeItem {
|
||||
|
||||
handle: string;
|
||||
|
||||
parentHandle?: string;
|
||||
|
||||
collapsibleState: TreeItemCollapsibleState;
|
||||
|
||||
label?: ITreeItemLabel;
|
||||
|
||||
description?: string | boolean;
|
||||
|
||||
icon?: UriComponents;
|
||||
|
||||
iconDark?: UriComponents;
|
||||
|
||||
themeIcon?: ThemeIcon;
|
||||
|
||||
resourceUri?: UriComponents;
|
||||
|
||||
tooltip?: string | IMarkdownString;
|
||||
|
||||
contextValue?: string;
|
||||
|
||||
command?: Command;
|
||||
|
||||
children?: ITreeItem[];
|
||||
|
||||
accessibilityInformation?: IAccessibilityInformation;
|
||||
}
|
||||
|
||||
export class ResolvableTreeItem implements ITreeItem {
|
||||
handle!: string;
|
||||
parentHandle?: string;
|
||||
collapsibleState!: TreeItemCollapsibleState;
|
||||
label?: ITreeItemLabel;
|
||||
description?: string | boolean;
|
||||
icon?: UriComponents;
|
||||
iconDark?: UriComponents;
|
||||
themeIcon?: ThemeIcon;
|
||||
resourceUri?: UriComponents;
|
||||
tooltip?: string | IMarkdownString;
|
||||
contextValue?: string;
|
||||
command?: Command;
|
||||
children?: ITreeItem[];
|
||||
accessibilityInformation?: IAccessibilityInformation;
|
||||
resolve: () => Promise<void>;
|
||||
private resolved: boolean = false;
|
||||
private _hasResolve: boolean = false;
|
||||
constructor(treeItem: ITreeItem, resolve?: (() => Promise<ITreeItem | undefined>)) {
|
||||
mixin(this, treeItem);
|
||||
this._hasResolve = !!resolve;
|
||||
this.resolve = async () => {
|
||||
if (resolve && !this.resolved) {
|
||||
const resolvedItem = await resolve();
|
||||
if (resolvedItem) {
|
||||
// Resolvable elements. Currently only tooltip.
|
||||
this.tooltip = resolvedItem.tooltip;
|
||||
}
|
||||
}
|
||||
this.resolved = true;
|
||||
};
|
||||
}
|
||||
get hasResolve(): boolean {
|
||||
return this._hasResolve;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITreeViewDataProvider {
|
||||
readonly isTreeEmpty?: boolean;
|
||||
onDidChangeEmpty?: Event<void>;
|
||||
getChildren(element?: ITreeItem): Promise<ITreeItem[]>;
|
||||
|
||||
}
|
||||
|
||||
export interface IEditableData {
|
||||
validationMessage: (value: string) => { content: string, severity: Severity } | null;
|
||||
placeholder?: string | null;
|
||||
startingValue?: string | null;
|
||||
onFinish: (value: string, success: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface IViewPaneContainer {
|
||||
onDidAddViews: Event<IView[]>;
|
||||
onDidRemoveViews: Event<IView[]>;
|
||||
onDidChangeViewVisibility: Event<IView>;
|
||||
|
||||
readonly views: IView[];
|
||||
|
||||
setVisible(visible: boolean): void;
|
||||
isVisible(): boolean;
|
||||
focus(): void;
|
||||
getActions(): IAction[];
|
||||
getSecondaryActions(): IAction[];
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined;
|
||||
getActionsContext(): unknown;
|
||||
getView(viewId: string): IView | undefined;
|
||||
saveState(): void;
|
||||
}
|
||||
Reference in New Issue
Block a user