Monaco Editor - Vitor-Xavier/Antlr4Exemplo GitHub Wiki

Monaco Editor

Instalação

Os pacotes necessários para a utilização do Moanco Editor em sua versão ESM são monaco-editor e monaco-editor-webpack-plugin, este último somente no projeto que contém o arquivo de configuração webpack.

Monaco Editor

yarn add monaco-editor

Monaco Editor Webpack Plugin

yarn add monaco-editor-webpack-plugin

Configuração

No arquivo webpack do projeto requerer o pacote referente a configuração de webpack do Monaco Editor, na configuração de módulos pode ser necessário indicar que o uso de loaders específicos para os estilos e fontes do Monaco Editor, e assim pode ser necessário excluir o caminho do Monaco Editor de demais loaders instalados, e por fim a definição do plugin webpack do Monaco, podendo definir apenas um conjunto de linguagens a ser importado.

const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
(...)
module.exports = {
    (...)
    module = {
        rules: {
            // Demais loaders do projeto, excluíndo o caminho do Monaco Editor.
            {
                test: /\.(scss|css)$/,
                exclude: ['node_modules/monaco-editor'],
                use: [
                    {
                        loader: 'css-loader?-url',
                    },
                ],
            },
            // Loaders de estilo específicos para o Monaco Editor.
            {
                test: /\.css$/,
                include: ['node_modules/monaco-editor')],
                use: ['style-loader', 'css-loader'],
            },
            // Loaders de fonte específicos para o Monaco Editor.
            {
                test: /\.ttf$/,
                include: ['node_modules/monaco-editor')],
                use: ['file-loader'],
            },
        }
    },
    (...)
    // Definindo plugin do Monaco Editor para somente um conjunto de linguagens.
    plugins: [
        new MonacoWebpackPlugin({
          languages: ['language'],
        }),
    ],
};

Utilização

import * as monaco from 'monaco-editor';

componentDidMount() {
    this._editor = monaco.editor.create(this._node, {
      theme: 'vs-dark',
      language: 'csharp',
      readOnly: false,
      value: "int a = 0;",
      contextmenu: true,
    });
}

render() {
    return (
        <div ref={c => (this._node = c)} style={{ height: '600px', width: '100%', border: '1px solid #ccc' }} />
    );
}

Extendendo um Tema

O Monaco Editor permite que novos temas também possam ser definidos, e então utilizados pelo Editor.

const customTheme = {
  base: 'vs',
  inherit: true,
  rules: [
    { token: 'keyword', foreground: '03fc1c' },
    { token: 'support.constant', foreground: 'fc0303' },
    { token: 'support.function', foreground: '4876d6' },
    { token: 'number', foreground: '34ebde' },
    { token: 'comment', foreground: '34eb93' },
    { token: 'string', foreground: '34eb93' },
  ],
};

monaco.editor.defineTheme('customTheme', customTheme);

Definindo Linguagem Customizada

Na definição de uma linguagem são definidos os delimitadores, tipos de dados, palavras-chave, operadores, símbolos, funções, comentários, espaços em branco etc, que são identificados através da lista de textos fornecida, ou de expressões regulares.

const languageDef = {
  wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
  defaultToken: '',
  brackets: [
    { open: '{', close: '}', token: 'delimiter.curly' },
    { open: '[', close: ']', token: 'delimiter.square' },
    { open: '(', close: ')', token: 'delimiter.parenthesis' },
  ],
  parenFollows: ['se', 'enquanto', 'parametro'],
  number: /\d+(\.\d+)?/,
  globalEntity: /\@[a-zA-Z0-9_.]+(\[[a-zA-Z0-9]+\])?[.]?[a-zA-Z0-9]+/,
  keywords: [
    'var',
    'enquanto',
    'senao',
    'se',
    'parar',
    'parametro',
    'caso',
    'padrao',
    'retorno',
    'verdadeiro',
    'falso',
  ],
  typeKeywords: ['lista', 'nulo'],
  operators: [
    '<',
    '>',
    '<=',
    '>=',
    '==',
    '!=',
    '+',
    '-',
    '*',
    '/',
    '^',
    '&&',
    '||',
    '!',
    '=',
    '+=',
    '-=',
    '*=',
    '/=',
    '^=',
  ],
  symbols: /[=><!~?:&|+\-*\/\^%]+/,
  function: /_[A-Za-z0-9_]+(?=\((.*?)\))/,
  tokenizer: {
    root: [
      [
        /[a-z$][\w$]*/,
        {
          cases: {
            '@typeKeywords': 'keyword',
            '@keywords': 'keyword',
            '@default': 'identifier',
          },
        },
      ],
      { include: '@comment' },
      { include: '@whitespace' },
      { include: '@numbers' },
      { include: '@functions' },
      { include: '@globalEntities' },
      [/[;,.]/, 'delimiter'],
      [/[{}()\[\]]/, '@brackets'],
      [/"/, { token: 'string.quote', next: '@string' }],
    ],
    globalEntities: [/@globalEntity/, 'support.constant'](/Vitor-Xavier/Antlr4Exemplo/wiki//@globalEntity/,-'support.constant'),
    whitespace: [/[\n\t\r\n]+/, 'white'](/Vitor-Xavier/Antlr4Exemplo/wiki//[\n\t\r\n]+/,-'white'),
    numbers: [/@number/, 'number'](/Vitor-Xavier/Antlr4Exemplo/wiki//@number/,-'number'),
    functions: [/@function/, 'support.function'](/Vitor-Xavier/Antlr4Exemplo/wiki//@function/,-'support.function'),
    comment: [/[\/][\/]+[\w\s]+/, 'comment'](/Vitor-Xavier/Antlr4Exemplo/wiki//[\/][\/]+[\w\s]+/,-'comment'),
    string: [
      [/[^\\"]+/, 'string'],
      [/\\./, 'string.escape.invalid'],
      [/"/, { token: 'string.quote', next: '@pop' }],
    ],
  },
};

monaco.languages.setMonarchTokensProvider('language', languageDef);

Hover

O hover representa uma informação adicional para um símbolo ou palavra, e são renderizados no estilo de tooltip.

O provedor de hover do Monaco Editor, é registrado por linguagem, e é chamado ao se passar o cursor do mouse em cima de cada trecho do texto, e então dentro do método provideHover possui um retorno, a caixa de texto do hover é exibida para a posição informada no range e com o texto de contents.

No exemplo abaixo, é definido um provedor de hover para a linguagem 'language', que verifica se a posição em que mouse parou seja uma palavra, e que esta seja encontrada na lista de funções functions, a descrição da função é retornada para ser exibida pelo hover.

const functions = [
  {
    name: "_DATA",
    description: "Cria uma nova data com base nos parâmetros informados."
  }
];
monaco.languages.registerHoverProvider("language", {
  provideHover: function(model, position) {
    const word = model.getWordAtPosition(position);
    if (word) {
      const fn = functions.find(item => item.name === word.word);
      if (fn) {
        return {
          range: new monaco.Range(
            position.lineNumber,
            word.startColumn,
            position.lineNumber,
            word.endColumn
          ),
          contents: [
            { value: `(função) ${fn.name}` },
            { value: fn.description }
          ]
        };
      }
    }
  }
});

Sugestões de Código

O provedor de sugestões disponibilizado pelo Monaco Editor é específico por linguagem, e possui como principal função provideCompletionItems, que deve retornar as sugestões conforme formato especificado pelo Monaco Editor, contendo label, o nome utilizado para verificar a compatibilidade com o texto sendo digitado, kind, que se refere ao tipo de sugestão, insertText, para prover o texto que será inserido, insertTextRules , que permite configurar como o texto será inserido, detail que prove detalhes adicionais sobre a sugestão, documentation que detalha a documentação da sugestão.

No exemplo abaixo, primeiramente são definidas sugestões de texto comum, e então os fragmentos de código, snippets, para a linguagem 'language', que podem ser ativados através do comando 'Ctrl+Espaço', e conforme o texto é digitado as opções correspondentes vão sendo filtradas.

monaco.languages.registerCompletionItemProvider("language", {
  provideCompletionItems: (model, position) => {
    const suggestions = [
      {
        label: "identificador",
        kind: monaco.languages.CompletionItemKind.Text,
        insertText: "${0:identificador}",
        insertTextRules:
          monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
      },
      {
        label: "texto",
        kind: monaco.languages.CompletionItemKind.Text,
        insertText: '"$0"',
        insertTextRules:
          monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
      },
      // Snippets
      {
        label: "var",
        kind: monaco.languages.CompletionItemKind.Snippet,
        insertText: "var ${1:identificador} = ${0};",
        insertTextRules:
          monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
        detail: "Declaração de variável",
        documentation: "var identificador = valor;"
      },
      {
        label: "se",
        kind: monaco.languages.CompletionItemKind.Snippet,
        insertText: ["se (${1:condicao}) {", "\t${2}", "}"].join("\n"),
        insertTextRules:
          monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
        detail: "Instrução Se",
        documentation: ["se (condicao) {", "\t", "}"].join("\n")
      },
      {
        label: "sesenao",
        kind: monaco.languages.CompletionItemKind.Snippet,
        insertText: [
          "se (${1:condicao}) {",
          "\t${2}",
          "} senao {",
          "\t${3}",
          "}"
        ].join("\n"),
        insertTextRules:
          monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
        detail: "Instrução Se-Senao",
        documentation: ["se (condicao) {", "\t", "} senao {", "\t", "}"].join("\n")
      }
    ];
    return { suggestions: suggestions };
  }
});