Ace Editor

Hello :wave:

Here’s a new component showcase, featuring the Ace editor, and more specifically its react wrapper.

The instruction to install and test it can be found in my repository:


The initial version of this component featured a more generic implementation which forwarded python arguments to the react-ace component, and returned values on chosen events. It wasn’t a very clean way to handle things, and more a way to experiment with the new component API. I decided to keep this version as recommended by @andfanilo :slight_smile:

Source code: Ace.tsx
import React, { useEffect } from "react"
import AceEditor from "react-ace"
import {
  ComponentProps,
  Streamlit,
  withStreamlitConnection,
} from "./streamlit"

import "ace-builds/webpack-resolver"

interface AceProps extends ComponentProps {
  args: {
    events: string[],
    props: any,
  }
}

function Ace({ args, width }: AceProps) {
  args.props.width = "100%"

  args.events.forEach(event => {
    args.props[event] = (...data: any) => {
      Streamlit.setComponentValue({
        name: event,
        data: data
      })
    }
  })
   
  useEffect(() => {
    Streamlit.setFrameHeight(args.props?.height)
  })

  return <AceEditor {...args.props} />
}

export default withStreamlitConnection(Ace)
Source code: __init__.py
import os
import streamlit as st
from collections import namedtuple

_RELEASE = False

if not _RELEASE:
    _ace = st.declare_component("ace", url="http://localhost:3002")
else:
    parent_dir = os.path.dirname(os.path.abspath(__file__))
    build_dir = os.path.join(parent_dir, "frontend/build")
    _ace = st.declare_component("ace", path=build_dir)


_AceEvent = namedtuple("_AceEvent", ["name", "data"])

def ace(events=[], key=None, **props):
    """Create a new instance of Ace editor."""
    event = _ace(props=props, events=events, key=key) or {}
    return _AceEvent(event.get("name", None), event.get("data", None))


if not _RELEASE:
    st.sidebar.title("Ace editor")
    event = ace(key="ace-editor",
        mode=st.sidebar.selectbox("Language mode.", options=[
            "abap", "abc", "actionscript", "ada", "alda", "apache_conf", "apex", "applescript", "aql", 
            "asciidoc", "asl", "assembly_x86", "autohotkey", "batchfile", "c9search", "c_cpp", "cirru", 
            "clojure", "cobol", "coffee", "coldfusion", "crystal", "csharp", "csound_document", "csound_orchestra", 
            "csound_score", "csp", "css", "curly", "d", "dart", "diff", "django", "dockerfile", "dot", "drools", 
            "edifact", "eiffel", "ejs", "elixir", "elm", "erlang", "forth", "fortran", "fsharp", "fsl", "ftl", 
            "gcode", "gherkin", "gitignore", "glsl", "gobstones", "golang", "graphqlschema", "groovy", "haml", 
            "handlebars", "haskell", "haskell_cabal", "haxe", "hjson", "html", "html_elixir", "html_ruby", "ini", 
            "io", "jack", "jade", "java", "javascript", "json", "json5", "jsoniq", "jsp", "jssm", "jsx", "julia", 
            "kotlin", "latex", "less", "liquid", "lisp", "livescript", "logiql", "logtalk", "lsl", "lua", "luapage", 
            "lucene", "makefile", "markdown", "mask", "matlab", "maze", "mediawiki", "mel", "mixal", "mushcode", 
            "mysql", "nginx", "nim", "nix", "nsis", "nunjucks", "objectivec", "ocaml", "pascal", "perl", "perl6", 
            "pgsql", "php", "php_laravel_blade", "pig", "plain_text", "powershell", "praat", "prisma", "prolog", 
            "properties", "protobuf", "puppet", "python", "qml", "r", "razor", "rdoc", "red", "redshift", "rhtml", 
            "rst", "ruby", "rust", "sass", "scad", "scala", "scheme", "scss", "sh", "sjs", "slim", "smarty", 
            "snippets", "soy_template", "space", "sparql", "sql", "sqlserver", "stylus", "svg", "swift", "tcl", 
            "terraform", "tex", "text", "textile", "toml", "tsx", "turtle", "twig", "typescript", "vala", "vbscript", 
            "velocity", "verilog", "vhdl", "visualforce", "wollok", "xml", "xquery", "yaml"
        ]),
        theme=st.sidebar.selectbox("Theme.", options=[
            "ambiance", "chaos", "chrome", "clouds", "clouds_midnight", "cobalt", "crimson_editor", "dawn",
            "dracula", "dreamweaver", "eclipse", "github", "gob", "gruvbox", "idle_fingers", "iplastic",
            "katzenmilch", "kr_theme", "kuroir", "merbivore", "merbivore_soft", "mono_industrial", "monokai",
            "nord_dark", "pastel_on_dark", "solarized_dark", "solarized_light", "sqlserver", "terminal",
            "textmate", "tomorrow", "tomorrow_night", "tomorrow_night_blue", "tomorrow_night_bright",
            "tomorrow_night_eighties", "twilight", "vibrant_ink", "xcode"
        ]),
        keyboardHandler=st.sidebar.selectbox("Keybinding mode.", options=[
            "emacs", "sublime", "vim", "vscode"
        ]),
        events=st.sidebar.multiselect("Events to listen.", options=[
            "onChange", "onCopy", "onPaste", "onSelectionChange", "onBlur", "onInput", "onScroll", "onValidate"
        ]),
        showGutter=st.sidebar.checkbox("Show gutter.", value=True),
        showPrintMargin=st.sidebar.checkbox("Show print margin.", value=True),
        highlightActiveLine=st.sidebar.checkbox("Highlight active line.", value=True),
        wrapEnabled=st.sidebar.checkbox("Wrap enabled.", value=False),
        readOnly=st.sidebar.checkbox("Read-only.", value=False),
        setOptions={
            "scrollPastEnd": True,
            "displayIndentGuides": False,
        }
    )

    st.write(event)

2 Likes

Nice !

While I understand your feeling when you say “just pass everything from Python to React component” is not a clean way of running, I think it’s a good setup for people who just want to play around with a React component inside Streamlit, are not too familiar with React and don’t think about sharing it to everyone, so I’d keep the code floating around here :slight_smile:

2 Likes

Am I understanding the purpose of this correctly in that you can pass code from the Streamlit app to the backend? So in theory if you wanted to make a Python IDE (or any other language it supports), you could?

Ahah, if you get the Python part of the component to spawn and manage a Jupyter kernel corresponding to the language of the editor, you could even emulate a Jupyter-Streamlit like environment by passing the code from an ACE editor back to Python Streamlit, transmit it to the kernel and then return the computed results in Streamlit frontend.

That would make a Jupyter Streamlit (maybe Polynote would be the better comparison though, as a polyglot language notebook) where each cell is an ACE editor :laughing:

I wouldn’t have said better!

I wanted to show this component in action as I saw Panel providing it as well. It also demonstrates that anyone can easily integrate any React component in Streamlit with not much code. In fact, I consider myself as a beginner with React, I’ve never really used it before now :smiley:

1 Like

Enable a new breed of non-notebook web clients to provision and use kernels

:eyes: :smirk:

I’m with you on this :slight_smile: it’s like giving frontend superpowers to any Python Data scientists, and I really hope we are able to convey this message to the community that “writing custom components is, in the Streamlit spirit, really easy !”

1 Like

Hey,

I’ve released a first version of the Ace editor here. You can install it from the wheel provided in the repository’s release, and I uploaded a demo file in the examples folder.

2 Likes

Awesome, glad we’re all moving into the packaged release phase!

Two minor things…it looks like your README points to the old repo name https://github.com/Ghasel/streamlit-component-ace. It would also make it more obvious what this was by having a picture or animated GIF (before you started working on it, I didn’t know what ACE editor was)

Thanks again for all the effort you are putting into building components!

Best,
Randy

3 Likes