swift-lynx
swift-lynx

Reputation: 3765

How do I add javascript autocomplete to markdown mode of monaco editor?

I want to be able to use the javascript-autocomplete of the monaco editor inside of markdown documents as well, because markdown documents can contain code snippets of type javascript:

```javascript
window.blah
```

I know that I could register a custom CompletionItemProvider for markdown, but I want to use the javascript one already provided by monaco. I have not found a way to receive their provider however.

Javascript syntax highlighting already works in markdown code blocks.

My idea was to somehow get their provider using something like this:

(await monaco.languages.typescript.getJavaScriptWorker()).something

For that to work I'd have to register javascript on the editor however. Even if I do that, I don't seem to be able to find anything on their worker that could help me.

While there's a method to register a CompletionItemProvider (https://microsoft.github.io/monaco-editor/api/modules/monaco.languages.html#registercompletionitemprovider), I couldn't find any that could help me get a registered provider.

How can I use the javascript-autocomplete in markdown code blocks as well?

Upvotes: 1

Views: 1285

Answers (1)

Mike Lischke
Mike Lischke

Reputation: 53367

You are on the right track. Here's how I use typescript/javascript worker APIs to provide their completions in a mixed language environment:

                return new Promise((resolve) => {
                    const workerPromise = (context.language === "javascript")
                        ? languages.typescript.getJavaScriptWorker()
                        : languages.typescript.getTypeScriptWorker();
                    void workerPromise.then((worker: (...uris: Uri[]) => Promise<TypescriptWorker>) => {
                        void worker(model.uri).then((service) => {
                            const offset = model.getOffsetAt(localPosition);
                            const promise = service.getCompletionsAtPosition(model.uri.toString(), offset);

                            void promise.then((completions: CompletionInfo | undefined) => {
                                if (completions) {
                                    const info = model.getWordUntilPosition(localPosition);
                                    const replaceRange: IRange = {
                                        startLineNumber: position.lineNumber,
                                        startColumn: info.startColumn,
                                        endLineNumber: position.lineNumber,
                                        endColumn: info.endColumn,
                                    };

                                    resolve({
                                        incomplete: false,
                                        suggestions: completions.entries.map((entry) => ({
                                            label: entry.name,
                                            kind: this.convertKind(entry.kind),
                                            range: replaceRange,
                                            insertText: entry.insertText || entry.name,
                                        } as CompletionItem)),
                                    });
                                } else {
                                    resolve({ incomplete: false, suggestions: [] });
                                }
                            });
                        });
                    });
                });

Important here is that you create submodels with only the content of the parts in a single language (see Monaco.createModel()) and use that to invoke the completion items code.

In my app I split the content of an editor into blocks (each covering only full lines and which have a specific language attached to them). So they have a start line and an end line. With this information I can create sub models for each block:

    /**
     * @returns A local model which contains only the text of this block. The caller must dispose of it!
     */
    public get model(): Monaco.ITextModel {
        const editorModel = super.model; // Model for the entire editor content.

        if (!this.internalModel || this.internalModel.isDisposed()) {
            const localModel = Monaco.createModel("", this.language);
            // Do other required preparations of the local model, if needed.
            this.internalModel = localModel;
        }

        if (editorModel && this.modelNeedsUpdate) {
            this.modelNeedsUpdate = false;
            this.internalModel.setValue(editorModel.getValueInRange(
                {
                    startLineNumber: this.startLine,
                    startColumn: 1,
                    endLineNumber: this.endLine,
                    endColumn: editorModel.getLineMaxColumn(this.endLine),
                }, Monaco.EndOfLinePreference.LF),
            );
        }

        return this.internalModel;
    }

Upvotes: 2

Related Questions