diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 2b7a163f047..c7b2fed89ac 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -36,7 +36,7 @@ import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingReso import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; -import { ILabelService, ResourceLabelFormatter } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -713,8 +713,7 @@ export class SimpleUriLabelService implements ILabelService { _serviceBrand: undefined; - private readonly _onDidRegisterFormatter = new Emitter(); - public readonly onDidChangeFormatters: Event = this._onDidRegisterFormatter.event; + public readonly onDidChangeFormatters: Event = Event.None; public getUriLabel(resource: URI, options?: { relative?: boolean, forceNoTildify?: boolean }): string { if (resource.scheme === 'file') { diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index b2422cfa473..64fe1845042 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -26,7 +26,11 @@ export interface ILabelService { getHostLabel(scheme: string, authority?: string): string; getSeparator(scheme: string, authority?: string): '/' | '\\'; registerFormatter(formatter: ResourceLabelFormatter): IDisposable; - onDidChangeFormatters: Event; + onDidChangeFormatters: Event; +} + +export interface IFormatterChangeEvent { + scheme: string; } export interface ResourceLabelFormatter { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index eece5353caa..6f4aa04679a 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -137,14 +137,14 @@ export class ResourceLabels extends Disposable { })); // notify when label formatters change - this._register(this.labelService.onDidChangeFormatters(() => { - this._widgets.forEach(widget => widget.notifyFormattersChange()); + this._register(this.labelService.onDidChangeFormatters(e => { + this._widgets.forEach(widget => widget.notifyFormattersChange(e.scheme)); })); // notify when untitled labels change - this.textFileService.untitled.onDidChangeLabel(model => { + this._register(this.textFileService.untitled.onDidChangeLabel(model => { this._widgets.forEach(widget => widget.notifyUntitledLabelChange(model.resource)); - }); + })); } get(index: number): IResourceLabel { @@ -311,8 +311,10 @@ class ResourceLabelWidget extends IconLabel { this.render(true); } - notifyFormattersChange(): void { - this.render(false); + notifyFormattersChange(scheme: string): void { + if (this.label?.resource?.scheme === scheme) { + this.render(false); + } } notifyUntitledLabelChange(resource: URI): void { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 3509fe604e0..dffc8df1cf2 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -40,9 +40,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, assertAllDefined, assertIsDefined } from 'vs/base/common/types'; -import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { basenameOrAuthority } from 'vs/base/common/resources'; +import { RunOnceScheduler } from 'vs/base/common/async'; interface IEditorInputLabel { name?: string; @@ -85,10 +85,9 @@ export class TabsTitleControl extends TitleControl { @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, - @ILabelService labelService: ILabelService, @IEditorService private readonly editorService: EditorServiceImpl ) { - super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService, labelService); + super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService); this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); @@ -392,10 +391,16 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimension); } + private updateEditorLabelAggregator = this._register(new RunOnceScheduler(() => this.updateEditorLabels(), 0)); + updateEditorLabel(editor: IEditorInput): void { // Update all labels to account for changes to tab labels - this.updateEditorLabels(); + // Since this method may be called a lot of times from + // individual editors, we collect all those requests and + // then run the update once because we have to update + // all opened tabs in the group at once. + this.updateEditorLabelAggregator.schedule(); } updateEditorLabels(): void { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 1077097edde..e191034f051 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -40,7 +40,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; -import { ILabelService } from 'vs/platform/label/common/label'; import { isFirefox } from 'vs/base/browser/browser'; export interface IToolbarActions { @@ -82,8 +81,7 @@ export abstract class TitleControl extends Themable { @IThemeService themeService: IThemeService, @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService protected configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService, - @ILabelService private readonly labelService: ILabelService + @IFileService private readonly fileService: IFileService ) { super(themeService); @@ -97,8 +95,9 @@ export abstract class TitleControl extends Themable { } private registerListeners(): void { + + // Update actions toolbar when extension register that may contribute them this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar())); - this._register(this.labelService.onDidChangeFormatters(() => this.updateEditorLabels())); } protected abstract create(parent: HTMLElement): void; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index f23aacde162..13dcddb9786 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -86,7 +86,7 @@ export class TitlebarPart extends Part implements ITitleService { private readonly properties: ITitleProperties = { isPure: true, isAdmin: false }; private readonly activeEditorListeners = this._register(new DisposableStore()); - private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); + private readonly titleUpdater = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); private contextMenu: IMenu; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 65251653849..35a73df65ac 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -609,8 +609,20 @@ export abstract class TextResourceEditorInput extends EditorInput { protected registerListeners(): void { // Clear label memoizer on certain events that have impact - this._register(this.labelService.onDidChangeFormatters(() => TextResourceEditorInput.MEMOIZER.clear())); - this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => TextResourceEditorInput.MEMOIZER.clear())); + 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.resource.scheme) { + + // Clear any cached labels from before + TextResourceEditorInput.MEMOIZER.clear(); + + // Trigger recompute of label + this._onDidChangeLabel.fire(); + } } getName(): string { @@ -685,10 +697,6 @@ export abstract class TextResourceEditorInput extends EditorInput { return false; // untitled is never readonly } - if (!this.fileService.canHandleResource(this.resource)) { - return true; // resources without file support are always readonly - } - return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 52f71ee6229..b7d2136893e 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -39,7 +39,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browse import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { ILabelService, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; @@ -92,7 +92,7 @@ suite('ExtensionsActions Test', () => { }()); instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); + instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); instantiationService.stub(IURLService, URLService); diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 8bc3fa0db09..0c2905e2708 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -5,9 +5,9 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import * as paths from 'vs/base/common/path'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -16,7 +16,7 @@ import { isEqual, basenameOrAuthority, basename, joinPath, dirname } from 'vs/ba import { tildify, getPathLabel } from 'vs/base/common/labels'; import { ltrim, endsWith } from 'vs/base/common/strings'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; -import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { match } from 'vs/base/common/glob'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -89,19 +89,20 @@ class ResourceLabelFormattersHandler implements IWorkbenchContribution { } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ResourceLabelFormattersHandler, LifecyclePhase.Restored); -export class LabelService implements ILabelService { +export class LabelService extends Disposable implements ILabelService { + _serviceBrand: undefined; private formatters: ResourceLabelFormatter[] = []; - private readonly _onDidChangeFormatters = new Emitter(); + + private readonly _onDidChangeFormatters = this._register(new Emitter()); + readonly onDidChangeFormatters = this._onDidChangeFormatters.event; constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - ) { } - - get onDidChangeFormatters(): Event { - return this._onDidChangeFormatters.event; + ) { + super(); } findFormatting(resource: URI): ResourceLabelFormatting | undefined { @@ -226,12 +227,12 @@ export class LabelService implements ILabelService { registerFormatter(formatter: ResourceLabelFormatter): IDisposable { this.formatters.push(formatter); - this._onDidChangeFormatters.fire(); + this._onDidChangeFormatters.fire({ scheme: formatter.scheme }); return { dispose: () => { this.formatters = this.formatters.filter(f => f !== formatter); - this._onDidChangeFormatters.fire(); + this._onDidChangeFormatters.fire({ scheme: formatter.scheme }); } }; }