普通人的IDE或我们为什么选择摩纳哥

编者注


上一篇文章中,我们讨论了Voximplant控制面板的发布,不要忘了提到更新的IDE。 今天,我们为此工具专门设计了一个替代品 -我们的同事Geloosa精心描述了选择技术的过程以及带有选项卡,自动完成和自定义样式的实现。 更方便地坐下,把其余的事情搁置一旁,去索具,摩纳哥的胆量正等待着好奇-不要滑倒,有很多:)喜欢阅读。


为代码编辑器选择哪个库?


Npm为代码编辑器生成400多个结果。 在大多数情况下,它们是为特定框架或项目制作的几个最受欢迎的lib的UI包装器,针对同一lib的插件或对其fork进行了修改的分叉,以及不用于在浏览器中编辑代码的操作,它们只是通过关键字进入输出。 因此,幸运的是,选择范围要窄得多。 还有几个库-轻量级但不是很实用的CodeFlask库,专为小片段和交互式示例而设计,但不适用于具有我们在桌面编辑器中惯用的功能的完整的Web IDE。

最后,我们有3个库可供选择: AceCodeMirrorMonaco Editor 。 其中最早的CodeMirror是由柏林人Marijn Haverbeke提出的一项私人计划,他在其在线教程Eloquent JavaScript中需要一个练习代码编辑器。 编辑器的第一版于2007年发布。 2010年,Ace的第一个版本在同一柏林的JSConf.eu上发布,Ajax.org随后为基于云的IDE Cloud9开发了Ace(实际上,Ace代表Ajax.org Cloud9 Editor)。 2016年,Cloud9被亚马逊收购,现已成为AWS的一部分。 最新的摩纳哥编辑器是VS Code的组件,由Microsoft于2015年底发布。

每个编辑器都有自己的优点和缺点;每个编辑器都用于多个大型项目中。 例如,Chrome和Firefox开发人员工具(Bitbucket中的IDE)在npm中的RunKit中使用CodeMirror。 Ace-MODX可汗学院Codecademy; 摩纳哥-在GitLab IDE和CodeSandbox中。 下面是一个比较表,可以帮助您选择最适合您的项目的库。

图书馆
王牌代码镜像摩纳哥
开发者Cloud9 IDE(Ajax.org),
现在是AmazonMozilla的一部分
玛丽恩·哈弗贝克微软公司
浏览器支持Firefox ^ 3.5
镀铬
Safari ^ 4.0
IE ^ 8.0
歌剧^ 11.5
Firefox ^ 3.0
镀铬
Safari ^ 5.2
IE ^ 8.0
歌剧^ 9.2
Firefox ^ 4.0
镀铬
Safari(v-?)
IE ^ 11.0
歌剧^ 15.0
语言支持
(语法突出显示)
> 120> 100> 20
中的字符数
的最新版本
cndjs.com
366608(v1.4.3)394,269(v5.44.0)2,064,949(v0.16.2)
最新版本的重量,
gzip
2.147 KB1.411 KB10.898 KB
渲染图多姆多姆DOM和部分<canvas>
(用于滚动和小地图)
该文件10人中有7人:没有搜索,不一定总是清晰
方法返回,令人怀疑
完整性和相关性
(并非所有链接都在扩展坞中起作用)
十分之六:与用户指南合并,
通过Ctrl + F搜索,
对完整性存在疑问
10人中有9人:很漂亮,而且搜索
交叉参考
-1分,缺乏解释
到某些标志的应用
从名字上不太明显
快速入门演示操作方法-带有代码示例的文本文档,
分别有带有代码示例的演示
(是的,它们分散在不同的页面上,
并非每个人都能正常工作,因此最容易通过Google进行搜索),
有一个演示,您可以在其中触摸不同的功能,
但建议通过UI控件进行管理,
也就是说,我们仍然必须分别搜索方法
连接他们
方法真的很差
基本上一切都散布在github上
和stackoverflow,但其中有一些带有示例的功能演示
实施代码
以游乐场的形式组合:
带有注释和一些演示的代码,您可以
立即尝试并评估
许多可能性
社区活动平均值平均值
开发者活动平均值平均值

按大小比较库是没有意义的,因为这完全取决于特定项目的连接方式和连接方式:使用其中一个版本(也有所不同)加载完成的文件,或者通过某种收集器运行npm软件包。 最重要的是使用了多少编辑器:是否加载了所有样式和主题,使用了多少以及哪些附加组件和插件。 例如,在CodeMirror中,开箱即用在Monaco和Ace中可用的大多数功能仅在附加组件中可用。 该表显示了CDN上最新版本中的字符数及其压缩文件的权重,以大致了解所涉及的顺序。

所有库都具有大致相同的基本功能集:代码自动格式化,折叠线,剪切/复制/粘贴,热键,添加用于突出显示和排序的新语法的功能,语法检查(仅在CodeMirror中通过加载项,在Ace中到目前为止仅针对JavaScript) / CoffeeScript / CSS / XQuery),工具提示和自动完成功能(在CodeMirror中-通过附加组件),按代码进行高级搜索(在CodeMirror中-通过附加组件),用于实现制表符和拆分模式的方法,diff模式和合并工具(在CodeMirror中) -在一个窗口中有加号或减号,或者在附加组件中有两个面板, 王牌 - 独立利布)。 由于年代久远,已经为CodeMirror编写了许多附加组件,但是它们的数量将影响编辑器的重量和速度。 摩纳哥可以开箱即用地做很多事情,而且我认为它比Ace和CodeMirror更好,更大。

我们之所以在摩纳哥停留有几个原因:

  1. 我们认为对项目至关重要的最先进的工具:
    • IntelliSense-提示和自动完成;
    • 在上下文菜单中和通过小地图进行智能代码导航;
    • 开箱即用的两面板差异模式。

  2. 用TypeScript编写。 我们的控制台使用Vue + Typescript编写,因此TS支持非常重要。 顺便说一下,Ace最近还支持TS,但是它最初是用JS编写的。 对于CodeMirror, DefinitelyTyped中有一些类型
  3. 它是其中最活跃的开发工具(可能是因为它是不久前发布的),错误得到更快地纠正,并且池请求也得到了解决。 为了进行比较,使用CodeMirror时,我们经历了一段悲伤的经历,当时错误多年没有得到纠正,我们将拐杖放在拐杖上,然后开车拐杖。
  4. 方便的自动生成的文档(为其完整性提供了希望),在接口和方法之间具有交叉引用。
  5. 以我们的口味,最漂亮的UI(可能还与创建时间有关)和简洁的API。
  6. 在问过开发人员的朋友之后,哪位编辑最引起头痛,Ace和CodeMirror是领导者。

另外,应该说工作速度。 昂贵的解析在并行工作线程中进行。 另外,所有计算都受到视口大小的限制(所有类型,颜色,渲染仅针对可见线进行计算)。 仅当代码包含100,000行时,它才会开始制动-可以计算出几秒钟的提示。 Ace也使用大量的工人进行大量计算,结果证明速度更快:在相同长度的代码中,提示几乎立即出现,并且可以快速处理200,000行(在官方网站上,即使是400万行也不是问题,尽管螺丝被加速,输入开始变慢,并且提示消失了(一百万之后)。 没有并行计算的CodeMirror几乎无法提取此类卷:它可以闪烁文本和语法突出显示。 由于文件中的100,000行在现实世界中很少见,因此我们对此视而不见。 即使拥有4万到5万行,摩纳哥也做得很好。

连接摩纳哥并使用基本功能(例如,与Vue集成)


连接方式


在这里,我将提供来自vue组件的代码示例,并使用适当的术语。 但是,所有这些都可以轻松移植到任何其他框架或纯JS。

摩纳哥的源代码可以在官方网站上下载并放入您的项目中,您可以从CDN中提取它,也可以通过npm连接到该项目。 我将讨论第三个选项并使用webpack进行构建。

我们放置了monaco-editor和一个用于组装的插件:

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

在webpack配置中,添加:

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

如果使用Vue和vue-cli-service进行构建,请添加到vue.config.js:

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

如果不需要Monaco的所有语言和功能,要减小捆绑包的大小,可以将具有设置MonacoWebpackPlugin对象传输到MonacoWebpackPlugin

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

插件的功能和语言的完整列表在这里

创建和自定义编辑器


我们导入editor并调用editor.create(el: HTMLElement, config?: IEditorConstructionOptions) ,将要在其中创建编辑器的DOM元素作为第一个参数传递。

在编辑器组件中:

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

编辑器的容器必须设置高度,以免高度不为零。 如果您在一个空的div中创建编辑器(高度为零,即K.O.),则Monaco将在编辑器窗口中以内联样式编写相同的高度。

editor.create的第二个可选参数是编辑器配置。 其中有一百多个选项,有关IEditorConstructionOptions接口的完整说明,请参见文档。

例如,我们将设置语言,主题和初始文本并启用换行(默认情况下,它们不被换行):

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

editor.create函数返回带有IStandaloneCodeEditor接口的对象。 通过它,您现在可以控制编辑器中发生的所有事情,包括更改初始设置:

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

现在让您痛苦的是: updateOptions接受带有IEditorOptions接口的对象,而不是IEditorConstructionOptions。 它们略有不同:IEditorConstructionOptions较宽,它包含此编辑器实例的属性和某些全局实例的属性。 updateOptions属性通过updateOptions全局更改-通过全局editor的方法更改。 因此,全局更改的那些实例在所有情况下都会更改。 在这些选项中, theme 。 创建两个具有不同主题的实例; 两者中的y将是最后一个给出的(此处为深色)。 全局editor.setTheme('vs')方法也将同时更改这两个主题。 这甚至会影响SPA另一页上的那些窗口。 这样的地方很少,但是您需要关注它们。

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


删除编辑器


销毁摩纳哥窗口时,必须调用dispose方法,否则将不会清除所有侦听器,并且此后创建的窗口可能无法正常工作,对某些事件进行多次响应:

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

标签


在文件编辑器中打开的选项卡使用相同的Monaco窗口。 要在它们之间切换,请使用IStandaloneCodeEditor方法:用于保存的getModel和用于更新编辑器模型的setModel 。 该模型存储文本,光标位置,撤消重做的动作历史记录。 要创建新文件的模型,请使用editor.createModel(text: string, language: string)全局方法。 如果文件为空,则无法创建模型并将null传递给setModel

查看代码
 <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> 


差异模式


对于差异模式,创建编辑器窗口时需要使用另一种editor方法createDiffEditor

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

它使用与editor.create相同的参数,但配置应具有IDiffEditorConstructionOptions接口,该接口与常规编辑器配置稍有不同,尤其是它没有value 。 通过setModel创建返回的IStandaloneDiffEditor后,设置要比较的文本:

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


上下文菜单,命令面板和热键


摩纳哥使用其自己的非浏览器上下文菜单,该菜单具有智能导航,可更改所有事件的多光标以及类似于VS Code(命令面板)的命令面板,其中包含一堆有用的命令和快捷键,可加速代码编写:

 摩纳哥上下文菜单 


 摩纳哥命令面板 


上下文菜单通过addAction方法扩展(在IStandaloneCodeEditorIStandaloneDiffEditor都可用),该菜单接受IActionDescriptor对象:

查看代码
 // ... <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; } 


为了仅将快捷方式绑定到操作而不在上下文菜单中显示该快捷方式,使用了相同的方法,仅未指定该操作的contextMenuGroupId

查看代码
 // ... //   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); //     } 


命令面板将包括所有添加的操作。

提示和自动完成


出于这些目的,摩纳哥使用了IntelliSense ,这很酷。 您可以阅读并在屏幕快照上看到链接,他可以显示多少有用的信息。 如果您的语言还没有自动完成功能,则可以通过registerCompletionItemProvider添加它。 对于JS和TS,已经有一个addExtraLib方法,允许您加载工具提示和自动完成的TypeScript定义:

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

在第一个参数中,该行传递定义,在第二个可选参数中传递库的名称。

自定义语言和主题


摩纳哥拥有用于确定其语言语法的Monarch模块。 语法非常标准地描述:设置了该语言特有的常规和标记之间的对应关系。

查看代码
 // ... //  ,    : 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); // ... } 


您还可以为令牌创建主题(具有IStandaloneThemeData接口的对象)并将其安装在全局editor

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

现在,所描述语言的文本将如下所示:


只要您有足够的想象力,就可以应用此功能。 例如,我们在开发人员面板中制作了一个呼叫日志查看器。 日志通常很长且难以理解,但是当它们以语法突出显示,智能搜索,折叠/展开线,必要的命令(例如,Prettify params)显示时,按其ID突出显示所有呼叫行或将日志中的时间转换为其他时区,然后进行挖掘在他们中变得容易得多(可单击屏幕快照):


结论


总而言之,我要说摩纳哥是火。 与他一起工作了几个月之后,我有了特别愉快的回忆。 如果您选择代码的编辑器,请确保进入其Playground并试用该代码,看看它还能做什么。 也许这正是您想要的。

Source: https://habr.com/ru/post/zh-CN445390/


All Articles