IDE einer normalen Person oder warum wir uns für Monaco entschieden haben

Anmerkung des Herausgebers


Im letzten Artikel haben wir über die Veröffentlichung des Voximplant-Kontrollfelds gesprochen, nicht zu vergessen die aktualisierte IDE. Heute widmen wir diesem Tool einen eigenen Longride - unser Kollege Geloosa hat sowohl den Prozess der Auswahl einer Technologie als auch die Implementierung mit Registerkarten, automatischer Vervollständigung und benutzerdefinierten Stilen sorgfältig beschrieben. Setzen Sie sich bequemer hin, legen Sie den Rest Ihrer Angelegenheiten beiseite und gehen Sie zum Gerät, wo die Eingeweide von Monaco auf die Neugierigen warten - rutschen Sie nicht aus, es gibt viele davon :) Viel Spaß beim Lesen.


Welche Bibliothek soll für den Code-Editor ausgewählt werden?


Npm erzeugt mehr als 400 Ergebnisse für den Code-Editor. Zum größten Teil sind dies UI-Wrapper einiger der beliebtesten Bibliotheken, die für ein bestimmtes Framework oder Projekt erstellt wurden, Plugins für dieselben Bibliotheken oder deren Gabeln mit Änderungen für sich selbst und auch nicht zum Bearbeiten von Code im Browser, der einfach über Schlüsselwörter in die Suchergebnisse gelangt ist. Zum Glück ist die Auswahl also viel enger. Noch ein paar Bibliotheken - a la CodeFlask , leichtgewichtig, aber nicht sehr funktional, entwickelt für kleine Schnipsel und interaktive Beispiele, aber nicht für eine vollwertige Web-IDE mit der Funktionalität, die wir in Desktop-Editoren gewohnt sind.

Am Ende haben wir 3 Bibliotheken zur Auswahl: Ace , CodeMirror und Monaco Editor . Das früheste davon, CodeMirror, war eine private Initiative des Berliner Marijn Haverbeke , der in seinem Online-Tutorial Eloquent JavaScript einen Übungscode-Editor benötigte. Die erste Version des Editors wurde 2007 veröffentlicht. 2010 wurde die erste Version von Ace auf der JSConf.eu im selben Berlin vorgestellt, die Ajax.org dann für seine Cloud-IDE Cloud9 entwickelte (tatsächlich steht Ace für Ajax.org Cloud9 Editor). Im Jahr 2016 wurde Cloud9 von Amazon gekauft und ist jetzt Teil von AWS. Der neueste Monaco Editor ist Bestandteil von VS Code und wurde Ende 2015 von Microsoft veröffentlicht.

Jeder Editor hat seine eigenen Stärken und Schwächen, jeder wird in mehr als einem großen Projekt verwendet. Beispielsweise wird CodeMirror in den Chrome- und Firefox-Entwicklertools, einer IDE in Bitbucket, in RunKit in npm verwendet. Ass - an der Codecademy, Khan Academy, MODX; Monaco - in der GitLab IDE und CodeSandbox. Das folgende Vergleichsdiagramm kann Ihnen bei der Auswahl der für Ihr Projekt am besten geeigneten Bibliothek helfen.

Bibliotheken
AssCodeMirrorMonaco
EntwicklerCloud9 IDE (Ajax.org),
jetzt Teil von AmazonMozilla
Marijn HaverbekeMicrosoft
Browser-UnterstützungFirefox ^ 3.5
Chrome
Safari ^ 4.0
IE ^ 8.0
Oper ^ 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
Sprachunterstützung
(Syntaxhervorhebung)
> 120> 100> 20
Anzahl der Zeichen in
neueste Versionen auf
cndjs.com
366 608 (v1.4.3)394,269 (v5.44.0)2,064,949 (v0,16,2)
Das Gewicht der neuesten Versionen,
gzip
2,147 KB1,411 KB10.898 KB
RendernDomDomDOM und teilweise <canvas>
(zum Scrollen und Minikarten)
Die Dokumentation7 von 10: keine Suche, nicht immer klar
dass Methoden zurückkehren, gibt es Zweifel
in Vollständigkeit und Relevanz
(Nicht alle Links funktionieren im Dock)
6 von 10: zusammengeführt mit Benutzerhandbuch,
Suche mit Strg + F,
Es bestehen Zweifel an der Vollständigkeit
9 von 10: schön, mit Suche und
Querverweis
-1 Punkt mangels Erklärung
zu einigen Flags, deren Anwendung
nicht ganz offensichtlich aus dem Namen
Schnellstart-DemosHow-to - Textdokumente mit Codebeispielen,
separat gibt es demos mit codebeispielen
(wahr, sie sind auf verschiedenen Seiten verstreut,
nicht jeder arbeitet und sie werden am einfachsten über Google gesucht),
Es gibt eine Demo, in der Sie verschiedene Funktionen berühren können.
Es wird jedoch vorgeschlagen, sie über UI-Steuerelemente zu verwalten.
das heißt, dann müssen wir immer noch separat nach Methoden suchen
um sie zu verbinden
How-to sind wirklich arm
Im Grunde ist alles auf Github verstreut
und Stackoverflow, aber es gibt Demos von Funktionen mit Beispielen
Code für ihre Implementierung
Kombiniert im Format eines Spielplatzes:
Code mit Kommentaren und einer Reihe von Demos können Sie
sofort versuchen und bewerten
viele Möglichkeiten
GemeinschaftsaktivitätDurchschnittHochDurchschnitt
EntwickleraktivitätDurchschnittDurchschnittHoch

Es macht keinen Sinn, Bibliotheken nach Größe zu vergleichen, da alles davon abhängt, was und wie eine Verbindung für ein bestimmtes Projekt hergestellt werden soll: Laden Sie die fertige Datei mit einem der Builds (die ebenfalls variieren) oder führen Sie das npm-Paket über eine Art Kollektor aus. Und das Wichtigste ist, wie oft der Editor verwendet wird: ob alle Stile und Themen geladen sind, wie viele und welche Add-Ons und Plug-Ins verwendet werden. In CodeMirror sind beispielsweise die meisten Funktionen, die in Monaco und Ace sofort verfügbar sind, nur mit Add-Ons verfügbar. Die Tabelle zeigt die Anzahl der Zeichen in den letzten Versionen auf dem CDN und das Gewicht ihrer komprimierten Dateien, um eine allgemeine Vorstellung davon zu erhalten, um welche Bestellungen es sich handelt.

Alle Bibliotheken verfügen über ungefähr die gleichen Grundfunktionen: automatische Formatierung von Code, Falten von Linien, Ausschneiden / Kopieren / Einfügen, Hotkeys, die Möglichkeit, neue Syntaxen zum Hervorheben und Ordnen hinzuzufügen, Syntaxprüfung (in CodeMirror nur über Add-Ons, in Ace bisher nur für JavaScript / CoffeeScript / CSS / XQuery), QuickInfos und automatische Vervollständigung (in CodeMirror - über Add-Ons), erweiterte Suche nach Code (in CodeMirror - über Add-Ons), Methoden zum Implementieren von Registerkarten und Split-Modus, Diff-Modus und ein Zusammenführungs-Tool (in CodeMirror) - entweder mit Vor- und Nachteilen in einem Fenster oder mit zwei Feldern durch ein Addon, Ace - Separate Lieb). Aufgrund seines Alters wurden viele Add-Ons für CodeMirror geschrieben, deren Anzahl sich jedoch sowohl auf das Gewicht als auch auf die Geschwindigkeit des Editors auswirkt. Monaco kann viele Dinge sofort erledigen und meiner Meinung nach besser und in größerem Umfang als Ace und CodeMirror.

Wir waren aus mehreren Gründen in Monaco:

  1. Die am weitesten entwickelten Tools, die wir für unser Projekt als kritisch erachtet haben:
    • IntelliSense - Tipps und automatische Vervollständigung;
    • Smart Code Navigation im Kontextmenü und durch Minikarte;
    • Zwei-Panel-Diff-Modus sofort einsatzbereit.

  2. Geschrieben in TypeScript. Unser Control Panel ist in Vue + Typescript geschrieben, daher war die TS-Unterstützung wichtig. Übrigens unterstützt Ace kürzlich auch TS, aber es wurde ursprünglich in JS geschrieben. Für CodeMirror gibt es Typen in DefinitelyTyped .
  3. Es wird am aktivsten darin entwickelt (möglicherweise weil es vor nicht allzu langer Zeit veröffentlicht wurde), Fehler werden schneller behoben und Pool-Anfragen werden bekämpft. Zum Vergleich hatten wir mit CodeMirror eine traurige Erfahrung, als Fehler jahrelang nicht behoben wurden und wir eine Krücke auf eine Krücke legten und eine Krücke fuhren.
  4. Praktische automatisch generierte Dokumentation (die Hoffnung auf Vollständigkeit gibt) mit Querverweisen zwischen Schnittstellen und Methoden.
  5. Nach unserem Geschmack die schönste Benutzeroberfläche (wahrscheinlich auch im Zusammenhang mit der Erstellungszeit) und eine übersichtliche API.
  6. Nachdem Ace und CodeMirror Freunde der Entwickler gefragt hatten, welcher der Editoren die meisten Kopfschmerzen verursachte, waren sie führend.

Separat sollte über die Geschwindigkeit der Arbeit gesagt werden. Das kostspielige Parsen findet in einem parallelen Worker-Thread statt. Außerdem sind alle Berechnungen durch die Größe des Ansichtsfensters begrenzt (alle Typen, Farben und Renderings werden nur für die sichtbaren Linien berechnet). Es beginnt erst zu bremsen, wenn der Code 100.000 Zeilen enthält - Eingabeaufforderungen können für einige Sekunden berechnet werden. Ace, das auch Worker für Heavy Computing einsetzt, erwies sich als schneller: Bei Code gleicher Länge werden Eingabeaufforderungen fast sofort angezeigt und es werden schnell 200.000 Zeilen verarbeitet (auf der offiziellen Website wird angegeben, dass selbst 4 Millionen Zeilen kein Problem darstellen sollten Die Schrauben wurden beschleunigt, die Eingabe wurde langsamer und die Eingabeaufforderungen verschwanden nach der ersten Million. CodeMirror, bei dem es keine parallelen Berechnungen gibt, kann solche Volumes kaum abrufen: Es kann sowohl Text- als auch Syntaxhervorhebungen flackern. Da 100.000 Zeilen in einer Datei in der realen Welt selten sind, haben wir dies ignoriert. Selbst mit 40-50 Tausend Zeilen leistet Monaco hervorragende Arbeit.

Anschließen von Monaco und Verwenden grundlegender Funktionen (z. B. Integration in Vue)


Verbindung


Hier werde ich Codebeispiele aus vue-Komponenten geben und die entsprechende Terminologie verwenden. All dies lässt sich jedoch problemlos auf jedes andere Framework oder reine JS portieren.

Der Quellcode von Monaco kann von der offiziellen Website heruntergeladen und in Ihr Projekt eingefügt werden. Sie können ihn von CDN abholen und über npm eine Verbindung zum Projekt herstellen. Ich werde über die dritte Option sprechen und mit Webpack bauen.

Wir haben einen Monaco-Editor und ein Plug-In für die Montage bereitgestellt:

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

Fügen Sie in der Webpack-Konfiguration Folgendes hinzu:

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

Wenn Sie zum Erstellen Vue und vue-cli-service verwenden, fügen Sie vue.config.js hinzu:

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

Wenn Sie nicht alle Sprachen und Funktionen von Monaco benötigen, um die Größe des Bundles zu verringern, können Sie MonacoWebpackPlugin Objekt mit den Einstellungen an MonacoWebpackPlugin :

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

Eine vollständige Liste der Funktionen und Sprachen für das Plugin finden Sie hier .

Erstellen und Anpassen eines Editors


Wir importieren den editor und rufen editor.create(el: HTMLElement, config?: IEditorConstructionOptions) , wobei wir das DOM-Element übergeben, in dem wir den Editor als erstes Argument erstellen möchten.

In der Editor-Komponente:

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

Der Container für den Editor muss unbedingt die Höhe so einstellen, dass er nicht Null wird. Wenn Sie den Editor in einem leeren Div (mit einer Höhe von Null - Ihrem K.O.) erstellen, schreibt Monaco dieselbe Höhe in einem Inline-Stil im Editorfenster.

Das zweite optionale Argument für editor.create ist die Editor-Konfiguration. Es gibt mehr als hundert Optionen. Eine vollständige Beschreibung der IEditorConstructionOptions- Schnittstelle finden Sie in der Dokumentation.

In einem Beispiel legen wir die Sprache, das Thema und den Anfangstext fest und aktivieren den Zeilenumbruch (standardmäßig werden sie nicht umbrochen):

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

Die Funktion editor.create gibt ein Objekt mit der IStandaloneCodeEditor- Schnittstelle zurück. Über diese Funktion können Sie jetzt alles steuern, was im Editor geschieht, einschließlich der Änderung der Anfangseinstellungen:

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

Nun zum Schmerz: updateOptions akzeptiert ein Objekt mit der IEditorOptions- Schnittstelle, nicht IEditorConstructionOptions. Sie unterscheiden sich geringfügig: IEditorConstructionOptions ist breiter und enthält die Eigenschaften dieser Editorinstanz sowie einige globale. updateOptions werden über updateOptions global geändert - über die Methoden des globalen editor . Dementsprechend ändern sich diejenigen, die sich global ändern, für alle Instanzen. Unter diesen Optionen ist theme . Erstellen Sie 2 Instanzen mit unterschiedlichen Themen. y von beiden wird das letzte sein (hier dunkel). Die globale Methode editor.setTheme('vs') ändert auch das Thema für beide. Dies wirkt sich auch auf die Fenster aus, die sich auf einer anderen Seite Ihres SPA befinden. Es gibt nur wenige solcher Orte, aber Sie müssen ihnen folgen.

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


Editor löschen


Wenn Sie ein Monaco-Fenster zerstören, müssen Sie die dispose Methode aufrufen. Andernfalls werden nicht alle Listener gelöscht und die danach erstellten Fenster funktionieren möglicherweise nicht richtig und reagieren mehrmals auf einige Ereignisse:

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

Tabs


Die im Datei-Editor geöffneten Registerkarten verwenden dasselbe Monaco-Fenster. Um zwischen ihnen zu wechseln, werden IStandaloneCodeEditor-Methoden verwendet: getModel zum Speichern und setModel zum Aktualisieren des Editor-Modells. Das Modell speichert Text, Cursorposition und Aktionsverlauf zum Rückgängigmachen. Um ein Modell einer neuen Datei zu erstellen, wird die globale Methode editor.createModel(text: string, language: string) verwendet. Wenn die Datei leer ist, können Sie kein Modell erstellen und null an setModel :

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


Diff-Modus


Für den Diff-Modus müssen Sie beim Erstellen des Editorfensters eine andere editor Methode verwenden - createDiffEditor :

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

Es werden dieselben Parameter wie editor.create , aber die Konfiguration sollte über eine IDiffEditorConstructionOptions- Schnittstelle verfügen, die sich geringfügig von der regulären Editor-Konfiguration unterscheidet. Insbesondere hat sie keinen value . Vergleichstexte werden nach dem Erstellen des zurückgegebenen IStandaloneDiffEditor über setModel festgelegt :

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


Kontextmenü, Befehlspalette und Tastenkombinationen


Monaco verwendet sein Nicht-Browser-Kontextmenü, in dem es eine intelligente Navigation, einen Multi-Cursor zum Ändern aller Vorkommen und eine Befehlspalette wie in VS Code (Befehlspalette) mit einer Reihe nützlicher Befehle und Verknüpfungen gibt, die das Schreiben von Code beschleunigen:

  Monaco Kontextmenü 


  Monaco Befehlspalette 


Das Kontextmenü wird durch die addAction Methode erweitert (sie ist sowohl in IStandaloneCodeEditor als auch in IStandaloneCodeEditor IStandaloneDiffEditor ), die ein IActionDescriptor- Objekt akzeptiert:

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


Um eine Verknüpfung nur an eine Aktion zu binden, ohne sie im Kontextmenü anzuzeigen, wird dieselbe Methode verwendet, nur die contextMenuGroupId der Aktion wird nicht angegeben:

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


Die Befehlspalette enthält alle hinzugefügten Aktionen.

Tipps und automatische Vervollständigung


Für diese Zwecke verwendet Monaco IntelliSense , was cool ist. Sie können auf den Screenshots den Link lesen und sehen, wie viele nützliche Informationen er anzeigen kann. Wenn Ihre Sprache noch keine automatische Vervollständigung hat, können Sie diese über registerCompletionItemProvider hinzufügen. Und für JS und TS gibt es bereits eine addExtraLib Methode, mit der Sie TypeScript-Definitionen für addExtraLib laden und automatisch vervollständigen können:

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

Im ersten Parameter übergibt die Zeile die Definitionen, im zweiten optional den Namen der Bibliothek.

Benutzerdefinierte Sprachen und Themen


Monaco verfügt über ein Monarch- Modul zur Bestimmung der Syntax seiner Sprachen. Die Syntax wird ganz normal beschrieben: Die Entsprechung zwischen den für diese Sprache charakteristischen Stammgästen und Token wird festgelegt.

Code anzeigen
 // ... //  ,    : 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); // ... } 


Sie können auch ein Thema für Ihre Token erstellen - ein Objekt mit der IStandaloneThemeData- Oberfläche - und es im globalen editor installieren:

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

Nun sieht der Text in der beschriebenen Sprache folgendermaßen aus:


Sie können diese Funktion so weit anwenden, wie Sie es sich vorstellen. Zum Beispiel haben wir in unserem Panel einen Anrufprotokoll-Viewer für Entwickler erstellt. Protokolle sind oft lang und unverständlich. Wenn sie jedoch mit Syntaxhervorhebung, intelligenter Suche, Falten / Erweitern von Zeilen, den erforderlichen Befehlen (z. B. Prettify-Parameter) angezeigt werden, alle Anrufleitungen anhand ihrer ID hervorgehoben werden oder die Zeit im Protokoll in eine andere Zeitzone übersetzt wird, graben Sie dann es wird in ihnen viel einfacher (der Screenshot ist anklickbar):


Fazit


Zusammenfassend werde ich sagen, dass Monaco Feuer ist. Nach monatelanger Arbeit mit ihm habe ich außergewöhnlich angenehme Erinnerungen. Wenn Sie einen Editor für den Code auswählen, gehen Sie auf dessen Spielplatz und spielen Sie mit dem Code herum. Sehen Sie, was er sonst noch tun kann. Vielleicht ist das genau das, wonach Sie suchen.

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


All Articles