88 NotebookRange ,
99 NotebookCell ,
1010 NotebookEditorRevealType ,
11- l10n
11+ l10n ,
12+ QuickPickItem ,
13+ NotebookEditor
1214} from 'vscode' ;
1315import z from 'zod' ;
1416
@@ -33,6 +35,7 @@ import {
3335 DeepnoteSqlMetadata
3436} from './deepnoteSchemas' ;
3537import { DATAFRAME_SQL_INTEGRATION_ID } from '../../platform/notebooks/deepnote/integrationTypes' ;
38+ import { Pocket } from '../../platform/deepnote/pocket' ;
3639
3740export type InputBlockType =
3841 | 'input-text'
@@ -45,6 +48,10 @@ export type InputBlockType =
4548 | 'input-file'
4649 | 'button' ;
4750
51+ export const TEXT_BLOCK_TYPES = [ 'text-cell-p' , 'text-cell-h1' , 'text-cell-h2' , 'text-cell-h3' ] as const ;
52+
53+ export type TextBlockType = ( typeof TEXT_BLOCK_TYPES ) [ number ] ;
54+
4855export function getInputBlockMetadata ( blockType : InputBlockType , variableName : string ) {
4956 const defaultInput = {
5057 deepnote_variable_name : variableName
@@ -181,6 +188,29 @@ export class DeepnoteNotebookCommandListener implements IExtensionSyncActivation
181188 this . disposableRegistry . push (
182189 commands . registerCommand ( Commands . AddButtonBlock , ( ) => this . addInputBlock ( 'button' ) )
183190 ) ;
191+ this . disposableRegistry . push (
192+ commands . registerCommand ( Commands . AddTextBlock , ( ) => this . addTextBlockThroughPicker ( ) )
193+ ) ;
194+ this . disposableRegistry . push (
195+ commands . registerCommand ( Commands . AddTextBlockHeading1 , ( ) =>
196+ this . addTextBlockCommandHandler ( { textBlockType : 'text-cell-h1' } )
197+ )
198+ ) ;
199+ this . disposableRegistry . push (
200+ commands . registerCommand ( Commands . AddTextBlockHeading2 , ( ) =>
201+ this . addTextBlockCommandHandler ( { textBlockType : 'text-cell-h2' } )
202+ )
203+ ) ;
204+ this . disposableRegistry . push (
205+ commands . registerCommand ( Commands . AddTextBlockHeading3 , ( ) =>
206+ this . addTextBlockCommandHandler ( { textBlockType : 'text-cell-h3' } )
207+ )
208+ ) ;
209+ this . disposableRegistry . push (
210+ commands . registerCommand ( Commands . AddTextBlockParagraph , ( ) =>
211+ this . addTextBlockCommandHandler ( { textBlockType : 'text-cell-p' } )
212+ )
213+ ) ;
184214 }
185215
186216 public async addSqlBlock ( ) : Promise < void > {
@@ -368,4 +398,92 @@ export class DeepnoteNotebookCommandListener implements IExtensionSyncActivation
368398 // Enter edit mode on the new cell
369399 await commands . executeCommand ( 'notebook.cell.edit' ) ;
370400 }
401+
402+ public async addTextBlockThroughPicker ( ) : Promise < void > {
403+ const TEXT_BLOCK_TYPE_LABELS = {
404+ 'text-cell-p' : l10n . t ( 'Paragraph' ) ,
405+ 'text-cell-h1' : l10n . t ( 'Heading 1' ) ,
406+ 'text-cell-h2' : l10n . t ( 'Heading 2' ) ,
407+ 'text-cell-h3' : l10n . t ( 'Heading 3' )
408+ } as const satisfies Record < TextBlockType , string > ;
409+
410+ const editor = window . activeNotebookEditor ;
411+ if ( ! editor ) {
412+ throw new Error ( l10n . t ( 'No active notebook editor found' ) ) ;
413+ }
414+
415+ const items : ( QuickPickItem & { textBlockType : TextBlockType } ) [ ] = TEXT_BLOCK_TYPES . map ( ( textBlockType ) => {
416+ const label = TEXT_BLOCK_TYPE_LABELS [ textBlockType ] ;
417+ const description = l10n . t ( 'Add a {0} text block' , label ) ;
418+ return {
419+ label,
420+ description,
421+ textBlockType
422+ } ;
423+ } ) ;
424+
425+ const selected = await window . showQuickPick ( items , {
426+ placeHolder : l10n . t ( 'Select a text block type' ) ,
427+ matchOnDescription : true ,
428+ matchOnDetail : true
429+ } ) ;
430+
431+ if ( selected == null ) {
432+ return ;
433+ }
434+
435+ logger . info ( `Selected text block type: ${ selected . textBlockType } ` ) ;
436+
437+ await this . addTextBlock ( { editor, textBlockType : selected . textBlockType } ) ;
438+ }
439+
440+ public async addTextBlockCommandHandler ( { textBlockType } : { textBlockType : TextBlockType } ) : Promise < void > {
441+ const editor = window . activeNotebookEditor ;
442+ if ( ! editor ) {
443+ throw new Error ( l10n . t ( 'No active notebook editor found' ) ) ;
444+ }
445+
446+ await this . addTextBlock ( { editor, textBlockType } ) ;
447+ }
448+
449+ public async addTextBlock ( {
450+ editor,
451+ textBlockType
452+ } : {
453+ editor : NotebookEditor ;
454+ textBlockType : TextBlockType ;
455+ } ) : Promise < void > {
456+ const TEXT_BLOCK_TYPE_EMPTY_VALUES = {
457+ 'text-cell-p' : '' ,
458+ 'text-cell-h1' : '# ' ,
459+ 'text-cell-h2' : '## ' ,
460+ 'text-cell-h3' : '### '
461+ } as const satisfies Record < TextBlockType , string > ;
462+
463+ const cellContent = TEXT_BLOCK_TYPE_EMPTY_VALUES [ textBlockType ] ;
464+
465+ const document = editor . notebook ;
466+ const selection = editor . selection ;
467+ const insertIndex = selection ? selection . end : document . cellCount ;
468+
469+ const result = await notebookUpdaterUtils . chainWithPendingUpdates ( document , ( edit ) => {
470+ const newCell = new NotebookCellData ( NotebookCellKind . Markup , cellContent , 'markdown' ) ;
471+ newCell . metadata = {
472+ __deepnotePocket : {
473+ type : textBlockType
474+ } satisfies Pocket
475+ } ;
476+ const nbEdit = NotebookEdit . insertCells ( insertIndex , [ newCell ] ) ;
477+ edit . set ( document . uri , [ nbEdit ] ) ;
478+ } ) ;
479+ if ( result !== true ) {
480+ throw new Error ( l10n . t ( 'Failed to insert text block' ) ) ;
481+ }
482+
483+ const notebookRange = new NotebookRange ( insertIndex , insertIndex + 1 ) ;
484+ editor . revealRange ( notebookRange , NotebookEditorRevealType . Default ) ;
485+ editor . selection = notebookRange ;
486+ // Enter edit mode on the new cell
487+ await commands . executeCommand ( 'notebook.cell.edit' ) ;
488+ }
371489}
0 commit comments