Skip to content

Commit f402a8b

Browse files
committed
feat: Handle multiple notebooks using the sidebar.
1 parent 20e213d commit f402a8b

27 files changed

+2363
-596
lines changed

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@
2626
- `deepnoteSerializer.ts` - Main serializer (orchestration)
2727
- `deepnoteActivationService.ts` - VSCode activation
2828
- Whitespace is good for readability, add a blank line after const groups and before return statements
29-
- Separate third-party and local file imports
29+
- Separate third-party and local file imports
30+
- How the extension works is described in @architecture.md

architecture.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# VSCode Deepnote Extension Architecture
2+
3+
This extension adds support for Deepnote notebooks in Visual Studio Code. Deepnote is a collaborative data science notebook platform, and this extension allows users to open, edit, and manage Deepnote project files (`.deepnote` files) directly within VS Code.
4+
5+
## Key Components
6+
7+
### 1. Notebook Serializer (`deepnoteSerializer.ts`)
8+
9+
The core component responsible for converting between Deepnote's YAML format and VS Code's notebook format.
10+
11+
**Responsibilities:**
12+
13+
- **Deserialization**: Converts Deepnote YAML files into VS Code NotebookData format
14+
- **Serialization**: Converts VS Code notebook changes back to Deepnote YAML format
15+
- **State Management**: Maintains original project data for accurate serialization
16+
17+
**Key Methods:**
18+
19+
- `deserializeNotebook()`: Parses YAML, converts blocks to cells
20+
- `serializeNotebook()`: Converts cells back to blocks, updates YAML
21+
- `findCurrentNotebookId()`: Determines which notebook to deserialize using manager state
22+
23+
### 2. Data Converter (`deepnoteDataConverter.ts`)
24+
25+
Handles the transformation between Deepnote blocks and VS Code notebook cells.
26+
27+
**Responsibilities:**
28+
29+
- Convert Deepnote blocks (code, markdown, SQL, etc.) to VS Code cells
30+
- Convert VS Code cells back to Deepnote blocks
31+
- Preserve block metadata and outputs during conversion
32+
33+
**Supported Block Types:**
34+
35+
- Code blocks (Python, R, JavaScript, etc.)
36+
- Markdown blocks
37+
38+
### 3. Notebook Manager (`deepnoteNotebookManager.ts`)
39+
40+
Manages the state of Deepnote projects and notebook selections.
41+
42+
**Responsibilities:**
43+
44+
- Store original project data for serialization
45+
- Track which notebook is selected for each project
46+
- Maintain project-to-notebook mapping using project IDs
47+
48+
**Key Features:**
49+
50+
- In-memory caching of project data
51+
- Project-ID based notebook selection tracking
52+
- Support for multiple notebooks per project
53+
54+
**Key Methods:**
55+
56+
- `getTheSelectedNotebookForAProject()`: Retrieves selected notebook ID for a project
57+
- `selectNotebookForProject()`: Associates a notebook ID with a project ID
58+
- `storeOriginalProject()`: Caches project data and sets current notebook
59+
60+
### 4. Explorer View (`deepnoteExplorerView.ts`)
61+
62+
Provides the sidebar UI for browsing and opening Deepnote notebooks.
63+
64+
**Responsibilities:**
65+
66+
- Create and manage the tree view in VS Code's sidebar
67+
- Handle user interactions (clicking on notebooks/files)
68+
- Register commands for notebook operations
69+
70+
**Commands:**
71+
72+
- `deepnote.refreshExplorer`: Refresh the file tree
73+
- `deepnote.openNotebook`: Open a specific notebook
74+
- `deepnote.openFile`: Open the raw .deepnote file
75+
- `deepnote.revealInExplorer`: Show active notebook info
76+
77+
### 5. Tree Data Provider (`deepnoteTreeDataProvider.ts`)
78+
79+
Implements VS Code's TreeDataProvider interface for the sidebar view.
80+
81+
**Responsibilities:**
82+
83+
- Scan workspace for `.deepnote` files
84+
- Parse project files to extract notebook information
85+
- Provide tree structure for the explorer view
86+
- Watch for file system changes
87+
88+
**Features:**
89+
90+
- Automatic workspace scanning
91+
- File system watching for real-time updates
92+
- Caching for performance optimization
93+
94+
### 6. Activation Service (`deepnoteActivationService.ts`)
95+
96+
Entry point for the Deepnote functionality within the extension.
97+
98+
**Responsibilities:**
99+
100+
- Register the notebook serializer with VS Code
101+
- Initialize the explorer view
102+
- Set up extension lifecycle
103+
104+
## Data Flow
105+
106+
### Opening a Notebook
107+
108+
1. **User Action**: User clicks on a notebook in the sidebar
109+
2. **Explorer View**: Handles the click, stores notebook selection using project ID
110+
3. **Notebook Manager**: Associates the notebook ID with the project ID
111+
4. **VS Code**: Opens the document using the base file URI and calls `deserializeNotebook()`
112+
5. **Serializer**:
113+
- Uses `findCurrentNotebookId()` to determine which notebook to load
114+
- Reads the YAML file and finds the selected notebook
115+
- Converts blocks to cells using the Data Converter
116+
6. **Display**: VS Code displays the notebook in the editor
117+
118+
### Saving Changes
119+
120+
1. **User Action**: User makes changes and saves (Ctrl+S)
121+
2. **VS Code**: Calls the serializer's `serializeNotebook()` method
122+
3. **Serializer**:
123+
- Retrieves original project data from Notebook Manager
124+
- Converts cells back to blocks using the Data Converter
125+
- Updates the YAML structure
126+
- Writes back to file
127+
4. **File System**: Updates the `.deepnote` file
128+
129+
## File Format
130+
131+
### Deepnote YAML Structure
132+
133+
```yaml
134+
version: 1.0
135+
metadata:
136+
modifiedAt: '2024-01-01T00:00:00Z'
137+
project:
138+
id: 'project-uuid'
139+
name: 'Project Name'
140+
notebooks:
141+
- id: 'notebook-uuid'
142+
name: 'Notebook Name'
143+
blocks:
144+
- id: 'block-uuid'
145+
type: 'code'
146+
source: "print('Hello')"
147+
outputs: []
148+
```
149+
150+
### VS Code Notebook Format
151+
152+
```typescript
153+
interface NotebookData {
154+
cells: NotebookCellData[];
155+
metadata: {
156+
deepnoteProjectId: string;
157+
deepnoteProjectName: string;
158+
deepnoteNotebookId: string;
159+
deepnoteNotebookName: string;
160+
deepnoteVersion: string;
161+
};
162+
}
163+
```
164+
165+
## Multi-Notebook Support
166+
167+
The extension supports opening multiple notebooks from the same `.deepnote` file:
168+
169+
1. **Project-Based Selection**: The Notebook Manager tracks which notebook is selected for each project
170+
2. **State Management**: When opening a notebook, the manager stores the project-to-notebook mapping
171+
3. **Fallback Detection**: The serializer can detect the current notebook from VS Code's active document context
172+
173+
## Technical Decisions
174+
175+
### Why YAML?
176+
177+
Deepnote uses YAML for its file format, which provides:
178+
179+
- Human-readable structure
180+
- Support for complex nested data
181+
- Easy to read Git diffs
182+
183+
### Why Project-ID Based Selection?
184+
185+
- Simpler than URI-based tracking - uses straightforward project ID mapping
186+
- The VS Code NotebookSerializer interface doesn't provide URI context during deserialization
187+
- Allows for consistent notebook selection regardless of how the document is opened
188+
- Manager-based approach centralizes state management and reduces complexity

package.json

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,18 @@
251251
}
252252
],
253253
"commands": [
254+
{
255+
"command": "deepnote.refreshExplorer",
256+
"title": "%deepnote.commands.refreshExplorer.title%",
257+
"category": "Deepnote",
258+
"icon": "$(refresh)"
259+
},
260+
{
261+
"command": "deepnote.revealInExplorer",
262+
"title": "%deepnote.commands.revealInExplorer.title%",
263+
"category": "Deepnote",
264+
"icon": "$(reveal)"
265+
},
254266
{
255267
"command": "dataScience.ClearCache",
256268
"title": "%jupyter.command.dataScience.clearCache.title%",
@@ -320,12 +332,6 @@
320332
"title": "%jupyter.command.jupyter.viewOutput.title%",
321333
"category": "Jupyter"
322334
},
323-
{
324-
"command": "jupyter.selectDeepnoteNotebook",
325-
"title": "%deepnote.command.selectNotebook.title%",
326-
"category": "Deepnote",
327-
"enablement": "notebookType == 'deepnote'"
328-
},
329335
{
330336
"command": "jupyter.notebookeditor.export",
331337
"title": "%DataScience.notebookExportAs%",
@@ -894,11 +900,6 @@
894900
"group": "navigation@2",
895901
"when": "notebookType == 'jupyter-notebook' && config.jupyter.showOutlineButtonInNotebookToolbar"
896902
},
897-
{
898-
"command": "jupyter.selectDeepnoteNotebook",
899-
"group": "navigation@2",
900-
"when": "notebookType == 'deepnote'"
901-
},
902903
{
903904
"command": "jupyter.continueEditSessionInCodespace",
904905
"group": "navigation@3",
@@ -1402,6 +1403,13 @@
14021403
"title": "%jupyter.command.jupyter.runFileInteractive.title%",
14031404
"when": "resourceLangId == python && !isInDiffEditor && isWorkspaceTrusted"
14041405
}
1406+
],
1407+
"view/item/context": [
1408+
{
1409+
"command": "deepnote.revealInExplorer",
1410+
"when": "view == deepnoteExplorer",
1411+
"group": "inline@2"
1412+
}
14051413
]
14061414
},
14071415
"configuration": {
@@ -2003,6 +2011,11 @@
20032011
"id": "jupyter",
20042012
"title": "Jupyter",
20052013
"icon": "$(notebook)"
2014+
},
2015+
{
2016+
"id": "deepnote",
2017+
"title": "Deepnote",
2018+
"icon": "resources/DnDeepnoteLineLogo.svg"
20062019
}
20072020
],
20082021
"panel": [
@@ -2021,6 +2034,17 @@
20212034
"name": "Jupyter Variables",
20222035
"when": "jupyter.hasNativeNotebookOrInteractiveWindowOpen"
20232036
}
2037+
],
2038+
"deepnote": [
2039+
{
2040+
"id": "deepnoteExplorer",
2041+
"name": "%deepnote.views.explorer.name%",
2042+
"when": "workspaceFolderCount != 0",
2043+
"iconPath": {
2044+
"light": "./resources/light/deepnote-icon.svg",
2045+
"dark": "./resources/dark/deepnote-icon.svg"
2046+
}
2047+
}
20242048
]
20252049
},
20262050
"debuggers": [

package.nls.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,5 +244,11 @@
244244
"jupyter.languageModelTools.configure_notebook.userDescription": "Ensure Notebook is ready for use, such as running cells.",
245245
"jupyter.languageModelTools.notebook_list_packages.userDescription": "Lists Python packages available in the selected Notebook Kernel.",
246246
"jupyter.languageModelTools.notebook_install_packages.userDescription": "Installs Python packages in the selected Notebook Kernel.",
247-
"deepnote.notebook.displayName": "Deepnote Notebook"
247+
"deepnote.notebook.displayName": "Deepnote Notebook",
248+
"deepnote.commands.refreshExplorer.title": "Refresh Explorer",
249+
"deepnote.commands.openNotebook.title": "Open Notebook",
250+
"deepnote.commands.openFile.title": "Open File",
251+
"deepnote.commands.revealInExplorer.title": "Reveal in Explorer",
252+
"deepnote.views.explorer.name": "Explorer",
253+
"deepnote.command.selectNotebook.title": "Select Notebook"
248254
}

resources/DnDeepnoteLineLogo.svg

Lines changed: 3 additions & 0 deletions
Loading

resources/dark/deepnote-icon.svg

Lines changed: 5 additions & 0 deletions
Loading

resources/light/deepnote-icon.svg

Lines changed: 5 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)