最近用 react-native 来做客户端,使用的IDE是 atom , atom 确实很强大,源码开发,插件任意。但是还有很多不完善的地方,在 jsx 的代码提示上不是很好。找了一个 react-native autocomplete 的插件,很早已经安装过了,但是一直感受不到他的强大之处。写 react 控件的时候,属性不会自动提醒,只有在 React.xxx 的时候才有菜单跳出来告诉你有哪些 React 类或者方法或者属性可以选择,也就是React native 的 API提醒。
纠结于我自己的代码库却没有任何的API提醒,而我又习惯了其他编辑器里的提醒,所以我想自己动手改插件,实现自己常用API的提醒以及组件属性的提醒,基于这个目的,我查看了 react-native autocomplete 的实现源码,一开始比较难读懂。因为我不知道插件执行的入口在哪里。而且这个插件可以知道当前编辑到哪个位置, scope 是什么(一开始我对 scope 没什么概念,看蒙了)。
要写插件,首先要看
http://flight-manual.atom.io/
https://github.com/atom/autocomplete-plus/wiki/Provider-API
https://atom-china.org/
一定要读一下 flight-manual.atom.io ,他告诉你怎么使用 atom ,怎么 hack atom 。
说明文档的调试,我还没有去试。我直接在现有的插件上改,进入 ~/.atom/package/react-native autocomplete 目录,在这里直接改 json 文件和 complete.js , api.js 几个文件。改完毕之后,我是直接退出atom重新进入,这个办法很土,但是对于刚接触 atom 的人来说,这个是我最快的方式。进入 atom 之后,我打开自己的工程,打开一个文件进行编辑,按快捷键 alt+command+i ,弹出调试器;然后按 command+p ,搜索出我要打断点的文件名,然后在文件左侧边栏点击加入断点;再回到工程里面的文件编辑器,输入文字,断点被激活,然后一步一步进行代码跟踪。
跟踪之后通过多次调试对比,发现 react-native autocomplete 不能正常展示节点属性的原因,修改了条件之后就可以了。
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _units = require("../helper/units.js");
var _units2 = _interopRequireDefault(_units);
var _components = require("../../completions/components.json");
//var _customComponents = require("../../completions/components.json");
var _customComponents = require("../../completions/customComponents.json");
var _components2 = _interopRequireDefault(_components);
var _customComponents2 = _interopRequireDefault(_customComponents);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var trailingWhitespace = /\s$/;
var attributeInput = /^([a-zA-Z][-a-zA-Z]*)$/;
var attributePattern = /\s+([a-zA-Z][-a-zA-Z]*)\s*=\s*$/;
var tagInput = /^<([a-zA-Z][-a-zA-Z]*|$)/;
var tagInput2 = /^\s*<([^>]+)$/;
var tagNotInput = />([a-zA-Z][-a-zA-Z]*|$)/;
var tagPattern = /<(([a-zA-Z][-a-zA-Z]*)|TabBarIOS.Item)(?:\s|$)/; //TabBarIOS.Item是特殊的标签
var COMPONENT = {
getTagNameCompletions: function getTagNameCompletions() {
var _completions = [];
_customComponents2.default.tags.completions.forEach(function (obj) {
_completions.push(_units2.default.clone(obj));
});
_components2.default.tags.completions.forEach(function (obj) {
_completions.push(_units2.default.clone(obj));
});
return _completions;
},
getAttributeNameCompletions: function getAttributeNameCompletions(editor, bufferPosition) {
var _completions = [],
tag = this.getPreviousTag(editor, bufferPosition);
if (_components2.default.attributes[tag]) {
_components2.default.attributes[tag].forEach(function (obj) {
_completions.push(_units2.default.clone(obj));
});
}
if (_customComponents2.default.attributes[tag]) {
_customComponents2.default.attributes[tag].forEach(function (obj) {
_completions.push(_units2.default.clone(obj));
});
}
return _completions;
},
getAttributeValueCompletions: function getAttributeValueCompletions(line) {
var _completions = [],
attribute = this.getPreviousAttribute(line);
if (_components2.default.values[attribute]) {
_components2.default.values[attribute].forEach(function (text) {
_completions.push({
"text": text,
"type": "value"
});
});
}
if (_customComponents2.default.values[attribute]) {
_customComponents2.default.values[attribute].forEach(function (text) {
_completions.push({
"text": text,
"type": "value"
});
});
}
return _completions;
},
getPreviousTag: function getPreviousTag(editor, bufferPosition) {
var ref;
var row = bufferPosition.row;
while (row >= 0) {
ref = tagPattern.exec(editor.lineTextForBufferRow(row));
if (ref) {
return ref[1];
}
row--;
}
return null;
},
getPreviousAttribute: function getPreviousAttribute(line) {
var ref, ref1;
var quoteIndex = line.length - 1;
while (line[quoteIndex] && !((ref = line[quoteIndex]) === '"' || ref === "'")) {
quoteIndex--;
}
line = line.substring(0, quoteIndex);
return (ref1 = attributePattern.exec(line)) != null ? ref1[1] : null;
},
isAttributeValue: function isAttributeValue(scopes) {
return this.hasTagScope(scopes) && this.hasAttributeValueScope(scopes) && !this.hasAttributeContentScope(scopes);
},
isAttribute: function isAttribute(prefix, scopes,line,bufferPosition) {
if (!trailingWhitespace.test(prefix) && !attributeInput.test(prefix)) {
return false;
}
var substring = line.substring(0,bufferPosition.column - 1);
if (!tagInput2.test(substring)) {
return false;
}
// var __hasTagScore = this.hasScopeDescriptor(scopes, ['meta.scope.tag.block', 'meta.tag.block.begin.jsx', 'meta.tag.block.end.jsx', 'entity.name.tag.jsx', 'tag.open.js', 'punctuation.definition.tag.begin.js', 'punctuation.definition.tag.end.js']) || this.hasScopeDescriptor(scopes, ['invalid.illegal.tag.end.jsx', 'tag.closed.js']);
// return __hasTagScore && !this.hasAttributeContentScope(scopes);//test
return (this.hasTagScope(scopes) || this.hasAttributeScope(scopes)) && !this.hasAttributeContentScope(scopes);
},
isTag: function isTag(scopes, line) {
var segment = line.split(' ').pop();
return tagInput.test(segment) && !tagNotInput.test(segment) && !this.hasScopeDescriptor(scopes, ['string.quoted.double.jsx', 'string.quoted.single.jsx', 'string.quoted.double.js', 'string.quoted.single.js']) && !this.hasAttributeContentScope(scopes);
},
hasTagScope: function hasTagScope(scopes) {
return this.hasScopeDescriptor(scopes, ['meta.tag.jsx','meta.scope.tag.block', 'meta.tag.block.begin.jsx', 'meta.tag.block.end.jsx', 'entity.name.tag.jsx', 'tag.open.js', 'punctuation.definition.tag.begin.js', 'punctuation.definition.tag.end.js']) && !this.hasScopeDescriptor(scopes, ['invalid.illegal.tag.end.jsx', 'tag.closed.js']);
},
hasAttributeScope: function hasAttributeScope(scopes) {
return this.hasScopeDescriptor(scopes, ['entity.other.attribute-name.jsx', 'punctuation.separator.key-value.attribute.jsx']);
},
hasAttributeValueScope: function hasAttributeValueScope(scopes) {
return this.hasScopeDescriptor(scopes, ['string.quoted.double.jsx', 'string.quoted.single.jsx', 'string.quoted.double.js', 'string.quoted.single.js']) && this.hasScopeDescriptor(scopes, ['meta.scope.inner', 'punctuation.definition.string.begin.jsx', 'punctuation.definition.string.end.jsx', 'punctuation.definition.string.begin.js', 'punctuation.definition.string.end.js']);
},
hasAttributeContentScope: function hasAttributeContentScope(scopes) {
return this.hasScopeDescriptor(scopes, ['punctuation.section.embedded.begin.jsx', 'punctuation.section.embedded.end.jsx', 'punctuation.definition.brace.curly.begin.js', 'punctuation.definition.brace.curly.end.js', 'meta.brace.curly.js', 'meta.embedded.expression.jsx', /meta.embedded.expression.(\S*).jsx/]);
},
hasScopeDescriptor: function hasScopeDescriptor(fromScopes, toScopes) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = toScopes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var scope = _step.value;
if (typeof scope === 'string') {
if (fromScopes.indexOf(scope) !== -1) {
return true;
}
} else {
var pass = false;
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = fromScopes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var text = _step2.value;
if (scope.test(text)) {
pass = true;
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
if (pass) return true;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return false;
}
};
exports.default = COMPONENT;
module.exports = exports['default'];
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _api = require("./modules/api.js");
var _api2 = _interopRequireDefault(_api);
var _component = require("./modules/component.js");
var _component2 = _interopRequireDefault(_component);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var provider = {
selector: '.source.js, .source.jsx',
filterSuggestions: true,
getSuggestions: function getSuggestions(request) {
var prefix = request.prefix,
bufferPosition = request.bufferPosition,
editor = request.editor,
scopes = request.scopeDescriptor.getScopesArray(),
line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);
return new Promise(function (resolve) {
var suggestion = null;
if (_component2.default.isAttributeValue(scopes)) {
suggestion = _component2.default.getAttributeValueCompletions(line);
} else if (_component2.default.isTag(scopes, line)) {
suggestion = _component2.default.getTagNameCompletions();
} else if (_component2.default.isAttribute(prefix, scopes,line,bufferPosition)) {
suggestion = _component2.default.getAttributeNameCompletions(editor, bufferPosition);
} else {
suggestion = _api2.default.getCompletions(line);
}
resolve(suggestion);
});
},
onDidInsertSuggestion: function onDidInsertSuggestion(request) {
if (request.suggestion.rightLabelHTML === 'react-native' && request.suggestion.type === 'attribute') {
setTimeout(function () {
atom.commands.dispatch(atom.views.getView(request.editor), 'autocomplete-plus:activate', {
activatedManually: false
});
}, 1);
}
}
};
exports.default = provider;