IDE de una persona normal o por qué elegimos Mónaco

Nota del editor


En el √ļltimo art√≠culo, hablamos sobre el lanzamiento del panel de control de Voximplant, sin olvidar mencionar el IDE actualizado. Hoy dedicamos un largo recorrido por separado a esta herramienta: nuestro colega Geloosa describi√≥ cuidadosamente tanto el proceso de elegir una tecnolog√≠a como la implementaci√≥n con pesta√Īas, autocompletar y estilos personalizados. Si√©ntate m√°s convenientemente, deja a un lado el resto de tus asuntos y ve al aparejo, donde las tripas de M√≥naco est√°n esperando a los curiosos: no te resbales, hay muchos de ellos :) Disfruta leyendo.


¬ŅQu√© biblioteca elegir para el editor de c√≥digo?


Npm produce m√°s de 400 resultados para el editor de c√≥digo. En su mayor parte, estos son contenedores UI de varias de las bibliotecas m√°s populares hechas para un marco o proyecto en particular, complementos para las mismas bibliotecas o sus horquillas con modificaciones para ellos mismos, as√≠ como no para editar el c√≥digo en el navegador, simplemente ingresaron a la salida por palabras clave. Entonces, afortunadamente, la elecci√≥n es mucho m√°s estrecha. Algunas bibliotecas m√°s: un CodeFlask , ligero, pero no muy funcional, dise√Īado para peque√Īos fragmentos y ejemplos interactivos, pero no para un IDE web completo con la funcionalidad a la que estamos acostumbrados en los editores de escritorio.

Al final, tenemos 3 bibliotecas para elegir: Ace , CodeMirror y Monaco Editor . El primero de ellos, CodeMirror, fue una iniciativa privada de Berliner Marijn Haverbeke , quien necesitaba un editor de c√≥digo de ejercicio en su tutorial en l√≠nea, Eloquent JavaScript . La primera versi√≥n del editor fue lanzada en 2007. En 2010, la primera versi√≥n de Ace se present√≥ en JSConf.eu en el mismo Berl√≠n, que Ajax.org desarroll√≥ para su nube IDE Cloud9 (de hecho, Ace significa Ajax.org Cloud9 Editor). En 2016, Cloud9 fue comprado por Amazon y ahora forma parte de AWS. El √ļltimo, Monaco Editor, es un componente de VS Code y fue publicado por Microsoft a finales de 2015.

Cada editor tiene sus propias fortalezas y debilidades; cada una se usa en más de un gran proyecto. Por ejemplo, CodeMirror se usa en las herramientas para desarrolladores de Chrome y Firefox, un IDE en Bitbucket, en RunKit en npm; Ace - en Codecademy, Khan Academy, MODX; Mónaco: en el IDE de GitLab y CodeSandbox. La siguiente es una tabla de comparación que puede ayudarlo a elegir la biblioteca más adecuada para su proyecto.

Bibliotecas
AsCodeMirrorMonaco
DesarrolladorCloud9 IDE (Ajax.org),
ahora parte de AmazonMozilla
Marijn HaverbekeMicrosoft
Soporte del navegadorFirefox ^ 3.5
Cromo
Safari ^ 4.0
IE ^ 8.0
Opera ^ 11.5
Firefox ^ 3.0
Cromo
Safari ^ 5.2
IE ^ 8.0
Opera ^ 9.2
Firefox ^ 4.0
Cromo
Safari (v -?)
IE ^ 11.0
Opera ^ 15.0
Soporte de idiomas
(resaltado de sintaxis)
> 120> 100> 20
N√ļmero de caracteres en
√ļltimas versiones en
cndjs.com
366 608 (v1.4.3)394,269 (v5.44.0)2.064.949 (v0.16.2)
El peso de las √ļltimas versiones,
gzip
2.147 KB1.411 KB10.898 KB
RenderizadoDomDomDOM y parcialmente <canvas>
(para desplazamiento y minimapa)
La documentaci√≥n7 de 10: sin b√ļsqueda, no siempre claro
que los métodos vuelven, hay dudas
en integridad y relevancia
(no todos los enlaces funcionan en el muelle)
6 de 10: fusionado con la guía del usuario,
buscar por Ctrl + F,
hay dudas sobre la integridad
9 de 10: hermoso, con b√ļsqueda y
referencia cruzada
-1 punto por falta de explicación
a algunas banderas cuya aplicación
no del todo obvio por el nombre
Demostraciones de inicio rápidoCómo hacerlo: documentos de texto con ejemplos de código,
por separado hay demos con ejemplos de código
(cierto, est√°n dispersos en diferentes p√°ginas,
no todos trabajan y se buscan más fácilmente a través de Google),
hay una demostración donde puedes tocar diferentes funciones,
pero se propone administrarlos a través de controles de interfaz de usuario,
es decir, a√ļn debemos buscar m√©todos por separado
para conectarlos
Cómo son realmente pobres
b√°sicamente todo est√° disperso en github
y stackoverflow, pero hay demostraciones de características con ejemplos
código para su implementación
Combinado en el formato de un patio de recreo:
código con comentarios y una serie de demos, puedes
inmediatamente intente y eval√ļe
muchas posibilidades
Actividad comunitariaMediaAltaMedia
Actividad del desarrolladorMediaMediaAlta

No tiene sentido comparar bibliotecas por tama√Īo, porque todo depende de qu√© y c√≥mo conectarse para un proyecto en particular: cargue el archivo terminado con una de las compilaciones (que tambi√©n var√≠an) o ejecute el paquete npm a trav√©s de alg√ļn tipo de recopilador. Y lo m√°s importante es cu√°nto se usa el editor: si se cargan todos los estilos y temas, cu√°ntos y qu√© complementos y complementos se usan. Por ejemplo, en CodeMirror, la mayor parte de la funcionalidad que funciona en M√≥naco y Ace fuera de la caja solo est√° disponible con complementos. La tabla muestra el n√ļmero de caracteres en versiones recientes en el CDN y el peso de sus archivos comprimidos para tener una idea general de qu√© √≥rdenes est√°n involucradas.

Todas las bibliotecas tienen aproximadamente el mismo conjunto de caracter√≠sticas b√°sicas: formato autom√°tico de c√≥digo, l√≠neas plegables, cortar / copiar / pegar, teclas de acceso r√°pido, la capacidad de agregar nuevas sintaxis para resaltar y ordenar, verificaci√≥n de sintaxis (en CodeMirror solo a trav√©s de complementos, en Ace hasta ahora solo para JavaScript / CoffeeScript / CSS / XQuery), informaci√≥n sobre herramientas y autocompletar (en CodeMirror - a trav√©s de complementos), b√ļsqueda avanzada por c√≥digo (en CodeMirror - a trav√©s de complementos), m√©todos para implementar pesta√Īas y modo dividido, modo diferencial y una herramienta de combinaci√≥n (en CodeMirror - ya sea con ventajas y desventajas en una ventana, o dos paneles a trav√©s de un complemento, Ace - Lieb independiente). Debido a su antig√ľedad, se han escrito muchos complementos para CodeMirror, pero su n√ļmero afectar√° tanto el peso como la velocidad del editor. M√≥naco puede hacer muchas cosas fuera de la caja y, en mi opini√≥n, mejor y en un volumen mayor que Ace y CodeMirror.

Nos alojamos en Mónaco por varias razones:

  1. Las herramientas más desarrolladas que consideramos críticas para nuestro proyecto:
    • IntelliSense - consejos y autocompletar;
    • navegaci√≥n de c√≥digo inteligente en el men√ļ contextual y a trav√©s del minimapa;
    • modo diferencial de dos paneles fuera de la caja.

  2. Escrito en TypeScript. Nuestro panel de control está escrito en Vue + Typecript, por lo que el soporte de TS fue importante. Por cierto, Ace recientemente también es compatible con TS, pero originalmente fue escrito en JS. Para CodeMirror, hay tipos en DefinitelyTyped .
  3. Se est√° desarrollando m√°s activamente en √©l (posiblemente porque se lanz√≥ hace poco), los errores se corrigen m√°s r√°pido y se resuelven las solicitudes de grupo. En comparaci√≥n, con CodeMirror tuvimos una experiencia triste, cuando los errores no se corrigieron durante a√Īos y pusimos una muleta en una muleta y manejamos una muleta.
  4. Conveniente documentación autogenerada (que da esperanza para su integridad) con referencias cruzadas entre interfaces y métodos.
  5. Para nuestro gusto, la interfaz de usuario más hermosa (probablemente también relacionada con el tiempo de creación) y una API concisa.
  6. Después de preguntar a los amigos de los desarrolladores cuál de los editores causó más dolores de cabeza, Ace y CodeMirror fueron los líderes.

Por separado, debe decirse sobre la velocidad del trabajo. El an√°lisis costoso se realiza en un subproceso de trabajo paralelo. Adem√°s, todos los c√°lculos est√°n limitados por el tama√Īo de la ventana gr√°fica (todos los tipos, colores y renderizados se calculan solo para aquellas l√≠neas que son visibles). Comienza a frenar solo si el c√≥digo contiene 100,000 l√≠neas; las indicaciones se pueden calcular durante varios segundos. Ace, que tambi√©n utiliza trabajadores para computaci√≥n pesada, result√≥ ser m√°s r√°pido: en un c√≥digo de la misma longitud, las indicaciones aparecen casi instant√°neamente, y r√°pidamente hace frente a 200,000 l√≠neas (en el sitio web oficial se afirma que incluso 4 millones de l√≠neas no deber√≠an ser un problema, aunque los tornillos se aceleraron, la entrada comenz√≥ a disminuir y las indicaciones desaparecieron despu√©s del primer mill√≥n). CodeMirror, donde no hay c√°lculos paralelos, apenas puede extraer esos vol√ļmenes: puede parpadear tanto el resaltado de texto como de sintaxis. Dado que 100,000 l√≠neas en un archivo son raras en el mundo real, hicimos la vista gorda a esto. Incluso con 40-50 mil l√≠neas, M√≥naco hace un excelente trabajo.

Conexión de Mónaco y uso de funciones básicas (por ejemplo, integración con Vue)


Conexión


Aquí daré ejemplos de código de componentes vue y usaré la terminología adecuada. Pero todo esto se transfiere fácilmente a cualquier otro marco o JS puro.

El código fuente de Mónaco se puede descargar en el sitio web oficial y poner en su proyecto, puede recogerlo de CDN, puede conectarse al proyecto a través de npm. Hablaré sobre la tercera opción y compilar usando webpack.

Ponemos monaco-editor y un complemento para ensamblar:

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

En la configuración del paquete web, agregue:

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

Si usa Vue y vue-cli-service para compilar, agregue a vue.config.js:

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

Si no necesita todos los idiomas y caracter√≠sticas de M√≥naco, para reducir el tama√Īo del paquete, puede transferir MonacoWebpackPlugin objeto con la configuraci√≥n a MonacoWebpackPlugin :

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

Una lista completa de características e idiomas para el complemento está aquí .

Crea y personaliza un editor


Importamos editor y llamamos editor.create(el: HTMLElement, config?: IEditorConstructionOptions) , pasando el elemento DOM en el que queremos crear el editor como primer argumento.

En el componente 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> 

El contenedor para el editor necesariamente debe establecer la altura para que no resulte ser cero. Si crea el editor en un div vacío (con altura cero, su K.O.), Mónaco escribirá la misma altura en un estilo en línea en la ventana del editor.

El segundo argumento opcional para editor.create es la configuración del editor. Hay más de un centenar de opciones, una descripción completa de la interfaz IEditorConstructionOptions se encuentra en la documentación.

Por ejemplo, estableceremos el idioma, el tema y el texto inicial y habilitaremos el ajuste de línea (de forma predeterminada, no están ajustados):

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

La función editor.create devuelve un objeto con la interfaz IStandaloneCodeEditor . A través de él, ahora puede controlar todo lo que sucede en el editor, incluido el cambio de la configuración inicial:

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

Ahora para el dolor: updateOptions acepta un objeto con la interfaz IEditorOptions , no IEditorConstructionOptions. Son ligeramente diferentes: IEditorConstructionOptions es m√°s amplio, incluye las propiedades de esta instancia del editor y algunas globales. updateOptions propiedades de la updateOptions se cambian a trav√©s de updateOptions , global, a trav√©s de los m√©todos del editor global. Y en consecuencia, aquellos que cambian globalmente cambian para todas las instancias. Entre estas opciones est√° el theme . Crea 2 instancias con diferentes temas; y de ambos ser√° el dado en el √ļltimo (oscuro aqu√≠). El editor.setTheme('vs') global editor.setTheme('vs') tambi√©n cambiar√° el tema para ambos. Esto afectar√° incluso aquellas ventanas que se encuentran en otra p√°gina de su SPA. Hay pocos lugares, pero debes seguirlos.

 <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> 


Eliminar editor


Cuando destruye una ventana de Mónaco, debe llamar al método de dispose ; de lo contrario, no se borrarán todos los oyentes y las ventanas creadas después de esto pueden no funcionar correctamente, reaccionando a algunos eventos varias veces:

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

Pesta√Īas


Las pesta√Īas abiertas en el editor de archivos usan la misma ventana de M√≥naco. Para cambiar entre ellos, se utilizan los m√©todos IStandaloneCodeEditor: getModel para guardar y setModel para actualizar el modelo del editor. El modelo almacena texto, posici√≥n del cursor, historial de acciones para deshacer y rehacer. Para crear un modelo de un nuevo archivo, se utiliza el m√©todo global editor.createModel(text: string, language: string) . Si el archivo est√° vac√≠o, no puede crear un modelo y pasar null a 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 de diferencia


Para el modo diff, debe usar otro método de editor al crear la ventana del editor: createDiffEditor :

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

Toma los mismos parámetros que editor.create , pero la configuración debe tener una interfaz IDiffEditorConstructionOptions , que es ligeramente diferente de la configuración del editor normal, en particular, no tiene value . Los textos para la comparación se establecen después de crear el IStandaloneDiffEditor devuelto a través de setModel :

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


Men√ļ contextual, paleta de comandos y teclas de acceso r√°pido


Monaco usa su propio men√ļ contextual, que no es del navegador, donde hay navegaci√≥n inteligente, un cursor m√ļltiple para cambiar todas las ocurrencias y una paleta de comandos como en VS Code (paleta de comandos) con un mont√≥n de comandos y accesos directos √ļtiles que aceleran la escritura de c√≥digos:

  Men√ļ contextual de M√≥naco 


  Paleta de comandos de Mónaco 


El men√ļ contextual se expande a trav√©s del m√©todo addAction (est√° disponible tanto en IStandaloneCodeEditor como en IStandaloneDiffEditor ), que acepta un 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 solo un acceso directo a una acci√≥n sin mostrarlo en el men√ļ contextual, se utiliza el mismo m√©todo, solo no se especifica el contextMenuGroupId la acci√≥n:

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


La paleta de comandos incluir√° todas las acciones agregadas.

Consejos y autocompletar


Para estos fines, M√≥naco utiliza IntelliSense , que es genial. Puede leer y ver en las capturas de pantalla el enlace sobre cu√°nta informaci√≥n √ļtil puede mostrar. Si su idioma a√ļn no tiene un autocompletado, puede agregarlo a trav√©s de registerCompletionItemProvider . Y para JS y TS, ya existe un m√©todo addExtraLib que le permite cargar definiciones de TypeScript para informaci√≥n sobre herramientas y autocompletar:

 // ... 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(); } 

En el primer parámetro, la línea pasa las definiciones, en el segundo, opcional, el nombre de la lib.

Idiomas y temas personalizados


Mónaco tiene un módulo Monarca para determinar la sintaxis de sus idiomas. La sintaxis se describe de manera bastante estándar: se establece la correspondencia entre los habituales y los tokens que son característicos de este lenguaje.

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


También puede crear un tema para sus tokens, un objeto con la interfaz IStandaloneThemeData , e instalarlo en el 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); // ... } 

Ahora el texto en el idioma descrito se verá así:


Puede aplicar esta funci√≥n, siempre que tenga suficiente imaginaci√≥n. Por ejemplo, creamos un visor de registro de llamadas en nuestro panel para desarrolladores. Los registros son a menudo largos e incomprensibles, pero cuando se muestran con resaltado de sintaxis, b√ļsqueda inteligente, l√≠neas plegables / expansivas, los comandos necesarios (por ejemplo, Prettify params), resaltando todas las l√≠neas de llamada por su id o traduciendo el tiempo en el registro a una zona horaria diferente, luego cava se vuelve mucho m√°s f√°cil en ellos (se puede hacer clic en la captura de pantalla):


Conclusión


En resumen, dir√© que M√≥naco es fuego. Despu√©s de meses de trabajar con √©l, tengo recuerdos excepcionalmente agradables. Si elige un editor para el c√≥digo, aseg√ļrese de ir a su Zona de juegos y jugar con el c√≥digo, ver qu√© m√°s puede hacer. Quiz√°s esto es exactamente lo que est√°s buscando.

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


All Articles