Open5

monaco-editor 周りの調査

mizchimizchi

Monaco Editor 周りの資料が日本語にないので、見つけ次第追加していく

mizchimizchi

vscode と monaco の highlighter は別実装。vscode は textmate 仕様の tslanguage format を使っている。
この正規表現は JS ではなく oniguruma 仕様で、wasm build を使ってコンパイルする必要がある。

https://github.com/NeekSandhu/onigasm

(vscode web ビルドでは onigasm を使っている)

Code Highlighter が monaco に対応していなくて、 tmlanguage しかないとき、monaco-textmate を使って変換する必要がある。

https://github.com/NeekSandhu/monaco-textmate

https://github.com/NeekSandhu/monaco-editor-textmate

magiql という graphql preview の実装を参考にして svelte の tmlanguage を動かそうとしたが、失敗した。後でもう一度やってみる。

https://github.com/nksaraf/magiql-browser/blob/main/src/MonacoProvider.tsx

https://github.com/sveltejs/language-tools/blob/master/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml

mizchimizchi

monaco-languageclient の使い方

https://github.com/TypeFox/monaco-languageclient

サーバーを建てずにブラウザで完結して monaco で補完したい。JS製の LSP Server をローカルで動かす monaco-languageclient を使いたい。そのために調べたこと。

基本的には https://github.com/TypeFox/monaco-languageclient/tree/master/examples/browser を見ればよい

概念整理

  • LSP: Language Server Protocol - エディタで補完を行うためのプロトコル。基本的にはコードとカーソル位置を送って、補完候補をもらう。
  • Language Service: サーバー側の実装。 ↑ のサンプルでは、 vscode-json-languageservice を使っている
  • Language Client: クライアントが LSP を食う規約
  • LSP Protocol: Language Service と Language Client が通信するためのプロトコル

概要

monaco のモデルを LSP 用のモデルに変換する。
一つのLSP変換関数があるわけではなく、language service を叩いて得られた結果をmonaco用に変換する

import {
  MonacoToProtocolConverter,
  ProtocolToMonacoConverter,
} from "monaco-languageclient/lib/monaco-converter";

const m2p = new MonacoToProtocolConverter();
const p2m = new ProtocolToMonacoConverter();

この変換プロトコルを使って jsonservice の doComplete を直接叩いて、それを monaco 用の provideCompletionItems に変換するコード。

monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, {
    provideCompletionItems(model, position, context, token): monaco.Thenable<monaco.languages.CompletionList> {
        const document = createDocument(model);
        const wordUntil = model.getWordUntilPosition(position);
        const defaultRange = new monaco.Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn);
        const jsonDocument = jsonService.parseJSONDocument(document);
        return jsonService.doComplete(document, m2p.asPosition(position.lineNumber, position.column), jsonDocument).then((list) => {
            return p2m.asCompletionResult(list, defaultRange);
        });
    },

    resolveCompletionItem(item, token): monaco.languages.CompletionItem | monaco.Thenable<monaco.languages.CompletionItem> {
        return jsonService.doResolve(m2p.asCompletionItem(item)).then(result => p2m.asCompletionItem(result, item.range));
    }
});

(非同期表現に独自の monaco.Thenable を使ってて、それが Promise と非互換なので async/await が使えない…)

LSPプロトコルを全部一括で変換するアダプタがあるわけではなく、機能ごとに個別に実装していくっぽい。