初步接触 atom 插件

前言


最近用 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  不能正常展示节点属性的原因,修改了条件之后就可以了。


定制API和组件

最初的想法是把 javascript 代码导出文档,然后解析文档,生成API和组件的 json 配置文件,用于提示时候读取;或者是每次 control+s 保存的时候,为当前文件生成 json 文档。前者有 jsdoc toolkit 可以导出文档,但是解析代码还要花点时间去写好测试。后者可以参考 jsdoc toolkit 怎么解析的类文件,然后生成 json 配置文件。论使用的方便性,还是后者比较好,但缺点是多人操作时候容易冲突;最后一种方式是直接改 jsdoc toolkit 源码,生成 json 配置文件,以后就试这个吧。

一步到位要做的事情太多,所以暂时还是手动去配置 json 文件。


下面只贴出初学者的代码,不敢扰动原插件作者大神,所以私自贴上修改后的代码在这里。

component.txtJavaCode
"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'];

provider.txtJavaCode
"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;



还有一个代码文件,脚本执行文件(团队合作的,要一起完善 json 配置文件,所以用git管理该 json 文件,然后通过脚本去更新到 ~/atom/package/react-native autocomplete/下面的 json 配置文件。)代码可以在这里下载:

http://download.csdn.net/detail/qhexin/9473681



你可能感兴趣的:(react)