前言摘要
大众编程语言的通用IDE已经很多、很成熟了,像JetBrains系列、VSCode、Eclipse等,但小众编程语言的通用IDE似乎不多(或者在通用IDE里面以插件形式支持),再小众一些的编程httpwatch语言就似乎没有了,为https和http的区别了弥补这一空白,于是决定开发一个面向小众编程语言的IDE(另外还有个小目标,后续再写)。
功能需求
- 语法高亮浏览器
- 代码补全
- 语法检查
- 代码执行
- vim模式
- 代码格式化
- API封装
技术选择
从零开发一个IDE太难,也没有必要,当然也仓鼠饲养八大禁忌有例外。在网上搜了很多资料,无意中发一篇整合Monaco Editor开源阅读app下载安装和Antlr的文章(后面有参考链接),正好满足以上功能需求,于是就以此为主体框架。
语浏览器哪个好言选择
之前接触过国人开发的aviator表达式引擎(现在已经算是比较完备的脚本语言),算是相对熟悉的小众编程语言,于是就用此做为试点。
功能实现
以下内容讲解功能实现的技术细节,以及踩过的坑、填过的坑。
语法高亮
语法高亮部分直接使用httpclientMonaco Editor中的Monarch库(命名挺有意思,前缀相同),支持直接配置词法高亮,包括:关键词、运算符、字面量(数字、字符串)、空白、注释,参考官方文档和AviatorScript的语法很容易进行配置。

代码补全
代码补全部分也是直接使用Monaco Editor提供的开源阅读配置功能,当我们写到一个单词的前缀时,自动把后面部分辰时是几点到几点提示出来,按Tab或回车就可以补全出来,比如我们焯是什么梗要输入println,输入p时,后面就提示完整内容,同时可以把整个语句都提示出来开源节流什么意思,提升了编码效率。

编辑切换为居中
代http://www.baidu.com码补全效果图
AviatorScript的官方JetBrains提供了大量的函数,把函数里面整理到excel中,然后批量拼接补全规则,就支持全量函数提示和关键词提示。由于文件太长,只粘贴一段
[{ label: 'println', kind: monaco.languages.CompletionItemKind.Text, insertText: 'println(${0:text})', insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, } 。。。 ]
语法检查
语法检查是最难、最耗时的部分,把官方提供的75个dehttp代理mo全部测试通过,花费了不少时间,由于是小众浏览器的历史语言,作者的创造空httpclient间较大,给语法解析带来了部分困难。
使用Antlr的g4按照AviatorScript的官方文档编写语法文件,之前写过g4文件相对比较熟悉,其中很难的部分就是字jetbrainspycharm是什么软件符歧义处理耗时比较多,下面是两个典型的场景
【+-】:既可以是加减号,也可以是正负号,之前把正负号和数字字面量一起解析,发现和加减号冲突,最后都统一当成表达式来解析,并且正负号优先级高于加减号。
正则表达式:AviatorSc仓鼠寿命ript使用JavaScript的语法形式,/regexp/,这个时候又和除号冲突,浏览器下载正则表达式要使用Antlr的反向语法,只能使用词法解析,就不使用使开源节流什么意思用之陈思思前的加减号的处理方式,最后把正则表达式和之前的运算符一起解析才算解决。

语法文件编写完,就是和Monaco Editor整合,之前的实现思路是:后台提供一个把代码解析成AST的服务,并且把语法错误信息一起返回,然后由前端编辑器提示语法错误,这种性能比较差,语法需要实时检查,每次更改都会发起服务端请求,耗时很多,幸好Antlr浏览器网站删除了怎么恢复提供了JavaScript的运行时,方案改成:把仓鼠寿命语法文件用Antlr生成JavaScript代码,然后加载JavaScript到浏览器中,在浏览器中实现AST生成和语法检查,这时性能就好了很多。下面是生成词法解jetbrainspycharm是什么软件析部分截图

当出现语法错误时,会给出行列号,然后结合Monaco Editor提供的语法错误校验、jetbrains是什么软件标注函数进行错误提示,下图是解析AST语法树和错误信息

Monaco Editor语法错误标注代码http协议部分,将AST中的错误信息传递到编辑器中
页面显示效果

代码执行
代码执行部分相对简单,后台起一个SpringBoot服务,引入AviatorScript的包,封装成一个REST服务调用执行,然后返回结果,代码如下:
import com.alibaba.fastjson.JSONObject; import com.googlecode.aviator.AviatorEvaluator; import com.googlecode.aviator.Expression; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; @SpringBootApplication @RestController @RequestMapping("/service") public class AviatorServiceApplication { Logger logger = LoggerFactory.getLogger(this.getClass()); public static void main(String[] args) { SpringApplication.run(AviatorServiceApplication.class, args); } @RequestMapping("/execute") public Object service(@RequestBody String input) { JSONObject logInfo = new JSONObject(true); logInfo.put("input", input); JSONObject inputJson = JSONObject.parseObject(input); String code = inputJson.getString("code"); Map param = new HashMap(); if (inputJson.containsKey("param")) { param = inputJson.getJSONObject("param"); } Map output = new HashMap(); PrintStream systemOut = System.out; try { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); System.setOut(new PrintStream(outputStream)); AviatorEvaluator.setTraceOutputStream(outputStream); Expression compiledExp = AviatorEvaluator.compile(code); Object result = compiledExp.execute(param); String printString = outputStream.toString(); output.put("output", result); output.put("print", printString); } catch (Exception e) { output.put("error", e.getMessage()); } finally { System.setOut(systemOut); } logInfo.put("output", output); logger.info("input-output:".concat(logInfo.toJSONString())); return output; } }
vim模式
平时写代码喜欢vim模式,并且有现成的npm包,就加上了,代码如下:
//配置加载 require.config({ paths: { vs: 'https://7072-prd-ffx26-1255301037.tcb.qcloud.la/321zou.com/dsl/monaco-editor/0.32.1/min/vs', 'monaco-vim': 'https://unpkg.com/monaco-vim/dist/monaco-vim', } }); //和monaco一起加载 require(['vs/editor/editor.main', 'monaco-vim'], function(a, theMonacoVim) { MonacoVim = theMonacoVim; // 后面使用 } // 编辑模式切换 function toggleVimMode() { if (MonacoVim) { if (isVimMode) { vimMode.dispose(); $("#toggleVimModeBtn").text("切换VIM模式"); $("#vimStatus").hide(); } else { $("#toggleVimModeBtn").text("切换普通模式"); var vimStatusNode = document.getElementById('vimStatus'); vimMode = MonacoVim.initVimMode(dslCodeEditor, vimStatusNode); $("#vimStatus").show(); } isVimMode = !isVimMode; } }
文末有参考链接
代码格式化
代码格式化这部分是粗粒度处理,有很多细节地方需要优化,同时使用了一些hack的方式。
整体思路就是:解析陈涉世家翻译及原文代码成AST,然后用AST进行代码重新拼接,解析成AST时自开源阅读app下载安装动忽略了空格,于是整个拼接的过程就是加空格和缩进。
什么时候加空格比什么时候加缩进更难一些
缩进规则:左花括号后面加缩进,右花括浏览器网站删除了怎么恢复号后面减缩进,进行递归处理。但是AviatorScript支持lambda表达式,于是缩进规则加上lambda部分。
空格规则:这部分比较多,运算符前后加空格,大中小括号不加空格,而且有冲突,函数调用时括号不加空格,表达式中的括号要加空格,直接看代码部分(这部分需要优化空间很大)
API部分
上面已实现了编辑器的功能,但是要以嵌入的方式集成到别的网页中,还需要提供API,目前使用iframe的方式嵌入,对iframe的嵌入场景提供API
API设计
在设计之初定了,在群里沟通交流,初步以下目标
- 稳固:长期有效,不受版本升级影响
- 易学:技能能够从其他编辑器上平滑迁移过来浏览器下载
- 易用:能高效便捷的实现编辑功能
如何达到稳浏览器下载固:
不稳固的就容易变动,变动就会带来升级成本,稳固成为第一要求。
怎么达到稳固啦?所谓万变不离其宗,需要把一个编辑器的“宗”给http代理找出来,这个就是编辑器的核心本质。
从使用流程上看编辑器,打开编辑开源阅读app下载安装器、加载文本、编辑文本、存储文本。jetbrains是什么软件
再https和http的区别加上一些限制条件和忽略内容:编辑器已经打开、不考浏览器怎么打开链接虑文件存取部分,剩下的就是:设置文本、编辑文本、获取文本,
其中编辑文浏览器网站删除了怎么恢复本比较复杂稍后考虑,浏览器历史记录设置那么前两个API就确定下来了:setTehttps和http的区别xt,getText
复杂的编辑部分就交给monaco-editor去处理,API只关心编辑的开始和结束两部分,这样就能达到稳固效果。
如果取名为getText,那么后续扩展为图片时,那么要增加api,似乎monaco的getValue更通用,后面一想浏览器历史记录设置真的需要扩展为图片编辑器么?
在网上搜开源节流什么意思索其http://www.baidu.com他编辑器的命名方式
- monaco-editor : getValue、setValue
- UE:getContent、setContent
- KingEditor:html
哪种http 404命名方式更好,不好说。这个时候需要回到初心,回到编辑器的定位,本质浏览器的历史记录在哪上是想封装一个代码编辑开源众包器,http 500于是更好的命名方式是:getCode、setCode。
同时设计API时翻阅了知乎上面的关于API设计部分,学习了很CSS多。
定了http 302以下几个API:
- getCode
- setCode
- getAST
- executeCode
- formatCode
- getEditMode
- setEditMode
API焯是什么梗实现
API实现包括两部分:API提供、API使用
API浏览器历史上的痕迹在哪里提供部分代码如下,在iframe里面把功能绑定到父页面中
if (parent && parent.bindDSLEditorAPI) { parent.bindDSLEditorAPI({ getCode: function() { return getCode(); }, setCode: function(code) { setCode(code); }, getAST: function() { return getAST(getCode()); }, executeCode: function(callback) { dslExecuteMap[urlParam.dsl](callback); }, formatCode: function() { formatCode(); }, getEditMode: function() { return isVimMode ? "vim" : "default"; }, setEditMode: function(mode) { if ("vim" === mode && !isVimMode) { toggleVimMode(); } else if ("default" === mode && isVimMode) { toggleVimMode(); } }, }); }
API调用部分,就接收绑定,然后直接调用编辑器的API功能服务,代码如下(目前只支持最新打开tab的绑定,存在切换tab后不生效的bug,绑定被替换)
var dslEditor = null; function bindDSLEditorAPI(_dslEditor) { dslEditor = _dslEditor; } var buttonAction = { getCode: function() { var code = dslEditor.getCode(); alert(code); }, setCode: function() { var code = prompt("input code:"); dslEditor.setCode(code); }, getAST: function() { var ast = dslEditor.getAST(); alert(JSON.stringify(ast)); }, executeCode: function() { dslEditor.executeCode(function(jsonResult) { alert(JSON.stringify(jsonResult)); }); }, formatCode: function() { dslEditor.formatCode(); }, getEditMode: function() { var mode = dslEditor.getEditMode(); alert(mode); }, setEditMode: function() { var mode = prompt("mode: vim or default", "vim"); dslEditor.setEditMode(mode); }, showAPI: function() { var help = "=========API列表=========\n\n"; help += "1, 获取代码: string getCode()\n"; help += "2, 设置代码: void setCode(code)\n"; help += "3, 获取AST: json getAST()\n"; help += "4, 执行代码: json executeCode()\n"; help += "5, 格式化代码: void formatCode()\n"; help += "6, 获取编辑模式: string getEditMode() default|vim\n"; help += "7, 设置编辑模式: void setEditMode(mode) default|vim\n"; alert(help); }, };
踩过几个坑
第一个坑
在交流群里面发现的一个BUG,当Monaco Editor使用Monar长沙师范学院ch时开源是什么意思,括号后回车光标位置不正确,括号里面的第一行没有缩进,尝试了缩进规则都不生效,官网也能复现。

填第一个坑:捕捉Mona开源众包co Editor的回车事件,手工插入缩进的空格,带浏览器怎么打开链接来一个副作用,代码补浏览器的历史app全不仓鼠寿命支持回车了,需要使用Tab来补全代码。
第二个坑
钉钉的浏览器不支持Monaco Editor,加vConsole居然不报错,最后只能加大量al浏览器哪个好ert找到此原因。
填第二个坑:当代码不支持Monaco Editor时,使用textarea替代,不影响执行,其中要开源是什么意思判断浏览器是否支持,使用开源节流了setTimeout进行多次判断monaco是否存在,但是在弱网环境下,有可能网速往monaco没有加载,出现误判的情况,于是需要N多次检测,才能达到相对平滑的效果。
第三个坑
修改了语法文件,但是一直不生开源阅读效,重试了N次,修改了N次,Ctrl+刷新,最后浏览器怎么打开链接发现新开窗口执行就正常了,看网络请求居然是304,一直使用服务端缓存。
第四个坑
css样式名和Monaco Edit浏览器历史上的痕迹在哪里or样式名冲突,导致代码提示显示不全,部分被遮挡,此BUG花浏览器历史上的痕迹在哪里了大量时间才定位出来,代码提示部分是动态加载出来开源阅读的,鼠标一动就消失了,chrome的开发者工具就使用不了,最后靠猜,代码提示对应的英文是suggest,于是搜索,居然找到了代码补全的dom位置,把整个suggest元素复制出来,粘贴到代码中直接静态调试,jetbrainspycharm是什么软件才发现样式名冲突了。
第五个坑
编辑器定位,浏览器的历史潜意识和显意识不一致,想的是IDE,用词确是编辑器,实际是文本http://192.168.1.1登录编辑器,本质是代码文本编辑器,这几个概念是有差异的,刚开始是混淆的,并没有意思到之间的细微差异,就出现了陈涉世家翻译及原文要不要支持图片编辑这个功能。
在线使用
访问网站:aviator.321zou.com/

后续规划
- VSCode插件
- React、Vue封装
- 整合LSP
- 其他小众编程语言拓展
- 整理代码开源
参考资料
AviatorScript:1. 介绍 语雀
Monaco Edito陈涉世家翻译及原文r和Antlr整合:使用Monaco和ANTLR编写基于浏览器的编辑器_danpu0978的博客-CSDN博客
Monaco Editor官网:Monaco Editor Monarch
Antlr官网:www.antlr.org/
Ant开源节流什么意思lr JavaScript运行时:github.com/antlr/antlr…
monaco-vim : monaco-vim
API设计1:如何设计出一些优雅的API开源矿工接口呢?
API设计2:阿里云云栖号:深度 | API 设计最佳实践的思考
评论(0)