22// Licensed under the MIT License.
33
44import { injectable , inject } from 'inversify' ;
5- import { commands , window , NotebookCellData , NotebookCellKind , NotebookEdit , NotebookRange } from 'vscode' ;
5+ import {
6+ commands ,
7+ window ,
8+ NotebookCellData ,
9+ NotebookCellKind ,
10+ NotebookEdit ,
11+ NotebookRange ,
12+ NotebookCell
13+ } from 'vscode' ;
614import { IExtensionSyncActivationService } from '../../platform/activation/types' ;
715import { IDisposableRegistry } from '../../platform/common/types' ;
816import { Commands } from '../../platform/common/constants' ;
917import { noop } from '../../platform/common/utils/misc' ;
1018import { chainWithPendingUpdates } from '../../kernels/execution/notebookUpdater' ;
11- import { DeepnoteBigNumberMetadataSchema } from './deepnoteSchemas' ;
19+ import {
20+ DeepnoteBigNumberMetadataSchema ,
21+ DeepnoteTextInputMetadataSchema ,
22+ DeepnoteTextareaInputMetadataSchema ,
23+ DeepnoteSelectInputMetadataSchema ,
24+ DeepnoteSliderInputMetadataSchema ,
25+ DeepnoteCheckboxInputMetadataSchema ,
26+ DeepnoteDateInputMetadataSchema ,
27+ DeepnoteDateRangeInputMetadataSchema ,
28+ DeepnoteFileInputMetadataSchema ,
29+ DeepnoteButtonMetadataSchema
30+ } from './deepnoteSchemas' ;
31+ import z from 'zod' ;
32+
33+ type InputBlockType =
34+ | 'input-text'
35+ | 'input-textarea'
36+ | 'input-select'
37+ | 'input-slider'
38+ | 'input-checkbox'
39+ | 'input-date'
40+ | 'input-date-range'
41+ | 'input-file'
42+ | 'button' ;
43+
44+ export function getInputBlockMetadata ( blockType : InputBlockType , variableName : string ) {
45+ const defaultInput = {
46+ deepnote_variable_name : variableName
47+ } ;
48+
49+ switch ( blockType ) {
50+ case 'input-text' :
51+ return DeepnoteTextInputMetadataSchema . parse ( defaultInput ) ;
52+ case 'input-textarea' :
53+ return DeepnoteTextareaInputMetadataSchema . parse ( defaultInput ) ;
54+ case 'input-select' :
55+ return DeepnoteSelectInputMetadataSchema . parse ( defaultInput ) ;
56+ case 'input-slider' :
57+ return DeepnoteSliderInputMetadataSchema . parse ( defaultInput ) ;
58+ case 'input-checkbox' :
59+ return DeepnoteCheckboxInputMetadataSchema . parse ( defaultInput ) ;
60+ case 'input-date' :
61+ return DeepnoteDateInputMetadataSchema . parse ( defaultInput ) ;
62+ case 'input-date-range' :
63+ return DeepnoteDateRangeInputMetadataSchema . parse ( defaultInput ) ;
64+ case 'input-file' :
65+ return DeepnoteFileInputMetadataSchema . parse ( defaultInput ) ;
66+ case 'button' :
67+ return DeepnoteButtonMetadataSchema . parse ( defaultInput ) ;
68+ default : {
69+ const exhaustiveCheck : never = blockType ;
70+ throw new Error ( `Unhandled block type: ${ exhaustiveCheck satisfies never } ` ) ;
71+ }
72+ }
73+ }
74+
75+ export function safeParseDeepnoteVariableNameFromContentJson ( content : string ) : string | undefined {
76+ try {
77+ return JSON . parse ( content ) [ 'deepnote_variable_name' ] ;
78+ } catch ( error ) {
79+ return undefined ;
80+ }
81+ }
82+
83+ export function getNextDeepnoteVariableName ( cells : NotebookCell [ ] , prefix : 'df' | 'query' | 'input' ) : string {
84+ const deepnoteVariableNames = cells . reduce < string [ ] > ( ( acc , cell ) => {
85+ const contentValue = safeParseDeepnoteVariableNameFromContentJson ( cell . document . getText ( ) ) ;
86+
87+ if ( contentValue != null ) {
88+ acc . push ( contentValue ) ;
89+ }
90+
91+ const parsedMetadataValue = z . string ( ) . safeParse ( cell . metadata . __deepnotePocket ?. variableName ) ;
92+
93+ if ( parsedMetadataValue . success ) {
94+ acc . push ( parsedMetadataValue . data ) ;
95+ }
96+
97+ return acc ;
98+ } , [ ] ) ;
99+
100+ const maxDeepnoteVariableNamesSuffixNumber =
101+ deepnoteVariableNames . reduce < number | null > ( ( acc , name ) => {
102+ const m = name . match ( new RegExp ( `^${ prefix } _(\\d+)$` ) ) ;
103+ if ( m == null ) {
104+ return acc ;
105+ }
106+
107+ const suffixNumber = parseInt ( m [ 1 ] ) ;
108+
109+ if ( isNaN ( suffixNumber ) ) {
110+ return acc ;
111+ }
112+
113+ return acc == null || suffixNumber > acc ? suffixNumber : acc ;
114+ } , null ) ?? 0 ;
115+
116+ return `${ prefix } _${ maxDeepnoteVariableNamesSuffixNumber + 1 } ` ;
117+ }
12118
13119/**
14120 * Service responsible for registering and handling Deepnote-specific notebook commands.
@@ -29,6 +135,33 @@ export class DeepnoteNotebookCommandListener implements IExtensionSyncActivation
29135 this . disposableRegistry . push (
30136 commands . registerCommand ( Commands . AddBigNumberChartBlock , ( ) => this . addBigNumberChartBlock ( ) )
31137 ) ;
138+ this . disposableRegistry . push (
139+ commands . registerCommand ( Commands . AddInputTextBlock , ( ) => this . addInputBlock ( 'input-text' ) )
140+ ) ;
141+ this . disposableRegistry . push (
142+ commands . registerCommand ( Commands . AddInputTextareaBlock , ( ) => this . addInputBlock ( 'input-textarea' ) )
143+ ) ;
144+ this . disposableRegistry . push (
145+ commands . registerCommand ( Commands . AddInputSelectBlock , ( ) => this . addInputBlock ( 'input-select' ) )
146+ ) ;
147+ this . disposableRegistry . push (
148+ commands . registerCommand ( Commands . AddInputSliderBlock , ( ) => this . addInputBlock ( 'input-slider' ) )
149+ ) ;
150+ this . disposableRegistry . push (
151+ commands . registerCommand ( Commands . AddInputCheckboxBlock , ( ) => this . addInputBlock ( 'input-checkbox' ) )
152+ ) ;
153+ this . disposableRegistry . push (
154+ commands . registerCommand ( Commands . AddInputDateBlock , ( ) => this . addInputBlock ( 'input-date' ) )
155+ ) ;
156+ this . disposableRegistry . push (
157+ commands . registerCommand ( Commands . AddInputDateRangeBlock , ( ) => this . addInputBlock ( 'input-date-range' ) )
158+ ) ;
159+ this . disposableRegistry . push (
160+ commands . registerCommand ( Commands . AddInputFileBlock , ( ) => this . addInputBlock ( 'input-file' ) )
161+ ) ;
162+ this . disposableRegistry . push (
163+ commands . registerCommand ( Commands . AddButtonBlock , ( ) => this . addInputBlock ( 'button' ) )
164+ ) ;
32165 }
33166
34167 private addSqlBlock ( ) : void {
@@ -91,4 +224,41 @@ export class DeepnoteNotebookCommandListener implements IExtensionSyncActivation
91224 editor . selection = new NotebookRange ( insertIndex , insertIndex + 1 ) ;
92225 } , noop ) ;
93226 }
227+
228+ private addInputBlock ( blockType : InputBlockType ) : void {
229+ const editor = window . activeNotebookEditor ;
230+ if ( ! editor ) {
231+ return ;
232+ }
233+ const document = editor . notebook ;
234+ const selection = editor . selection ;
235+ const cells = editor . notebook . getCells ( ) ;
236+ const deepnoteVariableName = getNextDeepnoteVariableName ( cells , 'input' ) ;
237+
238+ // Determine the index where to insert the new cell (below current selection or at the end)
239+ const insertIndex = selection ? selection . end : document . cellCount ;
240+
241+ // Get the appropriate schema and parse default metadata based on block type
242+ let defaultMetadata = getInputBlockMetadata ( blockType , deepnoteVariableName ) ;
243+
244+ const metadata = {
245+ __deepnotePocket : {
246+ type : blockType ,
247+ ...defaultMetadata
248+ }
249+ } ;
250+
251+ chainWithPendingUpdates ( document , ( edit ) => {
252+ const newCell = new NotebookCellData (
253+ NotebookCellKind . Code ,
254+ JSON . stringify ( defaultMetadata , null , 2 ) ,
255+ 'json'
256+ ) ;
257+ newCell . metadata = metadata ;
258+ const nbEdit = NotebookEdit . insertCells ( insertIndex , [ newCell ] ) ;
259+ edit . set ( document . uri , [ nbEdit ] ) ;
260+ } ) . then ( ( ) => {
261+ editor . selection = new NotebookRange ( insertIndex , insertIndex + 1 ) ;
262+ } , noop ) ;
263+ }
94264}
0 commit comments