-
Notifications
You must be signed in to change notification settings - Fork 121
PositroNB extension API integration and debugging #9860
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
base: main
Are you sure you want to change the base?
Conversation
if (notebookInstance) { | ||
const id = toPositronNotebookCommand(this.desc.id); | ||
if (id) { | ||
return accessor.get(ICommandService).executeCommand(id); |
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.
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.
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.
oh interesting. Seems good to me.
Awesome! ![]()
![]()
|
E2E Tests 🚀 |
add/remove notebook instance events make `public textModel` observable connect positron notebook instances to mainThreadNotebookEditors.$tryShowNotebookDocument integrate positron notebooks with the extension api fix circular imports fix import fixes comment
8bfbe91
to
c62920c
Compare
@cindyytong Thanks for the review!
|
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. |
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.
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. |
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.
* 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" |
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.
If the variable is not used for themeing you can avoid having to declare here with the format --_positron-notebook-selection-bar-width
/** | ||
* 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; | ||
} | ||
} |
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.
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> |
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.
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()) { |
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.
Maybe we should have a hasTextModel
method or getter.
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.
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.
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.
Ah, I agree, and INotebookEditor
has hasModel
as well
// If this is a Positron notebook, return the notebook instance ID. | ||
const notebookInstance = getNotebookInstanceFromEditorPane(editorPane); | ||
if (notebookInstance) { | ||
return notebookInstance.id; |
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.
nice improvement.
if (notebookInstance) { | ||
const id = toPositronNotebookCommand(this.desc.id); | ||
if (id) { | ||
return accessor.get(ICommandService).executeCommand(id); |
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.
oh interesting. Seems good to me.
.monaco-editor { | ||
/* Offset the editor to allow space for the selection bar. */ | ||
padding-left: var(--positron-notebook-selection-bar-width); |
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 debug icon is misaligned with run above / run below icons by 1pixel */ | ||
&.codicon-debug-alt-small { | ||
position: relative; | ||
top: -1px; | ||
} |
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.
Is this a codicon issue or something about the layout code?
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; | ||
} | ||
} |
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.
I like this a good bit.
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:
Release Notes
New Features
Bug Fixes
QA Notes
Nothing should break. You should be able to debug a cell in a Positro notebook editor.
@:notebooks