August 18th, 2022

Integrating VSCode editor into H2O Wave

RSS icon RSS Category: Tutorials, Wave

Current state

  • No syntax highlighting.
  • No auto-completion.
  • Regular font instead of monospaced which tends to be more readable.
  • No find/replace functionality and other editor goodies.
Existing textbox — not suited for code blocks.

General Approach

  1. Python part — regular Wave app.
  2. Javascript part — Monaco editor configuration.

The Javascript part

  • Save event — when the user hits CMD or CTRL + S, used for updating our left-hand side markdown card.
  • Change event — when the user types something, used to mark the editor card as dirty using *.
const baseUrl = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs'
// UNIMPORTANT: RequireJS paths config for monaco editor.
require.config({
  paths: {
    'vs': baseUrl,
    'vs/language': `${baseUrl}/language`,
  }
})
// UNIMPORTANT: Web worker registration for monaco editor.
window.MonacoEnvironment = {
  getWorkerUrl: function (workerId, label) {
    return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
      importScripts('${baseUrl}/base/worker/workerMain.js');`
    )}`
  }
}
// Load the editor javascript.
require(['vs/editor/editor.main'], async () => {
  // Render the editor into our existing div with ID "editor".
  const editor = monaco.editor.create(document.getElementById('editor'), {
    language: 'python',
  })
  // Use cmd (mac) or ctrl + S to save content.
  editor.addAction({
    id: 'save-content',
    label: 'Save',
    keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
    // Emit q.events.editor.save event on CMD+S.
    run: editor => window.wave.emit('editor', 'save', editor.getValue())
  })
  // When the editor text changes, emit q.events.editor.change event
  // to mark dirty state.
  editor.onDidChangeModelContent(() => window.wave.emit('editor', 'change', true))
})
app.js

The Python part

from h2o_wave import main, app, Q, ui


# UNIMPORTANT: Helper function to read the file.
def read_file(p: str) -> str:
    with open(p, encoding='utf-8') as f:
        return f.read()


@app('/')
async def serve(q: Q):
    if not q.client.initialized:
        q.page['editor'] = ui.markup_card(
            box='1 1 6 10',
            title='Editor',
            # Render a container div and expand it to occupy whole card.
            content='''
<div id="editor" style="position:absolute;top:45px;bottom:15px;right:15px;left:15px"/>
'''
        )
        q.page['text'] = ui.markdown_card(box='7 1 5 10', title='Text', content='')
        # Make sure to render the div prior to loading Javascript.
        await q.page.save()

        # Download the necessary javascript and render the Monaco editor.
        q.page['meta'] = ui.meta_card(
            box='',
            # Download external JS loader script.
            scripts=[ui.script('''
https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs/loader.min.js
''')],
            script=ui.inline_script(
              # Read our JS file and pass it as a string.
              content=read_file('app.js'),
              # Only run this script if "require" object is present
              # in browser's window object.
              requires=['require'],
              # Only run this script if our div container
              # with id "monaco-editor" is rendered.
              targets=['editor']
            ),
        ) 
        q.client.initialized = True

    await q.page.save()
app.py
if q.events.editor:
    if q.events.editor.save:
        q.page['text'].content = q.events.editor.save
        q.page['editor'].title = 'Editor'
    elif q.events.editor.change:
        # Show "dirty" state by appending an * to editor card title.
        q.page['editor'].title = '*Editor'
Event handling.

The result

Wave app with Monaco editor.
from h2o_wave import main, app, Q, ui


# UNIMPORTANT: Helper function to read the file.
def read_file(p: str) -> str:
    with open(p, encoding='utf-8') as f:
        return f.read()


@app('/')
async def serve(q: Q):
    if not q.client.initialized:
        q.page['editor'] = ui.markup_card(
            box='1 1 6 10',
            title='Editor',
            # Render a container div and expand it to occupy whole card.
            content='''
<div id="editor" style="position:absolute;top:45px;bottom:15px;right:15px;left:15px"/>
'''
        )
        q.page['text'] = ui.markdown_card(box='7 1 5 10', title='Text', content='')
        # Make sure to render the div prior to loading Javascript.
        await q.page.save()

        # Download the necessary javascript and render the actual Monaco editor.
        q.page['meta'] = ui.meta_card(
            box='',
            # Download external JS loader script.
            scripts=[ui.script('''
https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.33.0/min/vs/loader.min.js
''')],
            script=ui.inline_script(
              # Read our JS file and pass it as a string.
              content=read_file('app.js'),
              # Only run this script if "require" object is present
              # in browser's window object.
              requires=['require'],
              # Only run this script if our div container
              # with id "monaco-editor" is rendered.
              targets=['editor']
            ),
        ) 
        q.client.initialized = True 
  
    if q.events.editor:
        if q.events.editor.save:
            q.page['text'].content = q.events.editor.save
            q.page['editor'].title = 'Editor'
        elif q.events.editor.change:
            # Show "dirty" state by appending an * to editor card title.
            q.page['editor'].title = '*Editor'

    await q.page.save()

About the Author

Martin Turoci

Software engineer that hates repetitive tasks, praises automation, clean code and "right tool for the job" rule. Also a big javascript ecosystem fan.
In my free time, I teach ballroom dancing at both competitive and social level (as a former ballroom dancer).

Leave a Reply

+
10 Consejos para Convertirte en un Científico de Datos Exitoso

En este mundo que no deja de cambiar y sorprendernos, como científicos de datos debemos

January 19, 2023 - by Favio Vázquez
+
Explaining models built in H2O-3 — Part 1

Machine Learning explainability refers to understanding and interpreting the decisions and predictions made by a

December 22, 2022 - by Parul Pandey
+
H2O.ai at NeurIPS 2022

H2O.ai is proud to participate in the 36th Conference on Neural Information Processing Systems (NeurIPS)

December 6, 2022 - by Marcos V. Conde
+
A Brief Overview of AI Governance for Responsible Machine Learning Systems

Our paper “A Brief Overview of AI Governance for Responsible Machine Learning Systems” was recently

November 30, 2022 - by Navdeep Gill, Abhishek Mathur and Marcos V. Conde
+
H2O World Dallas Customer Talks

After three long years of not having an #H2OWorld, we finally held our first one

November 24, 2022 - by Vinod Iyengar
+
New in Wave 0.24.0

Another Wave release has arrived with quite a few exciting new features. Let's quickly go

November 21, 2022 - by Martin Turoci

Request a Demo

Explore how to Make, Operate and Innovate with the H2O AI Cloud today

Learn More