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

+
Enhancing H2O Model Validation App with h2oGPT Integration

As machine learning practitioners, we’re always on the lookout for innovative ways to streamline and

May 17, 2023 - by Parul Pandey
+
Building a Manufacturing Product Defect Classification Model and Application using H2O Hydrogen Torch, H2O MLOps, and H2O Wave

Primary Authors: Nishaanthini Gnanavel and Genevieve Richards Effective product quality control is of utmost importance in

May 15, 2023 - by Shivam Bansal
AI for Good hackathon
+
Insights from AI for Good Hackathon: Using Machine Learning to Tackle Pollution

At H2O.ai, we believe technology can be a force for good, and we're committed to

May 10, 2023 - by Parul Pandey and Shivam Bansal
H2O democratizing LLMs
+
Democratization of LLMs

Every organization needs to own its GPT as simply as we need to own our

May 8, 2023 - by Sri Ambati
h2oGPT blog header
+
Building the World’s Best Open-Source Large Language Model: H2O.ai’s Journey

At H2O.ai, we pride ourselves on developing world-class Machine Learning, Deep Learning, and AI platforms.

May 3, 2023 - by Arno Candel
LLM blog header
+
Effortless Fine-Tuning of Large Language Models with Open-Source H2O LLM Studio

While the pace at which Large Language Models (LLMs) have been driving breakthroughs is remarkable,

May 1, 2023 - by Parul Pandey

Request a Demo

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

Learn More