Skip to content

Commit a1711c4

Browse files
committed
feat: Display input block type in notebook cell status bar
Signed-off-by: Tomas Kislan <tomas@kislan.sk>
1 parent 6337c84 commit a1711c4

File tree

4 files changed

+297
-0
lines changed

4 files changed

+297
-0
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import {
5+
Disposable,
6+
notebooks,
7+
NotebookCell,
8+
NotebookCellStatusBarItem,
9+
NotebookCellStatusBarItemProvider
10+
} from 'vscode';
11+
import { IExtensionSyncActivationService } from '../../platform/activation/types';
12+
import { injectable } from 'inversify';
13+
import type { Pocket } from '../../platform/deepnote/pocket';
14+
15+
/**
16+
* Provides status bar items for Deepnote input block cells to display their block type.
17+
* Shows the type of input (e.g., "input-text", "input-slider", "button") in the cell status bar.
18+
*/
19+
@injectable()
20+
export class DeepnoteInputBlockCellStatusBarItemProvider
21+
implements NotebookCellStatusBarItemProvider, IExtensionSyncActivationService
22+
{
23+
private readonly disposables: Disposable[] = [];
24+
25+
// List of supported Deepnote input block types
26+
private readonly INPUT_BLOCK_TYPES = [
27+
'input-text',
28+
'input-textarea',
29+
'input-select',
30+
'input-slider',
31+
'input-checkbox',
32+
'input-date',
33+
'input-date-range',
34+
'input-file',
35+
'button'
36+
];
37+
38+
activate(): void {
39+
// Register the status bar item provider for Deepnote notebooks
40+
this.disposables.push(notebooks.registerNotebookCellStatusBarItemProvider('deepnote', this));
41+
}
42+
43+
provideCellStatusBarItems(cell: NotebookCell): NotebookCellStatusBarItem | undefined {
44+
// Check if this cell is a Deepnote input block
45+
// Get the block type from the __deepnotePocket metadata field
46+
const pocket = cell.metadata?.__deepnotePocket as Pocket | undefined;
47+
const blockType = pocket?.type;
48+
49+
if (!blockType || !this.isInputBlock(blockType)) {
50+
return undefined;
51+
}
52+
53+
const formattedName = this.formatBlockTypeName(blockType);
54+
55+
// Create a status bar item showing the block type
56+
// Using alignment value 2 (NotebookCellStatusBarAlignment.Right)
57+
const statusBarItem: NotebookCellStatusBarItem = {
58+
text: formattedName,
59+
alignment: 2, // NotebookCellStatusBarAlignment.Right
60+
tooltip: `Deepnote ${formattedName}`
61+
};
62+
63+
return statusBarItem;
64+
}
65+
66+
/**
67+
* Checks if the given block type is a Deepnote input block
68+
*/
69+
private isInputBlock(blockType: string): boolean {
70+
return this.INPUT_BLOCK_TYPES.includes(blockType.toLowerCase());
71+
}
72+
73+
/**
74+
* Formats the block type name for display (e.g., "input-text" -> "Input Text")
75+
*/
76+
private formatBlockTypeName(blockType: string): string {
77+
return blockType
78+
.split('-')
79+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
80+
.join(' ');
81+
}
82+
83+
dispose(): void {
84+
this.disposables.forEach((d) => d.dispose());
85+
}
86+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { expect } from 'chai';
5+
import { DeepnoteInputBlockCellStatusBarItemProvider } from './deepnoteInputBlockCellStatusBarProvider';
6+
import { NotebookCell, NotebookCellKind, NotebookDocument } from 'vscode';
7+
import { Uri } from 'vscode';
8+
9+
suite('DeepnoteInputBlockCellStatusBarItemProvider', () => {
10+
let provider: DeepnoteInputBlockCellStatusBarItemProvider;
11+
12+
setup(() => {
13+
provider = new DeepnoteInputBlockCellStatusBarItemProvider();
14+
});
15+
16+
teardown(() => {
17+
provider.dispose();
18+
});
19+
20+
function createMockCell(metadata?: Record<string, unknown>): NotebookCell {
21+
return {
22+
index: 0,
23+
notebook: {} as NotebookDocument,
24+
kind: NotebookCellKind.Code,
25+
document: {
26+
uri: Uri.file('/test/notebook.deepnote'),
27+
fileName: '/test/notebook.deepnote',
28+
isUntitled: false,
29+
languageId: 'json',
30+
version: 1,
31+
isDirty: false,
32+
isClosed: false,
33+
getText: () => '',
34+
save: async () => true,
35+
eol: 1,
36+
lineCount: 1,
37+
lineAt: () => ({}) as any,
38+
offsetAt: () => 0,
39+
positionAt: () => ({}) as any,
40+
validateRange: () => ({}) as any,
41+
validatePosition: () => ({}) as any
42+
} as any,
43+
metadata: metadata || {},
44+
outputs: [],
45+
executionSummary: undefined
46+
} as any;
47+
}
48+
49+
suite('Input Block Type Detection', () => {
50+
test('Should return status bar item for input-text block', () => {
51+
const cell = createMockCell({ __deepnotePocket: { type: 'input-text' } });
52+
const item = provider.provideCellStatusBarItems(cell);
53+
54+
expect(item).to.not.be.undefined;
55+
expect(item?.text).to.equal('Input Text');
56+
});
57+
58+
test('Should return status bar item for input-textarea block', () => {
59+
const cell = createMockCell({ __deepnotePocket: { type: 'input-textarea' } });
60+
const item = provider.provideCellStatusBarItems(cell);
61+
62+
expect(item).to.not.be.undefined;
63+
expect(item?.text).to.equal('Input Textarea');
64+
});
65+
66+
test('Should return status bar item for input-select block', () => {
67+
const cell = createMockCell({ __deepnotePocket: { type: 'input-select' } });
68+
const item = provider.provideCellStatusBarItems(cell);
69+
70+
expect(item).to.not.be.undefined;
71+
expect(item?.text).to.equal('Input Select');
72+
});
73+
74+
test('Should return status bar item for input-slider block', () => {
75+
const cell = createMockCell({ __deepnotePocket: { type: 'input-slider' } });
76+
const item = provider.provideCellStatusBarItems(cell);
77+
78+
expect(item).to.not.be.undefined;
79+
expect(item?.text).to.equal('Input Slider');
80+
});
81+
82+
test('Should return status bar item for input-checkbox block', () => {
83+
const cell = createMockCell({ __deepnotePocket: { type: 'input-checkbox' } });
84+
const item = provider.provideCellStatusBarItems(cell);
85+
86+
expect(item).to.not.be.undefined;
87+
expect(item?.text).to.equal('Input Checkbox');
88+
});
89+
90+
test('Should return status bar item for input-date block', () => {
91+
const cell = createMockCell({ __deepnotePocket: { type: 'input-date' } });
92+
const item = provider.provideCellStatusBarItems(cell);
93+
94+
expect(item).to.not.be.undefined;
95+
expect(item?.text).to.equal('Input Date');
96+
});
97+
98+
test('Should return status bar item for input-date-range block', () => {
99+
const cell = createMockCell({ __deepnotePocket: { type: 'input-date-range' } });
100+
const item = provider.provideCellStatusBarItems(cell);
101+
102+
expect(item).to.not.be.undefined;
103+
expect(item?.text).to.equal('Input Date Range');
104+
});
105+
106+
test('Should return status bar item for input-file block', () => {
107+
const cell = createMockCell({ __deepnotePocket: { type: 'input-file' } });
108+
const item = provider.provideCellStatusBarItems(cell);
109+
110+
expect(item).to.not.be.undefined;
111+
expect(item?.text).to.equal('Input File');
112+
});
113+
114+
test('Should return status bar item for button block', () => {
115+
const cell = createMockCell({ __deepnotePocket: { type: 'button' } });
116+
const item = provider.provideCellStatusBarItems(cell);
117+
118+
expect(item).to.not.be.undefined;
119+
expect(item?.text).to.equal('Button');
120+
});
121+
});
122+
123+
suite('Non-Input Block Types', () => {
124+
test('Should return undefined for code block', () => {
125+
const cell = createMockCell({ __deepnotePocket: { type: 'code' } });
126+
const item = provider.provideCellStatusBarItems(cell);
127+
128+
expect(item).to.be.undefined;
129+
});
130+
131+
test('Should return undefined for sql block', () => {
132+
const cell = createMockCell({ __deepnotePocket: { type: 'sql' } });
133+
const item = provider.provideCellStatusBarItems(cell);
134+
135+
expect(item).to.be.undefined;
136+
});
137+
138+
test('Should return undefined for markdown block', () => {
139+
const cell = createMockCell({ __deepnotePocket: { type: 'text-cell-p' } });
140+
const item = provider.provideCellStatusBarItems(cell);
141+
142+
expect(item).to.be.undefined;
143+
});
144+
145+
test('Should return undefined for cell with no type metadata', () => {
146+
const cell = createMockCell({});
147+
const item = provider.provideCellStatusBarItems(cell);
148+
149+
expect(item).to.be.undefined;
150+
});
151+
152+
test('Should return undefined for cell with undefined metadata', () => {
153+
const cell = createMockCell(undefined);
154+
const item = provider.provideCellStatusBarItems(cell);
155+
156+
expect(item).to.be.undefined;
157+
});
158+
});
159+
160+
suite('Status Bar Item Properties', () => {
161+
test('Should have correct tooltip for input-text', () => {
162+
const cell = createMockCell({ __deepnotePocket: { type: 'input-text' } });
163+
const item = provider.provideCellStatusBarItems(cell);
164+
165+
expect(item?.tooltip).to.equal('Deepnote Input Text');
166+
});
167+
168+
test('Should have correct tooltip for button', () => {
169+
const cell = createMockCell({ __deepnotePocket: { type: 'button' } });
170+
const item = provider.provideCellStatusBarItems(cell);
171+
172+
expect(item?.tooltip).to.equal('Deepnote Button');
173+
});
174+
175+
test('Should format multi-word block types correctly', () => {
176+
const cell = createMockCell({ __deepnotePocket: { type: 'input-date-range' } });
177+
const item = provider.provideCellStatusBarItems(cell);
178+
179+
expect(item?.text).to.equal('Input Date Range');
180+
expect(item?.tooltip).to.equal('Deepnote Input Date Range');
181+
});
182+
});
183+
184+
suite('Case Insensitivity', () => {
185+
test('Should handle uppercase block type', () => {
186+
const cell = createMockCell({ __deepnotePocket: { type: 'INPUT-TEXT' } });
187+
const item = provider.provideCellStatusBarItems(cell);
188+
189+
expect(item).to.not.be.undefined;
190+
expect(item?.text).to.equal('INPUT TEXT');
191+
});
192+
193+
test('Should handle mixed case block type', () => {
194+
const cell = createMockCell({ __deepnotePocket: { type: 'Input-Text' } });
195+
const item = provider.provideCellStatusBarItems(cell);
196+
197+
expect(item).to.not.be.undefined;
198+
expect(item?.text).to.equal('Input Text');
199+
});
200+
});
201+
});

src/notebooks/serviceRegistry.node.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import { DeepnoteKernelAutoSelector } from './deepnote/deepnoteKernelAutoSelecto
6565
import { DeepnoteServerProvider } from '../kernels/deepnote/deepnoteServerProvider.node';
6666
import { DeepnoteInitNotebookRunner, IDeepnoteInitNotebookRunner } from './deepnote/deepnoteInitNotebookRunner.node';
6767
import { DeepnoteRequirementsHelper, IDeepnoteRequirementsHelper } from './deepnote/deepnoteRequirementsHelper.node';
68+
import { DeepnoteInputBlockCellStatusBarItemProvider } from './deepnote/deepnoteInputBlockCellStatusBarProvider';
6869

6970
export function registerTypes(serviceManager: IServiceManager, isDevMode: boolean) {
7071
registerControllerTypes(serviceManager, isDevMode);
@@ -153,6 +154,10 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea
153154
serviceManager.addBinding(IDeepnoteKernelAutoSelector, IExtensionSyncActivationService);
154155
serviceManager.addSingleton<IDeepnoteInitNotebookRunner>(IDeepnoteInitNotebookRunner, DeepnoteInitNotebookRunner);
155156
serviceManager.addSingleton<IDeepnoteRequirementsHelper>(IDeepnoteRequirementsHelper, DeepnoteRequirementsHelper);
157+
serviceManager.addSingleton<IExtensionSyncActivationService>(
158+
IExtensionSyncActivationService,
159+
DeepnoteInputBlockCellStatusBarItemProvider
160+
);
156161

157162
// File export/import
158163
serviceManager.addSingleton<IFileConverter>(IFileConverter, FileConverter);

src/notebooks/serviceRegistry.web.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
IIntegrationStorage,
4949
IIntegrationWebviewProvider
5050
} from './deepnote/integrations/types';
51+
import { DeepnoteInputBlockCellStatusBarItemProvider } from './deepnote/deepnoteInputBlockCellStatusBarProvider';
5152

5253
export function registerTypes(serviceManager: IServiceManager, isDevMode: boolean) {
5354
registerControllerTypes(serviceManager, isDevMode);
@@ -106,6 +107,10 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea
106107
serviceManager.addSingleton<IIntegrationDetector>(IIntegrationDetector, IntegrationDetector);
107108
serviceManager.addSingleton<IIntegrationWebviewProvider>(IIntegrationWebviewProvider, IntegrationWebviewProvider);
108109
serviceManager.addSingleton<IIntegrationManager>(IIntegrationManager, IntegrationManager);
110+
serviceManager.addSingleton<IExtensionSyncActivationService>(
111+
IExtensionSyncActivationService,
112+
DeepnoteInputBlockCellStatusBarItemProvider
113+
);
109114

110115
serviceManager.addSingleton<IExportBase>(IExportBase, ExportBase);
111116
serviceManager.addSingleton<IFileConverter>(IFileConverter, FileConverter);

0 commit comments

Comments
 (0)