Skip to content

Commit a94b8a7

Browse files
author
Benjamin Pasero
committed
Untitled editor tab name and search entry differs (fixes microsoft#88969)
1 parent 8884913 commit a94b8a7

5 files changed

Lines changed: 96 additions & 41 deletions

File tree

src/vs/base/browser/ui/iconLabel/iconLabel.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ class FastLabelNode {
9292
export class IconLabel extends Disposable {
9393

9494
private domNode: FastLabelNode;
95-
private descriptionContainer: FastLabelNode;
95+
9696
private nameNode: Label | LabelWithHighlights;
97+
98+
private descriptionContainer: FastLabelNode;
9799
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
98100
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
99101

src/vs/workbench/browser/labels.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ export class ResourceLabels extends Disposable {
9797
@IDecorationsService private readonly decorationsService: IDecorationsService,
9898
@IThemeService private readonly themeService: IThemeService,
9999
@IFileService private readonly fileService: IFileService,
100-
@ILabelService private readonly labelService: ILabelService
100+
@ILabelService private readonly labelService: ILabelService,
101+
@ITextFileService private readonly textFileService: ITextFileService
101102
) {
102103
super();
103104

@@ -149,9 +150,15 @@ export class ResourceLabels extends Disposable {
149150
}
150151
}));
151152

153+
// notify when label formatters change
152154
this._register(this.labelService.onDidChangeFormatters(() => {
153155
this._widgets.forEach(widget => widget.notifyFormattersChange());
154156
}));
157+
158+
// notify when untitled labels change
159+
this.textFileService.untitled.onDidChangeLabel(resource => {
160+
this._widgets.forEach(widget => widget.notifyUntitledLabelChange(resource));
161+
});
155162
}
156163

157164
get(index: number): IResourceLabel {
@@ -220,9 +227,10 @@ export class ResourceLabel extends ResourceLabels {
220227
@IDecorationsService decorationsService: IDecorationsService,
221228
@IThemeService themeService: IThemeService,
222229
@IFileService fileService: IFileService,
223-
@ILabelService labelService: ILabelService
230+
@ILabelService labelService: ILabelService,
231+
@ITextFileService textFileService: ITextFileService
224232
) {
225-
super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, fileService, labelService);
233+
super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, fileService, labelService, textFileService);
226234

227235
this._label = this._register(this.create(container, options));
228236
}
@@ -323,6 +331,12 @@ class ResourceLabelWidget extends IconLabel {
323331
this.render(false);
324332
}
325333

334+
notifyUntitledLabelChange(resource: URI): void {
335+
if (resources.isEqual(resource, this.label?.resource)) {
336+
this.render(false);
337+
}
338+
}
339+
326340
setResource(label: IResourceLabelProps, options?: IResourceLabelOptions): void {
327341
const hasPathLabelChanged = this.hasPathLabelChanged(label, options);
328342
const clearIconCache = this.clearIconCache(label, options);
@@ -449,7 +463,6 @@ class ResourceLabelWidget extends IconLabel {
449463
};
450464

451465
const resource = this.label.resource;
452-
const label = this.label.name;
453466

454467
if (this.options && typeof this.options.title === 'string') {
455468
iconLabelOptions.title = this.options.title;
@@ -496,7 +509,18 @@ class ResourceLabelWidget extends IconLabel {
496509
}
497510
}
498511

499-
this.setLabel(label || '', this.label.description, iconLabelOptions);
512+
let label = this.label.name || '';
513+
if (resource?.scheme === Schemas.untitled) {
514+
// Untitled labels are very dynamic because they may change
515+
// whenever the content changes. As such we always ask the
516+
// text file service for the name of the untitled editor
517+
const untitledName = this.textFileService.untitled.get(resource)?.getName();
518+
if (untitledName) {
519+
label = untitledName;
520+
}
521+
}
522+
523+
this.setLabel(label, this.label.description, iconLabelOptions);
500524

501525
this._onDidRender.fire();
502526
}

src/vs/workbench/browser/parts/editor/textEditor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
285285
}
286286
}
287287

288-
protected getResource(): URI | undefined {
288+
private getResource(): URI | undefined {
289289
const codeEditor = getCodeEditor(this.editorControl);
290290
if (codeEditor) {
291291
const model = codeEditor.getModel();

src/vs/workbench/services/untitled/common/untitledTextEditorService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ export interface IUntitledTextEditorModelManager {
7575
*/
7676
readonly onDidChangeEncoding: Event<URI>;
7777

78+
/**
79+
* Events for when untitled text editor labels change.
80+
*/
81+
readonly onDidChangeLabel: Event<URI>;
82+
7883
/**
7984
* Events for when untitled text editors are disposed.
8085
*/
@@ -132,6 +137,9 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
132137
private readonly _onDidDisposeModel = this._register(new Emitter<URI>());
133138
readonly onDidDisposeModel = this._onDidDisposeModel.event;
134139

140+
private readonly _onDidChangeLabel = this._register(new Emitter<URI>());
141+
readonly onDidChangeLabel = this._onDidChangeLabel.event;
142+
135143
protected readonly mapResourceToInput = new ResourceMap<UntitledTextEditorInput>();
136144
private readonly mapResourceToAssociatedFilePath = new ResourceMap<boolean>();
137145

@@ -222,6 +230,7 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
222230
const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!options.associatedResource, options.mode, options.initialValue, options.encoding);
223231

224232
const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(input.getResource()));
233+
const labelListener = input.onDidChangeLabel(() => this._onDidChangeLabel.fire(input.getResource()));
225234
const encodingListener = input.onDidModelChangeEncoding(() => this._onDidChangeEncoding.fire(input.getResource()));
226235
const disposeListener = input.onDispose(() => this._onDidDisposeModel.fire(input.getResource()));
227236

@@ -234,6 +243,7 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
234243

235244
// Listeners
236245
dirtyListener.dispose();
246+
labelListener.dispose();
237247
encodingListener.dispose();
238248
disposeListener.dispose();
239249
});

src/vs/workbench/test/common/editor/untitledTextEditor.test.ts

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ suite('Workbench untitled text editors', () => {
4343
(accessor.untitledTextEditorService as UntitledTextEditorService).dispose();
4444
});
4545

46-
test('Untitled Text Editor Service', async (done) => {
46+
test('basics', async (done) => {
4747
const service = accessor.untitledTextEditorService;
4848
const workingCopyService = accessor.workingCopyService;
4949

@@ -108,7 +108,7 @@ suite('Workbench untitled text editors', () => {
108108
input2.dispose();
109109
});
110110

111-
test('Untitled with associated resource is dirty', () => {
111+
test('associated resource is dirty', () => {
112112
const service = accessor.untitledTextEditorService;
113113
const file = URI.file(join('C:\\', '/foo/file.txt'));
114114
const untitled = service.create({ associatedResource: file });
@@ -119,7 +119,7 @@ suite('Workbench untitled text editors', () => {
119119
untitled.dispose();
120120
});
121121

122-
test('Untitled no longer dirty when content gets empty (not with associated resource)', async () => {
122+
test('no longer dirty when content gets empty (not with associated resource)', async () => {
123123
const service = accessor.untitledTextEditorService;
124124
const workingCopyService = accessor.workingCopyService;
125125
const input = service.create();
@@ -136,7 +136,7 @@ suite('Workbench untitled text editors', () => {
136136
model.dispose();
137137
});
138138

139-
test('Untitled via create options', async () => {
139+
test('via create options', async () => {
140140
const service = accessor.untitledTextEditorService;
141141

142142
const model1 = await service.create().resolve();
@@ -168,15 +168,15 @@ suite('Workbench untitled text editors', () => {
168168
input.dispose();
169169
});
170170

171-
test('Untitled suggest name', function () {
171+
test('suggest name', function () {
172172
const service = accessor.untitledTextEditorService;
173173
const input = service.create();
174174

175175
assert.ok(input.suggestFileName().length > 0);
176176
input.dispose();
177177
});
178178

179-
test('Untitled with associated path remains dirty when content gets empty', async () => {
179+
test('associated path remains dirty when content gets empty', async () => {
180180
const service = accessor.untitledTextEditorService;
181181
const file = URI.file(join('C:\\', '/foo/file.txt'));
182182
const input = service.create({ associatedResource: file });
@@ -191,7 +191,7 @@ suite('Workbench untitled text editors', () => {
191191
model.dispose();
192192
});
193193

194-
test('Untitled with initial content is dirty', async () => {
194+
test('initial content is dirty', async () => {
195195
const service = accessor.untitledTextEditorService;
196196
const workingCopyService = accessor.workingCopyService;
197197

@@ -214,7 +214,7 @@ suite('Workbench untitled text editors', () => {
214214
model.dispose();
215215
});
216216

217-
test('Untitled created with files.defaultLanguage setting', () => {
217+
test('created with files.defaultLanguage setting', () => {
218218
const defaultLanguage = 'javascript';
219219
const config = accessor.testConfigurationService;
220220
config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage });
@@ -229,7 +229,7 @@ suite('Workbench untitled text editors', () => {
229229
input.dispose();
230230
});
231231

232-
test('Untitled created with files.defaultLanguage setting (${activeEditorLanguage})', () => {
232+
test('created with files.defaultLanguage setting (${activeEditorLanguage})', () => {
233233
const config = accessor.testConfigurationService;
234234
config.setUserConfiguration('files', { 'defaultLanguage': '${activeEditorLanguage}' });
235235

@@ -246,7 +246,7 @@ suite('Workbench untitled text editors', () => {
246246
input.dispose();
247247
});
248248

249-
test('Untitled created with mode overrides files.defaultLanguage setting', () => {
249+
test('created with mode overrides files.defaultLanguage setting', () => {
250250
const mode = 'typescript';
251251
const defaultLanguage = 'javascript';
252252
const config = accessor.testConfigurationService;
@@ -262,7 +262,7 @@ suite('Workbench untitled text editors', () => {
262262
input.dispose();
263263
});
264264

265-
test('Untitled can change mode afterwards', async () => {
265+
test('can change mode afterwards', async () => {
266266
const mode = 'untitled-input-test';
267267

268268
ModesRegistry.registerLanguage({
@@ -285,7 +285,7 @@ suite('Workbench untitled text editors', () => {
285285
model.dispose();
286286
});
287287

288-
test('encoding change event', async () => {
288+
test('service#onDidChangeEncoding', async () => {
289289
const service = accessor.untitledTextEditorService;
290290
const input = service.create();
291291

@@ -296,15 +296,52 @@ suite('Workbench untitled text editors', () => {
296296
assert.equal(r.toString(), input.getResource().toString());
297297
});
298298

299-
// dirty
299+
// encoding
300300
const model = await input.resolve();
301301
model.setEncoding('utf16');
302302
assert.equal(counter, 1);
303303
input.dispose();
304304
model.dispose();
305305
});
306306

307-
test('onDidChangeContent event', async function () {
307+
test('service#onDidChangeLabel', async () => {
308+
const service = accessor.untitledTextEditorService;
309+
const input = service.create();
310+
311+
let counter = 0;
312+
313+
service.onDidChangeLabel(r => {
314+
counter++;
315+
assert.equal(r.toString(), input.getResource().toString());
316+
});
317+
318+
// label
319+
const model = await input.resolve();
320+
model.textEditorModel.setValue('Foo Bar');
321+
assert.equal(counter, 1);
322+
input.dispose();
323+
model.dispose();
324+
});
325+
326+
test('service#onDidDisposeModel', async () => {
327+
const service = accessor.untitledTextEditorService;
328+
const input = service.create();
329+
330+
let counter = 0;
331+
332+
service.onDidDisposeModel(r => {
333+
counter++;
334+
assert.equal(r.toString(), input.getResource().toString());
335+
});
336+
337+
const model = await input.resolve();
338+
assert.equal(counter, 0);
339+
input.dispose();
340+
assert.equal(counter, 1);
341+
model.dispose();
342+
});
343+
344+
test('model#onDidChangeContent', async function () {
308345
const service = accessor.untitledTextEditorService;
309346
const input = service.create();
310347

@@ -330,7 +367,7 @@ suite('Workbench untitled text editors', () => {
330367
model.dispose();
331368
});
332369

333-
test('onDidChangeFirstLine event and input name', async function () {
370+
test('model#onDidChangeFirstLine and input name', async function () {
334371
const service = accessor.untitledTextEditorService;
335372
const input = service.create();
336373

@@ -386,7 +423,7 @@ suite('Workbench untitled text editors', () => {
386423
model.dispose();
387424
});
388425

389-
test('onDidChangeDirty event', async function () {
426+
test('model#onDidChangeDirty', async function () {
390427
const service = accessor.untitledTextEditorService;
391428
const input = service.create();
392429

@@ -405,22 +442,4 @@ suite('Workbench untitled text editors', () => {
405442
input.dispose();
406443
model.dispose();
407444
});
408-
409-
test('onDidDisposeModel event', async () => {
410-
const service = accessor.untitledTextEditorService;
411-
const input = service.create();
412-
413-
let counter = 0;
414-
415-
service.onDidDisposeModel(r => {
416-
counter++;
417-
assert.equal(r.toString(), input.getResource().toString());
418-
});
419-
420-
const model = await input.resolve();
421-
assert.equal(counter, 0);
422-
input.dispose();
423-
assert.equal(counter, 1);
424-
model.dispose();
425-
});
426445
});

0 commit comments

Comments
 (0)