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 中先进行初始化:

  1. 使用detach进行清理;
  2. 绑定事件;

接下来就使用 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中的补全器:

  1. getCompletions :获取补全列表;
  2. 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自定义提示实现方法的更多相关文章

  1. 超酷HTML5 Canvas图表应用Chart.js自定义提示折线图

    超酷HTML5 Canvas图表应用Chart.js自定义提示折线图 效果预览 实例代码 <div class="htmleaf-container"> <div ...

  2. javaScript prototype实例(正则) 自定义日期格式化方法

    一个JS自定义日期格式化方法,包括了不少知识点,以下方法来自jQuery DataTable中文的官方参考 //return (new Date(data)).Format("yyyy-MM ...

  3. vue2.0 自定义 提示框(Toast)组件

    1.自定义 提示框 组件 src / components / Toast / index.js /** * 自定义 提示框( Toast )组件 */ var Toast = {}; var sho ...

  4. vue 自定义 提示框(Toast)组件

    1.自定义 提示框 组件 src / components / Toast / index.js /** * 自定义 提示框( Toast )组件 */ var Toast = {}; var sho ...

  5. js关闭当前页面不弹出提示的方法

    js关闭当前页面不弹出提示的方法 js关闭当前页面不弹出提示的方法 "window.opener=null;window.open('','_self','');window.close() ...

  6. 转载 jQuery和js自定义函数和文件的方法(全网最全)

    jQuery和js自定义函数和文件的方法(全网最全)    版权声明:本文为像雾像雨又像风_http://blog.csdn.net/topdandan的原创文章,未经允许不得转载. https:// ...

  7. js自定义弹出框

    js自定义弹出框: 代码如下 <html> <head><title>自定义弹出对话框</title> <style type ="te ...

  8. jQuery Validate 表单验证插件----自定义一个验证方法

    一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW  访问密码 f224 二.引入依赖包 <script src="../../scripts/j ...

  9. jquery.validate.js默认配置,jquery.validate.js自定义提示信息

    jquery.validate.js默认配置,jquery.validate.js自定义提示信息 配置jQuery.validator默认的处理方法 >>>>>>& ...

随机推荐

  1. Dubbo2.7源码分析-SPI的应用

    SPI简介 SPI是Service Provider Interface的缩写,即服务提供接口(翻译出来好绕口,还是不翻译的好),实质上是接口,作用是对外提供服务. SPI是Java的一种插件机制,可 ...

  2. Wordpress性能优化:使用crontab+wp-cli代替wp-cron

    wp-cron的问题     Wordpress内置wp-cron的模块,可以用来执行定时任务,比如定时检查更新,定时发布文章等都需要用到,属于必备功能.但是该模块的特点是:它只能在用户发起请求时检查 ...

  3. 关于伪分布zookeeper集群启动出错(Error contacting service. It is probably not running.)

    今天在配置zookeeper伪分布集群的时候,发现竟然出错了,以前我都是在多台电脑上搭建,大家可以参考我写的Hadoop HA搭建中的zookeeper如何搭建 现在就来说一下为何会出错. 出错的原因 ...

  4. SpringBoot+Mybatis+Generator 逆向工程使用(二)

    Mybatis-Genarator 逆向工程使用 个人开发环境 java环境:Jdk1.8.0_60 编译器:IntelliJ IDEA 2017.1.4 mysql驱动:mysql-connecto ...

  5. BAT技术需求,你能达到多少?

    作为中国互联网界的传奇和标杆企业,BAT 三家公司的一举一动受互联网人的精密亲密关注.进入 BAT 成为大厂的一员成了许多互联网人职业生活生存追逐的方针之一. 本文的作者作为一个非科班毕业,出身于三流 ...

  6. Java反射拾遗

    定义:Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)检查类,接口,变量以及方法的信息.反射还可以让我们在运行期实例化对象,调用方法,通过调用get/set ...

  7. Sublime Text 自动换行

  8. opencv3.2.0图像处理之方框滤波boxFilter API函数

    /*.1.方框滤波:boxFilter函数(注:均值滤波是归一化后的方框滤波)*/ /*函数原型: void boxFilter(InputArray src, OutputArray dst, in ...

  9. 填报表导出excel非可写单元格锁定问题

     问题描述: 填报表单元格分为可写和不可写两种状态,当填报表在web上展现的时候可写单元格可以进行数据填报和修改,非可写单元格不可操作. 报表导出为excel时,润乾导出excel包默认情况下不对 ...

  10. python 字符串格式化符号含义及注释

    字符串格式化符号含义 符号 说明 %C 格式化字符及其ASCII码 %S 格式化字符串 %d 格式化整数 %o 格式化无符号八进制数 %x 格式化无符号十六进制数 %X 格式化无符号十六进制数(大写) ...