-
Notifications
You must be signed in to change notification settings - Fork 134
Positron notebooks - Add new functionality to edit tool for moving cells. #11023
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
E2E Tests 🚀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds notebook cell reordering capabilities to the Positron Assistant, allowing users to reorganize cells through natural language commands like "move all imports to the top cell" or "move the third cell to the bottom."
Key changes include:
- New
positron.notebooks.moveCellandpositron.notebooks.reorderCellsAPIs for single and batch cell reordering - Extension of
EditNotebookCellsToolwith a 'reorder' operation that accepts eitherfromIndex/toIndexparameters or anewOrderpermutation array - Comprehensive validation for indices and permutations with clear error messages
- Cycle-based algorithm for efficient permutation reordering with minimal cell moves
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/api/common/positron/extHostNotebookFeatures.ts | Added moveCell and reorderCells methods to the extension host API implementation |
| src/vs/workbench/api/common/positron/extHost.positron.protocol.ts | Added protocol method signatures for $moveCell and $reorderCells |
| src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts | Wired up the new notebook methods to the extension API factory |
| src/vs/workbench/api/browser/positron/mainThreadNotebookFeatures.ts | Implemented main thread handlers for cell moving/reordering with validation, cycle-based algorithm, and text model integration |
| src/positron-dts/positron.d.ts | Added TypeScript definitions for the new notebook APIs |
| extensions/positron-assistant/src/tools/notebookUtils.ts | Added validatePermutation function for validating cell reordering arrays |
| extensions/positron-assistant/src/tools/notebookTools.ts | Extended EditNotebookCellsTool to support 'reorder' operation with single move and batch reorder capabilities |
| extensions/positron-assistant/package.json | Updated tool schema to document the new reorder operation parameters |
| export function validatePermutation( | ||
| newOrder: number[], | ||
| cellCount: number | ||
| ): PermutationValidation { | ||
| // Check length matches | ||
| if (newOrder.length !== cellCount) { | ||
| return { | ||
| valid: false, | ||
| error: `Permutation length (${newOrder.length}) must match cell count (${cellCount})` | ||
| }; | ||
| } | ||
|
|
||
| // Handle empty notebook case | ||
| if (cellCount === 0) { | ||
| return { valid: true, isIdentity: true }; | ||
| } | ||
|
|
||
| // An identity permutation is just one where each index maps to itself. Aka a no-op. | ||
| let isIdentity = true; | ||
|
|
||
| // Check if any indices are outside of the valid range | ||
| for (const [i, index] of newOrder.entries()) { | ||
| if (!Number.isInteger(index) || index < 0 || index >= cellCount) { | ||
| return { | ||
| valid: false, | ||
| error: `Invalid index in permutation: ${index} (must be integer between 0 and ${cellCount - 1})` | ||
| }; | ||
| } | ||
|
|
||
| // Check for identity at the same time | ||
| if (index !== i) { | ||
| isIdentity = false; | ||
| } | ||
| } | ||
|
|
||
| // If identity permutation, no need to check further | ||
| if (isIdentity) { | ||
| return { valid: true, isIdentity: true }; | ||
| } | ||
|
|
||
| // Make sure there are no duplicates and all indices are present | ||
| const uniqueIndices = new Set(newOrder); | ||
|
|
||
| if (uniqueIndices.size !== cellCount) { | ||
| return { | ||
| valid: false, | ||
| error: 'Invalid permutation: must contain each index from 0 to cellCount-1 exactly once' | ||
| }; | ||
| } | ||
|
|
||
| return { valid: true, isIdentity: false }; | ||
| } |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validatePermutation function lacks test coverage. Given that this validation is critical for ensuring data integrity during cell reordering operations, comprehensive tests should be added to verify:
- Empty arrays (cellCount = 0)
- Identity permutations
- Valid permutations of various sizes
- Invalid cases: wrong length, out-of-range indices, non-integer values, duplicate indices
- Edge cases like single cell notebooks
Consider adding a test file extensions/positron-assistant/src/test/notebookUtils.test.ts with comprehensive test cases for this function.
| // Apply the reordering as a series of move operations | ||
| // We use a cycle-based approach to minimize moves: | ||
| // For each cycle in the permutation, we perform cycle_length - 1 moves | ||
| const currentOrder = [...Array(cellCount).keys()]; // [0, 1, 2, ..., n-1] | ||
| const visited = new Set<number>(); | ||
|
|
||
| for (let startPos = 0; startPos < cellCount; startPos++) { | ||
| if (visited.has(startPos) || newOrder[startPos] === currentOrder[startPos]) { | ||
| visited.add(startPos); | ||
| continue; | ||
| } | ||
|
|
||
| // Follow the cycle | ||
| let pos = startPos; | ||
| while (!visited.has(pos)) { | ||
| visited.add(pos); | ||
| const targetCellOriginalIndex = newOrder[pos]; | ||
| const currentPosOfTargetCell = currentOrder.indexOf(targetCellOriginalIndex); | ||
|
|
||
| if (currentPosOfTargetCell !== pos) { | ||
| // Move the cell from currentPosOfTargetCell to pos | ||
| textModel.applyEdits([{ | ||
| editType: CellEditType.Move, | ||
| index: currentPosOfTargetCell, | ||
| length: 1, | ||
| newIdx: pos | ||
| }], true, undefined, () => undefined, undefined, computeUndoRedo); | ||
|
|
||
| // Update our tracking of current positions | ||
| const movedValue = currentOrder.splice(currentPosOfTargetCell, 1)[0]; | ||
| currentOrder.splice(pos, 0, movedValue); | ||
| } | ||
|
|
||
| // Find the next position in the cycle | ||
| const nextPos = newOrder.indexOf(currentOrder[pos], pos + 1); | ||
| if (nextPos === -1 || visited.has(nextPos)) { | ||
| break; | ||
| } | ||
| pos = nextPos; | ||
| } | ||
| } |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The cycle-based reordering algorithm is clever but complex. While the comment at lines 460-461 provides a brief explanation, additional inline comments explaining the algorithm's logic would improve maintainability.
Consider adding comments that explain:
- Why
currentOrder.indexOf(targetCellOriginalIndex)is used at line 476 - What
currentOrder.indexOf(currentOrder[pos], pos + 1)achieves at line 493 - An example showing how a simple permutation like
[2, 0, 1]is processed through the cycles
This would help future maintainers understand the non-trivial algorithm without needing to reverse-engineer the logic.
| // Validate the permutation | ||
| if (newOrder.length !== cellCount) { | ||
| throw new Error(`Invalid newOrder length: ${newOrder.length}. Must match cell count: ${cellCount}`); | ||
| } | ||
|
|
||
| // Check that it's a valid permutation (each index 0 to n-1 appears exactly once) | ||
| const seen = new Set<number>(); | ||
| for (const index of newOrder) { | ||
| if (!Number.isInteger(index) || index < 0 || index >= cellCount) { | ||
| throw new Error(`Invalid index in newOrder: ${index}. Must be between 0 and ${cellCount - 1}`); | ||
| } | ||
| if (seen.has(index)) { | ||
| throw new Error(`Duplicate index in newOrder: ${index}. Each index must appear exactly once`); | ||
| } | ||
| seen.add(index); | ||
| } | ||
|
|
||
| // Check if this is a no-op (identity permutation) |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation logic here is duplicated from the validatePermutation function in extensions/positron-assistant/src/tools/notebookUtils.ts. This creates a maintenance burden where changes to validation rules need to be made in two places.
Consider removing this validation from $reorderCells and relying solely on the validation in notebookUtils.ts before the API is called, or extracting this into a shared utility that can be used on both the extension side and the main thread side if defense-in-depth validation is desired.
| // Validate the permutation | |
| if (newOrder.length !== cellCount) { | |
| throw new Error(`Invalid newOrder length: ${newOrder.length}. Must match cell count: ${cellCount}`); | |
| } | |
| // Check that it's a valid permutation (each index 0 to n-1 appears exactly once) | |
| const seen = new Set<number>(); | |
| for (const index of newOrder) { | |
| if (!Number.isInteger(index) || index < 0 || index >= cellCount) { | |
| throw new Error(`Invalid index in newOrder: ${index}. Must be between 0 and ${cellCount - 1}`); | |
| } | |
| if (seen.has(index)) { | |
| throw new Error(`Duplicate index in newOrder: ${index}. Each index must appear exactly once`); | |
| } | |
| seen.add(index); | |
| } | |
| // Check if this is a no-op (identity permutation) | |
| // Validate the permutation length | |
| if (newOrder.length !== cellCount) { | |
| throw new Error(`Invalid newOrder length: ${newOrder.length}. Must match cell count: ${cellCount}`); | |
| } | |
| // If this is a no-op (identity permutation), do nothing |
seeM
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice! I wonder if the two different modes of using reorder could confuse LLMs but we can test that out
Addresses #10553.
Summary
This PR adds notebook cell reordering capabilities to the Positron Assistant, enabling users to reorganize cells through natural language prompts like "move all imports to the top cell" or "move the third cell to the bottom."
move-demo.mov
The implementation extends the
EditNotebookCellsToolwith a newreorderoperation that supports both single cell moves (viafromIndex/toIndex) and full permutation reordering (vianewOrderarray). The feature includes validation for permutations and indices, confirmation prompts for the user, and integration with the notebook editor through thepositron.notebooks.reorderCellsAPI.Release Notes
New Features
Bug Fixes
QA Notes
@:positron-notebooks @:assistant