-
Notifications
You must be signed in to change notification settings - Fork 18
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
🤩 Inline expressions #92
Conversation
cbfc114
to
6215bbd
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.
Annotated review of what is going on!
src/MySTMarkdownCell.tsx
Outdated
super(options); | ||
|
||
this.__rendermime = options.rendermime.clone(); | ||
// this.__rendermime.addFactory(textRendererFactory); |
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.
We should add or improve the inline rendering.
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.
@agoose77, looking more at the text renderer -- I think we might actually just want to do this in react even?
I think we are going to have to have a bunch of custom renderers, that are specific to the inline execution, but that can probably all come later!
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.
yes, so before defering to the standard rendermime renderers, take a different rendering route based on mimetype
and skip the defaults altogether, for text etc...
For other things like widgets, we can maybe look at redefining/overriding the styles in CSS? we'd have to figure out how that plays with the style
attributes on widgets themselves. https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Styling.html#The-style-attribute
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 rendering plain-text in MyST might be reasonable. There's a big question over MyST integration in JLab as to how much we take over e.g. Markdown rendering currently goes through the default marked implementation.
Perhaps we want to recognise a particular flavour of Markdown, so that users can opt-in to displaying rich text via outputs. Also, I wonder what myst-nb does here — how do text/markdown
outputs get rendered (as MyST?)
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 downside of this is now other JupyterLab extensions can't override e.g. text/plain
rendering, but it's not necessarily a problem; we just need to define what should be the behaviour here. My take is that we special-case text/plain
, errors, LaTeX, and Markdown (maybe only if it's MyST markdown), and the rest goes through the rendermime interface.
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, agree with all of that! Will pick it up in a future PR!
|
||
export class MySTMarkdownCell | ||
extends MarkdownCell | ||
implements IMySTMarkdownCell | ||
{ | ||
private _doneRendering = new PromiseDelegate<void>(); |
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.
There is now a promise for when the initial mdast parse is complete for the cell, then this can trigger the inline execution.
const expressions = selectAll('inlineExpression', mdast); | ||
return expressions.map(node => (node as any).value); |
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.
Gather all of the expressions to be evaluated. This is just the body of the role.
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.
So much nicer.
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.
Any thoughts on what this should be called on the spec side for the actual node? We could also have it code with an executable flag on it?
That is closer to what JATS does.
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.
Assuming that we're referring to inlineExpression
, I'd probably prefer e.g. expr
or expression
?
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 guess there is some precedence in the spec at the moment.
https://myst-tools.org/docs/spec/commonmark#inline-code
I am inclined to leave it as is in this PR, and revisit when we start to standardize this in the spec?! My thinking is that we may want a difference in inline vs block-level expressions. e.g. an interactive figure inline maybe has a different renderer than the block level renderer. That could mean different mimebundles in the future.
These are all ephemeral at the moment, so it shouldn't matter if we want to change it!
src/MySTMarkdownCell.tsx
Outdated
<CellMetadataProvider | ||
metadata={metadata} | ||
trusted={this.model.trusted} | ||
rendermime={this.__rendermime} | ||
> |
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.
We add a cell metadata provider to give access to these values in the inline renderers.
const content: KernelMessage.IExecuteRequestMsg['content'] = { | ||
code: '', | ||
user_expressions: userExpressions | ||
}; |
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.
user_expressions are a Jupyter thing, that map is provided and executed.
const executor: JupyterFrontEndPlugin<void> = { | ||
id: 'jupyterlab-myst:executor', |
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.
We add another plugin to allow markdown cells to be executed.
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.
so 2 plugins are installed now? and the both work independently?
i.e. the other plugin takes care of Markdown cell rendering whilst this one takes care of execution post render? or are we also rendering here when there is an inline expression?
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 single JupyterLab extension can install multiple plugins. Here we provide the "execute inline expressions and store results" using this :executor
plugin. The renderering is still exclusively performed by the MySTMarkdownCell
work.
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.
It does mean that you can turn parts of it off at a time, which is quite nice!
} | ||
} | ||
|
||
export function InlineRenderer({ value }: { value?: string }): JSX.Element { |
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 react component gets attached in the inlineExpression
for mdast. We have to be careful here to create the rendermime one time, and then update it when the expression result changes. There is also ways to dispose of the renderer when this component gets removed.
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.
@stevejpurves you might have comments here from your experience working with react and thebe?!
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 ok especially if it works. I have had issues with render passes previously before a ref was resolved and solved that by using "callback refs" to only attach to the dom once the ref was available. I think that is more of an issue in components with conditional rendering -- which this one has i.e. when there is no expressionResult
but if this pattern works and we reliably get the output attached to the dom, then no need to change it.
const evalRole: RoleSpec = { | ||
name: 'eval', | ||
body: { | ||
type: ParseTypesEnum.string, | ||
required: true | ||
}, | ||
run(data: RoleData): GenericNode[] { | ||
const value = data.body as string; | ||
return [{ type: 'inlineExpression', value }]; | ||
} | ||
}; |
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 the role spec!
@@ -125,4 +147,6 @@ export function parseContent(notebook: StaticNotebook): void { | |||
cell.myst.post = mdast.children[index]; | |||
cell.mystRender(); | |||
}); | |||
|
|||
return Promise.all(promises).then(() => 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.
This content now waits on the render, and then returns so that execution can happen. We must parse the cells BEFORE we execute.
inlineExpression: (node, children) => { | ||
return <InlineRenderer value={node.value} />; | ||
} |
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 the new myst renderer for the node.
This comment was marked as outdated.
This comment was marked as outdated.
src/MySTMarkdownCell.tsx
Outdated
|
||
this.__rendermime = options.rendermime.clone(); | ||
// Note we cannot clone this, and it must be the parents (the notebooks) | ||
this.__rendermime = parent.rendermime; |
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 didn't get this far in my reading, nice spot.
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 am not totally sure why this happens, I feel like the clone should work!?
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 break anything on my local test build, but I don't think we should be cloning in any case. I think I probably did that in jupyterlab-imarkdown
in the very early work and never noticed it. We want to use the same rendermime registry as passed in:
this.__rendermime = parent.rendermime; | |
this.__rendermime = options.rendermime; |
const expressions = selectAll('inlineExpression', mdast); | ||
return expressions.map(node => (node as any).value); |
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.
So much nicer.
Co-authored-by: Angus Hollands <goosey15@gmail.com>
Binder is now working well!! |
src/inlineExpression.tsx
Outdated
|
||
export function InlineRenderer({ value }: { value?: string }): JSX.Element { | ||
const ref = useRef<HTMLDivElement>(null); | ||
const { metadata, trusted, rendermime } = useCellMetadata(); |
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 find the hook name confusing, as it makes me think e are reading the rendermime from metadata when its an runtime object coming from the notebook/tracker
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 have changed this to a cellProvider, which ... provides the cell! :)
import { NodeRenderer } from '@myst-theme/providers'; | ||
import { InlineRenderer } from './inlineExpression'; | ||
|
||
export const renderers: Record<string, NodeRenderer> = { |
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.
there are a lot of renderers flying around! these are mystRenderers specifically here though it seems
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.
Yeah, I think there is a wider clean up for the whole repo as we start to get a bit more complicated roles and parsing structure in this repo.
I am inclined to kick this down the road though!
Ok, @agoose77 and @stevejpurves. I think this PR is ready to come in! We will have another wuick PR on styles later today, and then aim to release as 1.1.0? or maybe 1.0.2? Feels more minor than patch? |
Minor, agreed! |
In it goes!! |
This PR add the ability to have inline expressions, and is largely based off work by @agoose77 in jupyterlab-imarkdown, see #72.
The bulk of this is working, but we should add the
textRendererFactory
to improve inline styles. The mime_bundle is stored in options and can be re-rendered without the kernel, which is important for other applications like MyST Websites.The inline expressions work, can be both with simple evaluations, inline widgets, or spark-lines using matplotlib.