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默认的处理方法 >>>>>>& ...
随机推荐
- if 和case
select case 'O' when 'O' then (1*5-6) when 'C' then (1*5-6) when 'EC' then (1*5-6) --else null --end ...
- .net core iis 502.5
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>n ...
- java 对CSV 文件的读取与生成
CSV文件是以逗号分隔值的文件格式,一般用WORDPAD或记事本(NOTE),EXCEL打开.CSV(逗号分隔值)是一种用来存储数据的纯文本文件,通常都是用于存放电子表格或数据的一种文件格式,对于CS ...
- Java:使用DOM4j来实现读写XML文件中的属性和元素
DOM4可以读取和添加XML文件的属性或者元素 读取属性: public static void ReadAttributes() throws DocumentException { File fi ...
- Java数据类型和不同数据类型在JVM内存分配
1.java数据类型分类 Java语言是强类型(Strongly typed)语言,强类型包含两方面的含义:①所有的变量必须先声明,后使用:②指定类型的变量只能接受类型与之匹配的值.这意味着每个变量和 ...
- 使用引用类型变量来访问所引用对象的属性和方法时,Java 虚拟机绑定规则
通过引用类型变量来访问所引用对象的属性和方法时,Java 虚拟机将采用以下绑定规则: 实例方法与引用变量实际引用的对象的方法进行绑定,这种绑定属于动态绑定,因为是在运行时由 Java 虚拟机动态决定的 ...
- Android开发之旅5:应用程序基础及组件
引言 上篇Android开发之旅:应用程序基础及组件介绍了应用程序的基础知识及Android的四个组件,本篇将介绍如何激活组关闭组件等.本文的主题如下: 1.激活组件:意图(Intents) 1.1. ...
- BZOJ3451:Tyvj1953 Normal
根据期望的线性性,答案就是 \(\sum\) 每个连通块出现次数的期望 而每个连通块次数的期望就是 \(\sum\) 连通块的根与每个点连通次数的期望 也就是对于一条路径 \((i,j)\),设 \( ...
- js-权威指南学习笔记15.3
1.如果要明确地选取一个表单元素,可以索引表单对象的elements属性:document.forms.address.elements.street--一个name为address的form下的na ...
- scss-!optional
optional翻译成汉语具有"可选的"的意思.顾名思义,!optional标记前面的扩展不必须生成一个新的选择器.看一段SCSS代码片段: p{ color:red; @exte ...