Ace Editor

Hello :wave:

Here’s a new component showcase, featuring 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)
4 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:

3 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/okld/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

4 Likes

I was trying to implement this component, but for some reason, even when running the demo script, the content string is empty. The st.write(content) call in the demo doesn’t write anything and calling len on the content returns 0.

UPDATE: I just discovered something really strange. When using chrome, content strings appears to populate and things behave normally but while using Safari, the content string remains empty. While I have noticed small differences in the Streamlit behaviors between chrome and safari, this if the first instance of something being completely broken.

Any chance we could get a flag built in to set the editor’s max line property to the JavaScript Infinite so that the editor resizes to fit the contents?

This is amazing @okld, thanks so much for building and sharing! I have one question which I have seen discussed in various Ace-related places (e.g. here, here and here).

I also want to use this component for SQL inputs, however the convention we always adhere to for readability is to have keywords (e.g. SELECT, FROM, WHERE) in upper case, not lower case. I know it’s a style thing, and there are conflicting schools of thought here which will never agree… but as such is there any way to configure this component to auto-complete keywords to upper case instead of lower? Happy to fork or contribute, I’m just new to this editor and I’m more of a Python/SQL guy than Javascript, so would really appreciate some advice on the best way forward. Thanks in advance!

1 Like

Hi community!
I was wondering if it’s possible to add/change the content of the auto-complete feature?
Thanks for your help!
Pierre

1 Like