diff --git a/docs/_quartodoc.yml b/docs/_quartodoc.yml index 967abf63c..921a453f9 100644 --- a/docs/_quartodoc.yml +++ b/docs/_quartodoc.yml @@ -158,6 +158,7 @@ quartodoc: - ui.output_table - ui.output_data_frame - ui.output_text + - ui.output_code - ui.output_text_verbatim - ui.output_ui - render.plot diff --git a/shiny/render/__init__.py b/shiny/render/__init__.py index ce47f4f64..a1876c33f 100644 --- a/shiny/render/__init__.py +++ b/shiny/render/__init__.py @@ -15,6 +15,7 @@ display, ) from ._render import ( + code, image, plot, table, @@ -28,6 +29,7 @@ "data_frame", "display", "text", + "code", "plot", "image", "table", diff --git a/shiny/render/_render.py b/shiny/render/_render.py index 5d03a2a6d..30f978402 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -9,6 +9,7 @@ # Can use `dict` in python >= 3.9 from typing import ( TYPE_CHECKING, + Any, Callable, Literal, Optional, @@ -46,6 +47,7 @@ __all__ = ( "text", + "code", "plot", "image", "table", @@ -61,6 +63,17 @@ class text(Renderer[str]): """ Reactively render text. + When used in Shiny Express applications, this defaults to displaying the text as + normal text on the web page. When used in Shiny Core applications, this should be + paired with :func:`~shiny.ui.output_text` in the UI. + + + Parameters + ---------- + inline + Used in Shiny Express only. If ``True``, the result is displayed inline. (This + argument is passed to :func:`~shiny.ui.output_text`.) + Returns ------- : @@ -74,13 +87,91 @@ class text(Renderer[str]): See Also -------- + ~shiny.render.code ~shiny.ui.output_text """ - def default_ui(self, id: str, placeholder: bool | MISSING_TYPE = MISSING) -> Tag: + def default_ui( + self, + id: str, + *, + inline: bool | MISSING_TYPE = MISSING, + ) -> Tag: + kwargs: dict[str, Any] = {} + set_kwargs_value(kwargs, "inline", inline, self.inline) + + return _ui.output_text(id, **kwargs) + + def __init__( + self, + _fn: Optional[ValueFn[str]] = None, + *, + inline: bool = False, + ) -> None: + super().__init__(_fn) + self.inline = inline + + async def transform(self, value: str) -> Jsonifiable: + return str(value) + + +# ====================================================================================== +# RenderCode +# ====================================================================================== + + +class code(Renderer[str]): + """ + Reactively render text as code (monospaced). + + When used in Shiny Express applications, this defaults to displaying the text in a + monospace font in a code block. When used in Shiny Core applications, this should be + paired with :func:`~shiny.ui.output_code` in the UI. + + Parameters + ---------- + placeholder + Used in Shiny Express only. If the output is empty or ``None``, should an empty + rectangle be displayed to serve as a placeholder? This does not affect behavior + when the output is nonempty. (This argument is passed to + :func:`~shiny.ui.output_code`.) + + + Returns + ------- + : + A decorator for a function that returns a string. + + Tip + ---- + The name of the decorated function (or ``@output(id=...)``) should match the ``id`` + of a :func:`~shiny.ui.output_code` container (see :func:`~shiny.ui.output_code` for + example usage). + + See Also + -------- + ~shiny.render.code + ~shiny.ui.output_code + """ + + def default_ui( + self, + id: str, + *, + placeholder: bool | MISSING_TYPE = MISSING, + ) -> Tag: kwargs: dict[str, bool] = {} - set_kwargs_value(kwargs, "placeholder", placeholder, None) - return _ui.output_text_verbatim(id, **kwargs) + set_kwargs_value(kwargs, "placeholder", placeholder, self.placeholder) + return _ui.output_code(id, **kwargs) + + def __init__( + self, + _fn: Optional[ValueFn[str]] = None, + *, + placeholder: bool = True, + ) -> None: + super().__init__(_fn) + self.placeholder = placeholder async def transform(self, value: str) -> Jsonifiable: return str(value) diff --git a/shiny/ui/__init__.py b/shiny/ui/__init__.py index 49a725aab..70cc4108d 100644 --- a/shiny/ui/__init__.py +++ b/shiny/ui/__init__.py @@ -110,6 +110,7 @@ output_plot, output_image, output_text, + output_code, output_text_verbatim, output_table, output_ui, @@ -296,6 +297,7 @@ "output_plot", "output_image", "output_text", + "output_code", "output_text_verbatim", "output_table", "output_ui", diff --git a/shiny/ui/_output.py b/shiny/ui/_output.py index 7accac6a1..aab99a35a 100644 --- a/shiny/ui/_output.py +++ b/shiny/ui/_output.py @@ -4,6 +4,7 @@ "output_plot", "output_image", "output_text", + "output_code", "output_text_verbatim", "output_table", "output_ui", @@ -270,6 +271,47 @@ def output_text( return container(id=resolve_id(id), class_="shiny-text-output") +def output_code(id: str, placeholder: bool = True) -> Tag: + """ + Create a output container for code (monospaced text). + + This is similar to :func:`~shiny.ui.output_text`, except that it displays the text + in a fixed-width container with a gray-ish background color and border. + + Parameters + ---------- + id + An output id. + placeholder + If the output is empty or ``None``, should an empty rectangle be displayed to + serve as a placeholder? (This does not affect behavior when the output is + nonempty.) + + Returns + ------- + : + A UI element + + Note + ---- + This function is currently the same as :func:`~shiny.ui.output_text_verbatim`, but + this may change in future versions of Shiny. + + See Also + -------- + * :func:`~shiny.render.text` + * :func:`~shiny.ui.output_text` + * :func:`~shiny.ui.output_text_verbatim` + + Example + ------- + See :func:`~shiny.ui.output_text` + """ + + cls = "shiny-text-output" + (" noplaceholder" if not placeholder else "") + return tags.pre(id=resolve_id(id), class_=cls) + + def output_text_verbatim(id: str, placeholder: bool = False) -> Tag: """ Create a output container for some text. diff --git a/tests/pytest/test_display_decorator.py b/tests/pytest/test_display_decorator.py index bfdb51b7e..aa8f330dc 100644 --- a/tests/pytest/test_display_decorator.py +++ b/tests/pytest/test_display_decorator.py @@ -165,7 +165,7 @@ def annotated(x: int, y: int) -> int: def test_implicit_output(): @display_body() def has_implicit_outputs(): - @render.text + @render.code def foo(): return "hello" @@ -173,7 +173,7 @@ def foo(): has_implicit_outputs() assert len(d) == 1 d0 = cast(Tagifiable, d[0]) - assert d0.tagify() == ui.output_text_verbatim("foo") + assert d0.tagify() == ui.output_code("foo") def test_no_nested_transform_unless_explicit(): diff --git a/tests/pytest/test_express_ui.py b/tests/pytest/test_express_ui.py index 2f5a7cc0d..4f97c9d5e 100644 --- a/tests/pytest/test_express_ui.py +++ b/tests/pytest/test_express_ui.py @@ -50,7 +50,7 @@ def text1(): assert ( ui.TagList(text1.tagify()).get_html_string() - == ui.output_text_verbatim("text1").get_html_string() + == ui.output_text("text1").get_html_string() ) @suspend_display @@ -61,22 +61,22 @@ def text2(): assert ui.TagList(text2.tagify()).get_html_string() == "" @ui_kwargs(placeholder=True) - @render.text - def text3(): + @render.code + def code1(): return "text" assert ( - ui.TagList(text3.tagify()).get_html_string() - == ui.output_text_verbatim("text3", placeholder=True).get_html_string() + ui.TagList(code1.tagify()).get_html_string() + == ui.output_code("code1", placeholder=True).get_html_string() ) @ui_kwargs(width=100) - @render.text - def text4(): + @render.code + def code2(): return "text" with pytest.raises(TypeError, match="width"): - text4.tagify() + code2.tagify() def test_suspend_display():