How to include Monaco Editor in div?

Hi,

I am creating a Theia extension, in this I have a view where I need to include Monaco editor as input in the form. Purpose is to provide end-user a complete editing experience backed by LSP instead of plain input textbox.

What I have done till now -
vscode-extension for language support is created , language client & server is getting initiated by this .
My Language support is working on standalone file editor.

What is required now is to be able to open the Monaco editor on the fly & using it as a input in the form.
Some of the use cases where we require this functionality in Theia - To edit label of nodes in diagram editor , inside newly introduced property view , form based editor etc.

What could be the best approach of achieving this ?

I found createInline method from Theia monaco Contribution -


Can this be used ?

Adding more details -

I have tried embedding it in my GLSP based editor on diagram nodes labels using below logic .Monaco editor gets embedded & also opening up on label editing , but editor features like content assist , error highlighting etc. are not working .

Code -

export class MonacoEditorWidget {
@inject(MonacoEditorProvider)
protected readonly editorProvider: MonacoEditorProvider;

//embedding code
protected createInputElement() {
const uri = new URI(“inmemory:/model.xtree”);
const options={
autoSizing: true,
minHeight: 1,
maxHeight: 10
};
this.editorProvider.createInline(uri,this.containerElement,options);
}

Please suggest what I am missing here ?
Please also suggest if there is any other better way of achieving it ?

It’s hard to tell what’s missing without the rest of your code, but you can check the Output implementation in Theia. The Output view is also wrapping a Monaco editor with its custom model and editor factory for it.

Hi @kittaakos,

This is the only code that I have written for the purpose of embedding the editor , rest of the code is related to GLSP & language contribution .
As in this case I do not have file but a in-memory string to edit therefore I am using the URI - "inmemory:/model.xtree” while creating monaco editor object. I was hoping that Theia language client will be able to understand this URI & connect the editor with LSP server, similar to monaco-languageclient which was being used in earlier version of Theia. But I guess some extra glue code is required to make it work.

Sure , I will check Output extension implementation but Output view is read only so it would be lacking completion , error highlighting support .
Are you suggesting me to create a custom implementation of editor model similar to debug & output extension as Theia language client is not able to understand the “inmemory” URI scheme ? Anything else that I would need to customize apart from this to enable LSP features on my embedded editor ?

Please let me know if my understanding is correct , thanks !!!

Does it already work (content assist, diagnostics, etc) in Theia? Can you open your model (with the inmemory:/model.xtree URI) in a regular editor in Theia? If so, I believe the only thing you need is a custom way to attach the editor to the desired DOM element so that it does not go through the EditorManager. Here is an example:

Yes , Content assist , diagnostics etc. is working on regular file based editor -

On embedded editor it does not work -


Here I am creating monaco editor inline on the fly when label edit event of the node is fired in the diagram editor.

inmemory:/model.xtree URI is not pointing to any actual resource , this is the dummy URI which I am using only for the purpose of creating the inline/embedded editor. I want embedded monaco editor to work on in-memory strings . I basically want a expression editor from which I can get & set the expression. In a simple terms , I am building a reusable widget which will look similar to TextArea but with LSP support to assist end-user in writing the expression .
Like any other form control (input,dropdown etc.) I will use this widget in my views of other extensions as well.

Hope this provides better understanding of my use-case.

Hi @kittaakos,

I have registered the inmemory scheme with Language Client -

const clientOptions: LanguageClientOptions = {

        documentSelector: [{ scheme: 'file', language: 'xtree'},{ scheme: 'inmemory', language: 'xtree'}],

    };

    languageClient = new LanguageClient ('xTreeLanguageClient', 'XTree Language Server', serverOptions, clientOptions);

Now I am able to get hit on “provideCompletionItems” method in “https://github.com/eclipse-theia/theia/blob/master/packages/plugin-ext/src/plugin/languages/completion.ts”. As no document is attached to my inmemory URI so it is getting returned from the method without generating any completions.

How can I get around this?

Posting the solution -

import { SModelRoot } from "@eclipse-glsp/client";
import { inject, injectable } from "inversify";
import { MonacoEditorProvider } from "@theia/monaco/lib/browser/monaco-editor-provider";
import URI from "@theia/core/lib/common/uri";
import { MambaWidget } from "@mamba/mamba-table-rule-sprotty/lib/direct-expression-editor/widget";
import { TextDocument } from "vscode-languageserver-textdocument";
import { LanguageClient } from "vscode-languageclient";
import { EditorWidget } from "@theia/editor/lib/browser";
import { SelectionService } from "@theia/core/lib/common/selection-service";
import { InMemoryResources } from "@theia/core/lib/common";
import { Widget, DockPanel } from "@theia/core/lib/browser";
import { MonacoEditor } from "@theia/monaco/lib/browser/monaco-editor";

@injectable()
export class MonacoEditorWidget implements MambaWidget {

  protected containerElement: HTMLElement;
  protected mainDiv: HTMLDivElement;
  @inject(MonacoEditorProvider)
  protected readonly editorProvider: MonacoEditorProvider;
  protected document: TextDocument;
  protected client: LanguageClient;
  protected notifyClose: () => any;
  @inject(SelectionService)
  protected readonly selectionService: SelectionService;
  @inject(InMemoryResources)
  protected readonly inMemoryResources: InMemoryResources;
  protected editorWidget: EditorWidget;
  protected editorContainer: DockPanel;

  initialize(containerElement: HTMLElement): void {
    this.containerElement = containerElement;
    this.createInputElement();
    this.containerElement.style.position = "absolute";
  }

  protected async createInputElement() {
    const uri = new URI("file:/d:/kuldeep/lab/theia-wksp/model.xtree");
    this.inMemoryResources.add(uri, "aaaaa");
    const editor = await this.editorProvider.get(uri);
    this.editorWidget = new EditorWidget(editor, this.selectionService);
    if (this.editor) {
      const model = this.editor.document;
      model.suppressOpenEditorWhenDirty = true;
    }
    this.editorContainer = new DockPanel({ spacing: 0, mode: "single-document" });
    this.editorContainer.addWidget(this.editorWidget);
    Widget.attach(this.editorContainer, this.containerElement);
  }

  private get editor(): MonacoEditor | undefined {
    const widget = this.editorWidget;
    if (widget instanceof EditorWidget) {
      if (widget.editor instanceof MonacoEditor) {
        return widget.editor;
      }
    }
    return undefined;
  }


  open(root: Readonly<SModelRoot>) {
    //  this.inputElement.focus();
  }

  registerOnClose(notifyClose: () => any) {
    this.notifyClose = notifyClose;
  }

}

Note - The above code is embedding the Monaco editor within GLSP based diagram editor, but the same concept can be applied at any place where we need to embed the Monaco editor in div.