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

+
H2O Wave joins Hacktoberfest

It’s that time of the year again. A great initiative by DigitalOcean called Hacktoberfest that aims to bring

September 29, 2022 - by Martin Turoci
+
Three Keys to Ethical Artificial Intelligence in Your Organization

There’s certainly been no shortage of examples of AI gone bad over the past few

September 23, 2022 - by H2O.ai Team
+
Using GraphQL, HTTPX, and asyncio in H2O Wave

Today, I would like to cover the most basic use case for H2O Wave, which is

September 21, 2022 - by Martin Turoci
+
머신러닝 자동화 솔루션 H2O Driveless AI를 이용한 뇌에서의 성차 예측

Predicting Gender Differences in the Brain Using Machine Learning Automation Solution H2O Driverless AI 아동기 뇌인지

August 29, 2022 - by H2O.ai Team
+
Make with H2O.ai Recap: Validation Scheme Best Practices

Data Scientist and Kaggle Grandmaster, Dmitry Gordeev, presented at the Make with H2O.ai session on

August 23, 2022 - by Blair Averett
+
5 Tips for Improving Your Wave Apps

Let’s quickly uncover a few simple tips that are quick to implement and have a

August 9, 2022 - by Martin Turoci

Start Your Free Trial