Skip to content

Commit 7b3a15c

Browse files
authored
Support rendering cell index in resource label (microsoft#202554)
* Support rendering cell index in resource label * localize the title
1 parent e1e6c5d commit 7b3a15c

File tree

5 files changed

+128
-36
lines changed

5 files changed

+128
-36
lines changed

src/vs/workbench/browser/labels.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { localize } from 'vs/nls';
67
import { URI } from 'vs/base/common/uri';
78
import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources';
89
import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
@@ -24,6 +25,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
2425
import { normalizeDriveLetter } from 'vs/base/common/labels';
2526
import { IRange } from 'vs/editor/common/core/range';
2627
import { ThemeIcon } from 'vs/base/common/themables';
28+
import { INotebookDocumentService } from 'vs/workbench/services/notebook/common/notebookDocumentService';
2729

2830
export interface IResourceLabelProps {
2931
resource?: URI | { primary?: URI; secondary?: URI };
@@ -308,7 +310,8 @@ class ResourceLabelWidget extends IconLabel {
308310
@IDecorationsService private readonly decorationsService: IDecorationsService,
309311
@ILabelService private readonly labelService: ILabelService,
310312
@ITextFileService private readonly textFileService: ITextFileService,
311-
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
313+
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
314+
@INotebookDocumentService private readonly notebookDocumentService: INotebookDocumentService
312315
) {
313316
super(container, options);
314317
}
@@ -465,6 +468,21 @@ class ResourceLabelWidget extends IconLabel {
465468
}
466469
}
467470

471+
if (!options.forceLabel && !isSideBySideEditor && resource?.scheme === Schemas.vscodeNotebookCell) {
472+
// Notebook cells are embeded in a notebook document
473+
// As such we always ask the actual notebook document
474+
// for its position in the document.
475+
const notebookDocument = this.notebookDocumentService.getNotebook(resource);
476+
const cellIndex = notebookDocument?.getCellIndex(resource);
477+
if (notebookDocument && cellIndex !== undefined && typeof label.name === 'string') {
478+
options.title = localize('notebookCellLabel', "{0} • Cell {1}", label.name, `${cellIndex + 1}`);
479+
}
480+
481+
if (typeof label.name === 'string' && notebookDocument && cellIndex !== undefined && typeof label.name === 'string') {
482+
label.name = localize('notebookCellLabel', "{0} • Cell {1}", label.name, `${cellIndex + 1}`);
483+
}
484+
}
485+
468486
const hasResourceChanged = this.hasResourceChanged(label);
469487
const hasPathLabelChanged = hasResourceChanged || this.hasPathLabelChanged(label);
470488
const hasFileKindChanged = this.hasFileKindChanged(options);

src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Lazy } from 'vs/base/common/lazy';
1414
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
1515
import { ResourceMap } from 'vs/base/common/map';
1616
import { Schemas } from 'vs/base/common/network';
17+
import { isEqual } from 'vs/base/common/resources';
1718
import { isDefined } from 'vs/base/common/types';
1819
import { URI } from 'vs/base/common/uri';
1920
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
@@ -43,6 +44,7 @@ import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/e
4344
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
4445
import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
4546
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
47+
import { INotebookDocument, INotebookDocumentService } from 'vs/workbench/services/notebook/common/notebookDocumentService';
4648

4749
export class NotebookProviderInfoStore extends Disposable {
4850

@@ -414,8 +416,9 @@ export class NotebookOutputRendererInfoStore {
414416
}
415417
}
416418

417-
class ModelData implements IDisposable {
419+
class ModelData implements IDisposable, INotebookDocument {
418420
private readonly _modelEventListeners = new DisposableStore();
421+
get uri() { return this.model.uri; }
419422

420423
constructor(
421424
readonly model: NotebookTextModel,
@@ -424,6 +427,10 @@ class ModelData implements IDisposable {
424427
this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model)));
425428
}
426429

430+
getCellIndex(cellUri: URI): number | undefined {
431+
return this.model.cells.findIndex(cell => isEqual(cell.uri, cellUri));
432+
}
433+
427434
dispose(): void {
428435
this._modelEventListeners.dispose();
429436
}
@@ -485,6 +492,7 @@ export class NotebookService extends Disposable implements INotebookService {
485492
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
486493
@IConfigurationService private readonly configurationService: IConfigurationService,
487494
@IStorageService private readonly _storageService: IStorageService,
495+
@INotebookDocumentService private readonly _notebookDocumentService: INotebookDocumentService
488496
) {
489497
super();
490498

@@ -752,7 +760,9 @@ export class NotebookService extends Disposable implements INotebookService {
752760
throw new Error(`notebook for ${uri} already exists`);
753761
}
754762
const notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, uri, data.cells, data.metadata, transientOptions);
755-
this._models.set(uri, new ModelData(notebookModel, this._onWillDisposeDocument.bind(this)));
763+
const modelData = new ModelData(notebookModel, this._onWillDisposeDocument.bind(this));
764+
this._models.set(uri, modelData);
765+
this._notebookDocumentService.addNotebookDocument(modelData);
756766
this._onWillAddNotebookDocument.fire(notebookModel);
757767
this._onDidAddNotebookDocument.fire(notebookModel);
758768
this._postDocumentOpenActivation(viewType);
@@ -776,6 +786,7 @@ export class NotebookService extends Disposable implements INotebookService {
776786
if (modelData) {
777787
this._onWillRemoveNotebookDocument.fire(modelData.model);
778788
this._models.delete(model.uri);
789+
this._notebookDocumentService.removeNotebookDocument(modelData);
779790
modelData.dispose();
780791
this._onDidRemoveNotebookDocument.fire(modelData.model);
781792
}

src/vs/workbench/contrib/notebook/common/notebookCommon.ts

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { decodeBase64, encodeBase64, VSBuffer } from 'vs/base/common/buffer';
6+
import { VSBuffer } from 'vs/base/common/buffer';
77
import { CancellationToken } from 'vs/base/common/cancellation';
88
import { IDiffResult } from 'vs/base/common/diff/diff';
99
import { Event } from 'vs/base/common/event';
@@ -31,6 +31,7 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
3131
import { IWorkingCopyBackupMeta, IWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy';
3232
import { IMarkdownString } from 'vs/base/common/htmlContent';
3333
import { IFileReadLimits } from 'vs/platform/files/common/files';
34+
import { parse as parseUri, generate as generateUri } from 'vs/workbench/services/notebook/common/notebookDocumentService';
3435

3536
export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook';
3637
export const NOTEBOOK_DIFF_EDITOR_ID = 'workbench.editor.notebookTextDiffEditor';
@@ -546,43 +547,13 @@ export interface INotebookContributionData {
546547

547548

548549
export namespace CellUri {
549-
550550
export const scheme = Schemas.vscodeNotebookCell;
551-
552-
553-
const _lengths = ['W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f'];
554-
const _padRegexp = new RegExp(`^[${_lengths.join('')}]+`);
555-
const _radix = 7;
556-
557551
export function generate(notebook: URI, handle: number): URI {
558-
559-
const s = handle.toString(_radix);
560-
const p = s.length < _lengths.length ? _lengths[s.length - 1] : 'z';
561-
562-
const fragment = `${p}${s}s${encodeBase64(VSBuffer.fromString(notebook.scheme), true, true)}`;
563-
return notebook.with({ scheme, fragment });
552+
return generateUri(notebook, handle);
564553
}
565554

566555
export function parse(cell: URI): { notebook: URI; handle: number } | undefined {
567-
if (cell.scheme !== scheme) {
568-
return undefined;
569-
}
570-
571-
const idx = cell.fragment.indexOf('s');
572-
if (idx < 0) {
573-
return undefined;
574-
}
575-
576-
const handle = parseInt(cell.fragment.substring(0, idx).replace(_padRegexp, ''), _radix);
577-
const _scheme = decodeBase64(cell.fragment.substring(idx + 1)).toString();
578-
579-
if (isNaN(handle)) {
580-
return undefined;
581-
}
582-
return {
583-
handle,
584-
notebook: cell.with({ scheme: _scheme, fragment: null })
585-
};
556+
return parseUri(cell);
586557
}
587558

588559
export function generateCellOutputUri(notebook: URI, outputId?: string) {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { VSBuffer, decodeBase64, encodeBase64 } from 'vs/base/common/buffer';
7+
import { ResourceMap } from 'vs/base/common/map';
8+
import { Schemas } from 'vs/base/common/network';
9+
import { URI } from 'vs/base/common/uri';
10+
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
11+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
12+
13+
export const INotebookDocumentService = createDecorator<INotebookDocumentService>('notebookDocumentService');
14+
15+
export interface INotebookDocument {
16+
readonly uri: URI;
17+
getCellIndex(cellUri: URI): number | undefined;
18+
}
19+
20+
const _lengths = ['W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f'];
21+
const _padRegexp = new RegExp(`^[${_lengths.join('')}]+`);
22+
const _radix = 7;
23+
export function parse(cell: URI): { notebook: URI; handle: number } | undefined {
24+
if (cell.scheme !== Schemas.vscodeNotebookCell) {
25+
return undefined;
26+
}
27+
28+
const idx = cell.fragment.indexOf('s');
29+
if (idx < 0) {
30+
return undefined;
31+
}
32+
33+
const handle = parseInt(cell.fragment.substring(0, idx).replace(_padRegexp, ''), _radix);
34+
const _scheme = decodeBase64(cell.fragment.substring(idx + 1)).toString();
35+
36+
if (isNaN(handle)) {
37+
return undefined;
38+
}
39+
return {
40+
handle,
41+
notebook: cell.with({ scheme: _scheme, fragment: null })
42+
};
43+
}
44+
45+
export function generate(notebook: URI, handle: number): URI {
46+
47+
const s = handle.toString(_radix);
48+
const p = s.length < _lengths.length ? _lengths[s.length - 1] : 'z';
49+
50+
const fragment = `${p}${s}s${encodeBase64(VSBuffer.fromString(notebook.scheme), true, true)}`;
51+
return notebook.with({ scheme: Schemas.vscodeNotebookCell, fragment });
52+
}
53+
54+
export interface INotebookDocumentService {
55+
readonly _serviceBrand: undefined;
56+
57+
getNotebook(uri: URI): INotebookDocument | undefined;
58+
addNotebookDocument(document: INotebookDocument): void;
59+
removeNotebookDocument(document: INotebookDocument): void;
60+
}
61+
62+
export class NotebookDocumentWorkbenchService implements INotebookDocumentService {
63+
declare readonly _serviceBrand: undefined;
64+
65+
private readonly _documents = new ResourceMap<INotebookDocument>();
66+
67+
getNotebook(uri: URI): INotebookDocument | undefined {
68+
if (uri.scheme === Schemas.vscodeNotebookCell) {
69+
const cellUri = parse(uri);
70+
if (cellUri) {
71+
const document = this._documents.get(cellUri.notebook);
72+
if (document) {
73+
return document;
74+
}
75+
}
76+
}
77+
78+
return this._documents.get(uri);
79+
}
80+
81+
addNotebookDocument(document: INotebookDocument) {
82+
this._documents.set(document.uri, document);
83+
}
84+
85+
removeNotebookDocument(document: INotebookDocument) {
86+
this._documents.delete(document.uri);
87+
}
88+
89+
}
90+
91+
registerSingleton(INotebookDocumentService, NotebookDocumentWorkbenchService, InstantiationType.Delayed);

src/vs/workbench/workbench.common.main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import 'vs/workbench/services/textresourceProperties/common/textResourceProperti
7777
import 'vs/workbench/services/textfile/common/textEditorService';
7878
import 'vs/workbench/services/language/common/languageService';
7979
import 'vs/workbench/services/model/common/modelService';
80+
import 'vs/workbench/services/notebook/common/notebookDocumentService';
8081
import 'vs/workbench/services/commands/common/commandService';
8182
import 'vs/workbench/services/themes/browser/workbenchThemeService';
8283
import 'vs/workbench/services/label/common/labelService';

0 commit comments

Comments
 (0)