Skip to content

Conversation

seeM
Copy link
Contributor

@seeM seeM commented Oct 9, 2025

This PR integrates Positron notebook editors with the extension API (addresses #9440) and hooks up the notebook runtime debugging extension (#9251).

This is a rework of #9843. It turned out to be simplest to manage Positron notebook state in the same place as upstream state, otherwise we have to restore to more complicated state merging and conflicts.

Debug cell button position:

image

Release Notes

New Features

  • N/A

Bug Fixes

  • N/A

QA Notes

Nothing should break. You should be able to debug a cell in a Positro notebook editor.

@:notebooks

@seeM seeM requested review from dhruvisompura and nstrayer October 9, 2025 14:49
if (notebookInstance) {
const id = toPositronNotebookCommand(this.desc.id);
if (id) {
return accessor.get(ICommandService).executeCommand(id);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an idea for how we could get compatibility with upstream commands. We could also get a bit smarter and translate and pass the arguments as well e.g. to reference cells.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh interesting. Seems good to me.

@cindyytong
Copy link

cindyytong commented Oct 9, 2025

Awesome!

Screenshot 2025-10-09 at 11 07 41 AM
  1. For the icon can we line them up
  2. Can we fix the spacing in between the icons to be consistent - i believe I mentioned this previously it might have been captured by something @nstrayer is working on already
  3. Are we planning to add a shortcut for this? Can capture this in a separate ticket if needed
  4. Do we have the ability to add a tooltip on hover to show i.e. Debug Cell (Shortcut if available)
    We should add this for all the buttons => Can pull out to seaprate ticket if needed too
Screenshot 2025-10-09 at 11 10 47 AM
  1. Can we see a video of what happens when you click the debug button

Copy link

github-actions bot commented Oct 9, 2025

E2E Tests 🚀
This PR will run tests tagged with: @:critical @:notebooks

readme  valid tags

@seeM seeM force-pushed the feature/debug-cell branch from 8bfbe91 to c62920c Compare October 10, 2025 14:27
@seeM
Copy link
Contributor Author

seeM commented Oct 10, 2025

@cindyytong Thanks for the review!

  • 1, 2 and 3 done. The keyboard shortcut isn't working consistently, but I suspect it's the same issue @nstrayer mentioned elsewhere about context keys. There might still be some inconsistency with spacing -- I'm not sure how we want to handle the ellipsis and trash can icons

    image
  • Also fixed the clipping of the editor "gutter" (on the left) and cell selection bar (see video below), although this has now pushed the code contents 7 pixels to the right.

  • Can we track tooltips separately? @rodrigosf672 brought this up as well and may have already filed

  • Video of a simple debugging case:
    Shouldn't be hard to change the debugging UX details now that all the infrastructure is setup.

    Screen.Recording.2025-10-10.at.16.11.43.mov

@cindyytong
Copy link

LGTM I don't see a ticket for tooltips so i will file separately

@rodrigosf672
Copy link
Member

LGTM I don't see a ticket for tooltips so i will file separately

@cindyytong and @seeM, I added the issue in #9896. Please feel free to make changes or add anything you want there.

@cindyytong
Copy link

Sorry for the late comment, I just noticed this when looking through version:

Positron Version: 2025.11.0 (Universal) build 50
Code - OSS Version: 1.104.0
Commit: a3249405df14df4c023426de4b587345bda0ac7f

Hovering over a breakpoint shows a modal that gets cut off. Does this live within the cell? Perhaps it can be overlaid ontop of the cell.
Screenshot 2025-10-12 at 4 09 00 PM

Copy link
Contributor

@nstrayer nstrayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments on code style etc but in general I think the approach is good. Much nicer to have to just have to match the MainThreadNotebookEditorsShape interface.

@@ -0,0 +1,227 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024-2025 Posit Software, PBC. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (C) 2024-2025 Posit Software, PBC. All rights reserved.
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.

"--positron-data-grid-column-header-sort-index-font-weight",
"--positron-data-grid-column-header-title-font-weight"
"--positron-data-grid-column-header-title-font-weight",
"--positron-notebook-selection-bar-width"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the variable is not used for themeing you can avoid having to declare here with the format --_positron-notebook-selection-bar-width

Comment on lines +24 to +113
/**
* Represents a PositronNotebookInstance on the main thread.
*/
export class MainThreadPositronNotebookInstance extends Disposable {
private readonly _onDidChangeProperties = this._register(new Emitter<INotebookEditorPropertiesChangeData>());

/** Event that fires when the notebook editor properties change */
readonly onDidChangeProperties = this._onDidChangeProperties.event;

constructor(
private readonly _instance: IPositronNotebookInstance,
) {
super();

// Fire an event when selections change
this._register(runOnChange(this._instance.selectionStateMachine.state, (state) => {
const selections = this.getSelections(state);
this._onDidChangeProperties.fire({ selections: { selections } });
}));

// TODO: Fire an event when visible ranges change
// this._onDidChangeProperties.fire({ visibleRanges: { ranges: [] } });
}

getId(): string {
return this._instance.id;
}

getDocumentUri(): UriComponents {
return this._instance.uri;
}

getViewType(): string {
return this._instance.textModel.get()?.viewType ?? 'jupyter-notebook';
}

getSelections(state?: SelectionStates): ICellRange[] {
// TODO: Double check this
state = state ?? this._instance.selectionStateMachine.state.get();
const selectedCells = getSelectedCells(state);

if (selectedCells.length === 0) {
return [];
}

// Group consecutive cells into ranges
const ranges: ICellRange[] = [];
let currentStart = selectedCells[0].index;
let currentEnd = currentStart + 1;

for (let i = 1; i < selectedCells.length; i++) {
const cellIndex = selectedCells[i].index;
if (cellIndex === currentEnd) {
// Consecutive cell, extend the range
currentEnd++;
} else {
// Non-consecutive, save current range and start new one
ranges.push({ start: currentStart, end: currentEnd });
currentStart = cellIndex;
currentEnd = currentStart + 1;
}
}

// Add the last range
ranges.push({ start: currentStart, end: currentEnd });

return ranges;
}

getVisibleRanges(): ICellRange[] {
// For now, return all cells as visible if we have a container
// TODO: Implement actual viewport calculation based on scroll position
if (this._instance.cellsContainer && this._instance.cells.get().length > 0) {
return [{ start: 0, end: this._instance.cells.get().length }];
}
return [];
}

setSelections(selections: readonly ICellRange[]): void {
// TODO: Implement set selections
}

revealRange(range: ICellRange, revealType: NotebookEditorRevealType): void {
// TODO: Implement reveal range
}

matches(editor: IEditorPane): boolean {
return editor instanceof PositronNotebookEditor && editor.notebookInstance === this._instance;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just make the notebook instance conform to this interface and avoid a secondary class?

Since map values are never referenced, we can retype as unknown and include Positron notebook instances
readonly visibleEditors: Map<string, IActiveNotebookEditor>
*/
readonly visibleEditors: Map<string, unknown>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big fan of use of unknown here. Vastly underutilized type.

for (const instance of this._positronNotebookService.listInstances()) {
// Don't add the instance until it has a text model, otherwise it'll get
// dropped in the extension host
if (instance.textModel.get()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have a hasTextModel method or getter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just get mildly uneasy about the observable implementation leaking. That being said it's a pretty straightforward api so it's probably never going to be an issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I agree, and INotebookEditor has hasModel as well

Comment on lines +127 to +130
// If this is a Positron notebook, return the notebook instance ID.
const notebookInstance = getNotebookInstanceFromEditorPane(editorPane);
if (notebookInstance) {
return notebookInstance.id;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice improvement.

if (notebookInstance) {
const id = toPositronNotebookCommand(this.desc.id);
if (id) {
return accessor.get(ICommandService).executeCommand(id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh interesting. Seems good to me.

Comment on lines +16 to +18
.monaco-editor {
/* Offset the editor to allow space for the selection bar. */
padding-left: var(--positron-notebook-selection-bar-width);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

Comment on lines +44 to +49

/* The debug icon is misaligned with run above / run below icons by 1pixel */
&.codicon-debug-alt-small {
position: relative;
top: -1px;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a codicon issue or something about the layout code?

Comment on lines +9 to +19
const _code2PositronCommandId: Record<string, string> = {
[EXECUTE_CELL_COMMAND_ID]: POSITRON_EXECUTE_CELL_COMMAND_ID,
};

export function toPositronNotebookCommand(commandId: string): string | undefined {
try {
return _code2PositronCommandId[commandId];
} catch {
return undefined;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this a good bit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants