-
Notifications
You must be signed in to change notification settings - Fork 85
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
Plots in an editor #4364
Plots in an editor #4364
Conversation
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.
Looking good and impressive work!
-
Left a few comments on code patterns etc..
-
I feel like we should consider the phrasing of this feature too, I'm not sure how much the user knows that the tabs are represented by "editors." I left a few comments on the code itself about this but I think just a bit more description will go a long way to alleviating potential confusion.
-
I noticed that we are using the grab cursor even though there's no scrolling available. Is this a choice to make it clear there's no right-click action menu stuff or just a left-over from the reused components?
-
On the product side a thing I am curious about is if we want to add the same controls that one gets in the plots pane to the editor? E.g. placing the actions toolbar in the editor pane. I think 90% of the usecases will be just looking at the plot larger, but I could imagine people wanting to be able to export etc from there as well.
This would also have implications on state transfer, e.g. aspect ratio being preserved across transition from the plots pane to the editor tab.
I'm also good to defer this stuff for a future PR as it could add a lot of work and delay people getting access to this.
constructor() { | ||
super({ | ||
id: PlotsEditorAction.ID, | ||
title: localize2('positronPlots.openEditor', 'Open Plot in Editor'), |
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 wonder if the phrase "Editor tab" is clearer. To me the whole of positron is an "editor."
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.
"Editor tab" is better phrasing. It's good to set this right because I hadn't noticed this all this time how misleading it can be!
src/vs/workbench/contrib/positronPlots/browser/positronPlotsService.ts
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlots/browser/positronPlotsService.ts
Outdated
Show resolved
Hide resolved
} | ||
|
||
export const EditorPlotsContainer = (props: EditorPlotsContainerProps) => { | ||
usePositronPlotsContext(); |
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 doesn't do anything, right? If there's some sort of effect in the hook we should probably name it as such but it doesn't look like it and things seem to work fine when I remove it.
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'll fix that. I forgot to remove it when I refactored how to display the plot.
|
||
export const EditorPlotsContainer = (props: EditorPlotsContainerProps) => { | ||
usePositronPlotsContext(); | ||
const render = (plotClient?: IPositronPlotClient) => { |
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.
Does plotClient
need to be optional here?
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'll fix it!
`${Schemas.positronPlotsEditor}:**/**`, | ||
{ | ||
id: PositronPlotsEditorInput.EditorID, | ||
label: localize('positronPlotsEditor', 'Plots Editor'), |
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 think something along the line of "Editor plot tab" or something is clearer. We're not editing plots. It's unfortunately verbose though.
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.
Good to clarify the name. It's not that much longer so it should look fine wherever it might show up in the UI.
src/vs/workbench/contrib/positronPlotsEditor/browser/positronPlotsEditor.tsx
Show resolved
Hide resolved
<PositronPlotsContextProvider | ||
commandService={this._commandService} | ||
configurationService={this._configurationService} | ||
contextKeyService={this._contextKeyService} | ||
contextMenuService={this._contextMenuService} | ||
hoverService={this._hoverService} | ||
keybindingService={this._keybindingService} | ||
languageRuntimeService={this._languageRuntimeService} | ||
positronPlotsService={this._positronPlotsService} | ||
notificationService={this._notificationService} | ||
> |
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 think we should create some sort of one-service-context-provider-to-rule-them-all to avoid every area of the react code having their own bespoke one. Not for this PR but just as a comment so I remember later!
render = (plotClient?: IPositronPlotClient) => { | ||
if (plotClient instanceof PlotClientInstance) { | ||
return <DynamicPlotInstance | ||
key={plotClient.id} | ||
width={this._width} | ||
height={this._height} | ||
plotClient={plotClient} | ||
zoom={ZoomLevel.Fill} />; | ||
} | ||
if (plotClient instanceof StaticPlotClient) { | ||
return <StaticPlotInstance | ||
key={plotClient.id} | ||
plotClient={plotClient} | ||
zoom={ZoomLevel.OneHundred} />; | ||
} | ||
|
||
return null; | ||
}; | ||
|
||
renderPlot = (plotClient: IPositronPlotClient) => { | ||
if (plotClient instanceof PlotClientInstance) { | ||
const dynamicPlot = plotClient as PlotClientInstance; | ||
dynamicPlot.render(this._height, this._width, 1); | ||
} | ||
}; |
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.
Looks like some left-over code here.
|
||
constructor( | ||
readonly resource: URI, | ||
// @IPositronPlotsService private readonly _positronPlotsService: IPositronPlotsService |
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 there a reason this is commented out and not deleted?
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.
Just more leftover code!
5e8ba94
to
5640edd
Compare
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.
Because this approach uses only one comm, we need to avoid disposing/closing the comm until all instances of the plot in the UI have been closed.
As it stands, cleaning up the plot from the Plots pane causes it to stop working in the editor tab; attempting to resize the editor tab results in a progress bar drawn right in the middle of the plot that never dismisses since the backend can't render the plot any more.
I also observed a fair bit of cross-talk between the plot in the editor and the plot in the Plots pane -- sometimes rendering it in one place caused both places to show a progress bar and/or the resized image to show up in both places. Might be expected at this stage?
Another issue is that you lose your editor plots on reload. You don't need to solve this right away, but we should have a plan to do it. See #3172 for context.
Makes sense to address the plot clients and the comm. I think it's good to use one comm so that any messages can update multiple plot clients. I did notice that the Runtimes view shows new runtime clients when opening a plot in an editor. But all those plot runtime clients are closed once the plot is closed. |
5640edd
to
0739250
Compare
Pushing a rebase on It looks like it's best to create a new comm backed by the same plot in the backend. It will require updating Ark as well but this should simplify the frontend. It turns out sharing a comm on the frontend makes it far more complicated with render queues, throttling, and managing the render events. |
If we move this complexity to the backend we will also need to implement for Python and all future language packs. I'd rather have the front end handle all of the complexity around events/throttling since it will keep the logic in one place and make things simpler at the API level. I'd imagine this could work by adding a service that can manage the plot comms, adding and removing UI instances of the plot (e.g. for the editor placement, the Plots pane, the Save dialog, etc.) and maintaining a single render queue but only delivering events/RPC results to the UI instance that owned the request. When the UI instance refcount drops to zero, we can dispose the comm. Willing to be convinced otherwise, lmk if you want to discuss realtime! |
710130b
to
9e63df7
Compare
Renders static and dynamic plots in the editor
Co-authored-by: Nick Strayer <nick.strayer@posit.co> Signed-off-by: Tim Mok <timtmok@users.noreply.github.com>
Opens another comm to a plot in the backend Implement Python plots in editor for the frontend
This reverts commit 5da4fa8.
16ca5c6
to
6104ec2
Compare
Fix rebase from main
This is now at a point where the The plots service tracks how many clients are still using the comm and will dispose the comm when no more clients are using it. It still uses a new plot client list for editors. I do think it's worth following up with another change to go back to one list by updating |
Use editor input dispose to determine the editor is finished with the plot client
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.
Looks good! I left some notes re: indentation but there are a bunch more places where the indentation got garbled (maybe an editor setting?).
I was able to verify that the newest set of changes address the lifecycle management issues; i.e. removing a plot from the Plots pane doesn't cause it to break in the Editor, and closing the Editor cleans up the comm correctly if it is the last remaining reference.
There are still some drawing issues; the progress bar doesn't seem to draw consistently in the editor pane, and when it does, it draws behind the plot.
There's also some crosstalk w/r/t the sizing policy; if you change the sizing policy in the Plots pane, it redraws the plot in the Editor pane too, which feels a little jarring. No need to try to address that now; we will eventually just want the editor plot tab to have its own toolbar where these things can be controlled/surfaced.
@@ -209,6 +118,9 @@ export class PlotClientInstance extends Disposable implements IPositronPlotClien | |||
onDidRenderUpdate: Event<IRenderedPlot>; | |||
private readonly _renderUpdateEmitter = new Emitter<IRenderedPlot>(); | |||
|
|||
/** | |||
* Event that fires when the plot wants to display itself. |
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.
Nit: indentation
|
||
export class PositronPlotCommProxy extends Disposable { | ||
/** | ||
* The currently active render request, if any. |
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.
Nit: indentation
private _currentRender?: DeferredRender; | ||
|
||
/** | ||
* The underlying comm |
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.
Nit: indentation
I noticed the progress misplacement as well. I opened #4562 to address it later. |
I expect #4358 to address the sizing policy. It should allow different sizing polices for the editors. Now that the plot client has the comm messages separated, it will be easier for each client to have its own render parameters. |
I noticed that the comm events weren't hooked up in the comm proxy. It was preventing the plot client from disposing when the comm closed. The editor won't close when the comm closes. I think a follow up can address this. It can do the same thing as the data explorer and show a message that the plot is gone. |
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 awesome and I'm excited to have this!
I like the new shape of things and everything works as expected.
Just had a few nits and general comments about react code organization/patterns that can be addressed now or later (or ignored if they're off base!)
Main stopper is the feature flag default, but should be good to go after that!
src/vs/workbench/contrib/positronPlots/browser/components/actionBars.tsx
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlots/browser/components/actionBars.tsx
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlots/browser/components/actionBars.tsx
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlots/browser/positronPlotsService.ts
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlotsEditor/browser/positronPlotsEditor.contribution.ts
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlotsEditor/browser/positronPlotsEditor.contribution.ts
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlotsEditor/browser/positronPlotsEditor.tsx
Outdated
Show resolved
Hide resolved
src/vs/workbench/services/languageRuntime/common/languageRuntimePlotClient.ts
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlotsEditor/browser/positronPlotsEditor.contribution.ts
Outdated
Show resolved
Hide resolved
Co-authored-by: Nick Strayer <nick.strayer@posit.co> Signed-off-by: Tim Mok <timtmok@users.noreply.github.com>
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.
Looking good -- this has shaped up real nicely!
onPressed={copyPlotHandler} />} | ||
{enableZoomPlot && <ZoomPlotMenuButton actionHandler={zoomPlotHandler} zoomLevel={props.zoomLevel} />} | ||
{enableSizingPolicy && | ||
{(enableSizingPolicy || enableSavingPlots || enableZoomPlot || enablePopoutPlot) ? <ActionBarSeparator /> : null} |
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.
super-nit: I would maybe extract the long or
logic here to a well-named variable to make it clearer why something is being rendered or not.
@@ -59,7 +59,7 @@ export interface ActionBarsProps { | |||
export const ActionBars = (props: PropsWithChildren<ActionBarsProps>) => { | |||
// Hooks. | |||
const positronPlotsContext = usePositronPlotsContext(); | |||
const [enableEditorPlot, setEnableEditorPlots] = React.useState<boolean>(false); | |||
const [useEditorPlots, setUseEditorPlots] = React.useState<boolean>(positronPlotsEditorEnabled(props.configurationService)); |
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.
Good call on the initial value to avoid a potentially unnecessary render.
Address #2270
This adds an action to display a plot from the Plots pane in an editor. It works on static and dynamic plots. It is an editor just for plots and hosts a React container to reuse the components from the Plots pane. So, it could easily display web plots.
A new editor and editor input has been created. The plot id is used for the editor title. It's not very descriptive and perhaps should be replaced with something that makes sense for a user. The plots service now tracks another list of plots for editors. The original plot client is cloned as a new object. It uses the same plot id as the original plot. The service now has an
openEditor()
method that opens the current plot in an editor.The entire feature is hidden behind an experimental flag. It is in the Application > Experimental category in the preferences. It is off by default until the rest of the editor actions are implemented. See the issue for linked follow-up work.
QA Notes
It's likely buggy with how the plot behaves with being moved into a new window and splitting the editor. It shares the sizing policy with the Plots pane so changing it there will re-render the editor in the new size.