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 };
}
});