前言摘要
大众编程语言的通用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 设计最佳实践的思考