From 343c93d65ac6a5bbfdc886a93fd35685ab205c6a Mon Sep 17 00:00:00 2001 From: Ilya Muradyan Date: Tue, 6 Apr 2021 18:03:53 +0300 Subject: [PATCH] Allow value rendering using renderers processor from API --- .../jetbrains/kotlinx/jupyter/api/Notebook.kt | 6 +++++ .../jupyter/api/TypeRenderersProcessor.kt | 21 ++++++++++++++++++ .../api/libraries/JupyterIntegration.kt | 8 +++++-- .../codegen/ResultsTypeRenderersProcessor.kt | 22 +++++++++++++++++++ .../jupyter/codegen/TypeRenderersProcessor.kt | 13 ----------- .../org/jetbrains/kotlinx/jupyter/apiImpl.kt | 5 +++++ .../codegen/TypeRenderersProcessorImpl.kt | 16 ++++++++++++-- .../org/jetbrains/kotlinx/jupyter/repl.kt | 12 ++++++---- .../jupyter/repl/impl/SharedReplContext.kt | 4 ++-- .../jupyter/test/repl/IntegrationApiTests.kt | 19 ++++++++++++++++ .../kotlinx/jupyter/test/testUtil.kt | 6 ++++- 11 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/TypeRenderersProcessor.kt create mode 100644 jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/ResultsTypeRenderersProcessor.kt delete mode 100644 jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/TypeRenderersProcessor.kt diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/Notebook.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/Notebook.kt index dbcb3d80c..3568e676a 100644 --- a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/Notebook.kt +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/Notebook.kt @@ -63,4 +63,10 @@ interface Notebook { * Current JRE info */ val jreInfo: JREInfoProvider + + /** + * Renderers processor gives an ability to render values and + * and add new renderers + */ + val renderersProcessor: TypeRenderersProcessor } diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/TypeRenderersProcessor.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/TypeRenderersProcessor.kt new file mode 100644 index 000000000..d82d7bc97 --- /dev/null +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/TypeRenderersProcessor.kt @@ -0,0 +1,21 @@ +package org.jetbrains.kotlinx.jupyter.api + +import org.jetbrains.kotlinx.jupyter.api.libraries.ExecutionHost + +/** + * [TypeRenderersProcessor] is responsible for rendering objects. + * You may use it to render values exactly like notebook renders results, + * and also register new renderers in runtime. + */ +interface TypeRenderersProcessor { + /** + * Renders [value] in context of this execution [host] + */ + fun renderValue(host: ExecutionHost, value: Any?): Any? + + /** + * Adds new [renderer] for this notebook. + * Don't turn on the optimizations for [PrecompiledRendererTypeHandler] + */ + fun registerWithoutOptimizing(renderer: RendererTypeHandler) +} diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt index 7dfa2f147..f8f886394 100644 --- a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/JupyterIntegration.kt @@ -71,10 +71,14 @@ abstract class JupyterIntegration : LibraryDefinitionProducer { } inline fun render(noinline renderer: CodeCell.(T) -> Any) { - val execution = ResultHandlerExecution { _, property -> + return renderWithHost { _, value: T -> renderer(this, value) } + } + + inline fun renderWithHost(noinline renderer: CodeCell.(ExecutionHost, T) -> Any) { + val execution = ResultHandlerExecution { host, property -> val currentCell = notebook.currentCell ?: throw IllegalStateException("Current cell should not be null on renderer invocation") - FieldValue(renderer(currentCell, property.value as T), property.name) + FieldValue(renderer(currentCell, host, property.value as T), property.name) } addRenderer(SubtypeRendererTypeHandler(T::class, execution)) } diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/ResultsTypeRenderersProcessor.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/ResultsTypeRenderersProcessor.kt new file mode 100644 index 000000000..367110892 --- /dev/null +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/ResultsTypeRenderersProcessor.kt @@ -0,0 +1,22 @@ +package org.jetbrains.kotlinx.jupyter.codegen + +import org.jetbrains.kotlinx.jupyter.api.Code +import org.jetbrains.kotlinx.jupyter.api.FieldValue +import org.jetbrains.kotlinx.jupyter.api.PrecompiledRendererTypeHandler +import org.jetbrains.kotlinx.jupyter.api.RendererTypeHandler +import org.jetbrains.kotlinx.jupyter.api.TypeRenderersProcessor +import org.jetbrains.kotlinx.jupyter.api.libraries.ExecutionHost + +interface ResultsTypeRenderersProcessor : TypeRenderersProcessor { + /** + * Renders cell result [field] represented as [FieldValue] in the [host] context + */ + fun renderResult(host: ExecutionHost, field: FieldValue): Any? + + /** + * Adds new [renderer] for this notebook. + * Returns code to be executed on execution host + * for [PrecompiledRendererTypeHandler]'s. + */ + fun register(renderer: RendererTypeHandler): Code? +} diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/TypeRenderersProcessor.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/TypeRenderersProcessor.kt deleted file mode 100644 index 116c8a85b..000000000 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/TypeRenderersProcessor.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.jetbrains.kotlinx.jupyter.codegen - -import org.jetbrains.kotlinx.jupyter.api.Code -import org.jetbrains.kotlinx.jupyter.api.FieldValue -import org.jetbrains.kotlinx.jupyter.api.RendererTypeHandler -import org.jetbrains.kotlinx.jupyter.api.libraries.ExecutionHost - -interface TypeRenderersProcessor { - - fun renderResult(host: ExecutionHost, field: FieldValue): Any? - - fun register(renderer: RendererTypeHandler): Code? -} diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/apiImpl.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/apiImpl.kt index b3c4232a4..a157d146d 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/apiImpl.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/apiImpl.kt @@ -8,6 +8,7 @@ import org.jetbrains.kotlinx.jupyter.api.DisplayResultWithCell import org.jetbrains.kotlinx.jupyter.api.JREInfoProvider import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion import org.jetbrains.kotlinx.jupyter.api.Notebook +import org.jetbrains.kotlinx.jupyter.api.TypeRenderersProcessor import java.lang.IllegalStateException class DisplayResultWrapper private constructor( @@ -98,6 +99,7 @@ class NotebookImpl( private val runtimeProperties: ReplRuntimeProperties, ) : Notebook { private val cells = hashMapOf() + internal var typeRenderersProcessor: TypeRenderersProcessor? = null override val cellsList: Collection get() = cells.values @@ -156,4 +158,7 @@ class NotebookImpl( override val lastCell: CodeCellImpl? get() = history(1) + + override val renderersProcessor: TypeRenderersProcessor + get() = typeRenderersProcessor ?: throw IllegalStateException("Type renderers processor is not initialized yet") } diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/TypeRenderersProcessorImpl.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/TypeRenderersProcessorImpl.kt index b030b60d1..a91bd5606 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/TypeRenderersProcessorImpl.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/codegen/TypeRenderersProcessorImpl.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlinx.jupyter.repl.ContextUpdater class TypeRenderersProcessorImpl( private val contextUpdater: ContextUpdater, -) : TypeRenderersProcessor { +) : ResultsTypeRenderersProcessor { private var counter = 0 private val typeRenderers: MutableList = mutableListOf() @@ -35,8 +35,20 @@ class TypeRenderersProcessorImpl( } } + override fun renderValue(host: ExecutionHost, value: Any?): Any? { + return renderResult(host, FieldValue(value, null)) + } + override fun register(renderer: RendererTypeHandler): Code? { - if (renderer !is PrecompiledRendererTypeHandler || !renderer.mayBePrecompiled) { + return register(renderer, true) + } + + override fun registerWithoutOptimizing(renderer: RendererTypeHandler) { + register(renderer, false) + } + + private fun register(renderer: RendererTypeHandler, doOptimization: Boolean): Code? { + if (!doOptimization || renderer !is PrecompiledRendererTypeHandler || !renderer.mayBePrecompiled) { typeRenderers.add(HandlerWithInfo(renderer, null)) return null } diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt index 5effb8062..35f341d5e 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl.kt @@ -17,7 +17,7 @@ import org.jetbrains.kotlinx.jupyter.codegen.FieldsProcessor import org.jetbrains.kotlinx.jupyter.codegen.FieldsProcessorImpl import org.jetbrains.kotlinx.jupyter.codegen.FileAnnotationsProcessor import org.jetbrains.kotlinx.jupyter.codegen.FileAnnotationsProcessorImpl -import org.jetbrains.kotlinx.jupyter.codegen.TypeRenderersProcessor +import org.jetbrains.kotlinx.jupyter.codegen.ResultsTypeRenderersProcessor import org.jetbrains.kotlinx.jupyter.codegen.TypeRenderersProcessorImpl import org.jetbrains.kotlinx.jupyter.common.looksLikeReplCommand import org.jetbrains.kotlinx.jupyter.compiler.CompilerArgsConfigurator @@ -307,7 +307,11 @@ class ReplForJupyterImpl( executedCodeLogging != ExecutedCodeLogging.Off ) - private val typeRenderersProcessor: TypeRenderersProcessor = TypeRenderersProcessorImpl(contextUpdater) + private val typeRenderersProcessor: ResultsTypeRenderersProcessor = run { + val processor = TypeRenderersProcessorImpl(contextUpdater) + notebook.typeRenderersProcessor = processor + processor + } private val fieldsProcessor: FieldsProcessor = FieldsProcessorImpl(contextUpdater) @@ -421,11 +425,11 @@ class ReplForJupyterImpl( currentClasspath.addAll(newClasspath) if (trackClasspath) { val sb = StringBuilder() - if (newClasspath.count() > 0) { + if (newClasspath.isNotEmpty()) { sb.appendLine("${newClasspath.count()} new paths were added to classpath:") newClasspath.sortedBy { it }.forEach { sb.appendLine(it) } } - if (oldClasspath.count() > 0) { + if (oldClasspath.isNotEmpty()) { sb.appendLine("${oldClasspath.count()} resolved paths were already in classpath:") oldClasspath.sortedBy { it }.forEach { sb.appendLine(it) } } diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt index af47831e8..e82d8f18f 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/SharedReplContext.kt @@ -6,7 +6,7 @@ import org.jetbrains.kotlinx.jupyter.api.Notebook import org.jetbrains.kotlinx.jupyter.codegen.ClassAnnotationsProcessor import org.jetbrains.kotlinx.jupyter.codegen.FieldsProcessor import org.jetbrains.kotlinx.jupyter.codegen.FileAnnotationsProcessor -import org.jetbrains.kotlinx.jupyter.codegen.TypeRenderersProcessor +import org.jetbrains.kotlinx.jupyter.codegen.ResultsTypeRenderersProcessor import org.jetbrains.kotlinx.jupyter.libraries.LibrariesScanner import org.jetbrains.kotlinx.jupyter.libraries.LibraryResourcesProcessor import org.jetbrains.kotlinx.jupyter.magics.MagicsProcessor @@ -16,7 +16,7 @@ internal data class SharedReplContext( val classAnnotationsProcessor: ClassAnnotationsProcessor, val fileAnnotationsProcessor: FileAnnotationsProcessor, val fieldsProcessor: FieldsProcessor, - val typeRenderersProcessor: TypeRenderersProcessor, + val typeRenderersProcessor: ResultsTypeRenderersProcessor, val magicsProcessor: MagicsProcessor, val resourcesProcessor: LibraryResourcesProcessor, val librariesScanner: LibrariesScanner, diff --git a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt index f4ae8e416..53b1f9686 100644 --- a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt +++ b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/IntegrationApiTests.kt @@ -122,4 +122,23 @@ class IntegrationApiTests { assertEquals("1. 420", repl.eval("42.1").resultValue) assertEquals("2. 150", repl.eval("15").resultValue) } + + @Test + fun `rendering processor should work fine`() { + val repl = makeRepl() + repl.eval( + """ + class A + class B(val a: A) + + USE { + render { "iA" } + renderWithHost { host, value -> "iB: " + notebook!!.renderersProcessor.renderValue(host, value.a) } + } + """.trimIndent() + ) + + val result = repl.eval("B(A())") + assertEquals("iB: iA", result.resultValue) + } } diff --git a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/testUtil.kt b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/testUtil.kt index 7ae174a82..ae4c444ee 100644 --- a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/testUtil.kt +++ b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/testUtil.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlinx.jupyter.api.DisplayResultWithCell import org.jetbrains.kotlinx.jupyter.api.JREInfoProvider import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion import org.jetbrains.kotlinx.jupyter.api.Notebook +import org.jetbrains.kotlinx.jupyter.api.TypeRenderersProcessor import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinition import org.jetbrains.kotlinx.jupyter.config.defaultRepositories @@ -59,7 +60,7 @@ fun assertStartsWith(expectedPrefix: String, actual: String) { } fun Collection>.toLibraries(): LibraryResolver { - val libJsons = map { it.first to it.second }.toMap() + val libJsons = associate { it.first to it.second } return getResolverFromNamesMap(parseLibraryDescriptors(libJsons)) } @@ -163,6 +164,9 @@ object NotebookMock : Notebook { get() = defaultRuntimeProperties.version!! override val jreInfo: JREInfoProvider get() = JavaRuntime + + override val renderersProcessor: TypeRenderersProcessor + get() = error("Not supposed to be called") } fun library(builder: JupyterIntegration.Builder.() -> Unit): LibraryDefinition {