ACE.js自定义提示实现方法
ACE.js自定义提示实现方法
仅仅把代码高亮了还不够,在正常的编辑器中当输入少量的几个字符串就可以根据它来提示可能的输入:
这样用起来能极大地提高输入的效率,而实现起来非常简单:
ace.require("ace/ext/language_tools");
var editor = ace.edit("editor");
editor.session.setMode("ace/mode/groovy");
editor.setTheme("ace/theme/tomorrow");
editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true
});
另外注意需要引入 ext-language_tools.js 文件!感觉看英文的文档有些地方不是很清楚(可能是英语水平的问题☺),于是我们继续开始读源码。
源码分析
我们设置了 enableLiveAutocompletion 后输入内容时会执行doLiveAutocomplete 方法:
var doLiveAutocomplete = function(e) {
var editor = e.editor;
var hasCompleter = editor.completer && editor.completer.activated;
if (e.command.name === "backspace") {// 删除动作
if (hasCompleter && !getCompletionPrefix(editor))
editor.completer.detach();
} else if (e.command.name === "insertstring") {// 输入动作
var prefix = getCompletionPrefix(editor);
if (prefix && !hasCompleter) {
if (!editor.completer) {
editor.completer = new Autocomplete();
}
editor.completer.autoInsert = false;
editor.completer.showPopup(editor);// 入口方法
}
}
};
对操作的类型及内容做一些简单的过滤之后就交由 Autocomplete 来完成实质性的工作:
this.showPopup = function(editor) {
// 初始化
if (this.editor)
this.detach();
this.activated = true;
this.editor = editor;
if (editor.completer != this) {
if (editor.completer)
editor.completer.detach();
editor.completer = this;
}
// 绑定方法
editor.on("changeSelection", this.changeListener);
editor.on("blur", this.blurListener);
editor.on("mousedown", this.mousedownListener);
editor.on("mousewheel", this.mousewheelListener);
// 更新补全信息列表
this.updateCompletions();
};
方法 showPopup 中先进行初始化:
- 使用detach进行清理;
- 绑定事件;
接下来就使用 updateCompletions 来获取补全列表信息并进行展示:
this.updateCompletions = function(keepPopupPosition) {
if (keepPopupPosition && this.base && this.completions) {
var pos = this.editor.getCursorPosition();
var prefix = this.editor.session.getTextRange({start: this.base, end: pos});
// 内容没有发生变化
if (prefix == this.completions.filterText)
return;
this.completions.setFilter(prefix);
if (!this.completions.filtered.length)
return this.detach();
if (this.completions.filtered.length == 1
&& this.completions.filtered[0].value == prefix
&& !this.completions.filtered[0].snippet)
return this.detach();
this.openPopup(this.editor, prefix, keepPopupPosition);
return;
}
var _id = this.gatherCompletionsId;
// 收集所有的补全信息并执行(全部用回调函数来搞看着好累- -!)
this.gatherCompletions(this.editor, function(err, results) {
var detachIfFinished = function() {
if (!results.finished) return;
return this.detach();
}.bind(this);
// 获取前缀
var prefix = results.prefix;
var matches = results && results.matches;
// 没有匹配到的时候就可以清理一下然后返回了
if (!matches || !matches.length)
return detachIfFinished();
if (prefix.indexOf(results.prefix) !== 0 || _id != this.gatherCompletionsId)
return;
this.completions = new FilteredList(matches);
// 是否精确匹配
if (this.exactMatch)
this.completions.exactMatch = true;
// 过滤,过滤完的结果保存在filtered中
this.completions.setFilter(prefix);
var filtered = this.completions.filtered;
// 检查过滤完的结果,没有匹配到的就清理并返回
if (!filtered.length)
return detachIfFinished();
if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet)
return detachIfFinished();
if (this.autoInsert && filtered.length == 1 && results.finished)
return this.insertMatch(filtered[0]);
// 展示内容
this.openPopup(this.editor, prefix, keepPopupPosition);
}.bind(this));
};
其中参数 keepPopupPosition 表示是否保持弹出框的位置保持不变:
补全框中的内容会随着你的输入变化而变化,但是位置却保持不变就是这个参数在起作用!
其中比较关键的用 gatherCompletions 来收集所有补全器提供的数据(感觉是用这个方法把language_tools.js和autocomplete.js打通):
this.gatherCompletions = function(editor, callback) {
var session = editor.getSession();
var pos = editor.getCursorPosition();
var line = session.getLine(pos.row);
var prefix = util.retrievePrecedingIdentifier(line, pos.column);
this.base = session.doc.createAnchor(pos.row, pos.column - prefix.length);
this.base.$insertRight = true;
var matches = [];
var total = editor.completers.length;
// 遍历执行每个补全器
editor.completers.forEach(function(completer, i) {
// 获取补全列表
completer.getCompletions(editor, session, pos, prefix, function(err, results) {
// 在没有发生错误的时候,将结果合并到matchs中
if (!err)
matches = matches.concat(results);
var pos = editor.getCursorPosition();
var line = session.getLine(pos.row);
// 调用回调函数
callback(null, {
prefix: util.retrievePrecedingIdentifier(line, pos.column, results[0] && results[0].identifierRegex),
matches: matches,
finished: (--total === 0)
});
});
});
return true;
};
在每个补全器的 getCompletions 方法中都会调用callback方法:将自己的结果合并到全局的数据中。获取补全器的数据之后就会调用 openPopup 方法来更新展示:
this.openPopup = function(editor, prefix, keepPopupPosition) {
if (!this.popup)
this.$init();
this.popup.setData(this.completions.filtered);
editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
var renderer = editor.renderer;
this.popup.setRow(this.autoSelect ? 0 : -1);
if (!keepPopupPosition) {
// 设置展示
this.popup.setTheme(editor.getTheme());
this.popup.setFontSize(editor.getFontSize());
var lineHeight = renderer.layerConfig.lineHeight;
// 设置位置
var pos = renderer.$cursorLayer.getPixelPosition(this.base, true);
pos.left -= this.popup.getTextLeftOffset();
var rect = editor.container.getBoundingClientRect();
pos.top += rect.top - renderer.layerConfig.offset;
pos.left += rect.left - editor.renderer.scrollLeft;
pos.left += renderer.gutterWidth;
// 展示内容
this.popup.show(pos, lineHeight);
} else if (keepPopupPosition && !prefix) {
this.detach();
}
};
回过头再来看language_tools.js中的补全器:
- getCompletions :获取补全列表;
- getDocTooltip :返回HTML格式的提示内容;
每个补全列表中的元素包含如下信息:
- caption :字幕,也就是展示在列表中的内容
- meta :展示类型
- name :名称
- value :值
- score :分数,越大的排在越上面
而getDocTooltip感觉又进一步地提升了写代码时候的体验(在写代码的时候就知道输入的是什么):
具体是怎么实现的呢?接着来看代码, Mode 中的 getCompletions 如下:
this.getCompletions = function(state, session, pos, prefix) {
// 获取当前Mode的关键字
var keywords = this.$keywordList || this.$createKeywordList();
// 根据关键字组装补全列表
return keywords.map(function(word) {
return {
name: word,
value: word,
score: 0,
meta: "keyword"
};
});
};
在当前文件中写过的单词被自动提示补全的逻辑在 text_completer.js 中实现(逻辑很简单),比较麻烦的是 enableSnippets ,这个后面有时间再看。
自定义补全
知道了ACE的补全运行的原理,那么现在扩展起来就比较简单了:
var languageTools = ace.require("ace/ext/language_tools");
languageTools.addCompleter({
getCompletions: function(editor, session, pos, prefix, callback) {
callback(null, [
{
name : "test",
value : "test",
caption: "test",
meta: "test",
type: "local",
score : 1000 // 让test排在最上面
}
]);
}
});
虽然看到的例子都是同步执行callback方法,但用异步来做也是完全没有问题的:
在上面看源码的时候还不明白为啥每次回调的时候都要更新显示而不是等全部执行完后更新一次~
在事件驱动的系统中接口的设计还是需要多思考、多推敲的啊!
总结
有了自动补全之后与IDE的距离又近了一步,不仅仅能加快脚步编写的速度,更重要的是代码的准确性也会有所提高,当然这还是不够的!
ACE.js自定义提示实现方法的更多相关文章
- 超酷HTML5 Canvas图表应用Chart.js自定义提示折线图
超酷HTML5 Canvas图表应用Chart.js自定义提示折线图 效果预览 实例代码 <div class="htmleaf-container"> <div ...
- javaScript prototype实例(正则) 自定义日期格式化方法
一个JS自定义日期格式化方法,包括了不少知识点,以下方法来自jQuery DataTable中文的官方参考 //return (new Date(data)).Format("yyyy-MM ...
- vue2.0 自定义 提示框(Toast)组件
1.自定义 提示框 组件 src / components / Toast / index.js /** * 自定义 提示框( Toast )组件 */ var Toast = {}; var sho ...
- vue 自定义 提示框(Toast)组件
1.自定义 提示框 组件 src / components / Toast / index.js /** * 自定义 提示框( Toast )组件 */ var Toast = {}; var sho ...
- js关闭当前页面不弹出提示的方法
js关闭当前页面不弹出提示的方法 js关闭当前页面不弹出提示的方法 "window.opener=null;window.open('','_self','');window.close() ...
- 转载 jQuery和js自定义函数和文件的方法(全网最全)
jQuery和js自定义函数和文件的方法(全网最全) 版权声明:本文为像雾像雨又像风_http://blog.csdn.net/topdandan的原创文章,未经允许不得转载. https:// ...
- js自定义弹出框
js自定义弹出框: 代码如下 <html> <head><title>自定义弹出对话框</title> <style type ="te ...
- jQuery Validate 表单验证插件----自定义一个验证方法
一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW 访问密码 f224 二.引入依赖包 <script src="../../scripts/j ...
- jquery.validate.js默认配置,jquery.validate.js自定义提示信息
jquery.validate.js默认配置,jquery.validate.js自定义提示信息 配置jQuery.validator默认的处理方法 >>>>>>& ...
随机推荐
- [转]使用BCP导出导入数据
本文转自:http://www.cnblogs.com/zerocc/p/3225723.html bcp 实用工具可以在 Microsoft SQL Server 实例和用户指定格式的数据文件间大容 ...
- input 控件监听回车确认按钮。
前端开发的同学捕捉回车按键经常会用到 if(event.keyCode == 13){ console.log("点击了回车按键");} 但是在微信上面,我们一般会用到指令 bin ...
- 动态We API(ABP官方文档翻译)
动态Web API层 创建动态Web API控制器 ForAll方法 重写ForAll ForMethods Http动词 WithVerb方法 HTTP特性 命名约定 API管理器 RemoteSe ...
- 左连接sql
<?php public function sumZong($id){ $sql =' SELECT * FROM vvt_league_user AS p Left join vvt_leag ...
- html-图像标签
图像标签 <img src="图片的路径"/> -src:图片的路径 -width:图片的宽度 -height:图片的高度 -alt:图片上显示的文字,把鼠标移动到图片 ...
- 关于CSS的知识
这两天在学习关于HTML的知识,今天学习到CSS的知识,将自己所收获的知识点归纳一下: 首先, CSS声明学习: 1.在head标签中使用style标签声明: ...
- react组件(react-grid-gallery)
react有很多好玩的组件,react-grid-gallery就是其中一个,主要处理图片展示,对图片进行放大与缩小 文档:https://www.npmjs.com/package/react-gr ...
- LGWR和DBWn的触发条件
Rolling Forward(前滚) Oracle启动实例并加载数据库,然后通过Online Redologs中的重做日志,重现实例崩溃前对数据库的修改操作.在恢复过程中对于已经提交的事务,但尚未写 ...
- iptable防火墙案例
[root@WX020 firewall]# cat /etc/sysconfig/iptables# Firewall configuration written by system-config- ...
- 在技术胖博客上学习ES6遇到的坑和想法
第一节:ES6的开发环境搭建 坑1:全局安装babel-cli已经不被官方推荐,改为局部安装(cnpm install babel-cli --save-dev): 坑2:babel src/inde ...