IDE de uma pessoa normal ou por que escolhemos Mônaco

Nota do Editor


No último artigo, falamos sobre o lançamento do painel de controle do Voximplant, sem esquecer de mencionar o IDE atualizado. Hoje, dedicamos uma longa pausa a essa ferramenta - nosso colega Geloosa descreveu cuidadosamente o processo de escolha de uma tecnologia e a implementação com guias, estilos de preenchimento automático e personalizados. Sente-se de forma mais conveniente, deixe de lado o resto dos seus assuntos e vá para o ataque, onde as tripas de Mônaco estão esperando pelos curiosos - não escorregue, há muitas delas :) Aproveite a leitura.


Qual biblioteca escolher para o editor de código?


Npm produz mais de 400 resultados para o editor de código. Na maioria das vezes, eles são invólucros da interface do usuário de várias das bibliotecas mais populares criadas para uma estrutura ou projeto específico, plug-ins para as mesmas bibliotecas ou seus garfos com modificações próprias, além de não serem para editar o código no navegador, eles simplesmente entram na saída por palavras-chave. Então, felizmente, a escolha é muito mais restrita. Mais algumas bibliotecas - como o CodeFlask , leve, mas não muito funcional, projetada para pequenos trechos e exemplos interativos, mas não para um IDE da Web completo com a funcionalidade que estamos acostumados nos editores de desktop.

No final, temos três bibliotecas para escolher: Ace , CodeMirror e Monaco Editor . O primeiro deles, CodeMirror, foi uma iniciativa privada do berliner Marijn Haverbeke , que precisava de um editor de código de exercício em seu tutorial online, o Eloquent JavaScript . A primeira versão do editor foi lançada em 2007. Em 2010, a primeira versão do Ace foi apresentada no JSConf.eu no mesmo Berlim, que o Ajax.org desenvolveu para a sua nuvem IDE Cloud9 (na verdade, Ace significa Editor do Ajax.org Cloud9). Em 2016, o Cloud9 foi comprado pela Amazon e agora faz parte da AWS. O mais recente, o Monaco Editor, é um componente do VS Code e foi publicado pela Microsoft no final de 2015.

Cada editor tem suas próprias forças e fraquezas e é usado em mais de um grande projeto. Por exemplo, o CodeMirror é usado nas ferramentas de desenvolvedor do Chrome e Firefox, um IDE no Bitbucket, no RunKit em npm; Ace - na Codecademy, Khan Academy, MODX; Mônaco - no IDE do GitLab e no CodeSandbox. A seguir, é apresentado um gráfico de comparação que pode ajudá-lo a escolher a biblioteca mais adequada ao seu projeto.

Bibliotecas
AceCodeMirrorMônaco
DesenvolvedorIDE Cloud9 (Ajax.org),
agora parte do AmazonMozilla
Marijn haverbekeMicrosoft
Suporte do navegadorFirefox ^ 3.5
Chrome
Safari ^ 4.0
IE ^ 8.0
Opera ^ 11.5
Firefox ^ 3.0
Chrome
Safari ^ 5.2
IE ^ 8.0
Opera ^ 9.2
Firefox ^ 4.0
Chrome
Safari (v -?)
IE ^ 11.0
Opera ^ 15.0
Suporte de idioma
(destaque da sintaxe)
> 120> 100> 20
Número de caracteres em
versões mais recentes em
cndjs.com
366 608 (v1.4.3)394.269 (v5.44.0)2.064.949 (v0.16.2)
O peso das últimas versões,
gzip
2.147 KB1.411 KB10.898 KB
RenderizaçãoDomDomDOM e parcialmente <canvas>
(para rolagem e minimapa)
A documentação7 de 10: nenhuma pesquisa, nem sempre clara
que os métodos retornem, existem dúvidas
em plenitude e relevância
(nem todos os links funcionam no dock)
6 em 10: mesclado com o guia do usuário,
pesquise por Ctrl + F,
existem dúvidas sobre completude
9 de 10: linda, com pesquisa e
referência cruzada
-1 ponto por falta de explicação
para algumas bandeiras cuja aplicação
não é muito óbvio do nome
Demonstrações de início rápidoComo fazer - documentos de texto com exemplos de código,
separadamente, há demos com exemplos de código
(verdade, eles estão espalhados em páginas diferentes,
nem todo mundo trabalha e é pesquisado com mais facilidade pelo Google),
há uma demonstração em que você pode tocar em diferentes recursos,
mas propõe-se gerenciá-los por meio de controles da interface,
ou seja, ainda devemos procurar separadamente métodos
para conectá-los
Como fazer são realmente pobres
basicamente tudo está espalhado no github
e stackoverflow, mas há demonstrações de recursos com exemplos
código para sua implementação
Combinado no formato de um playground:
código com comentários e várias demonstrações, você pode
tente imediatamente avaliar
muitas possibilidades
Atividade comunitáriaMédiaAltaMédia
Atividade do desenvolvedorMédiaMédiaAlta

Não faz sentido comparar bibliotecas por tamanho, porque tudo depende do que e como conectar-se a um projeto específico: carregue o arquivo finalizado com uma das construções (que também variam) ou execute o pacote npm através de algum tipo de coletor. E o mais importante é quanto o editor é usado: se todos os estilos e temas estão carregados, quantos e quais complementos e plug-ins são usados. Por exemplo, no CodeMirror, a maioria das funcionalidades que funcionam no Mônaco e no Ace prontamente estão disponíveis apenas com complementos. A tabela mostra o número de caracteres nas versões recentes na CDN e o peso de seus arquivos compactados para uma idéia geral de quais pedidos estão envolvidos.

Todas as bibliotecas têm aproximadamente o mesmo conjunto de recursos básicos: formatação automática de código, linhas dobradas, recortar / copiar / colar, teclas de atalho, capacidade de adicionar novas sintaxes para realçar e ordenar, verificação de sintaxe (no CodeMirror apenas através de complementos, no Ace até agora apenas para JavaScript / CoffeeScript / CSS / XQuery), dicas de ferramenta e preenchimento automático (no CodeMirror - através de complementos), pesquisa avançada por código (no CodeMirror - através de complementos), métodos para implementar guias e modo de divisão, modo diferencial e uma ferramenta de mesclagem (no CodeMirror - com vantagens e desvantagens em uma janela ou em dois painéis através de um complemento, Ace - Lieb separado). Devido à sua idade, muitos complementos foram criados para o CodeMirror, mas seu número afetará o peso e a velocidade do editor. O Mônaco pode fazer muitas coisas prontas e, na minha opinião, melhor e em um volume maior que o Ace e o CodeMirror.

Ficamos no Mônaco por várias razões:

  1. As ferramentas mais desenvolvidas que consideramos críticas para o nosso projeto:
    • IntelliSense - dicas e preenchimento automático;
    • navegação inteligente de código no menu de contexto e através do minimapa;
    • modo diferencial de dois painéis pronto para uso.

  2. Escrito em TypeScript. Nosso painel de controle está escrito em Vue + Typescript, portanto, o suporte ao TS era importante. A propósito, Ace recentemente também suporta TS, mas foi originalmente escrito em JS. Para CodeMirror, existem tipos em DefinitelyTyped .
  3. Ele é desenvolvido ativamente (possivelmente porque foi lançado há pouco tempo), os bugs são corrigidos mais rapidamente e as solicitações de pool são combatidas. Para comparação, com o CodeMirror, tivemos uma experiência triste, quando os bugs não eram corrigidos por anos e colocávamos uma muleta em uma muleta e a dirigíamos.
  4. Documentação conveniente gerada automaticamente (que dá esperança de que esteja completa) com referências cruzadas entre interfaces e métodos.
  5. Para nosso gosto, a interface do usuário mais bonita (provavelmente também relacionada ao horário da criação) e uma API concisa.
  6. Depois de perguntar aos amigos dos desenvolvedores qual dos editores causou mais dores de cabeça, Ace e CodeMirror foram os líderes.

Separadamente, deve-se dizer sobre a velocidade do trabalho. A análise cara ocorre em um encadeamento de trabalho paralelo. Além disso, todos os cálculos são limitados pelo tamanho da janela de exibição (todos os tipos, cores e renderização são calculados apenas para as linhas visíveis). Ele começa a frear apenas se o código contiver 100.000 linhas - os prompts podem ser calculados por vários segundos. O Ace, que também usa trabalhadores para computação pesada, mostrou-se mais rápido: no código do mesmo tamanho, os avisos aparecem quase instantaneamente e lida rapidamente com 200.000 linhas (no site oficial, afirma-se que mesmo 4 milhões de linhas não devem ser um problema, embora os parafusos foram acelerados, a entrada começou a desacelerar e as instruções desapareceram após o primeiro milhão). O CodeMirror, onde não há cálculos paralelos, dificilmente pode extrair esses volumes: pode piscar tanto o texto quanto a sintaxe. Como 100.000 linhas em um arquivo são raras no mundo real, fechamos os olhos para isso. Mesmo com 40 a 50 mil linhas, o Mônaco faz um excelente trabalho.

Conectando o Mônaco e usando recursos básicos (por exemplo, integração com o Vue)


Ligação


Aqui vou dar exemplos de código dos componentes vue e usar a terminologia apropriada. Mas tudo isso é facilmente transportado para qualquer outra estrutura ou JS puro.

O código-fonte do Mônaco pode ser baixado no site oficial e inserido no seu projeto, você pode buscá-lo na CDN, pode se conectar ao projeto via npm. Vou falar sobre a terceira opção e construir usando o webpack.

Colocamos o monaco-editor e um plug-in para montagem:

npm i -S monaco-editor npm i -D monaco-editor-webpack-plugin 

Na configuração do webpack, adicione:

 const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); module.exports = { // ... plugins: [ // ... new MonacoWebpackPlugin() ] }; 

Se você usar o Vue e o vue-cli-service para construir, adicione ao vue.config.js:

 const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); module.exports = { // ... configureWebpack: (config) => { // ... config.plugins.push(new MonacoWebpackPlugin()); } }; 

Se você não precisar de todos os idiomas e recursos do Monaco, para reduzir o tamanho do pacote, poderá transferir MonacoWebpackPlugin objeto com as configurações para o MonacoWebpackPlugin :

 new MonacoWebpackPlugin({ output: '', // ,     languages: ['markdown'], //     ,     features: ['format', 'contextmenu'] //      }) 

Uma lista completa de recursos e idiomas para o plugin está aqui .

Crie e personalize um editor


Importamos o editor e chamamos editor.create(el: HTMLElement, config?: IEditorConstructionOptions) , passando o elemento DOM no qual queremos criar o editor como o primeiro argumento.

No componente do editor:

 <template> <div ref='editor' class='editor'></div> </template> <script> import {editor} from 'monaco-editor'; import {Component, Prop, Vue} from 'vue-property-decorator'; @Component() export default class Monaco extends Vue { private editor = null; mounted() { this.editor = editor.create(this.$refs.editor); } } </script> <style> .editor { margin: auto; width: 60vw; height: 200px; } </style> 

O contêiner do editor deve necessariamente definir a altura para que não seja zero. Se você criar o editor em uma div vazia (com altura zero - seu K.O.), o Monaco gravará a mesma altura em um estilo embutido na janela do editor.

O segundo argumento opcional para editor.create é a configuração do editor. Há mais de uma centena de opções, uma descrição completa da interface IEditorConstructionOptions está na documentação.

Por exemplo, definiremos o idioma, o tema e o texto inicial e ativaremos o agrupamento de linhas (por padrão, eles não são agrupados):

 const config = { value: `function hello() { alert('Hello world!'); }`, language: 'javascript', theme: 'vs-dark', wordWrap: 'on' }; this.editor = editor.create(this.$refs.editor, config); 

A função editor.create retorna um objeto com a interface IStandaloneCodeEditor . Através dele, agora você pode controlar tudo o que acontece no editor, incluindo a alteração das configurações iniciais:

 //        read-only  this.editor.updateOptions({wordWrap: 'off', readOnly: true}); 

Agora, o problema: updateOptions aceita um objeto com a interface IEditorOptions , não IEditorConstructionOptions. Eles são um pouco diferentes: IEditorConstructionOptions é mais amplo, inclui as propriedades dessa instância do editor e algumas globais. updateOptions propriedades da updateOptions são alteradas por meio de updateOptions , global - por meio dos métodos do editor global. E, consequentemente, aqueles que mudam globalmente mudam para todas as instâncias. Entre essas opções está o theme . Crie 2 instâncias com temas diferentes; y de ambos será o dado no último (escuro aqui). O editor.setTheme('vs') global editor.setTheme('vs') também mudará de assunto para ambos. Isso afetará até mesmo as janelas que estão em outra página do seu SPA. Existem alguns lugares assim, mas você precisa segui-los.

 <template> <div ref='editor1' class='editor'></div> <div ref='editor2' class='editor'></div> </template> <script> // ... this.editor1 = editor.create(this.$refs.editor1, {theme: 'vs'}); this.editor2 = editor.create(this.$refs.editor2, {theme: 'vs-dark'}); // ... </script> 


Excluir editor


Ao destruir uma janela de Mônaco, você deve chamar o método de dispose , caso contrário, todos os ouvintes não serão limpos e as janelas criadas depois disso poderão não funcionar corretamente, reagindo a alguns eventos várias vezes:

 beforeDestroy() { this.editor && this.editor.dispose(); } 

Guias


As guias abertas no editor de arquivos usam a mesma janela do Mônaco. Para alternar entre eles, os métodos IStandaloneCodeEditor são usados: getModel para salvar e setModel para atualizar o modelo do editor. O modelo armazena texto, posição do cursor, histórico de ações para desfazer refazer. Para criar um modelo de um novo arquivo, o método global editor.createModel(text: string, language: string) é usado. Se o arquivo estiver vazio, você não poderá criar um modelo e passar null para setModel :

Ver código
 <template> <div class='tabs'> <div class='tab' v-for="tab in tabs" :key'tab.id' @click='() => switchTab(tab.id)'> {{tab.name}} </div> </div> <div ref='editor' class='editor'></div> </template> <script> import {editor} from 'monaco-editor'; import {Component, Prop, Vue} from 'vue-property-decorator'; @Component() export default class Monaco extends Vue { private editor = null; private tabs: [ {id: 1, name: 'tab 1', text: 'const tab = 1;', model: null, active: true}, {id: 2, name: 'tab 2', text: 'const tab = 2;', model: null, active: false} ]; mounted() { this.editor = editor.create(this.$refs.editor); } private switchTab(id) { const activeTab = this.tabs.find(tab => tab.id === id); if (!activeTab.active) { //    (     )    const model = !activeTab.model && activeTab.text ? editor.createModel(activeTab.text, 'javascript') : activeTab.model; //          this.tabs = this.tabs.map(tab => ({ ...tab, model: tab.active ? this.editor.getModel() : tab.model, active: tab.id === id })); //    this.editor.setModel(model); } } </script> 


Modo Diff


Para o modo diff, você precisa usar outro método de editor ao criar a janela do editor - createDiffEditor :

 <template> <div ref='diffEditor' class='editor'></div> </template> // ... mounted() { this.diffEditor = editor.createDiffEditor(this.$refs.diffEditor, config); } // ... 

Ele usa os mesmos parâmetros que o editor.create , mas a configuração deve ter uma interface IDiffEditorConstructionOptions , que é um pouco diferente da configuração regular do editor, em particular, não possui value . Os textos para comparação são definidos após a criação do IStandaloneDiffEditor retornado via setModel :

 this.diffEditor.setModel({ original: editor.createModel('const a = 1;', 'javascript'), modified: editor.createModel('const a = 2;', 'javascript') }); 


Menu de contexto, paleta de comandos e teclas de atalho


O Monaco usa seu menu de contexto que não é do navegador, onde há navegação inteligente, um cursor múltiplo para alterar todas as ocorrências e uma paleta de comandos, como no VS Code (paleta de comandos), com vários comandos e atalhos úteis que aceleram a escrita do código:

  Menu de contexto Mônaco 


  Paleta de comandos do Mônaco 


O menu de contexto é expandido através do método addAction (está disponível no IStandaloneCodeEditor e no IStandaloneDiffEditor ), que aceita um objeto IActionDescriptor :

Ver código
 // ... <div ref='diffEditor' :style='{display: isDiffOpened ? "block" : "none"}'></div> // ... //  KeyCode  KeyMod     import {editor, KeyCode, KeyMod} from "monaco-editor"; // ... private editor = null; private diffEditor = null; private isDiffOpened = false; private get activeTab() { return this.tabs.find(tab => tab.active); } mounted() { this.diffEditor = editor.createDiffEditor(this.$refs.diffEditor); this.editor = editor.create(this.$refs.editor); this.editor.addAction({ //  ,     . contextMenuGroupId: '1_modification', //   : 1 - 'navigation', 2 - '1_modification', 3 - '9_cutcopypaste'; //    contextMenuOrder: 3, //       label: 'Show diff', id: 'showDiff', keybindings: [KeyMod.CtrlCmd + KeyMod.Shift + KeyCode.KEY_D], //   // ,     //    run: this.showDiffEditor }); } //      private showDiffEditor() { this.diffEditor.setModel({ original: this.activeTab.initialText, modified: this.activeTab.editedText }); this.isDiffOpened = true; } 


Para vincular apenas um atalho a uma ação sem mostrá-lo no menu de contexto, o mesmo método é usado, apenas o contextMenuGroupId ação não é especificado:

Ver código
 // ... //   private myActions = [ { contextMenuGroupId: '1_modification', contextMenuOrder: 3, label: <string>this.$t('scenarios.showDiff'), id: 'showDiff', keybindings: [KeyMod.CtrlCmd + KeyMod.Shift + KeyCode.KEY_D], run: this.showDiffEditor }, // ,   Ctrl + C + L      { label: 'Get content length', id: 'getContentLength', keybindings: [KeyMod.CtrlCmd + KeyCode.Key_C + KeyCode.Key_L], run: () => this.editor && alert(this.editor.getValue().length) } ]; mounted() { this.editor = editor.create(this.$refs.editor); this.myActions.forEach(this.editor.addAction); //     } 


A paleta de comandos incluirá todas as ações adicionadas.

Dicas e preenchimento automático


Para esses fins, o Monaco usa o IntelliSense , o que é legal. Você pode ler e ver nas imagens o link de quantas informações úteis ele pode mostrar. Se o seu idioma ainda não tiver um preenchimento automático, você poderá adicioná-lo através do registerCompletionItemProvider . E para JS e TS, já existe um método addExtraLib que permite carregar definições TypeScript para dicas de ferramentas e preenchimento automático:

 // ... import {languages} from "monaco-editor"; // ... // ,          private myAddedLib = null; mounted() { // languages     Monaco this.myAddedLib = languages.typescript.javascriptDefaults.addExtraLib('interface MyType {prop: string}', 'myLib'); } beforeDestroy() { //  ,   this.myAddedLib && this.myAddedLib.dispose(); } 

No primeiro parâmetro, a linha passa as definições, no segundo, opcional, o nome da lib.

Temas e idiomas personalizados


O Mônaco possui um módulo Monarch para determinar a sintaxe de seus idiomas. A sintaxe é descrita de maneira bastante padronizada: a correspondência entre os regulares e os tokens característicos desse idioma é definida.

Ver código
 // ... //  ,    : private myLanguage = { defaultToken: 'text', //  , brackets: [{ open: '(', close: ')', token: 'bracket.parenthesis' }], // ,   , keywords: [ 'autumn', 'winter', 'spring', 'summer' ], //     tokenizer: { root: [{ regex: /\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/, action: { token: 'date' } }, { regex: /(boy|girl|man|woman|person)(\s[A-Za-z]+)/, action: ['text', 'variable'] } ] } }; mounted() { //     languages.register({ id: 'myLanguage' }); //      languages.setMonarchTokensProvider('myLanguage', this.myLanguage); // ... } 


Você também pode criar um tema para seus tokens - um objeto com a interface IStandaloneThemeData - e instalá-lo no editor global:

 // ... private myTheme = { base: 'vs', // ,      inherit: true, //       rules: [ {token: 'date', foreground: '22aacc'}, {token: 'variable', foreground: 'ff6600'}, {token: 'text', foreground: 'd4d4d4'}, {token: 'bracket', foreground: 'd4d4d4'} ] }; mounted() { editor.defineTheme('myTheme', this.myTheme); // ... } 

Agora o texto no idioma descrito ficará assim:


Você pode aplicar esse recurso, desde que tenha imaginação suficiente. Por exemplo, criamos um visualizador de log de chamadas em nosso painel para desenvolvedores. Os logs geralmente são longos e incompreensíveis, mas quando são exibidos com destaque de sintaxe, pesquisa inteligente, linhas dobráveis ​​/ expansíveis, os comandos necessários (por exemplo, parâmetros de Prettify), destacando todas as linhas de chamada por seu ID ou traduzindo a hora no log para um fuso horário diferente e, em seguida, escavar torna-se muito mais fácil neles (a captura de tela é clicável):


Conclusão


Em resumo, direi que Mônaco é fogo. Depois de meses trabalhando com ele, tenho lembranças excepcionalmente agradáveis. Se você escolher um editor para o código, não deixe de ir ao Playground e brincar com o código, veja o que mais ele pode fazer. Talvez seja exatamente isso que você está procurando.

Source: https://habr.com/ru/post/pt445390/


All Articles