Menci / monaco-tree-sitter

Highlight your Monaco Editor with tree-sitter grammar.
MIT License
35 stars 7 forks source link

Monaco tree-sitter

Highlight your Monaco Editor with tree-sitter grammar.

Build Status Dependencies Commitizen friendly code style: prettier License

Install

Install via NPM:

$ yarn add monaco-tree-sitter

Use

You can use it with Webpack. You'll need monaco-editor-webpack-plugin to load Monaco Editor and file-loader to load WASM.

Notice that the Webpack support of web-tree-sitter is broken, see tree-sitter/tree-sitter#559 for detail and demo for a workaround.

The minimal Webpack config required is like:

module: {
  rules: [
    // This is required for web-tree-sitter
    {
      test: /\.wasm$/,
      loader: "file-loader",
      type: "javascript/auto" // Disable Webpack's built-in WASM loader
    },
    // These two are required for monaco-editor-webpack-plugin
    // See https://github.com/microsoft/monaco-editor-webpack-plugin
    {
      test: /\.css$/,
      use: ["style-loader", "css-loader"]
    },
    {
      test: /\.ttf$/,
      use: ["file-loader"]
    }
  ]
},
plugins: [
  new MonacoWebpackPlugin()
],
node: {
  fs: "empty" // See https://github.com/tree-sitter/tree-sitter/issues/466
}

After setting-up Webpack, you can starting using it in your project.

First, you need a theme, a theme file is like tomorrow.json in the themes directory, containing theme for both monaco-editor and our highlighter based on tree-sitter. Load it with:

import { Theme } from "monaco-tree-sitter";

Theme.load(require("monaco-tree-sitter/themes/tomorrow"));

You also need to initialize web-tree-sitter, the bind library for tree-sitter:

import Parser = require("web-tree-sitter");
import { Theme } from "monaco-tree-sitter";

Theme.load(require("monaco-tree-sitter/themes/tomorrow"));

(async () => {
  await Parser.init().then(/* initialized */);
})();

To parse a language with tree-sitter, you need the language's parser. A full list of supported languages by tree-sitter is available here. There're also official prebuilt WASM binaries here can be downloaded.

Tree-sitter could only give us the AST of the code. To highlight we need some grammar rules (one rule is like: an identifier in an call expression is a function name). You can find the grammar rules for various languages in the grammars directory.

import Parser = require("web-tree-sitter");
import { Theme, Language } from "monaco-tree-sitter";
import treeSitterCpp from "./tree-sitter-cpp.wasm"; // Path to the language parser library WASM file

Theme.load(require("monaco-tree-sitter/themes/tomorrow"));

(async () => {
  await Parser.init();

  // Load the language's grammar rules
  const language = new Language(require("monaco-tree-sitter/grammars/cpp"));
  // Load the language's parser library's WASM binary
  await language.init(treeSitterCpp, Parser);
})();

Since this module provides highlighting both for Monaco Editor and without Monaco Editor. For better Webpack code splitting, we don't import monaco-editor module and you should provide the module you imported to us.

Create your Monaco Editor and apply the highlight on it:

import Monaco = require("monaco-editor");
import Parser = require("web-tree-sitter");
import { Theme, Language } from "monaco-tree-sitter";
import treeSitterCpp from "./tree-sitter-cpp.wasm"; // Path to the language parser library WASM file

Theme.load(require("monaco-tree-sitter/themes/tomorrow"));

(async () => {
  await Parser.init();

  // Load the language's grammar rules
  const language = new Language(require("monaco-tree-sitter/grammars/cpp"));
  // Load the language's parser library's WASM binary
  await language.init(treeSitterCpp, Parser);

  window.editor = Monaco.editor.create(document.body, {
    value: "int main() { return 0; }",
    // This "language" property only affects the monaco-editor's built-in syntax highlighter
    language: "cpp"
  });  

  const monacoTreeSitter = new MonacoTreeSitter(Monaco, editor, language);

  // You can change the language with monacoTreeSitter.changeLanguage()
  // Or change the theme with Theme.load()
  // Remember to refresh highlight with monacoTreeSitter.refresh() after changing the theme.
})();

Highlight

You can also use this module just as a code highlighter. In this case you don't need a Monaco Editor.

First, load a theme and initialize your language as above. Then just call the highlight() function:

import Parser = require("web-tree-sitter");
import { Theme, Language } from "monaco-tree-sitter";
import treeSitterCpp from "./tree-sitter-cpp.wasm"; // Path to the language parser library WASM file

Theme.load(require("monaco-tree-sitter/themes/tomorrow"));

(async () => {
  await Parser.init();

  // Load the language's grammar rules
  const language = new Language(require("monaco-tree-sitter/grammars/cpp"));
  // Load the language's parser library's WASM binary
  await language.init(treeSitterCpp, Parser);

  document.body.innerHTML = highlight(cppCode, language);

  // You can use highlight(code, language, true) to generate self-contained HTML code.
  // i.e. Use inline style instead of class name.
})();

Demo

You can find a demo in the demo directory. Just yarn build and python3 -m http.server then open it in your browser.

License

This project is licensed under the MIT license.

Credits

This project used code and assets from: